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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM