1. C++类与对象进阶解析
在完成C++类和对象的基础学习后,我们需要深入探讨几个关键的高级特性。构造函数和析构函数是类中最基础也最重要的成员函数,它们分别在对象创建和销毁时自动调用。构造函数用于初始化对象的数据成员,而析构函数则负责清理资源。
cpp复制class MyClass {
public:
MyClass() { // 构造函数
std::cout << "对象被创建" << std::endl;
}
~MyClass() { // 析构函数
std::cout << "对象被销毁" << std::endl;
}
};
注意:如果类中有动态分配的内存,必须在析构函数中进行释放,否则会造成内存泄漏。
2. 拷贝控制:三/五法则
现代C++中的拷贝控制涉及五个特殊成员函数:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。这被称为"五法则"。
cpp复制class String {
char* data;
size_t length;
public:
// 拷贝构造函数
String(const String& other) {
length = other.length;
data = new char[length + 1];
std::copy(other.data, other.data + length + 1, data);
}
// 移动构造函数
String(String&& other) noexcept {
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
~String() {
delete[] data;
}
};
在实际开发中,如果定义了其中任何一个特殊成员函数,通常需要定义全部五个,以确保对象在各种情况下的行为一致。
3. 运算符重载实战
运算符重载允许我们为自定义类型定义运算符的行为。最常见的需要重载的运算符包括赋值运算符(=)、比较运算符(==, !=)和算术运算符(+, -)。
cpp复制class Vector {
double x, y;
public:
Vector operator+(const Vector& rhs) const {
return Vector(x + rhs.x, y + rhs.y);
}
bool operator==(const Vector& rhs) const {
return x == rhs.x && y == rhs.y;
}
Vector& operator+=(const Vector& rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
};
提示:重载运算符时,应保持运算符的原始语义。例如,+运算符应该执行某种形式的加法操作,而不是完全无关的操作。
4. 友元与静态成员
友元(friend)打破了类的封装性,允许特定函数或类访问私有成员。静态成员(static)属于类本身而非特定对象。
cpp复制class BankAccount {
static double interestRate; // 静态成员变量
double balance;
public:
static void setInterestRate(double rate) { // 静态成员函数
interestRate = rate;
}
friend class Bank; // 友元类
friend void transfer(BankAccount& from, BankAccount& to, double amount); // 友元函数
};
double BankAccount::interestRate = 0.03; // 静态成员初始化
静态成员常用于存储类级别的信息,如对象计数、共享配置等。友元机制虽然强大,但应谨慎使用,因为它会破坏封装性。
5. 类的高级特性
5.1 嵌套类
嵌套类是在另一个类内部定义的类,常用于实现仅由外部类使用的辅助功能。
cpp复制class LinkedList {
private:
class Node { // 嵌套类
public:
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};
Node* head;
public:
// LinkedList的方法...
};
5.2 局部类
局部类是在函数内部定义的类,其作用域仅限于该函数。
cpp复制void someFunction() {
class LocalClass {
int value;
public:
LocalClass(int v) : value(v) {}
void print() { std::cout << value << std::endl; }
};
LocalClass lc(42);
lc.print();
}
6. 类模板
类模板允许我们编写与数据类型无关的通用类,编译器会根据使用时的具体类型生成对应的类。
cpp复制template <typename T>
class Stack {
std::vector<T> elements;
public:
void push(const T& value) {
elements.push_back(value);
}
T pop() {
if (elements.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
T value = elements.back();
elements.pop_back();
return value;
}
};
使用类模板时,需要指定具体类型:
cpp复制Stack<int> intStack;
Stack<std::string> stringStack;
7. 类型转换运算符
C++允许我们为类定义类型转换运算符,使对象可以隐式或显式转换为其他类型。
cpp复制class Rational {
int numerator, denominator;
public:
operator double() const { // 转换为double
return static_cast<double>(numerator) / denominator;
}
explicit operator bool() const { // 显式转换为bool
return numerator != 0;
}
};
重要:隐式类型转换可能导致意外的行为,因此对于可能引起歧义的转换,应该使用explicit关键字。
8. 类设计的最佳实践
- 单一职责原则:一个类应该只有一个引起它变化的原因
- 开放封闭原则:类应该对扩展开放,对修改封闭
- 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖抽象
- 接口隔离原则:客户端不应该被迫依赖它们不使用的接口
- 里氏替换原则:派生类必须能够替换它们的基类
在实际项目中,良好的类设计可以显著提高代码的可维护性和可扩展性。我个人的经验是,在设计类时多花时间思考接口和职责划分,可以避免后期的很多重构工作。
