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};

  编译器提示重复定义SmallMediumLargeJumbo。因为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变量。
  1.  
    constexpr float x = 42.0;
  2.  
    constexpr float y{108};
  3.  
    constexpr float z = exp(5, 3);
  4.  
    constexpr int i; // Error! Not initialized
  5.  
    int j = 0;
  6.  
    constexpr int k = j + 1; //Error! j not a constant expression
  7.  
    constexpr int sz = size(); //重要!!!只有当size是一个constexpr函数时,才是一条正确的声明语句

一般来说,如果你认定变量是一个常量表达式,那就把它声明成为constexpr类型。


constexpr 函数

constexpr 函数是在使用需要它的代码时,可以在编译时计算其返回值的函数。当其参数为 constexpr 值并且在编译时使用代码需要返回值时(例如,初始化一个 constexpr 变量或提供一个非类型模板参数),它会生成编译时常量。使用非constexpr 参数调用时,或编译时不需要其值时,它将与正则函数一样,在运行时生成一个值。

遵循以下规定:(1)函数的返回类型以及所有形参的类型都得是字面值类型;(2)函数体中必须只有一条return语句。

  1.  
    #include <iostream>
  2.  
    using namespace std;
  3.  
    // C++98/03
  4.  
    template<int N> struct Factorial
  5.  
    {
  6.  
    const static int value = N * Factorial<N - 1>::value;
  7.  
    };
  8.  
    template<> struct Factorial<0>
  9.  
    {
  10.  
    const static int value = 1;
  11.  
    };
  12.  
    // C++11
  13.  
    constexpr int factorial(int n)
  14.  
    {
  15.  
    return n == 0 ? 1 : n * factorial(n - 1);
  16.  
    }
  17.  
    // C++14
  18.  
    constexpr int factorial2(int n)
  19.  
    {
  20.  
    int result = 1;
  21.  
    for (int i = 1; i <= n; ++i)
  22.  
    result *= i;
  23.  
    return result;
  24.  
    }
  25.  
     
  26.  
    int main()
  27.  
    {
  28.  
    static_assert(Factorial<3>::value == 6, "error");
  29.  
    static_assert(factorial(3) == 6, "error");
  30.  
    static_assert(factorial2(3) == 6, "error");
  31.  
    int n = 3;
  32.  
    cout << factorial(n) << factorial2(n) << endl; //66
  33.  
    }

代码说明:

以上代码演示了如何在编译期计算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仅对指针有效,与指针所指对象无关。
 

  1.  
    const int *p = 0; // non-const pointer, const data
  2.  
    constexpr int *q = 0; // const pointer, non-const data

与其它常量指针类似,const指针既可以指向常量也可以指向一个非常量:

  1.  
    int j = 0;
  2.  
    constexpr int i = 2;
  3.  
    constexpr const int *p = &i; // const pointer, const data
  4.  
    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

 

posted @ 2020-11-13 17:42  Bigben  阅读(2450)  评论(0编辑  收藏  举报