C++中const关键字的功能总结

C++语言是C语言的升级版,它支持更多的语法形式,用起来更加方便,功能也更加强大。本文尝试分析C++中针对const关键字进行的改进。

在C语言中,const关键字仅用于修饰指针类型的变量,最常见的例子就是strcpy函数了: char *strcpy(char *strDestination, const char *strSource );。这里const的作用是防止指针所指的内容(源字符串)在函数内被改变。

在C++中,const可以用来修饰所有类型的变量了。图为C99的所有变量类型:

const修饰非指针型变量

之所以C++中支持让const修饰非指针型变量,是因为C语言中的宏的弊端。在C语言中,无参宏被大量使用的同时,留下了许多隐患。例如:

#define NUMBER 0xFFFFFFFF

这个宏定义,无法说明NUMBER的类型,所以不同的情况可以有不同的解释(例如整形、浮点型、有无符号等)。由此产生的隐患不会在编译过程发出警告中,而是往往在软件的使用过程中造成异常。

不仅如此,C语言中也没有方便的方法来限定宏的作用域(只能用#undefine#define的组合,来使一个宏在指定区域内无效),宏一旦被定义,它的作用域就是整个文件剩下的部分。

C++的设计者希望程序员用const修饰变量的语法,来代替之前大量使用的无参宏。这样做的好处:

  • 有类型的说明了,编译器现在知道如何去解释它
  • 有作用域的限制了,作用域规则和普通的变量一样

现在我们在定义一些常量的时候,就可以避免歧义了。同时为了和宏的用法统一,我们习惯于把const修饰的变量名称全部大写,例如:

#include "stdafx.h"

#include <string.h>
#include <iostream>

using namespace std;

//传统C语言宏定义
#define NUMBER 0xFFFFFFFF

//C++的const修饰变量定义方法
const int NUMBER_1 = 0xFFFFFFFF;
const unsigned int NUMBER_2 = 0xFFFFFFFF;

int main(int argv, char* argc[])
{
  //有歧义的写法
  cout << NUMBER <<endl;

  //无歧义的写法
  cout << NUMBER_1 << endl;
  cout << NUMBER_2 << endl;

  return 0;
}

程序的运行结果如图所示:

既然NUMBER_1是一个变量,那么可不可以通过指针的强大功能来突破const的限制,修改NUMBER_1的值呢?答案是不能的,通过下面的代码可以验证:

#include <string.h>
#include <iostream>

using namespace std;

int main(int argv, char* argc[])
{
  const int NUMBER_1 = 2;

  * (int *) &NUMBER_1 = 3;

  cout << NUMBER_1 << endl;

  return 0;
}

程序输出如图所示,NUMBER_1的值依然是2,并不是3,没能骗过编译器:

通过单步调试,可以发现:NUMBER_1的确在栈中有4字节的空间,而且* (int *) &NUMBER_1 = 3这行代码的确改变了此空间内的值,如图所示:

那为什么程序依然显示2呢?原因是编译器在编译的过程中,自动把NUMBER_1提前都换成数字2了,所以程序的cout << NUMBER_1 << endl;一句,已经变成了cout << 2 << endl;,尽管程序运行过程中NUMBER_1的值发生了改变(突破了const的语法限制),但这个改变发生在代码cout << NUMBER_1 << endl;的改变之后,并不会对程序结果造成影响。这一做法和C语言中的宏相似,区别是宏的作用时间是预处理阶段,而const变量的作用时间是编译过程中(预处理阶段之后)。

PS:此实验只能在debug版本中实现,在release版本中const修饰的变量在栈中是没有地址的(这个说法可能不对,待确定)。

综上所述,在C++中,我们完全可以用const修饰变量来替代无参宏

附:尝试通过传递引用的方式修改const变量NUMBER_1的值。其实和修改指针是一个原理,都是表面上看上去成功了,但是本质上无法突破const的限制,因为代码替换工作(类似于宏)发生在NUMBER_1被改变之前。

#include "stdafx.h"

#include <string.h>
#include <iostream>

using namespace std;

void fun_fail(int n)
{
  cout << "fun_fail " << n << endl;
}

void fun_success(int& n)
{
  cout << "fun_success " << n << endl;
  n = 4;    //尝试修改NUMBER_1的值,函数内成功,函数外无效。
  cout << "fun_success step 2 " << n << endl;    //输出是4。
}

int main(int argv, char* argc[])
{
  const int NUMBER_1 = 2;

  * (int *) &NUMBER_1 = 3;    //此处成功把NUMBER_1的内存从2修改为3。

  int n = NUMBER_1;    //n的值是2。

  cout << NUMBER_1 << endl;    //输出是2。

  //尝试输出3,失败。
  fun_fail(NUMBER_1);

  //尝试输出3,成功。本质上是传送了已被修改的一段内存地址给函数。
  fun_success((int&)NUMBER_1);

  /*虽然在fun_success里对NUMBER_1的引用做出了修改(修改为了4),但是函数之外,
  NUMBER_1的值依然没有改变,因为在编译过程中,下一行代码已经被换成
  "cout << 2 << endl;"了。代码的替换发生在NUMBER_1的内存改变之前。*/
  cout << NUMBER_1 << endl;    //输出依然是2。

  return 0;
}

const修饰指针型变量

C语言中const修饰指针型变量的唯一语法是 :const char *pString. 代表了指针指向的内容不可更改,而指针的内容可以更改。

C++中,const修饰指针型变量的用法变得更加强大,具体用法和解释如示例代码所示:

#include "stdafx.h"

int main(int argv, char* argc[])
{
  char *pTmp = "hello";
  char *pAnother = "world";

  //1. 
  //指针pString1指向的字符串不可修改。
  //指针pString1的内容可以修改。
  const char *pString1 = pTmp;
  pString1[1] = 'd';    //报错
  pString1 = pAnother;    //合法

  //2. 等同于1
  //如同 `unsigned int n = 1` 等价与 `int unsigned n = 1`. const和char位置可互换
  //指针pString2指向的字符串不可修改。
  //指针pString2的内容可以修改。
  char const *pString2 = pTmp;
  pString2[1] = 'd';    //报错
  pString2 = pAnother;    //合法

  //3.
  //指针pString3指向的字符串可修改。
  //指针pString3的内容不可修改。指针pString3必须初始化。
  char * const pString3 = pTmp;
  pString3[1] = 'd';    //合法
  pString3 = pAnother;    //报错

  //4.
  //指针pString4指向的字符串不可修改。
  //指针pString4的内容不可修改。指针pString4必须初始化。
  const char * const pString4 = pTmp;
  pString4[1] = 'd';    //报错
  pString4 = pAnother;    //报错

  return 0;
}

看上去有些杂乱,读的技巧是关注const和*的相对位置。const在左边就说明指针指向的内容不可改,const在右边就说明指针的内容不可改。之所以有限定指针的内容不可修改的需求,是为了保护一些关键指针,比如main函数的agrv指针,如果在程序的某处把它改为NULL,那么就永远不能再读取程序的参数列表了。

在编程的过程中给函数参数加上const是个好习惯,这样能避免很多能编译通过,但是运行时会出错的bug。例如,程序运行时经常发生的C05错误大多是因为对全局常量区的字符串进行写入造成的,可以在编程时在这些指向全局常量区的指针前加入const修饰符,来规避这种错误。

posted @ 2016-10-12 11:55  zhugehq  阅读(1269)  评论(0编辑  收藏  举报