c++ 常量与类常量
在java中,有类常量。因为java没有头文件,直接写在类定义即可。
c++17中,也支持直接写在头文件定义类中,记得一定加上inline:
static inline const string TOP_LEVEL_DOMAIN_STR{ "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])" };
参考<c++17入门经典> 11章,Static Constants。
c++类中的常量
定义属于这个类范围的常量
class test
{
private:
enum {Months = 12};
};
这种声明枚举不会创建类数据成员,这里枚举只是为了创建类数据成员,因此不用提供枚举名。类似上面的例子还有ios_base::fixed
等。
扩充:c++11作用域内的枚举
enum egg {Small, Medium, Large, Jumbo};
enum t_shirt {Small, Medium, Large, Xlarge};
编译器提示重复定义Small
、Medium
、Large
和Jumbo
。因为egg Small和t_shirt Small位于相同的作用域内。
c++11提供了一种新的枚举,它的作用域为类。可以使用关键字class
或者struct
。
enum class egg {Small, Medium, Large, Jumbo};
enum class t_shirt {Small, Medium, Large, Xlarge};
egg choice = egg::Large;
t_shirt Floyd = t_shirt::Large;
const常量
class test
{
private:
const int n;
public:
test():n(100){}
}
类的声明只是声明了类的形式,并没有创建对象,因此,在创建对象前,将没有用于储存值的空间。
c++98与c++11的区别
在C++98标准里,只有static const声明的整型成员能在类内部初始化,并且初始化值必须是常量表达式。这些限制确保了初始化操作可以在编译时期进行。
class X {
static const int m1 = 7; // 正确
const int m2 = 7; // 错误:无static
static int m3 = 7; // 错误:无const
static const string m5 = “odd”; //错误:非整型
};
C++11的基本思想是,允许非静态(non-static)数据成员在其声明处(在其所属类内部)进行初始化。这样,在运行时,需要初始值时构造函数可以使用这个初始值。现在,我们可以这么写:
class A {
public:
int a = 7;
};
//它等同于使用初始化列表:
class A {
public:
int a;
A() : a(7) {}
};
c++11这样的好处就是当构造函数需要多个初始化时就会变得很简洁。
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
(1)不能在类声明中初始化const数据成员。
以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。
class A
{…
const int SIZE = 100; // 错误,企图在类声明中初始化const数据成员
int array[SIZE]; // 错误,未知的SIZE
};
(2)const数据成员的初始化只能在类构造函数的初始化表中进行
例如
class A
{…
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的初始化表
{
…
}
A a(100); // 对象 a 的SIZE值为100
A b(200); // 对象 b 的SIZE值为200
(3)怎样才能建立在整个类中都恒定的常量呢?别指望const数据成员了,应该用类中的枚举常量来实现。
例如
class A
{…
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
};
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如PI=3.14159)。
//------------------------------------------------------------------------
如果你想得到一个可用于常量表达式中的常量,例如数组大小的定义,那么你有两种选择:
class X { static const int c1 = 7; enum { c2 = 19 }; char v1[c1]; char v2[c2]; // ... };
一眼望去,c1的定义似乎更加直截了当,但别忘了只有static const的整型或枚举型量才能如此初始化。
这就很有局限性,例如
class Y { const int c3 = 7; // error: not static static int c4 = 7; // error: not const static const float c5 = 7; // error not integral };
static变量
static变量不像普通的变量,static变量独立于一切类对象处在。static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化。因为static是所有对象共享的东西嘛,必须要比对象先存在的。
初始化:数据类型 类名::静态数据成员名=值;
这表明:
1、初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆
2、初始化时不加该成员的访问权限控制符private、public等
3、初始化时使用作用域运算符来表明它所属的类,因此,静态数据成员是类的成员而不是对象的成员。
class test
{
private:
public:
static int i;
};
int test::i = 100;//此句包含了声明和赋值,初始化不受private和protected访问限制,但是若是priivate,下面main函数就无法访问
int main()
{
cout << test::i << endl;
return 0;
}
好处:用static修饰的成员变量在对象中是不占内存的,因为他不是跟对象一起在堆或者栈中生成,用static修饰的变量在静态存储区生成的,所以用static修饰一方面的好处是可以节省对象的内存空间。所以一般类const变量一般改为static const变量,可以节省一些空间。
const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。
在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate = 2.25;static关键字只能用于类定义体内部的声明中,定义时不能标示为static
在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。
#ifdef A_H_ #define A_H_ #include <iostream> usingnamespace std; class A{ public: A(int a); staticvoid print();//静态成员函数 private: static int aa;//静态数据成员的声明 staticconst int count;//常量静态数据成员(可以在构造函数中初始化) const int bb;//常量数据成员 }; int A::aa=0;//静态成员的定义+初始化 const int A::count=25;//静态常量成员定义+初始化 A::A(int a):bb(a){//常量成员的初始化 aa+=1; } void A::print(){ cout<<"count="<<count<<endl; cout<<"aa="<<aa<<endl; } #endif void main(){ A a(10); A::print();//通过类访问静态成员函数 a.print();//通过对象访问静态成员函数 }
https://www.cnblogs.com/fuao2000/p/11006999.html
C++ constexpr类型说明符
字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型”(literal type)。
算术类型、引用和指针都属于字面值类型。某些类也是字面值类型,它们可能含有constexpr函数成员。自定义类Sales_item、IO库、string类型不属于字面值类型。
尽管指针和引用可以定义成constexpr,但它们的初始值受到严格限制。一个constexpr指针的初始值必须是nullptr、0或存储于某个固定地址中的对象。
函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。定义于函数体外的对象其地址固定不变,能用来初始化constexpr指针。允许函数定义一类有效范围超出函数本身的变量,如局部静态变量,这类变量和定义在函数体之外的变量一样有固定地址,因此constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。
聚合类
聚合类(aggregate class)允许利用者直接访问其成员,并且具有特殊的初始化形式。聚合类满足以下条件:
所有成员都是public的
没有定义构造函数
没有类内初始值
没有基类,也没有虚函数
怎么理解呢?
首先,看来看去聚合类其实就是一个C结构体;其次,聚合这个词,应该是相对组合的,表明了成员和类之间的松散关系。
当一个类是聚合类时,就可以使用初始值列表像下面这样初始化了:
struct Point{
int x;
int y;
};
Point pt = {10, 10};
字面值常量类
前面讲过constexpr函数,它的参数和返回值都必须是常量表达式。而常量表达式的最基本要素就是字面值类型。字面值类型除了包括算数类型,引用和指针以外,某些类也属于字面值类型,C++11称之为字面值常量类。主要包括两种情况:
首先数据成员都是字面类型的聚合类就是一种。上面的Point类就是一个例子。我们可以这样理解,字面值的聚合继续具有字面值的特征,这里主要是编译阶段可以求值。
还有一种情况,虽然不是聚合类,但是只要满足下面的条件,也是字面值常量类:
数据成员必须都必须是字面值类型。
类必须至少含有一个constexpr构造函数。
如果一个数据成员含有类内初始值,则初始值必须是常量表达式;如果成员属于某种类,初始值必须使用该类的constexpr构造函数。
类必须使用析构函数的默认定义。
对于这几个条件,作者这样理解:
满足条件1,就可以在编译阶段求值,这一点和聚合类一样。
满足条件2,就可以创建这个类的constexpr类型的对象。
满足条件3,就可以保证即使有类内初始化,也可以在编译阶段解决。
满足条件4,就可以保证析构函数没有不能预期的操作。
constexpr构造函数
通过前置constexpr关键字,就可以声明constexpr构造函数,同时:
除了声明为=default或者=delete以外,constexpr构造函数的函数体一般为空,使用初始化列表或者其他的constexpr构造函数初始化所有数据成员。
struct Point{
constexpr Point(int _x, int _y)
:x(_x),y(_y){}
constexpr Point()
:Point(0,0){}
int x;
int y;
};
constexpr Point pt = {10, 10};
这样声明以后,就可以在使用constexpr表达式或者constexpr函数的地方使用字面值常量类了。
关键字 constexpr 于 C++11 中引入并于 C++14 中得到改善。它表示常数表达式。与 const 相同,它可应用于变量,因此如果任何代码试图修改该值,均将引发编译器错误。与 const 不同,constexpr 也可应用于函数和类构造函数。 constexpr 指示值或返回值是常数,并且如果可能,将在编译时计算值或返回值。
constexpr变量
- 在一个复杂的系统中,很难分辨一个初始值到底是不是常量表达式。当然可以定义一个const变量并把它的初始值设为我们认为的某个常量表达式,但在实际使用时,尽管要求如此却常常发现初始值并非常量表达式的情况(例如上述的const int sz = get_size(); 语句)。
- C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
const
和constexpr
变量之间的主要区别在于:const
变量的初始化可以延迟到运行时,而constexpr
变量必须在编译时进行初始化。所有constexpr
变量均为常量,因此必须使用常量表达式初始化。- 尽管不能使用普通函数作为constexpr变量的初始值,但可以通过constexpr函数(编译时就可以计算其结果)初始化constexpr变量。
-
constexpr float x = 42.0;
-
constexpr float y{108};
-
constexpr float z = exp(5, 3);
-
constexpr int i; // Error! Not initialized
-
int j = 0;
-
constexpr int k = j + 1; //Error! j not a constant expression
-
constexpr int sz = size(); //重要!!!只有当size是一个constexpr函数时,才是一条正确的声明语句
一般来说,如果你认定变量是一个常量表达式,那就把它声明成为constexpr
类型。
constexpr
函数
constexpr 函数是在使用需要它的代码时,可以在编译时计算其返回值的函数。当其参数为 constexpr 值并且在编译时使用代码需要返回值时(例如,初始化一个 constexpr 变量或提供一个非类型模板参数),它会生成编译时常量。使用非constexpr 参数调用时,或编译时不需要其值时,它将与正则函数一样,在运行时生成一个值。
遵循以下规定:(1)函数的返回类型以及所有形参的类型都得是字面值类型;(2)函数体中必须只有一条return语句。
-
-
using namespace std;
-
// C++98/03
-
template<int N> struct Factorial
-
{
-
const static int value = N * Factorial<N - 1>::value;
-
};
-
template<> struct Factorial<0>
-
{
-
const static int value = 1;
-
};
-
// C++11
-
constexpr int factorial(int n)
-
{
-
return n == 0 ? 1 : n * factorial(n - 1);
-
}
-
// C++14
-
constexpr int factorial2(int n)
-
{
-
int result = 1;
-
for (int i = 1; i <= n; ++i)
-
result *= i;
-
return result;
-
}
-
-
int main()
-
{
-
static_assert(Factorial<3>::value == 6, "error");
-
static_assert(factorial(3) == 6, "error");
-
static_assert(factorial2(3) == 6, "error");
-
int n = 3;
-
cout << factorial(n) << factorial2(n) << endl; //66
-
}
代码说明:
以上代码演示了如何在编译期计算3的阶乘。
在C++11之前,在编译期进行数值计算必须使用模板元编程技巧。具体来说我们通常需要定义一个内含编译期常量value的类模板(也称作元函数)。这个类模板的定义至少需要分成两部分,分别用于处理一般情况和特殊情况。
代码示例中Factorial元函数的定义分为两部分:
当模板参数大于0时,利用公式 N!=N*(N-1)! 递归调用自身来计算value的值。
当模板参数为0时,将value设为1这个特殊情况下的值。
在C++11之后,编译期的数值计算可以通过使用constexpr声明并定义编译期函数来进行。相对于模板元编程,使用constexpr函数更贴近普通的C++程序,计算过程显得更为直接,意图也更明显。
但在C++11中constexpr函数所受到的限制较多,比如函数体通常只有一句return语句,函数体内既不能声明变量,也不能使用for语句之类的常规控制流语句。
如factorial函数所示,使用C++11在编译期计算阶乘仍然需要利用递归技巧。
C++14解除了对constexpr函数的大部分限制。在C++14的constexpr函数体内我们既可以声明变量,也可以使用goto和try之外大部分的控制流语句。
如factorial2函数所示,使用C++14在编译期计算阶乘只需利用for语句进行常规计算即可。
虽说constexpr函数所定义的是编译期的函数,但实际上在运行期constexpr函数也能被调用。事实上,如果使用编译期常量参数调用constexpr函数,我们就能够在编译期得到运算结果;而如果使用运行期变量参数调用constexpr函数,那么在运行期我们同样也能得到运算结果。
代码第32行所演示的是在运行期使用变量n调用constexpr函数的结果。
准确的说,constexpr函数是一种在编译期和运行期都能被调用并执行的函数。出于constexpr函数的这个特点,在C++11之后进行数值计算时,无论在编译期还是运行期我们都可以统一用一套代码来实现。编译期和运行期在数值计算这点上得到了部分统一。
constexpr和指针
还记得const与指针的规则吗?如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针本身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
与const不同,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关。
-
const int *p = 0; // non-const pointer, const data
-
constexpr int *q = 0; // const pointer, non-const data
与其它常量指针类似,const
指针既可以指向常量也可以指向一个非常量:
-
int j = 0;
-
constexpr int i = 2;
-
constexpr const int *p = &i; // const pointer, const data
-
constexpr int *p1 = &j; // const pointer, non-const data
参考:
https://blog.csdn.net/craftsman1970/article/details/80244873
https://blog.csdn.net/weixin_40087851/article/details/82754189
https://blog.csdn.net/YhL_Leo/article/details/50864210
https://blog.csdn.net/zwvista/article/details/54429416