一文總結 C++ 常量表達式、constexpr 和 const

来源:https://www.cnblogs.com/tengzijian/p/18018104
-Advertisement-
Play Games

TLDR 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠 ...


TLDR

  1. 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠讓代碼更清晰易讀。
  2. constexpr 函數可以把運行期計算遷移至編譯期,使得程式運行更快(但會增加編譯時間)。但如果 constexpr 函數中存在無法在編譯期求值的參數,則 constexpr 函數和普通一樣在運行時求值,此時的返回值不是常量表達式。

1. 常量表達式和 constexpr

C++11 中引入了 constexpr 關鍵字。constexpr 是 const expression 的縮寫,即常量表達式

常量表達式是指值不會改變編譯期可以得到結果的表達式。

1.1 特點

  1. 值不會改變(這一點和普通 const 一樣)
  2. 編譯期就能得到結果!(普通 const 不一定保證)

1.2 使用場景

C++ 在一些場景下必須使用常量表達式,比如:

  • 數組大小
  • 整型模板實參(如 std::array<T, N> 的長度參數 N)
  • switch-case 中的 case 標簽
  • 枚舉量的值
  • 對齊規格

1.3 常見的常量表達式

  1. 字面值(如 42)
  2. 用常量表達式初始化的 const 對象

一個對象(或表達式)是否是常量表達式取決於類型和初始值,如:

int i1 = 42;           // i1 不是常量表達式:初始值 42 是字面值,但 i1 不是 const 類型
const int i2 = i1;     // i2 不是常量表達式:初始值 i1 不是常量表達式
const int i3 = 42;     // i3 是常量表達式:用字面值 42 初始化的 const 對象
const int i4 = i3 + 1; // i4 是常量表達式:用常量表達式 i3 + 1 初始化的 const 對象
const int i5 = getValue(); // 如果 getValue() 是普通函數,則 i5 值要到運行時才能確定,則不是常量表達式

1.4 constexpr 變數

上面的例子可以看出,不能直接判斷一個 const 對象是否是常量表達式:例如 i4 是否是常量表達式取決於 i3 是否是常量表達式,而 i4 又可能用來初始化其他常量表達式。在複雜的系統中,很難一眼看出某個 const 對象是否是常量表達式。

C++11 允許把變數聲明為 constexpr 類型,此時編譯器會保證 constexpr 變數是常量表達式(否則編譯報錯)。換句話說,只要看到 constexpr 類型的變數,則一定能夠在編譯期取得結果,可以用在需要常量表達式的場景。

int i1 = 42;
constexpr int i2 = i1; // constexpr 變數 'i2' 必須由常量表達式初始化。不允許在常量表達式中讀取非 const 變數 'i1'
constexpr int i3 = 42; // i3 是常量表達式
constexpr int i4 = i3 + 1; // i4 是常量表達式
constexpr int i5 = getValue(); // 只有 getValue() 是 constexpr 函數時才可以,否則編譯報錯

1.5 constexpr 函數

constexpr 函數是指能用於常量表達式的函數。

需要強調的是,constexpr 函數既能用於要求常量表達式/編譯期常量的語境,也可以作為普通函數使用

註意:constexpr 函數不一定返回常量表達式!

只有 constexpr 的所有實參都是常量表達式/編譯期常量時,constexpr 函數的結果才是常量表達式/編譯期常量。只要有一個參數在編譯期未知,那就和普通函數一樣,在運行時計算。

constexpr int sum(int a, int b) {
  return a + b;
}

constexpr int i1 = 42;
constexpr int i2 = sum(i1, 52); // 所有參數都是常量表達式,sum 的結果也是常量表達式,在編譯期求值

int AddThree(int i) {
  return sum(i, 3); // i 不是常量表達式,此時 sum 作為普通函數使用
}

為了能保證 constexpr 函數在編譯時能隨時展開計算,constexpr 函數隱式內聯。內聯函數和 constexpr 函數不同於其他函數,允許定義多次,但要保證所有的定義一致。正因如此,內聯函數和 constexpr 函數一般定義在頭文件中

constexpr 限制

因為需要在編譯期求值,所以 constexpr 函數有一些限制:返回類型和所有形參的類型必須是字面值類型(literal type)。除了內置類型,用戶自定義的類也可以是字面值類型,因為它的構造函數和成員函數也可以是 constexpr 函數。

