在C++中,定义变量的时候可以指定常量属性,说明这个变量成为常量,无法直接改变;这个使用const限定符来限定,例如:
#include
using namespace std;
int main(int args, char* argv[])
{
const int a = args;
return 0;
}
从上面的例子我们可以发现,虽然a是一个const,但是使用一个变量初始化的。
在C++中还有另外一种声明方式,叫做常量表达式(constexpr),那就需要赋值的表达式为编译期间的常量,例如:
#include
using namespace std;
int main(int args, char* argv[])
{
constexpr int b = args;
return 0;
}
此时编译就会报错,如下:
constexpr.cpp: In function ‘int main(int, char**)’:
constexpr.cpp:6:20: error: ‘args’ is not a constant expression
constexpr int b = args;
^~~~
对于普通变量我们比较好理解,但是C++的类,也能声明成为字面值的常量类。
字面值的常量类有两种定义:
例如如下类:
#include
using namespace std;
class CPoint
{
public:
constexpr CPoint(int xx, int yy) : x(xx), y(yy){}
void setx(int xx){x = xx;}
void sety(int yy){y=yy;}
constexpr int getx() const {return x;}
constexpr int gety() const {return y;}
private:
int x;
int y;
};
int main(int args, char* argv[])
{
constexpr CPoint point(100, 200);
constexpr int data = point.getx() * point.gety();
cout << data << endl;
cout << point.getx() * point.gety() << endl;
return 0;
}
这个就是一个字面值常量类,这个字面值常量类可以定义constexpr CPoint
的对象,这种对象可以在编译的时候展开,但是展开也是有条件的,针对constexpr
才会展开,不过应该每个编译器的实现各有差异,我们看一下上面这个的反汇编代码:
constexpr CPoint point(100, 200);
0x0000000008000919 <+31>: movl $0x64,-0x20(%rbp)
0x0000000008000920 <+38>: movl $0xc8,-0x1c(%rbp)
19 constexpr int data = point.getx() * point.gety();
0x0000000008000927 <+45>: movl $0x4e20,-0x24(%rbp)
20 cout << data << endl;
0x000000000800092e <+52>: mov $0x4e20,%esi
0x0000000008000933 <+57>: lea 0x2006e6(%rip),%rdi # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
0x000000000800093a <+64>: callq 0x80007d0 <_ZNSolsEi@plt>
0x000000000800093f <+69>: mov %rax,%rdx
0x0000000008000942 <+72>: mov 0x200687(%rip),%rax # 0x8200fd0
0x0000000008000949 <+79>: mov %rax,%rsi
0x000000000800094c <+82>: mov %rdx,%rdi
0x000000000800094f <+85>: callq 0x80007a0 <_ZNSolsEPFRSoS_E@plt>
21 cout << point.getx() * point.gety() << endl;
0x0000000008000954 <+90>: lea -0x20(%rbp),%rax
0x0000000008000958 <+94>: mov %rax,%rdi
0x000000000800095b <+97>: callq 0x8000a12 <CPoint::getx() const>
0x0000000008000960 <+102>: mov %eax,%ebx
0x0000000008000962 <+104>: lea -0x20(%rbp),%rax
0x0000000008000966 <+108>: mov %rax,%rdi
0x0000000008000969 <+111>: callq 0x8000a22 <CPoint::gety() const>
0x000000000800096e <+116>: imul %ebx,%eax
0x0000000008000971 <+119>: mov %eax,%esi
0x0000000008000973 <+121>: lea 0x2006a6(%rip),%rdi # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
0x000000000800097a <+128>: callq 0x80007d0 <_ZNSolsEi@plt>
0x000000000800097f <+133>: mov %rax,%rdx
0x0000000008000982 <+136>: mov 0x200647(%rip),%rax # 0x8200fd0
0x0000000008000989 <+143>: mov %rax,%rsi
0x000000000800098c <+146>: mov %rdx,%rdi
0x000000000800098f <+149>: callq 0x80007a0 <_ZNSolsEPFRSoS_E@plt>
对于constexpr int data = point.getx() * point.gety();
这个,被编译器直接转换为了movl $0x4e20,-0x24(%rbp)
.
但是针对cout << point.getx() * point.gety() << endl;
并没有进行编译器展开,反汇编为:
callq 0x8000a12
。callq 0x8000a22
。imul %ebx,%eax
。也就是说,字面值常量类只是对常量表达式计算进行了优化(但是这种讲法应该不是特别准确的,每个编译器实现上应该有所差异)。
int main(int args, char* argv[])
{
CPoint point(100, 200);
constexpr int data = point.getx() * point.gety();
cout << data << endl;
cout << point.getx() * point.gety() << endl;
return 0;
}
如果去掉CPoint point
的常量表达式属性,那么就不会编译器展开了,如下:
constexpr.cpp: In function ‘int main(int, char**)’:
constexpr.cpp:19:33: in constexpr expansion of ‘point.CPoint::getx()’
constexpr.cpp:19:49: error: the value of ‘point’ is not usable in a constant expression
constexpr int data = point.getx() * point.gety();
^
constexpr.cpp:18:9: note: ‘point’ was not declared ‘constexpr’
CPoint point(100, 200);
由于不是常量,编译期间展开,直接出错。
字面值常量类可以定义字面值常量的对象,例如:constexpr CPoint point(xxx)
.
用作常量表达式初始化的时候,编译期间自动展开constexpr int data = point.getx() * point.gety();
.