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

开发 后端
很多人搞不清const、const_cast、constexpr的用法,稀里糊涂地用。一般而言,即使乱用,问题也不大,因为错大发了会崩,崩了自然会被修正,不崩自然也就没事。

[[422168]]

本文转载自微信公众号「码砖杂役」,作者我不想种地 。转载本文请联系码砖杂役公众号。

很多人搞不清const、const_cast、constexpr的用法,稀里糊涂地用。一般而言,即使乱用,问题也不大,因为错大发了会崩,崩了自然会被修正,不崩自然也就没事。但作为一个有追求的专业程序员,自当闻过则喜,搞清楚弄明白。

一、const

C语言的const用法

先讲const,这玩意儿怎么翻译我也拿不准,C语言中该关键字的用法比较简单,大概有如下几种用法:

[1] 修饰普通变量:变量只读,在程序运行过程中不可修改。

  1. const int i = 100; //i is read only 
  2. i = 200; //compile error, variable i can not assignable 

[2] 修饰指针 const T* p:表示不能通过p去修改p指向对象的内容,另一方面只能通过p调用T类的const成员函数

  1. const struct Foo *f = new Foo;  
  2. f->dataX = 100; //compile error 
  3.  
  4. const char* p = "abc";  
  5. p[1] = 'x'; //compile error 
  6. f->nonconst_member_function(); ///compile error (后面再讲) 

[3] 修饰指针 T* const p:表示指针只能在初始化时设置指向,之后便不能修改指向。

  1. char s1[] = "abc";  
  2. char s2[] = "xyz"
  3. char* const p = s1; 
  4. p = s2; //compile error 

[4] 修饰指针 const T* const p:表示既不能通过p修改它指向的对象,又不能更改p的指向。

  1. const char* const p = "abc"
  2. p[1] = 'B'; //compile error 
  3. p = "xyz"; //compile error 

[5] 修饰函数参数:c语言中const修饰参数反映的含义同上所述

小结:C语言中,const的用法差不多就这些,比较简单。

C++扩充了const的用法

[1] 修饰成员变量:const成员变量只能在初始化列表里做初始化,程序运行中不可修改;如果是const整型,则可以C++11标准之后直接初始化。

  1. struct Foo  
  2.     Foo() : PI(3.15) {} // PI is initialized by initializer list 
  3.     const int c = 100; //C++11 support 
  4.     const float PI; 
  5. }; 

[2] 修饰成员函数:表示该成员函数是只读函数,不会修改默认参数this的成员变量,如果修改会编译报错。

  1. class Foo 
  2.     int m_money; 
  3. public
  4.     int get_money() const //✅ 
  5.     { 
  6.         return m_money; 
  7.     } 
  8.  
  9.     int set_money(int money) const //❌ 
  10.     { 
  11.         m_money = money; //修改了this->m_money;需去掉函数const修饰 
  12.     }     
  13. }; 

[3] 修饰引用:引用是C++才有的语法特征,引用是别名,本质上跟指针差不多,所以const修饰引用跟修饰指针的语义和约束差不多。

  1. Foo f; 
  2. const Foo& r = f; 
  3. r.m_data = 1; //compile error 

[4] C++中对const修饰指针的补充

  1. struct Foo  
  2.   int const_member_function() const { return m_data; } 
  3.   int non_const_member_function(int data) { m_data = data; } 
  4.   int m_data; 
  5. }; 
  6.  
  7. int main() 
  8.   const Foo* f = new Foo; 
  9.   f->const_member_function();  //OK 
  10.   f->non_const_member_function(); //compile ERROR 
  11.   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属性,比如:

  1. Foo foo; 
  2. const Foo *f1 = &foo; 
  3. Foo* f2 = const_cast<Foo*>(f); 
  4. 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修饰,那么等于说是告诉编译器 “请大胆地将我看成编译时就能得出常量值的表达式去优化我”。

  1. constexpr func()  
  2.   return 10; 
  3.  
  4. int main() 
  5.   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,它基本上都反映了接口设计上的问题。

 

就酱,信不信随你!

 

责任编辑:武晓燕 来源: 码砖杂役
相关推荐

2011-07-20 10:06:54

CC++const

2024-03-11 15:32:50

C++开发

2010-02-02 14:06:50

C++ const变量

2011-06-21 10:44:31

const

2011-06-21 10:37:56

const

2024-02-23 18:04:37

C++const关键字

2023-09-26 22:37:16

C++const

2009-08-26 17:31:59

C# const常量

2020-10-26 09:18:50

RedisCluste

2011-07-20 16:57:05

C++const

2021-07-28 06:53:02

C++Const指针传递

2011-07-14 09:09:14

const

2009-08-27 15:17:40

C# const变量

2021-01-27 09:34:51

Visual C++Dev C++codelite

2009-08-27 10:54:09

C# const和st

2021-09-09 17:05:36

C++智能指针语言

2024-01-24 11:35:28

C++多返回值开发

2019-09-16 12:00:03

constC编程语言

2023-12-01 13:47:45

C语言conststat

2010-01-22 16:55:52

C++编译器
点赞
收藏

51CTO技术栈公众号