zoukankan      html  css  js  c++  java
  • 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修饰符,来规避这种错误。

  • 相关阅读:
    猴子分香蕉
    打鱼晒网
    质数/素数
    三角形-->九九乘法表
    eclipse 导入gradle引入多模块项目,引入eclipse后变成了好几个工程
    linux 命令基础大全
    SQL Server 增加链接服务器
    Postgresql数据库部署之:Postgresql 存在session 会话不能删除数据库
    Postgresql数据库部署之:Postgresql本机启动和Postgresql注册成windows 服务
    Git常用命令使用大全
  • 原文地址:https://www.cnblogs.com/zhugehq/p/5952218.html
Copyright © 2011-2022 走看看