C++中的const和constexpr詳解。本站提示廣大學習愛好者:(C++中的const和constexpr詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是C++中的const和constexpr詳解正文
C++中的const可用於潤飾變量、函數,且在分歧的處所有著分歧的寄義,現總結以下。
const的語義
C++中的const的目標是經由過程編譯器來包管對象的常量性,強迫編譯器將一切能夠違反const對象的常量性的操作都視為error。
對象的常量性可以分為兩種:物理常量性(即每一個bit都弗成轉變)和邏輯常量性(即對象的表示堅持不變)。C++中采取的是物理常量性,例以下面的例子:
struct A { int *ptr; }; int k = 5, r = 6; const A a = {&k}; a.ptr = &r; // !error *a.ptr = 7; // no error
a是const對象,則對a的任何成員停止賦值都邑被視為error,但假如不修改ptr,而是修改ptr指向的對象,編譯器就不會報錯。這現實上違反了邏輯常量性,由於A的表示曾經轉變了!
邏輯常量性的另外一個特色是,const對象中可以有某些用戶弗成見的域,轉變它們不會違反邏輯常量性。Effective C++中的例子是:
class CTextBlock { public: ... std::size_t length() const; private: char *pText; std::size_t textLength; // last calculated length of textblock bool lengthIsValid; // whether length is currently valid };
CTextBlock對象每次挪用length辦法後,都邑將以後的長度緩存到textLength成員中,而lengthIsValid對象則表現緩存的有用性。這個場景中textLength和lengthIsValid假如轉變了,實際上是不違反CTextBlock對象的邏輯常量性的,但由於轉變了對象中的某些bit,就會被編譯器阻攔。C++中為懂得決此成績,增長了mutable症結字。
本部門總結:C++中const的語義是包管物理常量性,但經由過程mutable症結字可以支撐一部門的邏輯常量性。
const潤飾變量
如上節所述,用const潤飾變量的語義是請求編譯器去阻攔一切對該變量的賦值行動。是以,必需在const變量初始化時就供給給它初值:
const int i; i = 5; // !error const int j = 10; // ok
這個初值可所以編譯時即肯定的值,也能夠是運轉期才肯定的值。假如給整數類型的const變量一個編譯時初值,那末可以用這個變量作為聲明數組時的長度:
const int COMPILE_CONST = 10; const int RunTimeConst = cin.get(); int a1[COMPLIE_CONST]; // ok in C++ and error in C int a2[RunTimeConst]; // !error in C++
由於C++編譯器可以將數組長度中湧現的編譯經常量直代替換為其字面值,相當於主動的宏調換。(gcc驗證發明,只要數組長度那邊直接做了調換,而其它用COMPILE_CONST賦值的處所並沒有停止調換。)
文件域的const變量默許是文件內可見的,假如須要在b.cpp中應用a.cpp中的const變量M,須要在M的初始化處增長extern:
//a.cpp extern const int M = 20; //b.cpp extern const int M;
普通以為將變量的界說放在.h文件中會招致一切include該.h文件的.cpp文件都有此變量的界說,在鏈接時會形成抵觸。但將const變量的界說放在.h文件中是可以的,編譯器會將這個變量放入每一個.cpp文件的匿名namespace中,因此屬因而分歧變量,不會形成鏈接抵觸。(留意:但假如頭文件中的const量的初始值依附於某個函數,而每次挪用此函數的前往值不固定的話,會招致分歧的編譯單位中看到的該const量的值不相等。猜想:此時將該const量作為某個類的static成員能夠會處理此成績。)
const潤飾指針與援用
const潤飾援用時,其意義與潤飾變量雷同。但const在潤飾指針時,規矩就有些龐雜了。
簡略的說,可以將指針變量的類型按變量名右邊比來的‘*'分紅兩部門,左邊的部門表現指針變量本身的性質,而右邊的部門則表現它指向元素的性質:
const int *p1; // p1 is a non-const pointer and points to a const int int * const p2; // p2 is a const pointer and points to a non-const int const int * const p3; // p3 is a const pointer and points to a const it const int *pa1[10]; // pa1 is an array and contains 10 non-const pointer point to a const int int * const pa2[10]; // pa2 is an array and contains 10 const pointer point to a non-const int const int (* p4)[10]; // p4 is a non-const pointer and points to an array contains 10 const int const int (*pf)(); // pf is a non-const pointer and points to a function which has no arguments and returns a const int ...
const指針的解讀規矩差不多就是這些了……
指針本身為const表現弗成對該指針停止賦值,而指向物為const則表現弗成對其指向停止賦值。是以可以將援用算作是一個本身為const的指針,而const援用則是const Type * const指針。
指向為const的指針是弗成以賦值給指向為非const的指針,const援用也弗成以賦值給非const援用,但反過去就沒有成績了,這也是為了包管const語義不被損壞。
可以用const_cast往來來往失落某個指針或援用的const性質,或許用static_cast來為某個非const指針或援用加上const性質:
int i; const int *cp = &i; int *p = const_cast<int *>(cp); const int *cp2 = static_cast<const int *>(p); // here the static_cast is optional
C++類中的this指針就是一個本身為const的指針,而類的const辦法中的this指針則是本身和指向都為const的指針。
類中的const成員變量
類中的const成員變量可分為兩種:非static常量和static常量。
非static常量:
類中的非static常量必需在結構函數的初始化列表中停止初始化,由於類中的非static成員是在進入結構函數的函數體之前就要結構完成的,而const常量在結構時就必需初始化,結構後的賦值會被編譯器阻攔。
class B { public: B(): name("aaa") { name = "bbb"; // !error } private: const std::string name; };
static常量:
static常量是在類中直接聲明的,但要在類外停止獨一的界說和初始值,經常使用的辦法是在對應的.cpp中包括類的static常量的界說:
// a.h class A { ... static const std::string name; }; // a.cpp const std::string A::name("aaa");
一個特例是,假如static常量的類型是內置的整數類型,如char、int、size_t等,那末可以在類中直接給出初始值,且不須要在類外再停止界說了。編譯器會將這類static常量直代替換為響應的初始值,相當於宏調換。但假如在代碼中我們像正常變量那樣應用這個static常量,如取它的地址,而不是像宏一樣只應用它的值,那末我們照樣須要在類外給它供給一個界說,但不須要初始值了(由於在聲明處曾經有了)。
// a.h class A { ... static const int SIZE = 50; }; // a.cpp const int A::SIZE = 50; // if use SIZE as a variable, not a macro
const潤飾函數
C++中可以用const去潤飾一個類的非static成員函數,其語義是包管該函數所對應的對象自己的const性。在const成員函數中,一切能夠違反this指針const性(const成員函數中的this指針是一個雙const指針)的操作都邑被阻攔,如對其它成員變量的賦值和挪用它們的非const辦法、挪用對象自己的非const辦法。但對一個聲明為mutable的成員變量所做的任何操作都不會被阻攔。這裡包管了必定的邏輯常量性。
別的,const潤飾函數時還會介入到函數的重載中,即經由過程const對象、const指針或援用挪用辦法時,優先挪用const辦法。
class A { public: int &operator[](int i) { ++cachedReadCount; return data[i]; } const int &operator[](int i) const { ++size; // !error --size; // !error ++cachedReadCount; // ok return data[i]; } private: int size; mutable cachedReadCount; std::vector<int> data; }; A &a = ...; const A &ca = ...; int i = a[0]; // call operator[] int j = ca[0]; // call const operator[] a[0] = 2; // ok ca[0] = 2; // !error
這個例子中,假如兩個版本的operator[]有著根本雷同的代碼,可以斟酌在個中一個函數中去挪用另外一個函數來完成代碼的重用(參考Effective C++)。這裡我們只能用非const版本去挪用const版本。
int &A::operator[](int i) { return const_cast<int &>(static_cast<const A &>(*this).operator[](i)); }
個中為了不挪用本身招致逝世輪回,起首要將*this轉型為const A &,可使用static_cast來完成。而在獲得到const operator[]的前往值後,還要手動去失落它的const,可使用const_cast來完成。普通來講const_cast是不推舉應用的,但這裡我們明白曉得我們處置的對象實際上是非const的,那末這裡應用const_cast就是平安的。
constexpr
constexpr是C++11中新增的症結字,其語義是“常量表達式”,也就是在編譯期可求值的表達式。最基本的常量表達式就是字面值或全局變量/函數的地址或sizeof等症結字前往的成果,而其它常量表達式都是由基本表達式經由過程各類肯定的運算獲得的。constexpr值可用於enum、switch、數組長度等場所。
constexpr所潤飾的變量必定是編譯期可求值的,所潤飾的函數在其一切參數都是constexpr時,必定會前往constexpr。
constexpr int Inc(int i) { return i + 1; } constexpr int a = Inc(1); // ok constexpr int b = Inc(cin.get()); // !error constexpr int c = a * 2 + 1; // ok
constexpr還能用於潤飾類的結構函數,即包管假如供給給該結構函數的參數都是constexpr,那末發生的對象中的一切成員都邑是constexpr,該對象也就是constexpr對象了,可用於各類只能應用constexpr的場所。留意,constexpr結構函數必需有一個空的函數體,即一切成員變量的初始化都放到初始化列表中。
struct A { constexpr A(int xx, int yy): x(xx), y(yy) {} int x, y; }; constexpr A a(1, 2); enum {SIZE_X = a.x, SIZE_Y = a.y};
constexpr的利益:
是一種很強的束縛,更好地包管法式的准確語義不被損壞。
編譯器可以在編譯期對constexpr的代碼停止異常年夜的優化,好比將用到的constexpr表達式都直代替換成終究成果等。
比擬宏來講,沒有額定的開支,但更平安靠得住。