欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++中的const和constexpr詳解

 更新時間:2016年01月02日 13:28:05   投稿:hebedich  
C++ const 和 constexpr 的區(qū)別呢,constexpr表示這玩意兒在編譯期就可以算出來(前提是為了算出它所依賴的東西也是在編譯期可以算出來的)。而const只保證了運行時不直接被修改(但這個東西仍然可能是個動態(tài)變量)。下面我們來詳細講解下。

C++中的const可用于修飾變量、函數(shù),且在不同的地方有著不同的含義,現(xiàn)總結(jié)如下。

const的語義

C++中的const的目的是通過編譯器來保證對象的常量性,強制編譯器將所有可能違背const對象的常量性的操作都視為error。

對象的常量性可以分為兩種:物理常量性(即每個bit都不可改變)和邏輯常量性(即對象的表現(xiàn)保持不變)。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的表現(xiàn)已經(jīng)改變了!

邏輯常量性的另一個特點是,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對象每次調(diào)用length方法后,都會將當前的長度緩存到textLength成員中,而lengthIsValid對象則表示緩存的有效性。這個場景中textLength和lengthIsValid如果改變了,其實是不違背CTextBlock對象的邏輯常量性的,但因為改變了對象中的某些bit,就會被編譯器阻止。C++中為了解決此問題,增加了mutable關鍵字。

本部分總結(jié):C++中const的語義是保證物理常量性,但通過mutable關鍵字可以支持一部分的邏輯常量性。

const修飾變量

如上節(jié)所述,用const修飾變量的語義是要求編譯器去阻止所有對該變量的賦值行為。因此,必須在const變量初始化時就提供給它初值:

const int i;
i = 5; // !error
const int j = 10; // ok

這個初值可以是編譯時即確定的值,也可以是運行期才確定的值。如果給整數(shù)類型的const變量一個編譯時初值,那么可以用這個變量作為聲明數(shù)組時的長度:

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++編譯器可以將數(shù)組長度中出現(xiàn)的編譯時常量直接替換為其字面值,相當于自動的宏替換。(gcc驗證發(fā)現(xiàn),只有數(shù)組長度那里直接做了替換,而其它用COMPILE_CONST賦值的地方并沒有進行替換。)

文件域的const變量默認是文件內(nèi)可見的,如果需要在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量的初始值依賴于某個函數(shù),而每次調(diào)用此函數(shù)的返回值不固定的話,會導致不同的編譯單元中看到的該const量的值不相等。猜測:此時將該const量作為某個類的static成員可能會解決此問題。)

const修飾指針與引用

const修飾引用時,其意義與修飾變量相同。但const在修飾指針時,規(guī)則就有些復雜了。

簡單的說,可以將指針變量的類型按變量名左邊最近的‘*'分成兩部分,右邊的部分表示指針變量自己的性質(zhì),而左邊的部分則表示它指向元素的性質(zhì):

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指針的解讀規(guī)則差不多就是這些了……

指針自身為const表示不可對該指針進行賦值,而指向物為const則表示不可對其指向進行賦值。因此可以將引用看成是一個自身為const的指針,而const引用則是const Type * const指針。

指向為const的指針是不可以賦值給指向為非const的指針,const引用也不可以賦值給非const引用,但反過來就沒有問題了,這也是為了保證const語義不被破壞。

可以用const_cast來去掉某個指針或引用的const性質(zhì),或者用static_cast來為某個非const指針或引用加上const性質(zhì):

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常量必須在構(gòu)造函數(shù)的初始化列表中進行初始化,因為類中的非static成員是在進入構(gòu)造函數(shù)的函數(shù)體之前就要構(gòu)造完成的,而const常量在構(gòu)造時就必須初始化,構(gòu)造后的賦值會被編譯器阻止。

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常量的類型是內(nèi)置的整數(shù)類型,如char、int、size_t等,那么可以在類中直接給出初始值,且不需要在類外再進行定義了。編譯器會將這種static常量直接替換為相應的初始值,相當于宏替換。但如果在代碼中我們像正常變量那樣使用這個static常量,如取它的地址,而不是像宏一樣只使用它的值,那么我們還是需要在類外給它提供一個定義,但不需要初始值了(因為在聲明處已經(jīng)有了)。

// 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修飾函數(shù)

C++中可以用const去修飾一個類的非static成員函數(shù),其語義是保證該函數(shù)所對應的對象本身的const性。在const成員函數(shù)中,所有可能違背this指針const性(const成員函數(shù)中的this指針是一個雙const指針)的操作都會被阻止,如對其它成員變量的賦值以及調(diào)用它們的非const方法、調(diào)用對象本身的非const方法。但對一個聲明為mutable的成員變量所做的任何操作都不會被阻止。這里保證了一定的邏輯常量性。

