加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱制作网_沈阳站长网 (https://www.024zz.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

一文讲 清C/C++ Const/Const_Cast/Constexpr

发布时间:2022-11-25 12:40:00 所属栏目:语言 来源:
导读:  很多人搞不清const、const_cast、constexpr的用法,稀里糊涂地用。一般而言,即使乱用,问题也不大,因为错大发了会崩,崩了自然会被修正,不崩自然也就没事。但作为一个有追求的专业程序员,自当闻过则喜,搞清
  很多人搞不清const、const_cast、constexpr的用法,稀里糊涂地用。一般而言,即使乱用,问题也不大,因为错大发了会崩,崩了自然会被修正,不崩自然也就没事。但作为一个有追求的专业程序员,自当闻过则喜,搞清楚弄明白。
  
  一、const
  C语言的const用法
  先讲const,这玩意儿怎么翻译我也拿不准,C语言中该关键字的用法比较简单,大概有如下几种用法:
  
  [1] 修饰普通变量:变量只读,在程序运行过程中不可修改。
  
  const int i = 100; //i is read only
  i = 200; //compile error, variable i can not assignable
  [2] 修饰指针 const T* p:表示不能通过p去修改p指向对象的内容,另一方面只能通过p调用T类的const成员函数
  
  const struct Foo *f = new Foo;  
  f->dataX = 100; //compile error
  
  const char* p = "abc";  
  p[1] = 'x'; //compile error
  f->nonconst_member_function(); ///compile error (后面再讲)
  [3] 修饰指针 T* const p:表示指针只能在初始化时设置指向,之后便不能修改指向。
  
  char s1[] = "abc";  
  char s2[] = "xyz";
  char* const p = s1;
  p = s2; //compile error
  [4] 修饰指针 const T* const p:表示既不能通过p修改它指向的对象,又不能更改p的指向。
  
  const char* const p = "abc";
  p[1] = 'B'; //compile error
  p = "xyz"; //compile error
  [5] 修饰函数参数:c语言中const修饰参数反映的含义同上所述
  
  小结:C语言中,const的用法差不多就这些,比较简单。
  
  C++扩充了const的用法
  
  [1] 修饰成员变量:const成员变量只能在初始化列表里做初始化,程序运行中不可修改;如果是const整型,则可以C++11标准之后直接初始化。
  
  struct Foo  
  {
      Foo() : PI(3.15) {} // PI is initialized by initializer list
      const int c = 100; //C++11 support
      const float PI;
  };
  [2] 修饰成员函数:表示该成员函数是只读函数,不会修改默认参数this的成员变量,如果修改会编译报错。
  
  class Foo
  {
      int m_money;
  public:
      int get_money() const //✅
      {
          return m_money;
      }
  
      int set_money(int money) const //❌
      {
          m_money = money; //修改了this->m_money;需去掉函数const修饰
      }     
  };
  [3] 修饰引用:引用是C++才有的语法特征,引用是别名,本质上跟指针差不多,所以const修饰引用跟修饰指针的语义和约束差不多。
  
  Foo f;
  const Foo& r = f;
  r.m_data = 1; //compile error
  [4] C++中对const修饰指针的补充
  
  struct Foo  
  {
    int const_member_function() const { return m_data; }
    int non_const_member_function(int data) { m_data = data; }
    int m_data;
  };
  
  int main()
  {
    const Foo* f = new Foo;
    f->const_member_function();  //OK
    f->non_const_member_function(); //compile ERROR
    return 0;
  }
  为什么呢?因为const成员函数相当于承诺不会修改this的成员变量,而该承诺会被编译器检查,如果没有履行承诺,则编译器会报错。而const Foo* f意味着不能通过f去修改f指针指向变量的内部值。
  
  通过f->data = 1的方式肯定是不行。
  
  另一方面,你只能通过f去调用它的const成员函数,因为const成员函数的语义就是不会修改this的值,编译器很容易执行这个校验。
  
  const修饰参数
  
  const可以修饰普通参数,也可以修饰指针/引用参数,因为形参是实参的副本,所以const修饰普通参数其实没什么意义,我们着重讲讲const修饰指针/引用参数。
  
  比如标准C库函数strcpy的签名:char *strcpy(char * dst, const char * src);
  
  dst表示目标地址,src表示源串,const修饰了源串,这是因为从源串拷贝到目标串,不需要修改源串内容,这相当于向strcpy调用者承诺:
  
  放心大胆的调用吧,strcpy函数实现保证不会修改src的内容,编译器会执行这种检查。
  
  这样,在review代码的时候,如果想追踪src在哪里被修改了,当看到strcpy的签名,就不用打开函数去看实现,只要不违背承诺,肯定不是这个函数内改动了src。
  
  const char *src是一种承诺,也是一种约束。调用的地方,const char*形式的形参,既传const char*实参,也可以传char*实参,因为参数const char*是更强的承诺。
  
  但反之不成立。比如第一个参数dst是不带const的,那么如果有一个变量类型为const char* p,那不能把p作为第一个参数传递进strcpy,编译不过。
  
  因为strcpy不承诺不修改dst,是一个更弱的承诺,只有声明为const指针的参数,才能传递const指针实参。
  
  const其他
  
  const还可以修饰返回值,还可以跟extern结合,但这些都是一些小语法技巧,一般开发用不太到,真碰到再查不迟。
  
  二、const_cast
  const_cast有什么用?
  
  const是C++的一个强制转换,它用来去掉const属性,比如:
  
  Foo foo;
  const Foo *f1 = &foo;
  Foo* f2 = const_cast<Foo*>(f);
  Foo* f3 = (Foo*)f;
  const_cast的作用跟强转差不多,C++加const_cast主要是为了功能完整性,const_cast作用于引用跟作用于指针差不多。
  
  为什么说const_cast几乎都反应接口设计有问题
  
  程序设计要言行一致,遵守承诺,这意味着:不应该把参数声明为const指针,而函数实现里借助强制去掉const属性。
  
  首先,这样做是危险的,比如const char* p = "abc"; p指向常量字符串被作为参数传递,被强转+修改,则会导致程序crash。
  
  其次,这样做是分裂的,因为你加const修饰相当于让编译器帮你执行检查,以便在你违背承诺的时候通过编译期检查报错提醒你,但在它真正向你报错的时候,你又说别管啦,老子就是要蛮干。
  
  const_cast或者通过c风格强转,基本上都暴露出设计上的问题。
  
  设计良好的程序基本上不需要const强转。因为const约束在调用链会传播,所以,你需要一以贯之的遵守约定,找到导致需要const强转的错误源头,这可能会多费一点时间,但它是值得的。
  
  三、constexpr
  const没有区分编译期常量和运行期常量,constexpr是C++11开始提出的关键字,被限定为编译器常量,其意义与14版本有一些区别。
  
  C++11中的constexpr指定的函数返回值和参数必须要保证是字面值,而且必须有且只有一行return代码,这给函数的设计者带来了更多的限制,比如通常只能通过return 三目运算符+递归来计算返回的字面值。
  
  而C++14中只要保证返回值和参数是字面值就行了,函数体中可以加入更多的语句,方便了更灵活的计算。
  
  这里我们主要讲constexpr和const的区别。
  
  constexpr可以用来修饰变量、函数、构造函数。一旦以上任何元素被constexpr修饰,那么等于说是告诉编译器 “请大胆地将我看成编译时就能得出常量值的表达式去优化我”。
  
  constexpr func()  
  {
    return 10;
  }
  
  int main()
  {
    int arr[func()];
  }
  编译期大胆地将func()做了优化,在编译期就确定了func计算出的值10而无需等到运行时再去计算。
  
  这就是constexpr的第一个作用:给编译器足够的信心在编译期去做被constexpr修饰的表达式的优化。
  
  constexpr还有另外一个特性,虽然它本身的作用之一就是希望程序员能给编译器做优化的信心,但它却猜到了自己可能会被程序员欺骗,而编译器并不会对此“恼羞成怒”中止编译。
  
  四、结论
  C/C++程序应该积极的使用const/constexpr,什么叫积极使用?只要有可能,那么我们就应该用const/constexpr。
  
  只要可能就应该用xx,这种话一般而言都是错的,但用在const/constexpr却很正确,因为使用const/constexpr基本上都会让你的程序更健壮、更快,const修饰的整型变量,在gcc开优化选项的时候,有可能被直接编译到汇编代码指令,而非生成一个变量,而constexpr的优化作用在前面一节已经阐述。
  
  与之对应的是:只要有可能,就不要使用const_cast,它基本上都反映了接口设计上的问题。
  
  就酱,信不信随你!
 

(编辑:我爱制作网_沈阳站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!