1. C++类与对象的核心概念回顾
在C++编程中,类和对象是最基础也最重要的概念之一。类可以看作是一个自定义的数据类型,它封装了数据(成员变量)和操作这些数据的方法(成员函数)。而对象则是类的具体实例,就像"人类"是一个类,"张三"就是人类的一个具体对象。
提示:理解类和对象的关系,就像理解"设计图"和"根据设计图建造的房子"之间的关系。类就是设计图,对象就是实际建造的房子。
C++中的类具有三大特性:
- 封装:将数据和操作数据的方法绑定在一起
- 继承:可以从现有类派生出新类
- 多态:同一操作作用于不同对象可以产生不同结果
2. 构造函数的深入探讨
2.1 构造函数的基本用法
构造函数是类中一种特殊的成员函数,它在创建对象时自动调用。构造函数的主要作用是初始化对象的状态。
cpp复制class Student {
public:
// 构造函数
Student(string name, int age) {
this->name = name;
this->age = age;
}
private:
string name;
int age;
};
// 创建对象时自动调用构造函数
Student stu("张三", 20);
2.2 构造函数的重载
和普通函数一样,构造函数也可以重载,即一个类可以有多个参数列表不同的构造函数。
cpp复制class Rectangle {
public:
Rectangle() { // 无参构造函数
width = height = 0;
}
Rectangle(int w, int h) { // 带参构造函数
width = w;
height = h;
}
Rectangle(int size) { // 另一个构造函数
width = height = size;
}
private:
int width, height;
};
2.3 初始化列表
C++提供了初始化列表语法,可以在构造函数体执行前初始化成员变量。这种方式效率更高,特别是对于const成员和引用成员,必须使用初始化列表。
cpp复制class Point {
public:
Point(int x, int y) : x(x), y(y) { // 初始化列表
// 构造函数体
}
private:
const int x;
const int y;
};
3. 拷贝控制:拷贝构造函数和移动构造函数
3.1 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它用同类的另一个对象来初始化新创建的对象。
cpp复制class String {
public:
String(const String& other) { // 拷贝构造函数
size = other.size;
data = new char[size];
std::copy(other.data, other.data + size, data);
}
private:
char* data;
size_t size;
};
3.2 移动构造函数(C++11引入)
移动构造函数是C++11引入的新特性,它通过"窃取"资源而不是复制资源来提高性能。
cpp复制class String {
public:
String(String&& other) noexcept { // 移动构造函数
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
private:
char* data;
size_t size;
};
注意:移动构造函数通常应该标记为noexcept,这样标准库容器在重新分配内存时会优先使用移动而非拷贝。
4. static关键字的多种用法
4.1 静态成员变量
静态成员变量属于类本身,而不是类的某个对象。所有对象共享同一个静态成员变量。
cpp复制class Counter {
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
private:
static int count; // 声明静态成员变量
};
int Counter::count = 0; // 定义并初始化静态成员变量
4.2 静态成员函数
静态成员函数不依赖于任何对象,可以直接通过类名调用。静态成员函数只能访问静态成员变量。
cpp复制class MathUtils {
public:
static double square(double x) {
return x * x;
}
};
// 调用静态成员函数
double result = MathUtils::square(5.0);
4.3 静态局部变量
在函数内部使用static关键字声明的变量,其生命周期会持续到程序结束,但作用域仍限于该函数。
cpp复制void func() {
static int count = 0; // 只初始化一次
++count;
cout << "调用次数: " << count << endl;
}
5. 类的其他高级特性
5.1 友元函数和友元类
友元打破了封装性,允许特定的非成员函数或类访问私有成员。
cpp复制class Box {
private:
double width;
// 声明友元函数
friend void printWidth(Box box);
// 声明友元类
friend class BoxPrinter;
};
void printWidth(Box box) {
// 可以访问私有成员width
cout << "宽度: " << box.width << endl;
}
class BoxPrinter {
public:
void print(Box box) {
// 可以访问Box的私有成员
cout << "Box宽度: " << box.width << endl;
}
};
5.2 const成员函数
const成员函数承诺不会修改对象的状态,可以在const对象上调用。
cpp复制class Account {
public:
double getBalance() const { // const成员函数
return balance;
}
private:
double balance;
};
const Account acc; // const对象
double b = acc.getBalance(); // 可以调用const成员函数
5.3 mutable成员变量
mutable修饰的成员变量可以在const成员函数中被修改。
cpp复制class Cache {
public:
int getValue(int key) const {
++accessCount; // 即使函数是const也可以修改mutable变量
return cache[key];
}
private:
std::map<int, int> cache;
mutable int accessCount = 0; // mutable变量
};
6. 类设计的最佳实践
6.1 三大法则(Rule of Three)
如果一个类需要自定义以下任何一个,那么它通常需要自定义所有三个:
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
6.2 五大法则(Rule of Five,C++11)
在C++11中,增加了移动构造函数和移动赋值运算符,扩展为五大法则:
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
6.3 零法则(Rule of Zero)
理想情况下,类应该不需要自定义任何特殊的成员函数,而是依赖编译器生成的默认版本。这通常通过使用智能指针和其他RAII类来实现。
cpp复制class SafeResource {
public:
// 不需要自定义析构函数、拷贝/移动操作
// 编译器生成的版本就能正确工作
private:
std::unique_ptr<Resource> resource; // 使用智能指针
};
7. 实际应用中的常见问题与解决方案
7.1 对象切片问题
当派生类对象被赋值给基类对象时,会发生对象切片,派生类特有的部分会被"切掉"。
解决方案:
- 使用指针或引用
- 使用智能指针
- 使用抽象基类
cpp复制class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Derived d;
Base b = d; // 对象切片,Derived特有部分丢失
Base* pb = new Derived(); // 正确,通过指针避免切片
7.2 静态成员初始化顺序问题
不同编译单元中的静态成员初始化顺序是不确定的,可能导致问题。
解决方案:
- 使用局部静态变量(Meyers' Singleton)
- 明确控制初始化顺序
cpp复制class Logger {
public:
static Logger& instance() {
static Logger logger; // 保证线程安全(C++11起)
return logger;
}
private:
Logger() {} // 私有构造函数
};
7.3 多线程环境下的静态成员
静态成员的初始化在多线程环境下可能会有竞争条件。
解决方案:
- 使用C++11的局部静态变量(线程安全)
- 使用std::call_once
- 手动同步
cpp复制class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11保证线程安全
return instance;
}
private:
Singleton() {}
};
8. 现代C++中的类特性(C++11/14/17/20)
8.1 默认和删除的函数
可以显式要求编译器生成默认实现或删除特定函数。
cpp复制class NonCopyable {
public:
NonCopyable() = default;
~NonCopyable() = default;
// 禁止拷贝
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
8.2 override和final关键字
override明确表示要覆盖基类虚函数,final阻止进一步覆盖或继承。
cpp复制class Base {
public:
virtual void foo() {}
};
class Derived : public Base {
public:
void foo() override {} // 明确表示覆盖
virtual void bar() final {} // 不能再被覆盖
};
class FinalClass final {}; // 不能被继承
8.3 结构化绑定(C++17)
可以方便地解构类成员。
cpp复制struct Point { int x, y; };
Point p{1, 2};
auto [x, y] = p; // x=1, y=2
8.4 三向比较运算符(C++20)
简化比较运算符的实现。
cpp复制class Value {
public:
auto operator<=>(const Value&) const = default;
// 自动生成 ==, !=, <, <=, >, >=
};
在实际开发中,我发现合理使用现代C++特性可以显著提高代码的安全性和可维护性。特别是在资源管理方面,遵循RAII原则(资源获取即初始化)可以避免很多内存泄漏和资源泄漏问题。对于大型项目,良好的类设计是代码可维护性的关键,建议在项目初期就规划好类的层次结构和接口设计。
