贵阳市网站建设_网站建设公司_UI设计_seo优化
2025/12/20 20:35:17 网站建设 项目流程

1. 核心法则:以星号 * 为界

Text 中提到:“如果 const 出现在星号左边...如果 const 出现在星号右边...”。

这可以总结为 “左定值,右定址”(或者叫“左内容,右指针”)。

我们在星号处画一条竖线:

  • const\* 左边 ($\text{const } T *$ 或 $T \text{ const } *$):
    • 锁定的是:指针指向的数据 (Content)
    • 含义:你不能通过这个指针去修改数据,但你可以改变指针指向哪里。
  • const\* 右边 ($* \text{ const}$):
    • 锁定的是指针本身 (Pointer itself)
    • 含义:指针一旦指向了某个地址,就不能再指向别处(像胶水粘住了一样),但你可以修改该地址上的数据。

2. 代码实例解析

让我们用刚才的法则来重新看文中的例子:

char greeting[] = "Hello";// 1. 没有任何 const
char *p = greeting;
// p 可以指向别处 (p++)
// *p 可以修改内容 (*p = 'h')// 2. const 在左边 (两种写法一样)
const char *p = greeting;
char const *p = greeting;
// p 可以指向别处 (p++) -> ✅ 指针是自由的
// *p = 'H';            -> ❌ 错误!数据是 const 的// 3. const 在右边
char * const p = greeting;
// p = &other_char;     -> ❌ 错误!指针是 const 的 (粘住了)
// *p = 'H';            -> ✅ 数据可以修改// 4. 两边都有 const
const char * const p = greeting;
// p 指向哪里不能改,p 指向的数据也不能改。
// 这是最严格的限制。

3. 两种常见的写法(面试常考)

文中特别提到:

void f1(const Widget *pw);

void f2(Widget const *pw);

这两者完全等价

  • 习惯建议:虽然两者通用,但在 C++ 社区中,第一种写法 (const Widget *) 更加普遍和流行。
  • 如何解读:如果你遇到第二种写法 (Widget const *) 觉得别扭,试着从右往左读
    • * (pointer to) -> const (constant) -> Widget.
    • "A pointer to a constant Widget."

4. 迭代器的 Const 陷阱:const iterator vs const_iterator

这是很多 C++ 程序员(甚至是老手)容易搞混的概念。因为迭代器是模仿指针行为设计的,所以它们继承了指针的 const 规则。

我们可以用简单的映射来记忆:

  • const std::vector<int>::iterator
    • 对应T* const (指针常量)。
    • 含义:迭代器本身不能移动(不能 ++iter),但它指向的数据可以修改(*iter = 10 允许)。
    • 实际用途:很少用。因为不能移动的迭代器通常没什么用(只能一直指着同一个位置)。
  • std::vector<int>::const_iterator
    • 对应const T* (常量指针)。
    • 含义:迭代器可以移动(++cIter 允许),但它指向的数据是只读的(*cIter = 10 禁止)。
    • 实际用途非常常用! 当你只需要遍历容器读取数据而不修改时,应该总是使用 const_iterator

关键点:如果你想表达“在这个循环中我不想修改容器里的元素”,你应该用 const_iterator,而不是在 iterator 前面加 const

5. 函数返回值的保护:防止“暴行”

Scott Meyers 举了一个非常经典的例子:

const Rational operator*(const Rational& lhs, const Rational& rhs);

为什么要给返回值(两个数的乘积)加上 const

如果不加 const,返回值就是一个临时对象(Temporary Object),在 C++ 中,你是允许对临时对象赋值的(虽然这通常没有意义)。这就导致了文中提到的“暴行”:

(a * b) = c; // 如果 operator* 返回非 const,这句代码能通过编译!

为什么这很危险?

  1. 逻辑荒谬:给“乘积”赋值没有任何数学意义。

  2. 隐藏 Bug:最常见的是输入错误,原本想写判断 ==,结果写成了赋值 =

    if (a * b = c) { ... } // 想要做比较,却变成了赋值
    
    • 如果 abint,编译器会报错,因为你不能给 7 赋值。
    • 如果 ab 是自定义类且返回值不是 const,编译器就会放行,Bug 就此诞生。

5.1现代解决方法

5.1.1 引用限定符登场

我们要达到的完美状态是:

  1. operator* 返回非 const 的临时对象(为了支持移动)。
  2. 但是禁止在这个临时对象上调用 operator=

解决方法:给 operator= 加上引用限定符 &

class Rational {
public:// 注意函数末尾的 '&'// 这意味着:只有当 *this 是一个 左值 (Lvalue) 时,才能调用这个函数Rational& operator=(const Rational& rhs) & {// ... 赋值逻辑 ...return *this;}// 移动赋值同理,通常也希望只对左值赋值Rational& operator=(Rational&& rhs) & {// ...return *this;}
};// operator* 现在可以返回非 const 了,支持移动优化!
Rational operator*(const Rational& lhs, const Rational& rhs) {return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
5.1.2. 效果演示

现在编译器会这样处理:

Rational a, b, c;// 场景 1: 正常赋值
a = b; 
// 'a' 是左值(有名对象),满足 operator= 的 '&' 限定。编译通过。// 场景 2: 那个“暴行”
(a * b) = c; 
// 1. (a * b) 返回一个临时对象(右值)。
// 2. 试图调用 operator=。
// 3. 编译器检查:operator= 要求对象必须是左值 ('&')。
// 4. (a * b) 是右值,不匹配。
// 5. 编译报错!

这就完美了:既阻止了给临时对象赋值的荒谬行为,又保证了返回值是非 const 的,可以被高效地 Move。


5.1.3. 进阶:同时重载 &&&

引用限定符不仅用于 operator=,还可以用来区分同一个函数的“左值版本”和“右值版本”,从而进行深度优化。

例子:获取内部数据的 data() 函数

class BigData {std::vector<int> numbers;
public:// 如果调用者是左值(持久对象),我们只能返回拷贝(或引用)std::vector<int> data() const & {return numbers; // 发生拷贝}// 如果调用者是右值(临时对象,马上要挂了),我们可以直接把资源 Move 出去!std::vector<int> data() && {return std::move(numbers); // 零拷贝,直接窃取}
};// 使用
BigData obj;
auto v1 = obj.data();        // 调用 data() &,发生拷贝(安全)auto v2 = BigData().data();  // 调用 data() &&,发生移动(高效!)
// BigData() 是临时对象,没必要拷贝它的 numbers,直接偷走就行

6. 避免代码重复 (Avoiding Duplication)

当你的类中同时存在 constnon-const 版本的同一个函数(例如 operator[]),且逻辑非常复杂时,你不想写两遍代码。

技巧让 non-const 版本调用 const 版本

这是一个稍微有点“反直觉”的高级技巧,需要用到两次转型:

class TextBlock {
public:const char& operator[](std::size_t position) const {// ... 假设这里有很长的边界检查和日志记录代码 ...return text[position];}char& operator[](std::size_t position) {// 1. static_cast: 将 *this 转为 const,以便调用 const 版本// 2. const_cast: 将返回值的 const 移除return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);}// 注意:反过来做(const 调用 non-const)是危险的,因为可能会在 const 函数中意外修改数据。
};

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询