当前位置: 首页 > news >正文

Item3--尽可能使用 const

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 函数中意外修改数据。
};
http://www.rkmt.cn/news/127745.html

相关文章:

  • 工厂模式
  • 2025 最新广州澳洲留学机构 TOP5 评测!大湾区优质教育培训机构,课程体系+升学保障权威榜单发布,助力学子圆梦世界名校 - 全局中转站
  • 160. 相交链表
  • AI也会“三思而后答“?揭秘Self-RAG智能检索术
  • 多语言AI翻译引擎助力跨语种论文写作,保持学术术语准确性
  • 19、AdobeAcrobatProDC
  • 如何理解 Agentic AI、LLM格局
  • Type-C领夹麦:重塑移动收音新体验
  • Item1--C++ 是语言联邦
  • 基于SVPWM改进的异步电机/感应电机直接转矩控制:解决传统DTC转矩纹波大的问题“参考文...
  • 光伏大棚智慧管理:ELK数据中枢
  • 10/10的AI论文工具推荐:覆盖数学建模复现率99%与自动排版
  • 高效学术工具:6个AI论文辅助系统,智能润色使内容更精准
  • 10款AI论文写作工具评测:高效实现数学建模优秀论文复现与专业排版
  • AN-93双麦降噪远场拾音模块|嘈杂环境也能清晰拾音,赋能全场景音频交互
  • 震惊!这家酶制剂供应商竟让行业炸锅
  • wsl使用git
  • DPJ-139 基于单片机的教室灯光自动控制器的研究(源代码+proteus仿真)
  • 剖析CVE-2025-64243漏洞:e-plugins Directory Pro授权缺失风险
  • 温度传感器PT1000与NTC10K介绍
  • 最新微软邮箱注册机,作者博客 xxcc点me
  • 第五章作业
  • 海南翡翠/和田玉推荐——以玉为媒,以金为证——吉瑞尚金珠宝:让民族文化在珠宝光影中走向世界 - charlieruizvin
  • 详细介绍:音视频学习(七十一):图像深度与图像通道数
  • 幽冥大陆(五十五)ASR SetThreadInformation C语言识别到自动化软件
  • 推荐6个AI论文网站,提供降重与自然改写功能避免标红
  • 宝,你越敢跟男人‘瞎要’,他越把你当宝
  • 小程序/APP接入分账系统:4大核心注意事项,避开合规与技术坑
  • 转录组分析(六)——样本信息表
  • 论文降重必备:6个AI网站排名,改写后语句通顺无标红风险