• 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

  • 相关阅读:
    【习题 8-9 1613】 K-Graph Oddity
    【习题 8-8 UVA
    【Hello 2018 D】Too Easy Problems
    【Hello 2018 C】Party Lemonade
    【Hello 2018 B】Christmas Spruce
    【Hello 2018 A】 Modular Exponentiation
    【习题 8-7 UVA
    【习题 8-6 UVA
    【习题 8-4 UVA
    【习题 8-3 UVA
  • 原文地址:https://www.cnblogs.com/bigben0123/p/13970572.html
Copyright © 2020-2023  润新知