本章目标:类、封装、构造函数、析构函数、继承、多态、 虚函数、移动语义(C++11 起最重要的部分之一)。 这是 C++ 最核心的知识块。
class Dog {
public: // 公开:外面能访问
Dog(std::string name, int age)
: name_(std::move(name)), age_(age) {} // 构造函数
void bark() const {
std::println("{}: 汪汪!", name_);
}
int age() const { return age_; } // 读取器 getter
private: // 私有:只有类内部能访问
std::string name_;
int age_;
};
Dog d("旺财", 3);
d.bark();
要点:
class Point {
public:
Point() {} // 默认构造
Point(int x, int y) : x_(x), y_(y) {} // 带参构造
// 初始化列表 : x_(x), y_(y) —— 成员在冒号后初始化
// (比在函数体里赋值更高效、更安全)
private:
int x_ = 0, y_ = 0; // 默认成员初始化器
};
三种写法等价:
Point a; // 默认构造(注意不是 Point a()!)
Point b{3, 4}; // 花括号,推荐
Point c = Point{3, 4}; // 显式
新手大坑:Point a(); 会被解析成"函数声明",不是创建对象! 用花括号 Point a{}; 就永远不会踩这个坑。
class File {
public:
~File() { // 析构函数:~类名
std::println("文件已关闭");
}
};
把释放逻辑写在析构函数里——但优先用智能指针/标准库, 让析构函数保持"默认"。
C++ 传参、返回、赋值都会触发"拷贝"或"移动"。
std::string s1 = "hello";
std::string s2 = s1; // 拷贝:s2 是独立副本
std::string s3 = std::move(s1); // 移动:s1 的资源"搬家"到 s3
// s1 变成空壳(内容是合法的但未指定)
为什么需要移动? 拷贝大字符串 = 复制所有字节。移动只把"指针+长度"交接过去,O(1)。
什么时候自动移动?返回局部对象时(RVO/移动),临时对象传参时。 自己写类需要移动语义时再定义移动构造/赋值,新手先会用 std::move。
"Rule of Zero"(零规则):如果类里的成员都能自管理 (string、vector、智能指针),你什么都不用写——默认拷贝、 移动、析构全都正确。这是现代 C++ 最推荐的写法。
class Animal {
public:
virtual void speak() const { // virtual = 虚函数,可被覆盖
std::println("动物叫");
}
virtual ~Animal() = default; // 重要!虚析构
};
class Cat : public Animal {
public:
void speak() const override { // override = 明确覆盖父类
std::println("喵");
}
};
class Dog : public Animal {
public:
void speak() const override { std::println("汪"); }
};
用法(多态的核心):
std::vector<std::unique_ptr<Animal>> zoo;
zoo.push_back(std::make_unique<Cat>());
zoo.push_back(std::make_unique<Dog>());
for (const auto& a : zoo) a->speak();
// 输出:喵、汪 —— 同一个接口,不同行为!
三个关键点:
——否则 delete 父类指针时不会调用子类析构,泄漏/崩溃
class Shape {
public:
virtual double area() const = 0; // 纯虚函数
virtual ~Shape() = default;
};
// Shape 是抽象类:不能创建 Shape 对象,只能被继承
class Circle : public Shape {
double r_;
public:
explicit Circle(double r) : r_(r) {}
double area() const override { return 3.14159 * r_ * r_; }
};
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
用途:定义"接口契约",所有子类必须实现。 这就是面向对象设计的核心武器。
有虚函数的类,内部藏了一个"虚函数表"(vtable), 记录每个虚函数真正应该调谁的实现。 调用虚函数 = 查表跳转,比普通函数调用慢一点点(可忽略)。 "同一个指针,指向不同子类,调出不同行为"——就是多态。
现代 C++ 建议:"is-a"(确实是)才用继承; "has-a"(拥有)用组合(成员对象/智能指针成员)。
class Engine {}; // 组合:
class Car {
Engine engine_; // Car 有一个 Engine(has-a)
};
class Dog : public Animal {}; // 继承:Dog 是一种 Animal(is-a)
继承滥用会让类层次又深又脆,能用组合就用组合。
struct 默认 public;class 默认 private。就这么点区别。
约定:只装数据的小东西用 struct,有行为的用 class。
struct Config {
int width = 800;
int height = 600;
std::string title;
};
练习题
验证它可以拷贝、可以移动、析构正确(Rule of Zero 体验)。