1. C++面向对象编程基础
在C++的世界里,类和对象是构建复杂系统的基石。作为一名从C语言转战C++的老程序员,我至今记得第一次理解类概念时的顿悟时刻——那感觉就像突然获得了组织代码的超能力。与C语言中分散的结构体和函数不同,类将数据和对数据的操作完美封装在一起,这正是面向对象编程(OOP)的核心思想。
1.1 从结构体到类的进化
C语言的结构体只能定义数据成员,而C++的类则可以包含:
- 数据成员(属性)
- 成员函数(方法)
- 访问控制(public/private/protected)
cpp复制// C风格结构体
struct Person_C {
char name[20];
int age;
};
// C++类
class Person_CPP {
private:
string name;
int age;
public:
void introduce() {
cout << "I'm " << name << ", " << age << " years old." << endl;
}
};
这种封装性带来了几个革命性优势:
- 数据和行为绑定,更符合现实世界认知
- 隐藏实现细节,只暴露必要接口
- 减少命名冲突,提高代码可维护性
1.2 类的定义与实例化
定义类的基本语法:
cpp复制class ClassName {
access_specifier:
member1;
member2;
...
};
实例化对象的几种方式:
cpp复制// 栈上分配
ClassName obj1;
// 堆上分配
ClassName* obj2 = new ClassName();
// 作为其他类的成员
class Container {
ClassName memberObj;
};
关键经验:优先使用栈分配,除非对象生命周期需要延长或对象很大。栈分配的对象会自动释放,避免内存泄漏。
2. 类成员深度解析
2.1 访问控制的三重境界
C++提供三种访问修饰符:
- public:类外可直接访问
- private:仅类内可访问(默认)
- protected:类内和派生类可访问
cpp复制class AccessDemo {
public:
int publicVar;
private:
string privateVar;
protected:
double protectedVar;
};
设计原则:
- 数据成员通常设为private(封装性)
- 对外接口设为public
- protected用于继承体系
踩坑提醒:忘记设置访问权限是新手常见错误。未指定时class默认private,struct默认public。
2.2 成员函数与this指针
成员函数的特别之处在于它隐式接收this指针,指向调用该函数的对象实例。
cpp复制class Counter {
int count;
public:
void increment() {
this->count++; // 显式使用this
// 等价于 count++;
}
};
this指针的典型应用场景:
- 区分成员变量和参数
cpp复制void setName(string name) {
this->name = name;
}
- 链式调用
cpp复制return *this;
2.3 特殊成员函数
编译器会自动生成6个特殊成员函数:
- 默认构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数(C++11)
- 移动赋值运算符(C++11)
cpp复制class SpecialFuncs {
public:
SpecialFuncs(); // 默认构造
~SpecialFuncs(); // 析构
SpecialFuncs(const SpecialFuncs&); // 拷贝构造
SpecialFuncs& operator=(const SpecialFuncs&); // 拷贝赋值
SpecialFuncs(SpecialFuncs&&); // 移动构造
SpecialFuncs& operator=(SpecialFuncs&&); // 移动赋值
};
重要原则:遵循三大法则(Rule of Three)——如果需要自定义拷贝构造、拷贝赋值或析构中的任何一个,通常需要自定义全部三个。
3. 构造函数与初始化
3.1 构造函数重载
类可以有多个构造函数,通过参数列表区分:
cpp复制class Student {
string name;
int score;
public:
Student() : name("Unknown"), score(0) {} // 默认构造
Student(string n) : name(n), score(0) {} // 带名字构造
Student(string n, int s) : name(n), score(s) {} // 完整构造
};
初始化列表语法:
cpp复制: member1(value1), member2(value2)
比在构造函数体内赋值更高效,特别是对于:
- const成员
- 引用成员
- 没有默认构造的类成员
3.2 委托构造函数(C++11)
一个构造函数可以调用同类中的另一个构造函数:
cpp复制class Employee {
string id;
string department;
public:
Employee(string id) : id(id), department("Unassigned") {}
Employee(string id, string dep) : Employee(id) {
department = dep;
}
};
3.3 explicit关键字
防止隐式转换带来的意外:
cpp复制class MyNumber {
int num;
public:
explicit MyNumber(int n) : num(n) {}
};
void func(MyNumber n) {}
// func(5); // 错误!不能隐式转换
func(MyNumber(5)); // 正确,显式构造
最佳实践:单参数构造函数都应声明为explicit,除非确实需要隐式转换。
4. 静态成员与友元
4.1 静态成员
静态成员属于类而非对象:
- 静态数据成员:所有对象共享
- 静态成员函数:只能访问静态成员
cpp复制class BankAccount {
static int totalAccounts; // 声明
public:
static int getTotal() { return totalAccounts; }
};
int BankAccount::totalAccounts = 0; // 定义并初始化
使用场景:
- 计数器
- 共享配置
- 工具函数
4.2 友元机制
打破封装的特权关系:
cpp复制class Secret {
int secretCode;
friend class Spy; // 友元类
friend void hack(Secret&); // 友元函数
};
class Spy {
public:
void expose(Secret& s) {
cout << s.secretCode << endl; // 可以访问private成员
}
};
void hack(Secret& s) {
s.secretCode = 0; // 友元函数也可以访问
}
使用建议:
- 谨慎使用,会破坏封装性
- 常见用于运算符重载
- 测试类可能需要成为被测类的友元
5. 实战经验与性能考量
5.1 对象大小与内存对齐
使用sizeof查看对象大小:
cpp复制class Empty {};
class Simple {
int a;
char b;
};
cout << sizeof(Empty) << endl; // 输出1(不能为0)
cout << sizeof(Simple) << endl; // 通常是8(考虑对齐)
内存对齐原则:
- 成员按声明顺序存储
- 编译器可能插入填充字节
- 对齐要求因平台而异
优化技巧:
- 按大小降序排列成员
- 将常用成员放在前面
- 注意虚函数表指针的开销
5.2 内联成员函数
在类定义内实现的成员函数默认inline:
cpp复制class InlineDemo {
public:
void fastMethod() { /*...*/ } // 隐式inline
void slowMethod();
};
inline void InlineDemo::slowMethod() { /*...*/ } // 显式inline
适用场景:
- 简单、频繁调用的函数
- 性能关键路径
- 小型getter/setter
注意:inline只是建议,编译器最终决定是否内联。过度使用可能导致代码膨胀。
5.3 常见陷阱与调试技巧
- 未初始化成员变量:
cpp复制class Danger {
int *ptr; // 未初始化!
public:
Danger() {} // 忘记初始化ptr
};
解决方案:总是初始化所有成员,使用初始化列表
- 浅拷贝问题:
cpp复制class ShallowCopy {
char *data;
public:
ShallowCopy(const char* str) {
data = new char[strlen(str)+1];
strcpy(data, str);
}
~ShallowCopy() { delete[] data; }
// 缺少拷贝构造和拷贝赋值!
};
ShallowCopy a("hello");
ShallowCopy b = a; // 灾难!双重释放
- 对象切片(Object Slicing):
cpp复制class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Derived d;
Base b = d; // 只复制Base部分,Derived部分被"切片"
调试建议:
- 在构造函数和析构函数中加入日志
- 使用valgrind检测内存问题
- 对复杂类实现完整的特殊成员函数