C++11 中 constexpr 函數還有一些額外限制(C++14 沒有這些限制):

  • 返回值類型不能是 void
  • 函數體內只能有且只有一條 return 語句(但可以用 ? : 三目運算符和遞歸)
  • 如果是類的成員函數,則為隱式 const 成員函數

1.6 使用 constexpr 的好處

  1. 編譯器可以保證 constexpr 對象是常量表達式(能夠在編譯期取得結果),而 const 對象不能保證。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠讓代碼更清晰易讀
  2. constexpr 函數可以把運行期計算遷移至編譯期,使得程式運行更快(但會增加編譯時間)

對於常量表達式(編譯期值已知),編譯器可以進行更多優化,比如放到只讀記憶體中。但這並不是 constexpr 特有的,有的 const 變數也是常量表達式

1.7 小結

  1. 修飾對象的時候,可以把 constexpr 當作加強版的 const:const 對象只表明值不會改變,不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果
  2. constexpr 函數既可以用於編譯期計算,也可以作為普通函數在運行期使用

擴展閱讀

  • 《C++ Primer 第五版》p58,p214,p267

  • 《Effective Modern C++》條款 15:只要有可能使用 constexpr,就使用它


本文作者:Zijian/TENG(微信公眾號:好記性如爛筆頭),轉載請註明原文鏈接:https://www.cnblogs.com/tengzijian/p/18018104
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 在日常的開發中,我們經常能碰見服務端需要主動推送給客戶端數據的業務場景,比如數據大屏的實時數據,比如消息中心的未讀消息,比如聊天功能等等。 本文主要介紹SSE的使用場景和如何使用SSE。 服務端向客戶端推送數據的實現方案有哪幾種? ...
  • 這篇筆記總結自網課DNS解析和優化【渡一教育】 DNS用於將功能變數名稱轉換成IP地址。 特點: DNS解析過程耗費時間長; DNS有本地緩存。 DNS解析完成得到IP地址,這個IP地址會存儲到本地設備,後續再讀這個功能變數名稱會直接返回本地緩存的IP地址。 用戶瀏覽網頁中的DNS解析流程 首先用戶輸入url地 ...
  • 在 Vue 中說到v-bind大多數時候都是想到template中動態綁定script中的響應式數據。但其實在單文件組件(SFC)中, ...
  • Vite腳手架在打包代碼的時候,會把源代碼里對於靜態資源的訪問路徑轉換為打包後靜態資源文件的路徑。動態訪問靜態資源通常導致讀取不到文件,因為源代碼中使用的路徑是src中的,而打包之後靜態資源帶上了文件指紋,代碼中的路徑卻沒有隨之改變。 ...
  • 前言 單例模式是最簡單的一種模式。在Go中,單例模式指的是全局只有一個實例,並且它負責創建自己的對象。單例模式有減少記憶體和系統資源開銷、防止多個實例產生衝突等優點。 因為單例模式保證了實例的全局唯一性,並且只被初始化一次,所以比較適合全局共用一個實例,且只需要被初始化一次的場景,例如資料庫實例、全局 ...
  • 前言 策略模式定義了一系列演算法,並將每個演算法封裝起來,使它們可以互相替換,且演算法的變換不會影響使用演算法的客戶。 在項目開發中,我們經常要根據不同的場景,採取不同的措施,也就是不同的策略。假設我們需要對a、b這兩個整數進行計算,根據條件的不同,需要執行不同的計算方式。我們可以把所有的操作都封裝在同一個 ...
  • 在編程的世界里,我們經常需要對數據進行迴圈處理,常用的兩種方法就是:for迴圈和foreach迴圈。想象你站在一條裝滿寶貝的傳送帶前,你要親手檢查每一件寶貝。使用for迴圈就像是你親手控制傳送帶的速度和方向,而使用foreach迴圈則是傳送帶自動運轉,你只需專註於寶貝本身。 ...
  • Java 方法重載 方法重載 允許在同一個類中定義多個具有相同名稱的方法,但 參數列表 必須不同。 語法: returnType methodName(parameter1, parameter2, ..., parameterN) { // 方法體 } 示例: public class Main ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...