📌 阅读时长:25分钟 | 关键词:C++、类、对象、构造函数、析构函数、this指针、静态成员、const成员
引言
前几篇文章我们一直在和"过程式"的代码打交道------函数、指针、数组。但从这一篇开始,我们要换一种思维:面向对象。面向对象不是一种语法,而是一种组织代码的哲学------把数据和操作数据的方法打包在一起,形成一个"类",就像现实世界中的"车"有颜色、速度这些属性,也有加速、刹车这些行为。
一、什么是类?什么是对象?
类 是自定义的数据类型,描述了"有一类事物是什么样的"。对象是类的实例,是"具体的某一个"。
cpp
class Dog { // 这是类:定义了狗是什么样的
public:
std::string name;
int age;
void bark() {
std::cout << name << ":汪汪!" << std::endl;
}
};
int main() {
Dog myDog; // 这是对象:一只具体的狗
myDog.name = "旺财";
myDog.age = 3;
myDog.bark(); // 输出:旺财:汪汪!
}
| 概念 | 类比 | C++ 关键词 |
|---|---|---|
| 类 | 蓝图/设计图纸 | class |
| 对象 | 根据蓝图造出的具体实物 | 类的变量 |
二、访问控制:public 和 private
类的成员可以设置不同的访问权限,这是面向对象封装性的核心:
cpp
class BankAccount {
public: // 外部可以访问
void deposit(double amount) { balance += amount; }
double getBalance() const { return balance; }
private: // 只有类内部可以访问
double balance = 0.0;
};
int main() {
BankAccount acc;
acc.deposit(1000);
std::cout << acc.getBalance() << std::endl; // 1000 ✅
// acc.balance = -100; // ❌ 编译错误!private 成员无法直接访问
}
| 访问修饰符 | 类内部 | 派生类 | 外部 |
|---|---|---|---|
public |
✅ | ✅ | ✅ |
private |
✅ | ❌ | ❌ |
protected |
✅ | ✅ | ❌ |
💡 默认规则:
class默认是private,struct默认是public。
三、构造函数:对象的"出生证明"
构造函数在对象创建时自动调用,负责初始化。名称与类名相同,没有返回值。
3.1 默认构造函数 vs 有参构造函数
cpp
class Cat {
public:
std::string name;
int age;
// 默认构造函数(无参)
Cat() : name("未知"), age(0) {
std::cout << "一只猫诞生了" << std::endl;
}
// 有参构造函数
Cat(const std::string &n, int a) : name(n), age(a) {
std::cout << name << " 诞生了!" << std::endl;
}
void meow() const {
std::cout << "喵!我是 " << name << "," << age << " 岁" << std::endl;
}
};
int main() {
Cat c1; // 调用默认构造函数
Cat c2("Tom", 2); // 调用有参构造函数
c1.meow(); // 喵!我是 未知,0 岁
c2.meow(); // 喵!我是 Tom,2 岁
}
3.2 构造函数初始化列表
初始化列表在 : 后面直接初始化成员,比在函数体内赋值更高效 ------特别是对于 const 成员和引用成员,必须使用初始化列表:
cpp
class MyClass {
public:
const int id; // const 成员必须用初始化列表
int &ref; // 引用成员也必须用初始化列表
int value;
MyClass(int i, int &r, int v) : id(i), ref(r), value(v) {
// 主体可以留空
}
};
3.3 析构函数:对象消亡时的"告别"
名称在类名前加 ~,无参无返回值,对象销毁时自动调用。常用于释放动态分配的资源:
cpp
class ResourceHolder {
private:
int *data;
public:
ResourceHolder() {
data = new int[10];
std::cout << "资源已分配" << std::endl;
}
~ResourceHolder() { // 析构函数
delete[] data;
std::cout << "资源已释放" << std::endl;
}
};
| 构造函数 vs 析构函数 | 构造 | 析构 |
|---|---|---|
| 调用时机 | 对象创建时 | 对象销毁时 |
| 名称 | ClassName(...) |
~ClassName() |
| 参数 | 可有参可无参 | 不能有参数 |
| 数量 | 可多个(重载) | 只能有一个 |
四、隐藏的 this 指针
每个非静态成员函数都有一个隐含的 this 指针,指向调用该函数的当前对象:
cpp
class MyClass {
public:
int value;
void setValue(int value) {
this->value = value; // this->value 是成员变量,value 是参数
}
void print() const {
// this 在 const 函数中类型为 const MyClass* const
std::cout << this->value << std::endl;
}
};
五、静态成员:属于类本身,不属于任何一个对象
cpp
class MyClass {
public:
static int count; // 静态成员变量声明
MyClass() { count++; } // 每创建一个对象,计数+1
static void printCount() { // 静态成员函数
std::cout << "当前对象数:" << count << std::endl;
// std::cout << value; // ❌ 静态函数不能访问非静态成员!(没有 this)
}
};
int MyClass::count = 0; // ⚠️ 静态成员变量必须在类外部定义(初始化)
int main() {
MyClass::printCount(); // 0 --- 通过类名直接调用
MyClass a, b, c;
MyClass::printCount(); // 3
}
| 静态成员 | 访问方式 | 有无 this 指针 | 可否访问非静态成员 |
|---|---|---|---|
| 静态变量 | 类名::变量名 或 对象.变量名 |
无关 | --- |
| 静态函数 | 类名::函数名() 或 对象.函数名() |
无 | ❌ |
六、const 成员:不可修改的承诺
cpp
class MyClass {
public:
const int id; // const 成员变量
MyClass(int i) : id(i) {} // 只能用初始化列表赋值
int getValue() const { // const 成员函数
// value = 10; // ❌ 不能修改任何成员变量
return value;
}
private:
int value = 0;
};
| const 用于 | 语法 | 含义 |
|---|---|---|
| 成员变量 | const int id; |
初始化后不能修改 |
| 成员函数 | int get() const; |
承诺不修改对象状态 |
| 对象 | const MyClass obj; |
只能调用 const 成员函数 |
小结
| 序号 | 知识点 | 一句话总结 |
|---|---|---|
| 1 | 类与对象 | 类是蓝图,对象是实物;成员变量=属性,成员函数=行为 |
| 2 | 访问控制 | public 对外开放,private 内部使用,protected 子类可见 |
| 3 | 构造函数 | 对象创建时自动调用,初始化列表优于函数体赋值 |
| 4 | 析构函数 | 对象销毁时自动调用,释放资源 |
| 5 | this 指针 | 指向当前对象的隐含指针,区分同名成员与参数 |
| 6 | 静态成员 | 属于类本身,所有对象共享,无 this 指针 |
| 7 | const 成员 | 变量不可改(初始化列表赋值),函数承诺不改状态 |
下一篇文章,我们将深入拷贝控制------浅拷贝的陷阱、深拷贝的实现、以及 C++11 的智能指针初探。
本文是「C++ 从基础到项目实战」系列的第 5 篇。关注我,不错过后续更新。