cs106l-lecture-5-1-classes-and-const-correctness

Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming


🎮 CS106L Lecture 5.1 — Classes and Const Correctness

🏛 课程:Stanford CS106L — Standard C++ Programming Language

📍 主题:类与 const 正确性(Classes & Const Correctness)

🎯 目标:理解类的封装机制与 const 修饰符的真正含义,学会编写类型安全且可维护的面向对象代码。


在 STL 模块中,我们通过泛型算法实现了“函数式抽象”。

从这一讲开始,我们进入 类与对象的世界(OOP 抽象)

即:如何用类型表达思想。

“函数抽象行为,类抽象存在。”

本讲的关键思想是:

💡 “正确使用 const,让代码更安全、更语义化。”


Class|类

Object|对象

Encapsulation|封装

Const Correctness|常量正确性

Immutable Object|不可变对象

Const Method|常量成员函数

Const Iterator|常量迭代器

RAII|资源自动管理


“const 是 C++ 的契约系统。

它告诉编译器——这里的数据不会被改动,

也告诉团队——你可以信任这段代码。”



C++ 的类(class)是封装数据与函数的抽象体。

📦 示例 1:类的基本形式

// Student.h
class Student {
public:
    Student(std::string name, int id);
    void print() const;   // const 方法
private:
    std::string name_;
    int id_;
};

// Student.cpp
#include "Student.h"
#include <iostream>

Student::Student(std::string name, int id)
    : name_(name), id_(id) {}

void Student::print() const {
    std::cout << name_ << " (" << id_ << ")\n";
}

🧩 讲解:

  • 类的声明放在 .h,实现放在 .cpp
  • 构造函数使用初始化列表(Initializer List)
  • print() 后的 const 表示:该成员函数不会修改对象状态

使用位置示例含义
1️⃣ 常量变量const int n = 10;变量值不可变
2️⃣ 常量指针int *const p = &n;指针本身不能改
3️⃣ 指向常量的指针const int* p = &n;不能通过指针改值
4️⃣ 常量成员函数void print() const;不修改成员变量

📦 示例 2:四种 const 语义

int a = 5;
const int* p1 = &a;   // *p1 只读
int* const p2 = &a;   // p2 不可变
const int* const p3 = &a; // 双重保护

🧠 口诀:

“const 贴谁,谁不变。”

即:

  • const int* → 指向的内容不变
  • int* const → 指针自身不变

成员函数可以被声明为 const

表示它不会修改类的状态

📦 示例 3:const 成员函数

class Circle {
public:
    Circle(double r) : radius(r) {}
    double getArea() const {
        return 3.14159 * radius * radius;
    }
private:
    double radius;
};

📦 错误示例:

double getArea() const {
    radius = 0;  // ❌ error: assignment of member in const function
}

🧩 编译器行为:

编译器在 const 成员函数中将 this 指针视为:

const Circle* const this;

→ 即函数体中无法修改成员变量。


对象类型允许调用的函数
非 const 对象所有函数
const 对象仅可调用 const 成员函数

📦 示例 4:const 对象调用规则

const Circle c1(5);
c1.getArea();     // ✅ const 函数可用
c1.setRadius(10); // ❌ 非 const 函数禁止

🧠 记忆口诀:

“const 对象,只信 const 方法。”


有时需要在 const 方法中修改某些状态(如缓存、计数器)。

此时可以用 mutable 修饰。

📦 示例 5:mutable 用法

class Cache {
public:
    double get() const {
        ++accessCount; // ✅ allowed due to mutable
        return data;
    }
private:
    double data = 3.14;
    mutable int accessCount = 0;
};

🧩 应用场景:

  • 缓存(lazy evaluation)
  • 调试或统计访问次数

C++ 容器提供两种迭代器:

类型说明
iterator可读写
const_iterator只读(不能修改元素)

📦 示例 6:使用 const_iterator

std::vector<int> v = {1, 2, 3};
for (std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it)
    std::cout << *it << " ";

📤 输出:1 2 3

📦 错误示例:

*it = 5; // ❌ 不能修改 const_iterator

🧠 实用建议:

  • 函数参数若不修改容器 → 使用 const std::vector<T>&
  • 遍历时若不修改元素 → 使用 const_iterator

📦 示例 7:函数签名中的 const

void print(const std::vector<int>& v) {
    for (auto& x : v) std::cout << x << " ";
}

const std::string getName() const; // 不推荐:返回值拷贝时 const 无意义

🧩 规则总结:

  • 参数:使用 const & 避免拷贝且保证只读
  • 返回值:若按值返回,const 多余;若返回引用,const 可防止修改原对象

const_cast 可移除 const 限制,但风险极高。

📦 示例 8:const_cast

void foo(const int* p) {
    int* q = const_cast<int*>(p);
    *q = 99; // ⚠️ 未定义行为
}

🧩 用法建议:

  • 仅在必须与旧 API(如 C 函数)交互时使用
  • 在现代 C++ 中应尽量避免

🧠 口诀:

“const_cast 是炸药,用错就爆炸。”


RAII(Resource Acquisition Is Initialization)保证资源的自动释放,

const 一起可形成安全的不变式。

📦 示例 9:RAII + const

class FileGuard {
public:
    FileGuard(const std::string& path)
        : file(fopen(path.c_str(), "r")) {}
    ~FileGuard() { if (file) fclose(file); }
    FILE* get() const { return file; }
private:
    FILE* file;
};

🧩 讲解:

  • 构造即获取资源
  • 析构即释放
  • get()const,保证访问安全

“const 定界线,改动受约束;

mutable 留活口,安全有章数。”


实验 1:const 成员函数防止意外修改

class A {
public:
    int get() const { return x; }
    void set(int val) { x = val; }
private:
    int x = 10;
};

const A a;
a.get();   // ✅ OK
a.set(5);  // ❌ error

实验 2:mutable 的例外机制

class B {
public:
    void access() const { ++count; }
    int getCount() const { return count; }
private:
    mutable int count = 0;
};
B b;
b.access();
std::cout << b.getCount(); // 输出 1 ✅

  • 在类中标记所有不会修改状态的函数为 const
  • 优先使用 const & 参数传递,避免拷贝与副作用。
  • 对内部计数器或缓存使用 mutable,谨慎设计不变式。
  • 使用 const_iterator 表明“只读遍历”意图。
  • 禁止滥用 const_cast,若必须使用,应隔离在单独函数中。
  • RAII + const 是现代 C++ 内存安全的基础。

场景写法效果
常量对象const T obj;只读
成员函数void func() const;不可修改成员
指针常量T* const p;指针地址不可改
指向常量的指针const T* p;不可修改目标内容
成员变量可变mutable T x;const 方法中可改
只读遍历const_iterator防止误写容器

“C++ 的 const 不只是修饰符,而是契约、文档与编译期安全保障。”


是否希望我继续整理下一讲

👉 Lecture 5.2 — Class Templates & Type Traits

(类模板、偏特化、enable_iftype traits、模板元编程基础)

保持同样 Notion 格式与代码解释?