星標公眾號,讓嵌入式知識 “投喂” 不停歇!
想象一下,你要蓋一棟房子。
面向過程 (Procedure-Oriented):就像你自己親力親為地去蓋。你的思維是線性的:“我先去搬磚 -> 然后和水泥 -> 接著砌墻 -> 最后封頂”。你關注的是步驟,是一步一步的動詞(動作)。寫代碼就像寫菜譜,第一步做什么,第二步做什么。
// 面向過程的C語言風格代碼示例:做一頓飯
void washVegetables() { /* 洗菜 */ }
void cutVegetables() { /* 切菜 */ }
void cook() { /* 炒菜 */ }
int main() {
washVegetables(); // 第一步:洗
cutVegetables(); // 第二步:切
cook(); // 第三步:炒
return 0;
}
面向對象 (Object-Oriented):就像你是一個建筑師或包工頭。你的思維是先設計藍圖,再組織分工。你會先想:“我需要一棟房子,這棟房子由門、窗、墻、屋頂組成”。這里的“房子”、“門”、“窗”就是對象。你關注的是事物,是名詞(東西),以及這些東西各自有什么功能(方法)。
// 面向對象的C++風格代碼示例:組織一頓飯
class Chef {// 1. 先設計一個“廚師”藍圖
public:
void washVegetables() { /* 廚師能洗菜 */ }
void cutVegetables() { /* 廚師能切菜 */ }
void cook() { /* 廚師能炒菜 */ }
};
int main() {
Chef chefWang; // 2. 根據藍圖,請來一個叫chefWang的廚師(創建一個對象)
// 3. 指揮廚師去做事(調用對象的方法)
chefWang.washVegetables();
chefWang.cutVegetables();
chefWang.cook();
return0;
}
核心區別:
為了更直觀地理解兩者的工作流程差異,請看下面的流程圖:

“類”(Class)是面向對象的核心概念。剛才的“廚師藍圖”就是一個類。我們再打個比方:
類 (Class):就像是汽車的設計圖紙。圖紙上規定了這輛車應該有:
對象 (Object):就是根據那張設計圖紙制造出來的真實汽車。根據一張圖紙,可以造出千千萬萬輛真實的汽車。
讓我們用代碼來畫一張“小狗”的設計圖:
#include <iostream>
#include <string>
usingnamespacestd; // 為了代碼簡潔,先告訴編譯器我們使用標準命名空間
// 1. 畫一張“小狗”的設計圖(定義一個類)
class Dog {
// 通常會把屬性設為private,像鎖在籠子里,保護起來,防止被隨意修改。后面會細講。
private:
string name; // 小狗的名字(成員變量)
int age; // 小狗的年齡(成員變量)
// 通常把行為設為public,像公共接口,誰都可以調用。
public:
// 2. 小狗的行為(成員函數)
void setName(string dogName) { // 給小狗起名的方法
name = dogName;
}
void setAge(int dogAge) { // 設置小狗年齡的方法
age = dogAge;
}
void bark() { // 小狗叫的方法
cout << name << " says: Wang Wang! I'm " << age << " years old." << endl;
// cout 可以理解為“打印到屏幕上”, endl 是換行。
// << 可以想象成“把內容輸送過去”。
}
};
int main() {
// 3. 根據圖紙制造一只真實的小狗(創建一個對象)
Dog myDog;
// 4. 指揮我的小狗做事(調用對象的方法)
myDog.setName("Wang Cai"); // 給我的小狗起名叫“旺財”
myDog.setAge(2); // 設置它的年齡為2歲
myDog.bark(); // 讓我的小狗叫
return0;
}
輸出結果:
Wang Cai says: Wang Wang! I'm 2 years old.
看,我們不需要關心bark()函數內部是怎么實現的,我們只需要知道“讓小狗叫”這個行為就行了。這就是封裝:把復雜的實現細節隱藏起來,只暴露簡單的使用接口。
下面我們用一張類圖來更形象地展示Dog類的設計:

