在 C/C++ 语言中,const关键字是一种修饰符。所谓“修饰符”,就是在编译器进行编译的过程中,给编译器一些“要求”或“提示”,但修饰符本身,并不产生任何实际代码。就 const 修饰符而言,它用来告诉编译器,被修饰的这些东西,具有“只读”的特点。在编译的过程中,一旦我们的代码试图去改变这些东西,编译器就应该给出错误提示。
所以,const修饰符的作用主要是利用编译器帮助我们检查自己代码的正确性。
虽然const对于最终代码没有影响,但是尽可能使用const,将帮助我们避免很多错误,提高程序正确率。
在C/C++中,常见 const 用法有以下几种:
一、const 变量
const 变量指的是,此变量的值是只读的,不应该被改变。
- 如果我们在程序中试图修改 const 变量的值,在编译的时候,编译器将给出错误提示。
- 正因为 const 变量的值在给定以后不能改变,所以 const 变量必须被初始化。(如果不初始化,之后还怎么赋值呢?)如果我们不初始化 const 变量,编译时也会有错误提示。
二、const 类对象
const类对象指的是,此类对象不应该被改变。
- const 类对象与 const 变量并无实质不同,只在于类对象的 “改变” 定义。
- 类对象的 “改变” 定义:改变任何成员变量的值,调用任何非const成员函数
三、指向 const 变量的指针
指向 const 变量的指针,指的是一个指针,其中保存着一个 const 变量的地址。
- 由于指针指向一个 const 变量,所以通过指针,不能修改这个 const 变量的值。
- 虽然指针指向的值不能改变,但是指针的指向可以改变。
四、const 指针
const指针指的是,一个指针本身经过 const 修饰,自身内容(指针指向)不应该发生改变。
- 指针的指向一经确定,不能改变。指针指向的内容,可以改变。
指针也是一种变量,只不过其内容保存的是地址而已。所以const指针的内容不能改变,也即是它的指向不能改变。
const指针和指向const变量的指针,在写法上容易使人混淆。给大家介绍一种我自己用着比较顺手的区分方法:从右向左,依次结合,const就近结合。
比如,int * const p 可以这样进行解读:
1、int * ( const p ):变量p 经过 const 修饰,为只读变量。
2、int (* (const p)):(const p 现在作为一个整体) 只读变量p是一个指针。
3、(int (* (const p))):(同样的 * const p 作为一个整体) 这个只读的指针p,指向一个int型变量。
于是,可以区分出 int * const p 是一个指向 int 型的const指针。
再比如,const int * p 可以这样解读:
1、const int (* p):变量p是一个指针。
2、(const int) (* p):(const与就近的 int 结合)这个指针指向 const int 型变量。
所以,const int * p 是一个指向 const 整形变量的指针。
六、const 变量作为函数参数
在函数调用的过程中,函数的参数是建立在函数的栈上的变量。既然是变量,就可以通过 const 进行修饰。
- 将函数参数声明为 const 类型,表示对于函数来说,这个参数是一个 const 变量。也就是说,函数内部不能够改变这个参数的值。
- 将函数参数是一个指针,把它声明为 “指向 const 变量的指针” ,允许上层使用 ”指向 const 变量的指针“ 或 普通指针 作为参数,调用函数。(如果参数声明的是普通指针,则不允许使用 指向 const 变量的指针 作为参数调用)(与编译器有关)
为什么 int * 可以隐式转换为 const int *,但是反向就不可以呢?相信各位读者已经想到了。隐式转换不放宽对于变量的要求,而 const 型的变量显然比非 const 型变量要求严格,所以不能由 const int * 转为 int *。
七、const 返回值
const 型的返回值,指的是函数的返回值为一个 const 变量。
- 函数返回const返回值,主要用于函数返回const引用。
八、const 成员变量
const 成员变量指的是类中的成员变量为只读,不能够被修改(包括在类外部和类内部)。
- const 成员变量必须被初始化(在相关构造函数的初始化列表中),初始化后,不能够被修改。
- 静态 const 成员变量需要在类外部单独定义并初始化(可定义在头文件)
类对象的实例化过程可以理解为包含以下步骤:首先,开辟整个类对象的内存空间。之后,根据类成员情况,分配各个成员变量的内存空间,并通过构造函数的初始化列表进行初始化。最后,执行构造函数中的代码。由于 const 成员变量必须在定义(分配内存空间)时,就进行初始化。所以需要在够在函数的初始化列表中初始化。const成员在初始化之后,其值就不允许改变了,即便在构造内部也是不允许的。
静态成员变量并不属于某个类对象,而是整个类共有的。静态成员变量可以不依附于某个实例化后的类对象进行访问。那么,静态成员变量的值,应该在任何实例化操作之前,就能够进行改变(否则,只有实例化至少一个对象,才能访问静态成员)。所以,静态成员变量不能够由构造函数进行内存分配,而应该在类外部单独定义,在实例化任何对象之前,就开辟好空间。又由于 const 成员变量 必须初始化,所以静态成员变量必须在定义的时候就初始化。
九、const 成员函数
const成员函数指的是,此函数不应该修改任何成员变量。
- 传给const成员函数的this指针,是指向 const 对象 的 const 指针。
- const成员函数,不能够修改任何成员变量,除非成员变量被 mutable 修饰符修饰。
在成员函数调用的过程中,都有一个 this 指针被当做参数隐性地传递给成员函数(可能通过栈,也可能通过CPU寄存器)。这个this指针,指向调用这个函数的对象(这样,成员函数才能找到成员变量的地址,从而对其进行操作)。这个this指针,是个 const指针,不能修改其指向(你不希望这个对象的函数,修改了那个对象的成员变量,对吧?)。
传递给const成员函数的this指针,指向一个const对象。也就是说,在const成员函数内部,这个this指针是一个指向const对象的const指针。通过第二节的探讨,相信大家已经能够明白,为什么const成员函数不能修改任何成员变量了。
mutable 修饰符使得const函数的行为有了一些灵活性。相当于提醒编译器,这个成员变量比较特殊,就不要进行任何只读检查了。
我们在第二节留下了一个问题 “为什么 const 对象只能够调用const成员函数呢?”,其实是这样的。由于对象本身通过 const 修饰,那么指向这个对象的指针也就是指向const对象的const指针了。换句话说,指向这个对象的this指针就是指向const对象的const指针。一般成员函数要求的this指针(别忘了this指针也是一个参数)为:指向对象的const指针。所以此处发生了参数不匹配,无法进行调用。而 const 成员函数要求的this指针,恰恰是 指向const对象的const指针。所以依然能够调用。