1826 字
9 分钟
C++ 核心编程笔记:类、引用与指针

这篇笔记承接上一篇关于“指针与引用”的内容,重点聚焦于类的组织形式、static 的多重含义以及对象内部的机制。同时,为了内容的完整性,我们将再次梳理引用与指针的核心区别。

一、Class 与 Struct:孪生兄弟#

很多从其他语言(如 C# 或 Java)转来的开发者通常认为 struct 是轻量级的值类型,而 class 是引用类型。但在 C++ 中,它们几乎是一模一样的

1.1 唯一的区别:默认可见性#

在 C++ 编译器眼中,classstruct 的区别仅仅在于默认的访问权限(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.cppB.cpp 中都定义了 static int s_Variable,它们是互不干扰的两个独立变量。如果不加 static,链接器在合并目标文件时会报“重复符号”(Duplicate Symbol)错误。

2.2 场景二:在类/结构体内部(Shared Memory)#

static 出现在类定义内部时。

  • 含义“这个成员属于类本身,而不是属于某个对象。”
  • 内存机制:无论你实例化了多少个对象(甚至一个都不实例化),static 成员变量在内存中只有一份。所有对象共享这一份数据。
  • 静态方法
    • 静态方法没有 this 指针。
    • 因此,静态方法无法访问类中的非静态成员(因为它不知道该去读哪个对象的 xy)。

代码演示:

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 常见用途#

  1. 区分同名参数: 当成员变量名和参数名冲突时,使用 this 来明确指代成员变量。

    void SetX(int x) {
    this->x = x; // 将参数 x 赋值给成员变量 x
    }
  2. 链式调用(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:是连接“类方法”与“对象实例”的桥梁,让函数知道它正在操作哪一块内存。
  • 引用与指针:引用是安全的别名,指针是灵活的地址工具。能用引用就用引用,需要可空或动态特性时用指针。
C++ 核心编程笔记:类、引用与指针
https://fuwari.vercel.app/posts/class/
作者
某不知名的根号三
发布于
2026-01-27
许可协议
CC BY-NC-SA 4.0