另外,const修飾函數(shù)時還會參與到函數(shù)的重載中,即通過const對象、const指針或引用調(diào)用方法時,優(yōu)先調(diào)用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[]有著基本相同的代碼,可以考慮在其中一個函數(shù)中去調(diào)用另一個函數(shù)來實現(xiàn)代碼的重用(參考Effective C++)。這里我們只能用非const版本去調(diào)用const版本。

int &A::operator[](int i) {
  return const_cast<int &>(static_cast<const A &>(*this).operator[](i));
}

其中為了避免調(diào)用自身導致死循環(huán),首先要將*this轉(zhuǎn)型為const A &,可以使用static_cast來完成。而在獲取到const operator[]的返回值后,還要手動去掉它的const,可以使用const_cast來完成。一般來說const_cast是不推薦使用的,但這里我們明確知道我們處理的對象其實是非const的,那么這里使用const_cast就是安全的。

constexpr

constexpr是C++11中新增的關鍵字,其語義是“常量表達式”,也就是在編譯期可求值的表達式。最基礎的常量表達式就是字面值或全局變量/函數(shù)的地址或sizeof等關鍵字返回的結(jié)果,而其它常量表達式都是由基礎表達式通過各種確定的運算得到的。constexpr值可用于enum、switch、數(shù)組長度等場合。

constexpr所修飾的變量一定是編譯期可求值的,所修飾的函數(shù)在其所有參數(shù)都是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還能用于修飾類的構(gòu)造函數(shù),即保證如果提供給該構(gòu)造函數(shù)的參數(shù)都是constexpr,那么產(chǎn)生的對象中的所有成員都會是constexpr,該對象也就是constexpr對象了,可用于各種只能使用constexpr的場合。注意,constexpr構(gòu)造函數(shù)必須有一個空的函數(shù)體,即所有成員變量的初始化都放到初始化列表中。

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的代碼進行非常大的優(yōu)化,比如將用到的constexpr表達式都直接替換成最終結(jié)果等。
相比宏來說,沒有額外的開銷,但更安全可靠。

相關文章

  • 詳解Visual Studio 2019(VS2019) 基本操作

    詳解Visual Studio 2019(VS2019) 基本操作

    這篇文章主要介紹了詳解Visual Studio 2019(VS2019) 基本操作,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • C語言員工信息管理系統(tǒng)源代碼

    C語言員工信息管理系統(tǒng)源代碼

    這篇文章主要為大家詳細介紹了C語言員工信息管理系統(tǒng)源代碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • C語言使用單鏈表實現(xiàn)學生信息管理系統(tǒng)

    C語言使用單鏈表實現(xiàn)學生信息管理系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言使用單鏈表實現(xiàn)學生信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • 解讀C語言非void函數(shù)卻沒有return會怎么樣

    解讀C語言非void函數(shù)卻沒有return會怎么樣

    這篇文章主要介紹了解讀C語言非void函數(shù)卻沒有return會怎么樣的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • C語言折半查找法介紹及使用示例

    C語言折半查找法介紹及使用示例

    折半查找法也叫做?分查找,顧名思義就是把數(shù)據(jù)分成兩半,再判斷所查找的key在哪?半中,再重復上述步驟知道找到?標key,下面這篇文章主要給大家介紹了關于C語言折半查找法的相關資料,需要的朋友可以參考下
    2022-08-08
  • C++?Boost?weak_ptr智能指針超詳細講解

    C++?Boost?weak_ptr智能指針超詳細講解

    智能指針是一種像指針的C++對象,但它能夠在對象不使用的時候自己銷毀掉。雖然STL提供了auto_ptr,但是由于不能同容器一起使用(不支持拷貝和賦值操作),因此很少有人使用。它是Boost各組件中,應用最為廣泛的一個
    2022-11-11
  • C++ 字符串string和整數(shù)int的互相轉(zhuǎn)化操作

    C++ 字符串string和整數(shù)int的互相轉(zhuǎn)化操作

    這篇文章主要介紹了C++ 字符串string和整數(shù)int的互相轉(zhuǎn)化操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • C++動態(tài)規(guī)劃中關于背包問題講解

    C++動態(tài)規(guī)劃中關于背包問題講解

    可能有些讀者有接觸過動態(tài)規(guī)劃,可能也有一些讀者以前完全不知道動態(tài)規(guī)劃這個東西,別擔心,我這篇文章會為讀者做一個入門,好讓讀者掌握這個重要的知識點
    2023-03-03
  • C++實現(xiàn)拓撲排序(AOV網(wǎng)絡)

    C++實現(xiàn)拓撲排序(AOV網(wǎng)絡)

    這篇文章主要為大家詳細介紹了C++實現(xiàn)拓撲排序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • VC++ 自定義控件的建立及使用方法

    VC++ 自定義控件的建立及使用方法

    這篇文章主要介紹了VC++ 自定義控件的建立及使用方法的相關資料,十分的詳細,需要的朋友可以參考下
    2015-06-06

最新評論