圖示:Dog類將數據(name, age)私有(-)封裝,只對外提供公共(+)的方法來交互。
現在有個問題:在類的成員函數內部,如果參數名字和成員變量名字一樣怎么辦?比如:
void setName(string name) {
name = name; // 哪個name是參數?哪個name是成員變量?分不清了!
}
這就好比你在一個房間里,房間里有一個大喇叭(成員變量),你手里拿著一個小喇叭(參數)。你說“喇叭開喊”,別人怎么知道是哪個喇叭?
為了解決這個問題,C++提供了一個叫做 this指針 的秘密武器。
this指針:可以理解為對象自己的“身份證”或者“自指針”。在任何一個成員函數內部,this指針都默默地指向調用這個函數的那個對象自己。我們用this來修改上面的代碼:
class Dog {
private:
string name;
int age;
public:
void setName(string name) { // 參數名和成員變量名可以相同了
this->name = name; // this->name 表示“我自己的name”,
// 等號右邊的name是傳進來的參數。
// 這就好比說:“我自己的名字(this->name) = 你傳過來的新名字(name)”
}
void setAge(int age) {
this->age = age; // “我自己的年齡 = 你傳過來的新年齡”
}
void bark() {
// 即使這里不用,this指針也是存在的,可以寫成:
// cout << this->name << " says: Wang Wang!" << endl;
cout << name << " says: Wang Wang!" << endl; // 通常name前隱含了this->
}
};
一句話記住this:this就像每個對象心里的“我”字,它總是指向自己。當出現名字沖突時,用this->來明確指明“我自己的那個變量”。
現在我們來學習兩個特殊的函數:對象的“出生證明”和“遺囑”。
void都沒有)。~**。#include <iostream>
usingnamespacestd;
class Dog {
private:
string name;
int* ptr; // 一個指針,用來演示析構函數的作用
public:
// 1. 構造函數(可以重載,有多個版本)
Dog() { // 默認構造函數(無參)
name = "Unnamed";
ptr = newint(0); // 在對象創建時,動態申請一塊內存
cout << "A dog named \"" << name << "\" is born! (Default Constructor)" << endl;
}
Dog(string name, int age) { // 帶參數的構造函數
this->name = name;
ptr = newint(age); // 申請內存,并用age的值初始化
cout << "A dog named \"" << name << "\" is born! (Parameterized Constructor)" << endl;
}
// 2. 析構函數(只有一個,沒有參數)
~Dog() {
delete ptr; // 在對象銷毀時,釋放之前申請的內存,防止內存泄漏
cout << "The dog named \"" << name << "\" says goodbye... (Destructor)" << endl;
}
void bark() {
cout << name << " says: Wang Wang! I'm " << *ptr << " years old." << endl;
}
};
int main() {
cout << "Start of main" << endl;
{ // 一個新的作用域開始
Dog dog1; // 調用默認構造函數,對象dog1誕生!
Dog dog2("Buddy", 3); // 調用帶參構造函數,對象dog2誕生!
dog1.bark();
dog2.bark();
} // 作用域結束,dog1和dog2的生命周期到了,它們的析構函數會被自動調用!
cout << "End of main" << endl;
return0;
}
輸出結果:
Start of main
A dog named "Unnamed" is born! (Default Constructor)
A dog named "Buddy" is born! (Parameterized Constructor)
Unnamed says: Wang Wang! I'm 0 years old.
Buddy says: Wang Wang! I'm 3 years old.
The dog named "Buddy" says goodbye... (Destructor)
The dog named "Unnamed" says goodbye... (Destructor)
End of main
注意看析構函數調用的順序:后創建的對象(dog2)先被銷毀,先調用析構函數,就像拆房子要從上往下拆一樣。
const是“常量”(constant)的縮寫,意思是“不變的”。它有兩種主要用法:
就像你買了一個“防修改保護套”給對象套上,聲明這個對象自身是常量,不可修改。
const Dog myDog("Lucky", 5); // 創建一個常量小狗Lucky
// myDog.setName("Not Lucky"); // 錯誤!常量對象不能調用可能修改其內容的函數!
myDog.bark(); // 如果bark()函數保證不會修改對象,那么它就可以被調用
這就引出了下一個問題:如何讓bark()函數向編譯器保證它不會修改對象呢?
在成員函數的參數列表后面加上const關鍵字,這個函數就變成了常量成員函數。它向編譯器和使用者承諾:“我絕不會修改這個對象里的任何成員變量”。
class Dog {
// ... 其他成員同上 ...
// 常量成員函數:承諾不會修改對象狀態
void bark() const { // 注意函數聲明后的const
// this->age = 10; // 錯誤!在const成員函數內不允許修改成員變量!
cout << name << " says: Wang Wang! (I promise I won't change anything!)" << endl;
}
// 非常量成員函數:允許修改對象
void haveBirthday() {
(*ptr)++; // 年齡加1
}
};
int main() {
const Dog lucky("Lucky", 5); // 常量對象
lucky.bark(); // 正確:bark()是const函數,可以被常量對象調用
// lucky.haveBirthday(); // 錯誤!haveBirthday()不是const函數,常量對象不能調用
Dog regularDog("Regular", 2); // 非常量對象
regularDog.bark(); // 正確:非常量對象可以調用const函數
regularDog.haveBirthday(); // 正確:非常量對象也可以調用非const函數
return0;
}
生活化比喻:
const對象就像一個被玻璃罩罩住的博物館展品,只能看,不能摸。const成員函數就像是展品旁邊一個“只讀”的講解員,他只會給你講解(讀數據),絕不會動手改變展品(寫數據)。static意思是“靜態的”。類的static成員(變量或函數)有一個非常特別的屬性:它不屬于任何一個對象,而是屬于整個類。
#include <iostream>
usingnamespacestd;
class Dog {
private:
string name;
// 靜態成員變量:記錄創建了多少只小狗(不屬于任何一只狗,屬于Dog類本身)
staticint count; // 僅僅是在類內聲明,需要在類外單獨定義和初始化
public:
Dog(string n) : name(n) {
count++; // 每創建一只小狗,計數器就+1
cout << name << " joined! Total dogs: " << count << endl;
}
~Dog() {
count--; // 每消失一只小狗,計數器就-1
cout << name << " left! Total dogs: " << count << endl;
}
// 靜態成員函數:它沒有this指針,因為它不屬于某個特定對象
static int getCount() {
// cout << name; // 錯誤!靜態函數不能訪問普通的非靜態成員變量(name)
return count; // 正確!靜態函數可以訪問靜態成員變量
}
};
// 在類外定義和初始化靜態成員變量(很重要!)
int Dog::count = 0; // “::”是作用域解析運算符,意思是“Dog類里的count”
int main() {
cout << "Initial dog count: " << Dog::getCount() << endl; // 通過類名直接訪問靜態函數
Dog* dog1 = new Dog("Wang Cai");
cout << "Now, total dogs: " << Dog::getCount() << endl; // 輸出:1
{
Dog dog2("Xiao Bai");
cout << "Now, total dogs: " << Dog::getCount() << endl; // 輸出:2
// 也可以通過對象訪問靜態函數,但它屬于類,不屬于對象
cout << "Same count, accessed by dog2: " << dog2.getCount() << endl; // 輸出:2
} // dog2析構,count減1
cout << "After Xiao Bai left: " << Dog::getCount() << endl; // 輸出:1
delete dog1;
cout << "Final dog count: " << Dog::getCount() << endl; // 輸出:0
return0;
}
**記住static**:它是類的“公共財產”和“公共服務”,與具體的對象無關。
在C++中,類的private和protected成員是受保護的,外部函數和其他類的函數不能訪問。這就像你的私家臥室,外人不能進。
但有時候,你可能會希望你的最好的朋友可以破例進入你的臥室。friend(友元)就是這個“破例”。
注意:友元關系是單向的,也不具有傳遞性(你朋友的朋友不是你的朋友)。要謹慎使用友元,因為它破壞了封裝性。
#include <iostream>
usingnamespacestd;
// 提前聲明Dog類,這樣Friend函數才知道它的存在
class Dog;
// 一個全局函數(非成員函數)
void friendFunction(const Dog& d);
class Dog {
private:
string secret; // 小狗的秘密,私有成員
public:
Dog() : secret("I hid my bone under the tree!") {} // 初始化秘密
// 聲明友元函數!注意,這是在類的內部聲明的。
// 這句聲明就是在說:“friendFunction函數是我的朋友,讓它進我的私人房間!”
friend void friendFunction(const Dog& d);
};
// 實現友元函數
void friendFunction(const Dog& d) {
// 因為這個函數是Dog類的友元,所以它可以訪問Dog的私有成員secret
cout << "The dog tells me a secret: \"" << d.secret << "\"" << endl;
// 如果不是友元,這行代碼會報錯:無法訪問private成員
}
int main() {
Dog myDog;
// myDog.secret; // 錯誤!main函數不是友元,不能訪問私有成員。
friendFunction(myDog); // 正確!友元函數可以訪問。
return0;
}
輸出結果:
The dog tells me a secret: "I hid my bone under the tree!"
友元比喻:就像公司里,所有員工的薪資都是保密的(private),但HR經理的職能需要查看所有人的薪資,那么就可以把HR經理設為所有員工的friend。但普通員工之間不能互相查看薪資。
好啦,今天我們用了大量的比喻和簡單的代碼,學習了C++面向對象最最基礎的概念。我們來回顧一下:
| Class & Object | 設計圖紙 | |
| this pointer | ||
| Constructor/Destructor | 出生證明 | |
| const Member | 玻璃罩展品 | |
| static Member | 公共班費 | |
| friend | 最好的朋友 |
面向對象編程的思想就是:將數據和操作數據的方法捆綁在一起,形成一個“對象”,然后通過對象之間的交互來完成復雜的程序。它更符合我們人類對現實世界的認知方式。
猜你喜歡:
數據驅動編程:讓你的嵌入式代碼更優雅!
嵌入式中輕量級通信協議利器!
適用于嵌入式的輕量級環緩沖區管理庫!