C++總結:C++中的const和constexpr

来源:http://www.cnblogs.com/shouce/archive/2016/01/06/5104354.html
-Advertisement-
Play Games

C++中的const可用於修飾變數、函數,且在不同的地方有著不同的含義,現總結如下。const的語義C++中的const的目的是通過編譯器來保證對象的常量性,強制編譯器將所有可能違背const對象的常量性的操作都視為error。對象的常量性可以分為兩種:物理常量性(即每個bit都不可改變)和邏輯常量...


C++中的const可用於修飾變數、函數,且在不同的地方有著不同的含義,現總結如下。

const的語義

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

對象的常量性可以分為兩種:物理常量性(即每個bit都不可改變)和邏輯常量性(即對象的表現保持不變)。C++中採用的是物理常量性,例如下麵的例子:

1 2 3 4 5 6 7 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++中的例子是:

1 2 3 4 5 6 7 8 9 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變數初始化時就提供給它初值:

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

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

1 2 3 4 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:

1 2 3 4 5 //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在修飾指針時,規則就有些複雜了。

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

1 2 3 4 5 6 7 8 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性質:

1 2 3 4 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常量在構造時就必須初始化,構造後的賦值會被編譯器阻止。

1 2 3 4 5 6 7 8 class B { public:     B(): name("aaa") {         name = "bbb"; // !error     } private:     const std::string name; };

static常量:

static常量是在類中直接聲明的,但要在類外進行唯一的定義和初始值,常用的方法是在對應的.cpp中包含類的static常量的定義:

1 2 3 4 5 6 7 8 // a.h class A {     ...     static const std::string name; };   // a.cpp const std::string A::name("aaa");

一個特例是,如果static常量的類型是內置的整數類型,如char、int、size_t等,那麼可以在類中直接給出初始值,且不需要在類外再進行定義了。編譯器會將這種static常量直接替換為相應的初始值,相當於巨集替換。但如果在代碼中我們像正常變數那樣使用這個static常量,如取它的地址,而不是像巨集一樣只使用它的值,那麼我們還是需要在類外給它提供一個定義,但不需要初始值了(因為在聲明處已經有了)。

1 2 3 4 5 6 7 8 // 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方法。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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版本。

1 2 3 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。

1 2 3 4 5 6 7 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構造函數必須有一個空的函數體,即所有成員變數的初始化都放到初始化列表中。

1 2 3 4 5 6 7 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的好處:

  1. 是一種很強的約束,更好地保證程式的正確語義不被破壞。
  2. 編譯器可以在編譯期對constexpr的代碼進行非常大的優化,比如將用到的constexpr表達式都直接替換成最終結果等。
  3. 相比巨集來說,沒有額外的開銷,但更安全可靠。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近在使用python做介面測試,發現python中http請求方法有很多種,現彙總如下:一、python自帶庫----urllib2python自帶庫urllib2使用的比較多,簡單使用如下:import urllib2response = urllib2.urlopen('http://loca...
  • Demo:import java.nio.file.Path;import java.nio.file.Paths;public class PathInfoTest { public static void main(String[] args) { /...
  • 支付寶介面使用文檔說明 支付寶非同步通知(notify_url)與return_url.現支付寶的通知有兩類。A伺服器通知,對應的參數為notify_url,支付寶通知使用POST方式B頁面跳轉通知,對應的參數為return_url,支付寶通知使用GET方式 (通知地址不需要像以前一樣去賬戶內設置,而...
  • 在實際編程中,往往存在著這樣的“數據集”,它們的數值在程式中是穩定的,而且“數據集”中的元素是有限的。 例如星期一到星期日七個數據元素組成了一周的“數據集”,春夏秋冬四個數據元素組成了四季的“數據集”。 enum 的全稱為 enumeration, 是 JDK 1.5 中引入的新特性,存放在...
  • Demo:import java.nio.file.Path;import java.nio.file.Paths;/** * @author jinxing * @系統 MAC OS X * @用例1 [使用]絕對路徑 * @用例2 [使用]相對路徑 * @用例3 相對路徑[轉換成]絕對路徑 * ...
  • 有的小伙伴會問:博主,沒有Mac怎麼學Swift語言呢,我想學Swift,但前提得買個Mac。非也,非也。如果你想瞭解或者初步學習Swift語言的話,你可以登錄這個網站:http://swiftstub.com/。該網站可以線上運行出代碼結果,也可以說這是一個線上的Playground。你可以...
  • Path通常代表文件系統中的位置,能瀏覽任何類型的文件系統,包括zip歸檔文件系統;文件系統中的幾個概念:目錄樹、根目錄、絕對路徑、相對路徑;NIO.2中的Path是一個抽象構造,你所創建和處理的Path可以不馬上綁定到對應的物理位置上;——物理文件系統的處理通常由Files輔助類實現;基礎類類說明...
  • 當在網上問為什麼Python比C語言更慢,回答最多的就是Python中有動態類型。然而,動態類型確實會在性能方面有影響,但是這並不是主要原因。 動態類型(像Python一樣的主要編程語言都一樣)使得編譯器很難優化性能。動態使得每次執行都可能很不同,編譯器難以優化。然而,正如Alex在談話中提到的,....
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...