1. C++类与对象的核心概念回顾
在C++编程中,类和对象是最基础也是最重要的概念之一。类(Class)可以看作是一个自定义的数据类型,它封装了数据(成员变量)和操作这些数据的方法(成员函数)。而对象(Object)则是类的具体实例,就像"人类"是一个类,"张三"就是"人类"这个类的一个具体对象。
提示:理解类和对象的关系,可以想象"类"是蓝图,"对象"是根据蓝图建造的房子。同一张蓝图可以建造多栋房子,同样一个类也可以创建多个对象。
C++中的类通常包含以下几个关键部分:
- 访问修饰符(public、private、protected)
- 成员变量(属性)
- 成员函数(方法)
- 构造函数和析构函数
- this指针
2. 类的三大特性深入解析
2.1 封装:数据与行为的捆绑
封装是面向对象编程的第一大特性,它将数据和对数据的操作捆绑在一起,并对外隐藏实现细节。在C++中,我们通过访问修饰符来实现封装:
cpp复制class BankAccount {
private: // 私有成员,外部无法直接访问
double balance;
public: // 公有接口,外部可以访问
void deposit(double amount) {
if (amount > 0) balance += amount;
}
double getBalance() const {
return balance;
}
};
注意:良好的封装可以防止数据被意外修改,提高代码的安全性和可维护性。在实际开发中,成员变量通常设为private,只通过public方法提供访问接口。
2.2 继承:代码复用的利器
继承允许我们基于现有类创建新类,新类继承了原有类的特性,并可以添加新的特性或修改继承的特性。C++支持多种继承方式:
cpp复制// 基类
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数,使Shape成为抽象类
virtual ~Shape() {} // 虚析构函数
};
// 派生类
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
void draw() const override {
std::cout << "Drawing a circle with radius " << radius << std::endl;
}
};
继承的三种方式:
- public继承:基类的public成员在派生类中仍为public,protected仍为protected
- protected继承:基类的public和protected成员在派生类中都变为protected
- private继承:基类的public和protected成员在派生类中都变为private
2.3 多态:同一接口,不同实现
多态允许我们通过基类的指针或引用来调用派生类的方法。C++通过虚函数实现运行时多态:
cpp复制void drawShape(const Shape& shape) {
shape.draw(); // 根据实际对象类型调用相应的draw方法
}
int main() {
Circle circle(5.0);
drawShape(circle); // 调用Circle的draw方法
return 0;
}
多态的实现依赖于虚函数表(vtable)机制。当类中包含虚函数时,编译器会为该类创建一个虚函数表,每个对象会包含一个指向虚函数表的指针(vptr)。
3. 类设计的高级技巧
3.1 构造函数与析构函数的最佳实践
构造函数用于初始化对象,析构函数用于清理资源。在C++11之后,我们有了更多构造函数的选择:
cpp复制class MyString {
private:
char* data;
size_t length;
public:
// 默认构造函数
MyString() : data(nullptr), length(0) {}
// 参数化构造函数
MyString(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 移动构造函数 (C++11)
MyString(MyString&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// 析构函数
~MyString() {
delete[] data;
}
// 禁用拷贝构造函数和拷贝赋值
MyString(const MyString&) = delete;
MyString& operator=(const MyString&) = delete;
};
重要提示:遵循"三/五法则"——如果一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,那么它通常需要全部这三个函数。在C++11后扩展为"五法则",增加了移动构造函数和移动赋值运算符。
3.2 运算符重载的艺术
运算符重载可以让我们的类使用起来像内置类型一样自然:
cpp复制class Vector {
private:
double x, y;
public:
Vector(double x, double y) : x(x), y(y) {}
// 加法运算符重载
Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}
// 输出运算符重载(通常声明为友元函数)
friend std::ostream& operator<<(std::ostream& os, const Vector& v) {
return os << "(" << v.x << ", " << v.y << ")";
}
};
常见的可重载运算符包括:
- 算术运算符:+、-、*、/、%
- 关系运算符:==、!=、<、>、<=、>=
- 赋值运算符:=、+=、-=等
- 下标运算符:[]
- 函数调用运算符:()
- 类型转换运算符:operator type()
3.3 静态成员与友元
静态成员属于类而不是对象,所有对象共享同一份静态成员:
cpp复制class Counter {
private:
static int count; // 静态成员变量声明
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; } // 静态成员函数
};
int Counter::count = 0; // 静态成员变量定义
友元(friend)可以突破封装,允许特定函数或类访问私有成员:
cpp复制class Box {
private:
double width;
public:
friend void printWidth(const Box& box); // 友元函数
friend class BoxPrinter; // 友元类
};
void printWidth(const Box& box) {
std::cout << box.width; // 可以访问私有成员
}
4. 现代C++中的类特性
4.1 智能指针与资源管理
现代C++推荐使用智能指针来管理动态分配的内存,避免内存泄漏:
cpp复制#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void use() { std::cout << "Using resource\n"; }
};
void process() {
auto res = std::make_unique<Resource>(); // 独占所有权
res->use();
auto sharedRes = std::make_shared<Resource>(); // 共享所有权
auto anotherRef = sharedRes;
} // 自动释放资源
主要的智能指针类型:
- std::unique_ptr:独占所有权,不能拷贝只能移动
- std::shared_ptr:共享所有权,使用引用计数
- std::weak_ptr:不增加引用计数的共享指针,用于解决循环引用
4.2 移动语义与完美转发
C++11引入的移动语义可以避免不必要的拷贝,提高性能:
cpp复制class Buffer {
private:
int* data;
size_t size;
public:
// 移动构造函数
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移动赋值运算符
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
完美转发允许函数模板将其参数原封不动地转发给其他函数:
cpp复制template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 完美转发
}
4.3 Lambda表达式与函数对象
Lambda表达式是现代C++中强大的特性,可以方便地创建匿名函数:
cpp复制auto compare = [](int a, int b) { return a < b; };
std::vector<int> nums = {3, 1, 4, 1, 5, 9};
std::sort(nums.begin(), nums.end(), compare);
// 捕获列表
int threshold = 5;
auto countAbove = [threshold](int x) { return x > threshold; };
int above = std::count_if(nums.begin(), nums.end(), countAbove);
Lambda表达式的完整语法:
cpp复制[capture](parameters) mutable -> return-type { body }
5. 常见问题与最佳实践
5.1 对象生命周期管理
理解对象的创建和销毁时机至关重要:
- 栈上对象:在作用域结束时自动销毁
- 堆上对象:需要手动delete或由智能指针管理
- 成员对象:随包含它的对象一起创建和销毁
- 静态对象:在程序启动时创建,结束时销毁
- 临时对象:在完整表达式结束时销毁
注意:避免返回局部对象的引用或指针,这会导致悬垂引用/指针问题。
5.2 继承与多态的陷阱
在使用继承和多态时需要注意:
- 基类析构函数应该声明为virtual,否则通过基类指针删除派生类对象会导致未定义行为
- 避免过度使用继承,优先考虑组合而非继承
- 小心菱形继承问题,可以使用虚继承解决
- 重写虚函数时使用override关键字,避免拼写错误
cpp复制class Base {
public:
virtual ~Base() {} // 虚析构函数
virtual void foo() const;
};
class Derived : public Base {
public:
void foo() const override; // 明确表示重写
};
5.3 性能优化技巧
- 返回值优化(RVO)和命名返回值优化(NRVO):现代编译器会自动优化返回值的拷贝
- 小对象适合在栈上分配,大对象适合在堆上分配
- 避免不必要的拷贝,使用移动语义
- 虚函数调用有额外开销,在性能关键路径上谨慎使用
- 对象布局考虑缓存友好性
在实际项目中,我发现遵循这些原则可以显著提高代码质量和性能:
- 单一职责原则:一个类只做一件事
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:派生类应该能够替换基类
- 接口隔离原则:客户端不应该被迫依赖它不用的接口
- 依赖倒置原则:依赖抽象而非具体实现
