加入收藏 | 设为首页 | 会员中心 | 我要投稿 应用网_丽江站长网 (http://www.0888zz.com/)- 科技、建站、数据工具、云上网络、机器学习!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

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

发布时间:2021-11-07 12:51:06 所属栏目:语言 来源:互联网
导读:很多人搞不清const、const_cast、constexpr的用法,稀里糊涂地用。一般而言,即使乱用,问题也不大,因为错大发了会崩,崩了自然会被修正,不崩自然也就没事。但作为一个有追求的专业程序员,自当闻过则喜,搞清楚弄明白。 一、const C语言的const用法 先讲
很多人搞不清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,它基本上都反映了接口设计上的问题。
 
就酱,信不信随你!

(编辑:应用网_丽江站长网)

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

    热点阅读