这篇笔记承接上一篇关于“指针与引用”的内容,重点聚焦于类的组织形式、static 的多重含义以及对象内部的机制。同时,为了内容的完整性,我们将再次梳理引用与指针的核心区别。
一、Class 与 Struct:孪生兄弟
很多从其他语言(如 C# 或 Java)转来的开发者通常认为 struct 是轻量级的值类型,而 class 是引用类型。但在 C++ 中,它们几乎是一模一样的。
1.1 唯一的区别:默认可见性
在 C++ 编译器眼中,class 和 struct 的区别仅仅在于默认的访问权限(Access Modifier):
- Class:成员默认为
private。 - Struct:成员默认为
public。
class Player { int x, y; // 默认为 Private,外部无法直接访问public: void Move(int xa, int ya);};
struct Vector2 { int x, y; // 默认为 Public,外部可以直接访问};1.2 业界惯例(Best Practices)
虽然技术上你可以用 struct 做任何 class 能做的事(包括继承、虚函数等),但为了代码的可读性,C++ 开发者通常遵循以下约定:
- 使用 Struct:当通过它来表示一组纯粹的数据集合(POD - Plain Old Data),只包含数据成员,几乎没有复杂的方法逻辑时。例如:坐标点、颜色值、数据包。
- 使用 Class:当对象包含复杂的逻辑、私有数据、或者涉及继承体系时。
二、Static 关键字:C++ 中的“变色龙”
static 是 C++ 中最令人困惑的关键字之一,因为它在不同的位置有截然不同的含义。我们需要分场景来理解。
2.1 场景一:在类/结构体外部(Internal Linkage)
当你在 .cpp 文件中(全局作用域)定义一个 static 变量或函数时。
- 含义:“不要让链接器(Linker)看到我。”
- 作用:该变量/函数只在当前编译单元(当前的
.cpp文件)内部可见。 - 应用:这类似于许多语言中的
private模块级变量。如果你在A.cpp和B.cpp中都定义了static int s_Variable,它们是互不干扰的两个独立变量。如果不加static,链接器在合并目标文件时会报“重复符号”(Duplicate Symbol)错误。
2.2 场景二:在类/结构体内部(Shared Memory)
当 static 出现在类定义内部时。
- 含义:“这个成员属于类本身,而不是属于某个对象。”
- 内存机制:无论你实例化了多少个对象(甚至一个都不实例化),
static成员变量在内存中只有一份。所有对象共享这一份数据。 - 静态方法:
- 静态方法没有
this指针。 - 因此,静态方法无法访问类中的非静态成员(因为它不知道该去读哪个对象的
x或y)。
- 静态方法没有
代码演示:
struct Entity { int x, y; // 实例变量:每个对象都有独立的 x, y static int sharedX; // 静态变量:所有对象共用这一个 sharedX
static void Print() { // std::cout << x << std::endl; // ❌ 错误!无法访问非静态成员 std::cout << sharedX << std::endl; // ✅ 正确!大家共用一个 }};
// 【重要】静态成员变量必须在类外定义,否则链接器找不到符号int Entity::sharedX;
int main() { Entity::sharedX = 5; // 无需创建对象即可直接访问
Entity e1; e1.sharedX = 10; // 也可以通过对象访问,但实际上改的是同一个静态内存
Entity::Print(); // 输出 10}三、局部静态变量(Local Static):单例的神器
这是 static 的第三种用法,出现在函数内部。
- 生命周期:与程序的生命周期一致(类似全局变量)。
- 作用域:仅在该函数内部可见。
- 初始化时机:只在第一次调用该函数时初始化,后续调用会直接跳过初始化,复用之前的值。
经典应用:单例模式(Meyers’ Singleton)
这是 C++ 中实现单例模式最优雅、最简洁的方式(且在 C++11 后是线程安全的):
class Singleton {public: // 返回引用,避免拷贝 static Singleton& Get() { // 第一次调用 Get() 时,instance 被创建 // 以后再调用,直接返回存活在静态内存区的 instance static Singleton instance; return instance; }
void Hello() {}};
int main() { // 使用方式 Singleton::Get().Hello();}这种写法避免了全局变量的初始化顺序问题,实现了完美的“懒加载”(Lazy Initialization)。
四、this 指针:幕后的隐藏参数
在编写类的非静态成员函数时,我们经常听到 this 指针,它到底是什么?
4.1 本质
this 是一个隐藏的参数,存在于所有非静态成员函数中。它是一个指针,指向当前调用该方法的对象实例。
4.2 编译器眼中的代码
你以为你写的:
void Entity::Print() { std::cout << x << std::endl;}编译器实际处理的(伪代码):
void Entity_Print(Entity* this) { std::cout << this->x << std::endl;}4.3 常见用途
-
区分同名参数: 当成员变量名和参数名冲突时,使用
this来明确指代成员变量。void SetX(int x) {this->x = x; // 将参数 x 赋值给成员变量 x} -
链式调用(Method Chaining): 返回
*this(即对象的引用),可以让方法连续调用。class Builder {public:Builder& SetWidth(int w) { width = w; return *this; }Builder& SetHeight(int h) { height = h; return *this; }private:int width, height;};// 调用:Builder b;b.SetWidth(100).SetHeight(200);
五、引用与指针:核心区别再梳理
虽然我们在另一篇笔记中详细讨论了指针和引用,但理解它们的区别对于掌握 C++ 类设计至关重要。
5.1 本质区别
- 指针:是一个变量,存储的是内存地址。可以为空(
nullptr),可以重新赋值指向其他对象。 - 引用:是对象的别名。必须初始化,且一旦绑定不可更改。
5.2 使用场景对比
| 特性 | 指针 (Pointer) | 引用 (Reference) |
|---|---|---|
| 可空性 | 可以为 nullptr | 必须绑定有效对象 |
| 可重新赋值 | 可以改变指向 | 不可改变指向 |
| 语法 | 需要解引用 (*, ->) | 像普通变量一样使用 |
| 主要用途 | 动态内存管理、可选参数 | 函数参数传递、返回值优化 |
5.3 常见误区
- 野指针:指针未初始化或指向已释放的内存。务必初始化指针(如
nullptr)并在delete后置空。 - 悬空引用:返回局部变量的引用。函数结束时局部变量销毁,引用指向非法内存。
5.4 性能考量
在传递大型对象(如自定义类)时,优先使用 const 引用 (const MyClass&),避免不必要的拷贝开销,同时保证数据安全。
总结
- Class vs Struct:别纠结,本质一样。默认私有用 Class,默认公有用 Struct。纯数据结构用 Struct,复杂对象用 Class。
- Static:
- 在
.cpp全局:是“私有的全局变量”。 - 在 Class 内部:是“共享的类成员”。
- 在函数内部:是“只初始化一次的持久变量”。
- 在
- this:是连接“类方法”与“对象实例”的桥梁,让函数知道它正在操作哪一块内存。
- 引用与指针:引用是安全的别名,指针是灵活的地址工具。能用引用就用引用,需要可空或动态特性时用指针。