const 修饰变量

面试官: 看到你的简历上写着熟悉 C++,那我们来聊聊 const 关键字吧。它在 C++ 中有很多用途。

我的第一个问题是:const 用于修饰一个变量时,它意味着什么?它和 C 语言中的 #define 宏定义常量相比,有什么主要的优势?

1
2
3
4
5
// 方式一
const int MAX_SIZE = 100;

// 方式二
#define MAX_SIZE 100

回答:

  • const 关键字修饰变量,表示这个变量是一个常量,不能被修改。
  • #define 宏定义常量相比,const 变量更加有下面的优势:
    • 类型检查const 是有类型的变量,编译器会进行类型检查,而 #define 是纯文本变量,没有类型概念,容易引发隐性错误——编译通过但运行出错。
    • 作用域控制const 类型的变量可以定义在局部作用域中,而 #define 是全局替换,容易产生命名冲突
    • 调试支持const 变量可以在调试器中查看它的值和地址,而 #define 变量在预处理阶段就被替换到,调试器无法识别。
    • 取地址操作const 变量可以取地址,而 #define 是文本替换,没有地址的概念。
    • 跨文件访问const 变量可以通过 extern 声明在多个文件中共享,而 #define 无法跨文件共享,只能重新定义。
  • 当然,const 最主要的优势还是类型检查作用域控制

const 与指针结合

面试官 (继续提问): 好的,你对 const 修饰普通变量的理解非常透彻。我们来看它和指针的结合。

请你解释一下,下面这三种声明中,p 的类型有什么不同?分别都约束了什么东西不能被修改?

1
2
3
const int* p;       // 1
int* const p;       // 2
const int* const p; // 3

(提示:一个有用的技巧是“从右往左读”,或者看 const* 的哪一边。)

回答:

  • 第一种情况,const int* p 表示这是一个指向 const int 类型变量的指针,即指针所指的变量为常量,不能修改,但是指针值本身可以修改。
  • 第二种情况,int* const p 表示这是一个指针值为常量的指针,即指针值(地址)不能修改,但是所指变量的值可以修改。
  • 第三种情况,结合了上面两种情况,表示这是一个“指向常量且指针值为常量的指针”。
  • 总结:从右往左读,如果 const 更靠近指针 p,那么就说明指针值为常量;如果更靠近 int(或者其它类型名),那么说明指针所指变量为常量。

const 修饰类的成员函数

面试官 (继续提问): 好的,我们再来看 const 的第三种主要用法,也就是修饰类的成员函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MyClass {
public:
    int getValue() const {
        // ...
        return value_;
    }

    void setValue(int v) {
        value_ = v;
    }

private:
    int value_;
};
  1. getValue() 函数末尾的这个 const 关键字,它向编译器做出了一个怎样的承诺
  2. 这个承诺具体体现在 getValue() 函数内部,它不能做什么事情?
  3. 这个承诺对调用 getValue() 函数的对象有什么要求?比如,一个 constMyClass 对象,它能调用 getValue() 吗?它能调用 setValue() 吗?
1
2
3
const MyClass obj;
obj.getValue(); // OK or Error?
obj.setValue(10); // OK or Error?

回答:

  • getValue() 末尾的 const,是在向编译器承诺:“这个 getValue() 成员函数,只能读取成员变量的值,而不能做任何修改。”
  • 具体来说,这个承诺要求 getValue() 内部不能修改成员变量的值,也不能调用末尾没有 const 的其他成员函数。
  • 一个 constMyClass 对象,它只能调用同样是 const 类型的成员函数,而不能调用末尾没有 const 的成员函数。所以,它可以调用 getValue(),但是不能调用 setValue()

面试官 (总结与深化): 你说的完全正确。

const 成员函数的核心:

  1. 承诺 (Contract)

    正如你所说,const 成员函数承诺不会修改对象的任何数据成员

  2. 内部约束 (Internal Constraint)

    • 不能修改成员变量:在 const 成员函数内部,所有的非 static 成员变量都会被当作 const 对待。this->value_ = 10; 这样的代码会编译失败。(但是,static 类型的成员变量可以被 const 成员函数修改!)
    • 不能调用非 const 成员函数:正如你补充的,它也不能调用同一个对象的任何const 成员函数。因为非 const 成员函数没有做出“不修改”的承诺,编译器为了维持 const 的安全性,禁止了这种调用。
  3. 外部约束 (External Constraint)

    • const 对象只能调用 const 成员函数
      1
      2
      3
      
      const MyClass obj;
      obj.getValue(); // OK!
      obj.setValue(10); // Error! 编译器会阻止你,因为它害怕 setValue 会修改这个 const 对象。
      
    • const 对象则两者都可以调用。

this 指针的奥秘 (底层原理) 这个约束的底层原理,和 this 指针的类型有关:

  • const 成员函数中,this 指针的类型是 MyClass* const (一个常量指针,指向一个const 的对象)。
  • const 成员函数中,this 指针的类型是 const MyClass* const (一个常量指针,指向一个 const 的对象)。

因为 const 成员函数的 this 指向的是一个 const 对象,所以它自然就不能修改对象的成员,也不能调用那些需要非 const 对象的函数了。

你对 const 成员函数的理解已经非常透彻了。


总结

面试结束

今天我们系统性地、深入地复习了 const 关键字的三大核心用法:

  1. 修饰变量 (vs #define)。
  2. 修饰指针 (常量指针 vs 指向常量的指针)。
  3. 修饰成员函数 (约束内部行为和外部调用)。

你对每一个知识点的回答都非常准确和深入,展现了你扎实的 C++ 基础。这次复习非常成功!