在这篇文章中,我总结了一些C/C++语言中的 const 修饰符的常见用法,供你们参考。 const 的用法,也是技术性面试中常见的基础问题,但愿可以帮你们梳理一下知识,给你们一点点帮助。做者是菜鸟一枚,不免出错,还望各位大牛不吝赐教。面试
首先,来看看const的基本含义。在 C/C++ 语言中,const关键字是一种修饰符。所谓“修饰符”,就是在编译器进行编译的过程当中,给编译器一些“要求”或“提示”,但修饰符自己,并不产生任何实际代码。就 const 修饰符而言,它用来告诉编译器,被修饰的这些东西,具备“只读”的特色。在编译的过程当中,一旦咱们的代码试图去改变这些东西,编译器就应该给出错误提示。编程
因此,const修饰符的做用主要是利用编译器帮助咱们检查本身代码的正确性。咱们使用const在源码中标示出“不该该改变”的地方,而后利用编译器,帮助咱们检查这些地方是否真的没有被改变过。若是咱们不当心去修改了这些地方,编译器就会报错,从而帮助咱们纠正错误。使用const和不使用const,对于最终编译产生的代码并无影响。小程序
虽然const对于最终代码没有影响,可是尽量使用const,将帮助咱们避免不少错误,提升程序正确率。函数
在C/C++中,常见 const 用法有如下几种:优化
1、const 变量this
const 变量指的是,此变量的值是只读的,不该该被改变。spa
const int debugLevel = 10; const int logLevel; // 编译错误:未初始化const变量(这个错误是否提示,和所用的编译器有关) debugLevel = 5; // 编译错误:给只读变量赋值
在C++中,const 全局变量被用来替换通常常量宏定义。由于虽然 const 变量的值不能改变,可是依然是变量,使用时依然会进行类型检查,要比宏定义的直接替换方法更严格一些(下文还会讨论这个问题)。debug
结构体变量也是一种变量,const 结构体变量是什么含义呢?指针
1 struct debugInfo 2 { 3 int debugLevel; 4 int line; 5 }; 6 7 int main( int argc, char *argv[]) 8 { 9 const struct debugInfo debug_const1; // 编译错误:未初始化只读变量(与编译器实现有关) 10 11 struct debugInfo debug_not_const; 12 13 debug_not_const.debugLevel = 10; 14 debug_not_const.line = 5; 15 16 const struct debugInfo debug_const2 = debug_not_const; 17 18 debug_const2.debugLevel = 10; // 编译错误:不容许修改只读变量 19 debug_const2.line = 2; // 编译错误:不容许修改只读变量 20 21 return 0; 22 }
在C中,const 结构体变量表示结构体中任何数据域均不容许改变,且须要另外一个结构体变量进行初始化。在C++中,struct与class除了默认访问权限以外,并没有本质区别。在下一节进行讨论。code
2、const 类对象
const类对象指的是,此类对象不该该被改变。
class CDebugModule { public: CDebugModule() {}; ~CDebugModule() {}; public: int m_debugLevel; public: void SetDebugLevel(int debugLevel) { m_debugLevel = debugLevel;}; void PrintDebugLevel(void) { cout << m_debugLevel;}; void PrintDebugLevel_const(void) const { cout << m_debugLevel;}; // const 类成员函数 }; int main( int argc, char *argv[]) { const CDebugModule debug; debug.m_debugLevel = 10; // 编译出错:不能直接改变成员变量 debug.SetDebugLevel(20); // 编译出错:不能经过成员函数改变成员变量 debug.PrintDebugLevel(); // 编译出错:不能调用非 const 成员函数 debug.PrintDebugLevel_const(); // 正常 return 0; }
不能改变 const 类对象的任何成员变量,这一点比较好理解,由于 const 自己就带有不可改变变量取值(内部状态)的含义。为什么const 类成员不能调用非const成员函数呢?咱们将在 第九节“const 成员函数” 进行探讨。在C++中,struct和class没有明显差异,再也不赘述。
3、指向 const 变量的指针
指向 const 变量的指针,指的是一个指针,其中保存着一个 const 变量的地址。
const int debugLevel = 10; const int logLevel = 10; const int *p = &debugLevel; p = &logLevel; // 正常,指针的指向能够改变 *p = 10; // 编译错误,指针指向的位置是只读的
在 *p = 10, 这一句,编译器是经过“指针的类型”(const int *)仍是经过其“指向变量的类型”(const int )来判断只读的呢?咱们能够经过下面这个小程序来求证一下:
1 const int debugLevel = 10; // const 变量 2 int logLevel = 10; // 普通变量 3 4 const int *p; 5 int *q; 6 7 p = &logLevel; // 咱们让指向 const 变量的指针指向一个普通变量 8 q = (int*)&debugLevel; // 让指向普通变量的指针,指向一个 const 变量 9 10 *q = 5; // 编译正常 11 *p = 5; // 编译出错:位置为只读
经过十、11行程序的编译结果,咱们能够看出,若是指针的类型为“指向const变量的指针”,即便其指向的内容是非const变量,也没法经过这个指针改变数据内容。反之,若是指针的类型是“指向非const变量的指针”,即便指向的是const变量,也能够经过这个指针改变const变量的内容(稍后讨论这一点)。因此,编译器是经过 “指针的类型” 来判断是否只读的。
说到这点,我以为能够这么解释。由于咱们没有使用面向对象编程,也就不具有动态判断对象具体类型的能力。因此,编译器只可以静态地判断对象的类型。这样,编译器就只能识别出指针的类型,而不清楚指针指向的内容的具体类型了。固然也就只能经过指针类型来判断内容是否只读了。
在上面,咱们经过指针,“改变”了const变量的内容,若是咱们在上边的程序中添加上输出,会是什么结果?
12 printf("debugLevel = %d\n", debugLevel); 13 printf("*q = %d\n", *q); 14 printf("debugLevel address = %x\n", &debugLevel); 15 printf("q = %x\n", q);
从上边的说明,咱们能够想象,debugLevel的值,被咱们经过指针改变了,因此输出应该是:
debugLevel = 5 *q = 5 debugLevel address = 0xbfaff718 q = 0xbfaff718
可是,事实上,这个结果是不肯定的!跟您所用的编译器和优化级别有关。我在g++ 4.1.2上,编译运行得出了如下结果:
debugLevel = 10 // 直接打印能够发现,const 变量的值未改变 ! *q = 5 // 经过指针访问,发现 const 变量的值改变了! debugLevel address = 0xa6a65318 q = 0xa6a65318 // 指针的指向并无错误
乍一看,好像同一个地址的东西,采用变量名访问和采用指针访问,获得的结果居然不同。其实,之因此产生这种结果,是因为在编译器变异的过程当中,对 const 变量的访问进行了优化。编译器将 const 变量直接替换为对应的内容。也就是说,在编译的过程当中 :
printf("debugLevel = %d\n", debugLevel);
这个语句,被直接替换成了:
printf("debugLevel = %d\n", 10);
因此,才产生了上边的现象。固然,这种替换不必定会发生,跟编译器和优化等级相关。
上文已经提到了,C++建议使用 const 全局变量来替换通常常量的宏定义。经过这个例子能够看出,使用 const 全局变量以后,因为编译器会替换其为具体内容,因此在程序实际运行中,并不会产生一次变量访问,也就使得 const 全局变量和宏定义具备相同的执行效率。同时,使用 const 全局变量,可让编译器帮助咱们进行变量类型检查,提升正确率。
指针也是变量的一种,因此天然有 const 类型指针。
4、const 指针
const指针指的是,一个指针自己通过 const 修饰,自身内容(指针指向)不该该发生改变。
int logLevel = 10; int logId = 0; int * const p = &logLevel; int * const q; // 编译错误,未初始化 const 变量(这个错误是否报告,和所用的编译器有关) *p = 5; // 正常,const指针指向内容能够改变 p = &logId // 编译错误,const指针自身内容(指向)不能改变
指针也是一种变量,只不过其内容保存的是地址而已。因此const指针的内容不能改变,也便是它的指向不能改变。
const指针和指向const变量的指针,在写法上容易令人混淆。给你们介绍一种我本身用着比较顺手的区分方法:从右向左,依次结合,const就近结合。
好比,int * const p 能够这样进行解读:
一、int * ( const p ):变量p 通过 const 修饰,为只读变量。
二、int (* (const p)):(const p 如今做为一个总体) 只读变量p是一个指针。
三、(int (* (const p))):(一样的 * const p 做为一个总体) 这个只读的指针p,指向一个int型变量。
因而,能够区分出 int * const p 是一个指向 int 型的const指针。
再好比,const int * p 能够这样解读:
一、const int (* p):变量p是一个指针。
二、(const int) (* p):(const与就近的 int 结合)这个指针指向 const int 型变量。
因此,const int * p 是一个指向 const 整形变量的指针。
采用这个方法,相信你们能够本身分辨 int const * p的含义了。
值得注意的是,有的编译器对重复的 const 不会报错!因此容许像 const int const *p; 这种写法。在分析这种“错误”的写法时,只要把重复修饰的const忽略便可。
5、指向 const 变量的 const 指针
这种状况是 const 指针和 指向 const 变量的指针的结合,相信你们已经可以本身分析,再也不赘述。
6、const 变量做为函数参数
在函数调用的过程当中,函数的参数是创建在函数的栈上的变量。既然是变量,就能够经过 const 进行修饰。
1 // 接收一个int变量,并在函数内部,认为它是只读的 2 void OutputInt_const( const int a ) 3 { 4 a = 5; // 编译错误:不容许修改只读变量 5 printf("a = %d\n", a); 6 } 7 8 // 接收一个int变量,在函数内部,认为它是普通的 9 void OutputInt_not_const( int a ) 10 { 11 a = 5; // 正常 12 printf("a = %d\n", a); 13 } 14 15 // 接收一个 指向const型整形数的指针 16 void OutputInt_p_const( const int *a ) 17 { 18 *a = 5; // 编译错误: 19 printf("*a = %d\n", *a); 20 } 21 22 // 接收一个普通指针 23 void OutputInt_p_not_const( int *a ) 24 { 25 *a = 5; 26 printf("*a = %d\n", *a); 27 } 28 29 // 主函数 30 int main( int argc, char *argv[]) 31 { 32 int logLevel = 10; 33 const int debugLevel = 5; 34 35 OutputInt_const(logLevel); 36 OutputInt_const(debugLevel); 37 38 OutputInt_not_const(logLevel); 39 OutputInt_not_const(debugLevel); 40 41 OutputInt_p_const(&logLevel); 42 OutputInt_p_const(&debugLevel); 43 44 OutputInt_p_not_const(&logLevel); 45 OutputInt_p_not_const(&debugLevel); // 编译错误:从 const int * 到 int * 转换失败(与编译器有关) 46 47 return 0; 48 }
为何对于指针参数的要求特殊?其实咱们能够仔细想一下 const 在修饰参数时的做用。
首先,函数参数是函数内部可见的一个变量。在const 修饰函数参数时,仅仅表示此函数内部对于这个变量的限制。对于传进来的参数,在外边到底是什么样子的,函数内部并不关心。因此,函数 void OutputInt_const( const int a ) 并不会在乎传入的参数在main函数中是不是只读的。它只会在函数内部,将入参看成只读变量处理。
既然 const 修饰函数参数时,不会限制入参是否为只读,为何 OutputInt_p_not_const( int *a ) 和 OutputInt_p_const( const int *a ) 的调用方式有区别呢(见4四、45行)?
其实答案很简单,const 在此处并非修饰函数参数的!此处的 const ,与 int * 组合,描述了参数的一种类型。OutputInt_p_const函数要求的参数是:指向只读整形的指针。因此,只要调用时传入的参数不是一个指向只读整形数的指针,就会发生类型不匹配。在示例41行的调用中,使用一个 int * 来调用 OutputInt_p_const 函数,发生类型不匹配,可是 int * 能够隐式转换为 const int *,因此此处调用能够成功。但在45行中,采用 const int * 来调用须要 int * 的 OutputInt_p_not_const 函数,发生类型不匹配, const int * 不可以隐式转换为 int *,因此此处调用失败。
为何 int * 能够隐式转换为 const int *,可是反向就不能够呢?相信各位读者已经想到了。隐式转换不放宽对于变量的要求,而 const 型的变量显然比非 const 型变量要求严格,因此不能由 const int * 转为 int *。
7、const 返回值
const 型的返回值,指的是函数的返回值为一个 const 变量。
1 #include <string> 2 3 using namespace std; 4 5 // 返回 const 引用的函数 6 const string& SetVersion_const(string & versionInfo) 7 { 8 versionInfo = "V0.0.3"; 9 return versionInfo; 10 } 11 12 // 返回普通引用的函数 13 string& SetVersion_not_const(string & versionInfo) 14 { 15 versionInfo = "V0.0.3"; 16 return versionInfo; 17 } 18 19 // 主函数 20 int main( int argc, char *argv[]) 21 { 22 string versionInfo; 23 24 SetVersion_const(versionInfo) = "V0.0.5"; // 编译错误,返回的引用为 const 引用,不容许修改。 25 26 SetVersion_not_const(versionInfo) = "V0.0.5"; // 正常,返回的引用为普通引用,能够修改内容。 27 28 return 0; 29 }
引用是一个对象的别名,至关于 const 指针,其指向一经肯定,就不能改变了。而 const 引用,则至关于指向 const 变量的 const 指针,其指向和指向的内容均不容许改变。因此在函数返回 const 引用时,不可以经过函数返回的引用对实际对象进行任何修改,即使对象自己不是 const 的。在本例中,versionInfo 在 main 函数中不是const的。SetVersion_const 函数返回了一个指向 versionInfo 的 const 引用,不能经过此引用,对 versionInfo 进行修改。
为何会出现这种现象?相信你们都能理解了。请参考 指向 const 变量指针 的相关内容。
8、const 成员变量
const 成员变量指的是类中的成员变量为只读,不可以被修改(包括在类外部和类内部)。
1 class CDebugModule 2 { 3 public: 4 CDebugModule(); 5 ~CDebugModule(); 6 7 public: 8 const int m_debugLevel; 9 static const int m_debugInfo; 10 11 }; 12 13 const int CDebugModule::m_debugInfo = 1; // 静态常量成员须要在类外进行单独定义和初始化 14 15 CDebugModule::CDebugModule() 16 : m_debugLevel(10) // const 成员在构造函数初始化列表中初始化 17 { 18 } 19 20 CDebugModule::~CDebugModule() 21 { 22 } 23 24 int main(int argc, char *argv[]) 25 { 26 CDebugModule debugModule; 27 28 debugModule.m_debugLevel = 10; // 编译错误,不能改变只读成员 29 CDebugModule::m_debugInfo = 10; // 编译错误,不能改变只读成员 30 31 return 0; 32 }
类对象的实例化过程能够理解为包含如下步骤:首先,开辟整个类对象的内存空间。以后,根据类成员状况,分配各个成员变量的内存空间,并经过构造函数的初始化列表进行初始化。最后,执行构造函数中的代码。因为 const 成员变量必须在定义(分配内存空间)时,就进行初始化。因此须要在够在函数的初始化列表中初始化。const成员在初始化以后,其值就不容许改变了,即使在构造内部也是不容许的。
静态成员变量并不属于某个类对象,而是整个类共有的。静态成员变量能够不依附于某个实例化后的类对象进行访问。那么,静态成员变量的值,应该在任何实例化操做以前,就可以进行改变(不然,只有实例化至少一个对象,才能访问静态成员)。因此,静态成员变量不可以由构造函数进行内存分配,而应该在类外部单独定义,在实例化任何对象以前,就开辟好空间。又因为 const 成员变量 必须初始化,因此静态成员变量必须在定义的时候就初始化。
9、const 成员函数
const成员函数指的是,此函数不该该修改任何成员变量。
1 class CDebugModule 2 { 3 public: 4 CDebugModule() {}; 5 ~CDebugModule(); 6 7 public: 8 int m_debugLevel_not_mutable; // 不带 mutable 修饰的成员变量 9 mutable int m_debugLevel_mutable; // 带 mutable 修饰的成员变量 10 11 public: 12 void SetDebugLevel_not_const(int debugLevel); // 非 const 成员函数 13 void SetDebugLevel_const(int debugLevel) const; // const 成员函数 14 }; 15 16 void CDebugModule::SetDebugLevel_not_const(int debugLevel) 17 { 18 m_debugLevel_not_mutable = debugLevel; 19 m_debugLevel_mutable = debugLevel; 20 return; 21 } 22 23 void CDebugModule::SetDebugLevel_const(int debugLevel) const 24 { 25 m_debugLevel_not_mutable = debugLevel; // 编译错误,const 成员函数不能修改通常的成员变量 26 m_debugLevel_mutable = debugLevel; // 正常,当成员变量被 mutable 修饰时,const成员函数就能修改了 27 return; 28 } 29 30 int main(int argc, char *argv[]) 31 { 32 CDebugModule debugModule; 33 34 debugModule.SetDebugLevel_not_const(10); 35 debugModule.SetDebugLevel_const(10); 36 37 return 0; 38 }
在成员函数调用的过程当中,都有一个 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指针。因此依然可以调用。
10、总结
const 变量 |
const int a; |
不能修改值,必须初始化 |
const 类对象 |
const MyClass a; |
不能修改为员变量的值,不能调用非 const 函数 |
指向 const 变量的指针 |
const int * a; |
指向内容不可变,指向可变 |
const 指针 |
int * const a; |
指向内容可变,指向不可变 |
指向 const 变量的 const 指针 |
const int * const a; |
指向内容不可变,指向也不可变 const 引用 |
const 变量做为函数参数 |
void myfun(const int a); |
函数内部不能改变此参数 指向 const 变量的指针作参数,容许上层用通常指针调用。(反之不可) |
const 返回值 |
const string& myfun(void); |
用于返回const引用 上层不能使用返回的引用,修改对象 |
const 成员变量 |
const int a; static const int a; |
必须在初始化列表初始化,以后不能改变 static const 成员变量须要单独定义和初始化 |
const 成员函数 |
void myfun(void) const; |
this指针为 指向const对象的const指针 不能修改 非mutable 的成员变量 |
本文的内容就这么多了,感谢您可以看到最后,但愿对您可以有一点点帮助 ^_^