关于constexpr(C++ 11) 和 const

一、二者区别

const 表示“只读”的语义,constexpr 表示“常量”的语义 。

constexpr(常量表达式):是指值不会改变并且在编译过程就能得到计算结果的表达式。

常量表达式的优点是将计算过程转移到编译时期,那么运行期就不再需要计算了,程序性能也就提升了。

复杂系统中很难分辨⼀个初始值是不是常量表达式,可以将变量声明为constexpr类型,由编译器来验证变量的值是否是⼀个常量表达式。

1.修饰变量

①普通变量

constexpr 只能定义编译期常量,⽽ const 可以定义编译期常量,也可以定义运⾏期常量

二者修饰的变量在定义时必须初始化。

编译期常量:在编译器就能得到值。

运行期常量:在运行时才能得到值。

const int a = 5;    //编译期常量
constexpr int aa = 5;    //编译期常量,不会报错

string s = "hello";
constexpr int aaa = s.length();    //运行期常量,编译错误

②成员变量

二者修饰的类成员只能通过构造函数的初始化列表初始化。

2.修饰指针

首先了解两个概念:顶层const, 底层const。
顶层const代表指针变量自身无法修改;底层const代表指针所指对象无法修改。

用const修饰指针:左定值(底层),右定向(顶层)

int a = 3;
int b = 4;

const int *p = &a;    //左定值,不能修改指向变量的值,但是能修改指向的变量
*p = 33;    //编译错误,无法修改*p的值
p = &b;     //可以运行,p可以指向其他变量

int *const p = &a;    //右定向,可以修改指向变量的值,但是不能再指向其他变量。
*p = 33;    //可以运行,*p的值可以被修改
p = &b;     //编译错误,p不能再指向其他变量

用constexpr修饰指针:constexpr只对指针有效,与指针所指对象无关。

这句话的意思是constexpr修饰的指针是一个指向变量的常对象,指针的方向不能改变,指向的对象可以改变。

// a的定义必须放在函数体外
int a = 5;

// 函数体内
constexpr int *p1 = &a;
cout << *p1 << endl; // 5
*p1 = 7;
cout << a << endl; // 7
p1 = nullptr; // 错误,constexpr指针无法修改

3.修饰普通函数

constexpr函数是指能⽤于常量表达式的函数。

constexpr int new() {return 42;}

函数的返回类型和所有形参类型都是字⾯值类型,函数体有且只有⼀条return语句。 为了可以在编译过程展开,constexpr函数被隐式转换成了内联函数。 constexpr和内联函数可以在程序中多次定义,⼀般定义在头⽂件。

从C++11开始,constexpr函数不仅可以返回常量,还可以进行递归操作。
从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句。

4.修饰成员函数

const修饰成员函数,通常称为const函数,表示该函数不会修改类的状态(即不会通过任何方式修改类数据成员)。另外,const类对象,只能调用const函数,确保不会修改类的数据成员。
constexpr无法修饰成员函数,只能作为函数返回值类型,表明该函数返回的是一个编译期可确定的常量。

constexpr构造函数必须有⼀个空的函数体,即所有成员变量的初始化都放到初始化列表中。对象调⽤的成员函数必须使⽤ constexpr 修饰

class ss {
public:
    int m_a;
    int m_size;
public:
    constexpr ss(int a, int b) : m_a(a), m_size(b) {}

public:
    int size() const { 
        m_size = 10;    // 编译错误,不能写任何数据成员
        return m_size; 
    }  
    const int size() { 
        m_size = 20;    // 函数体可以修改数据成员,但返回类型是const,也就是调用者无法修改
        return m_size; 
    }  
    constexpr int getMaxSize() { return INT_MAX; }  // 不能返回非常量值
    int getMaxSize() constexpr { return INT_MAX; }  //编译错误,constexpr无法修饰成员函数,只能修饰返回值
};

二、修改const修饰的变量

1.修改全局变量

全局const变量是存放于rodata(只读)区的,无论如何都不能修改。

2.修改局部变量

若要修改const修饰的局部变量的值,需要加上关键字volatile。

    const int w = 2;
    volatile const int m = 3;
    
    int* p = (int*) & w;
    *p = 4;
    cout << &w << endl; //000000088D9FF734
    cout << p << endl;  //000000088D9FF734
    cout << *p << endl; //  4
    cout << w << endl;  //  2

    int* pp = (int*) & m;
    *pp = 4;
    cout << (void*) & m << endl;//0000000E291BFA14
    cout << pp << endl;         //0000000E291BFA14
    cout << *pp << endl;        //  4
    cout << m << endl;          //  4

对于w来说,虽然没有报错,修改成功,但是w的值仍然是2。

可是指针的地址和w的地址是一样的啊,这是为什么呢?难道一个地址可以存放两个不同的值吗?这显然是不符合常理的。

其实,这是C++中的常量折叠现象:const变量(即常量)值放在编译器的符号表中,计算时编译器直接从表中取值,省去了访问内存的时间,这是编译器进行的优化。w是const变量,编译器对w在预处理的时候就进行了替换。编译器只对const变量的值读取一次。所以打印的是2。w实际存储的值被指针p所改变。简而言之就是读取const类型的变量不会取内存中读取,而是在编译器符号表中读取。

而对于m来说,m是volatile const类型变量,告诉编译器该变量属于易变的,不要对此句进行优化,每次计算时要去内存中读取该变量的值,进而避免出现常量折叠的问题。

volatile

定义: [与const绝对对⽴的,是类型修饰符]

影响编译器编译的结果,⽤该关键字声明的变量表示该变量随时可能发⽣变 化,与该变量有关的运算,不要进⾏编译优化;会从内存中重新装载内容,⽽不是直接从寄存器拷⻉内容。

作⽤: 指令关键字,确保本条指令不会因编译器的优化⽽省略,且要求每次直接读值,保证对特殊地址的稳定访问

使⽤场合: 在中断服务程序和cpu相关寄存器的定义

举例说明: 空循环

for(volatile int i=0; i<100000; i++); // 它会执⾏,不会被优化掉 

至于为什么在输出m的地址时需要用void*强转,请移步为什么对于volatile int *p,cout << p输出会是这样? - 知乎 (zhihu.com)

三、使用const的好处

1、可以定义const常量
 这样可以避免由于无意间修改数据而导致的编程错误。

2、便于进行类型检查
 const常量有数据类型,而宏常量(define)没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。

3、为函数重载提供了一个参考
 const修饰的函数可以看作是对同名函数的重载。

4、可以节省空间,避免不必要的内存分配
 const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象宏一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而宏定义的常量在内存中有若干个拷贝。

5、提高了效率
 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期的常量,没有了存储与读内存的操作,使得它的效率也很高。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值