11、类的详细介绍
类不只是把几个变量和函数写到一起。它要表达一个对象允许处于哪些状态、哪些操作能改变它,以及资源由谁管理。先讲清对象与成员,再讨论构造、复制、移动,代码才不会只剩下语法名词。
类与对象分别是什么
本文使用 C++17。类声明一种类型,对象是这个类型的具体实例。每个对象拥有自己的非静态数据成员;成员函数可以通过隐含的 this 访问当前对象。
class 与 struct 都能有构造函数、成员函数、继承和模板。二者的主要语言区别是默认成员访问权限与默认继承访问权限:class 默认 private,struct 默认 public,不是一个“只能放数据”、另一个“才能有函数”。
普通成员与静态成员
两个对象的 number 可以分别为 10 与 20,而静态数据成员 A::count 是同一个共享对象。静态成员函数没有 this,不能不经某个实例就直接访问它的非静态成员。

图中的 count=2 是示例显式设置的值,不是编译器自动统计对象个数。也不要把“类共享一个静态成员”理解为标准规定存在一整块固定的“类内存区”。
构造与析构负责建立和结束对象状态
构造函数通过成员初始化列表初始化成员;初始化顺序由成员在类中的声明顺序决定,不是列表书写顺序。成员函数体里再赋值,是在成员已经初始化之后执行另一项操作。
析构函数在对象生命周期结束时执行。若成员使用 string、vector、unique_ptr 等管理资源,往往可以依靠成员自动清理,优先遵守“零法则”,不要为了演示语法就重复手工 new/delete。
public 提供外部接口,private 隐藏实现并维持不变量,protected 用于允许派生类访问。访问控制是编译期语言规则,不是用于保护秘密数据的安全边界。
C++17 完整示例
下面的类有显式单参数构造、静态成员函数、默认复制移动及一个比较运算符,所有数据都由值类型管理。
#include <cassert>
#include <iostream>
#include <utility>
class A {
public:
A() = default;
explicit A(int value) : number_(value) {}
A(const A&) = default;
A& operator=(const A&) = default;
A(A&&) = default;
A& operator=(A&&) = default;
~A() = default;
int number() const { return number_; }
static int count() { return count_; }
static void setCount(int value) { count_ = value; }
bool operator<(const A& other) const { return number_ < other.number_; }
private:
int number_ = 0;
inline static int count_ = 0;
};
int main() {
A first(10);
A second(20);
A::setCount(2);
assert(first < second);
assert(first.count() == second.count());
A copied = first;
A moved = std::move(second);
copied = moved;
assert(copied.number() == 20);
std::cout << first.number() << ' ' << moved.number() << ' ' << A::count() << '\n';
}
输出 10 20 2。示例中的移动处理的是 int 成员,按成员移动并不保证比复制更快,也不承诺把源成员清零。
复制构造与复制赋值不是一回事
A copied = first 在创建新对象,是复制构造;copied = moved 是给已存在对象赋值。所谓“赋值构造函数”容易混淆,应分别称复制构造函数与复制赋值运算符。
移动构造或移动赋值只有在重载决议选择相应操作时才会发生。std::move 本身只是转换表达式类别,不会自动搬走资源。具名的右值引用参数在表达式中仍是左值,需要按意图继续转移或转发。
持有裸资源时,默认逐成员复制可能复制同一资源句柄,引发重复释放;这时应明确深拷贝、禁止拷贝或改用 RAII 成员,而不是仅仅“补上五个函数”就认为安全。
default、delete 与 explicit 解决不同问题
= default请求生成默认实现,仍可能因为成员不支持相应操作而被定义为删除;不保证某个操作永远可调用。= delete禁止指定操作,例如禁止复制一个独占资源管理器。它与释放动态对象的 delete 表达式不是同一种语法用途。- explicit 限制隐式转换,例如 A 的构造函数不允许把整数 10 在普通实参传递中悄悄转成 A;直接写
A(10)仍然允许。 - 运算符重载不能创造新运算符、改变优先级或参数个数;应该保持与类型语义一致,不要为了炫技改写习惯含义。
练习与依据
给类增加 vector 成员,观察默认复制后的两个容器是否独立;再换成 unique_ptr 成员,解释为什么默认复制不再可用。结合 C++17 类与特殊成员规则核查,而不是依赖一次运行观察对象的固定内存布局。
