constexpr
1、常量表达式是什么
在编译时就能确定其值的表达式。换句话说,常量表达式的值在编译过程中就已经是已知且不会改变的。常量表达式是由 数据类型 和 初始值 共同决定的。(注意区分const 和 常量表达式)
常量表达式的特点:
- 值在编译时已知:常量表达式的值在编译阶段就能确定,而不是在运行时。
- 不会改变:常量表达式的值一旦确定,就不会再改变。
- 不包含函数调用:除了几个特定的内置函数(如sizeof)外,常量表达式通常-不包含函数调用,因为函数调用可能会在运行时产生不同的结果。
- 通常用于初始化常量:常量表达式常用于初始化常量,如数组的大小、枚举值、constexpr变量等。
以下为几个例子:
const int a1 = 10; //a1为常量表达式
const int a2 = a1 + 10; //a2为常量表达式
int a3 = 10; //a3不为常量表达式,非const int类型
const int a4 = fun(); //需要运行时确定值,非编译时期
2、constexpr
constexpr是C++中的一个关键字,用于指定常量表达式。
constexpr函数是能用于常量表达式的函数,它们的返回值类型及所有形参的类型都必须是字面值类型,并且函数体中必须有且只有一条return语句。constexpr函数被隐式地指定为内联函数,以便在编译过程中随时展开。
int fun()
{
return 10;
}
int main()
{
constexpr int a = 10;
constexpr int b = a + 10;
constexpr int c = fun(); //error,fun函数必须是constexpr类型
return 0;
}
constexpr int fun()
{
}
int main()
{
constexpr int a = 10;
constexpr int b = a + 10;
constexpr int c = fun(); //error,fun函数必须有返回值
return 0;
}
3、constexpr和指针
在C++中,constexpr
用于指示一个值或表达式是常量表达式,它的值在编译时是已知的且不会改变。
以下是关于constexpr
和指针的一些要点:
-
指向常量的指针:
一个指针可以是constexpr
,如果它指向的对象是常量,并且该指针在初始化后不会改变。但是,这并不保证所指向的对象的内容也是常量(只是指针本身是常量)。constexpr int* ptr = &a; // 指向常量的指针 // 注意:a 必须是一个在编译时已知地址的常量
-
指向字面量的指针:
指向字符串字面量或其他字面量的指针也可以是constexpr
,因为这些字面量的地址在编译时是已知的。constexpr char* str = "Hello, constexpr!"; // 指向字符串字面量的指针
-
指针的初始化:
constexpr
指针必须在声明时初始化,并且之后不能修改。constexpr int value = 42; constexpr int* ptr = &value; // 正确:在声明时初始化 ptr = &anotherValue; // 错误:不能修改constexpr指针
-
指针运算:
由于constexpr
指针指向的值在编译时是已知的,因此你不能对它们进行指针运算(如递增或递减),除非这些运算的结果在编译时也是已知的。 -
指向数组的指针:
你可以有一个constexpr
指针指向数组的首元素,但这并不意味着整个数组都是constexpr
。只有数组的内容在编译时已知,并且数组本身被声明为constexpr
时,这样的数组才是constexpr
数组。 -
指向非常量数据的
constexpr
指针:
虽然技术上可以有一个constexpr
指针指向非常量数据(即不是const
的数据),但这样做通常没有太多意义,因为constexpr
的主要目的是表示编译时常量。 -
constexpr
函数和指针:
在constexpr
函数中,你可以返回指向常量数据的指针,但返回的指针必须指向在编译时已知地址的对象。constexpr int* getPointer() { static int value = 42; // 静态局部变量具有固定的地址 return &value; }
4、constexpr和构造函数
构造函数不能是const类型,但是字面值常量类的构造函数可以是constexpr类型。
class Test {
public:
constexpr Test(int val) : value(val) {}
// constexpr构造函数
// constexpr成员函数
constexpr int getValue() const { return value; }
private:
int value; // 字面值类型的数据成员
};
constexpr构造函数也可以声明为 = default。
class MyClass
{
public:
int value;
// 这是一个constexpr的默认构造函数
constexpr MyClass() = default;
// 如果value有constexpr构造函数,则这也是可能的
// constexpr MyClass(int v) : value(v) {}
};
// 现在可以在编译时创建一个MyClass的实例
constexpr MyClass myObject;
常见误区:
之前学习constexpr函数必须有参数和返回值,但是构造函数不能有返回值,那么为什么可以有constexpr类型的构造函数呢?
constexpr 关键字对于构造函数的要求并不是它必须有参数和返回值,而是说如果一个构造函数被声明为 constexpr,那么它必须能够在编译时生成一个常量表达式。这意味着构造函数的初始化列表和函数体中的操作都必须是编译时常量表达式。
constexpr 构造函数允许你在编译时创建一个对象,你可以使用 constexpr 构造函数来初始化 constexpr 变量,这样你就可以在编译时知道该变量的确切值。
class MyClass
{
public:
int value;
//这是一个constexpr构造函数
constexpr MyClass(int v) : value(v) {}
};
//使用constexpr构造函数在编译时初始化一个对象
constexpr MyClass myObject(42);
//myObject.value在编译时是已知的,因此可以在编译时使用的上下文中使用它
static_assert(myObject.value == 42, "Value is not 42!"); // 编译时将检查这个断言
总结来说,constexpr 构造函数允许你在编译时创建和初始化对象,而不是要求构造函数必须有参数和返回值。构造函数本身不返回任何值(它们的返回类型是 void),但 constexpr 构造函数允许你在编译时知道对象的状态。
constexpr构造函数必须初始化所有数据成员
class MyClass
{
public:
int a;
float b;
// constexpr构造函数使用初始化列表来初始化所有成员
constexpr MyClass(int val_a, float val_b) : a(val_a), b(val_b) {}
};
// 使用constexpr构造函数在编译时初始化一个对象
constexpr MyClass myObject(10, 3.14f);
// 现在myObject的a和b成员在编译时都是已知的
static_assert(myObject.a == 10, "a is not 10!");
static_assert(myObject.b == 3.14f, "b is not 3.14!");
删除b的初始化:
constexpr MyClass(int val_a, float val_b) : a(val_a) {} //b为初始化,报错