摘要訊息 : 分清 const 標識符的作用和定位.

0. 前言

由於 C++ 支援多重指標, 它和 const 標識結合之後會讓人感到迷惑. 例如 const int *const *const **const i {nullptr}; 這個陳述式中, 各個 const 分別標識哪一個部分?

《C++ Primer》這本書的第五版中引入了兩個概念 : 頂層 const 和底層 const. 書中的介紹非常簡單, 頂層 const 用來標識變數, 底層 const 用來標識記憶體. 但是有了這個解釋之後, 又有多少人能夠正確回答上面這個問題呢?

為了解決這個問題, 我們重新對 const 標識符進行學習.

更新紀錄 :

  • 2022 年 3 月 20 日進行第一次更新和修正.

1. 改寫宣告

拿到一個宣告陳述式 const T a, 我們可以將其改寫為 T const a. 這兩個宣告最終的效果是一樣的, 只是 const 和型別 T 的出現順序不一樣. 但是改寫之後, 我們能夠更佳輕鬆地識別各個 const 的作用.

例題 1. 改寫 const int *const *const **const i.

:

我們只需要交換第一個 constint 的位置, 便可以得到 int const *const *const **const i.

\blacksquare

2. 從右至左

我們仍然以 const int *const *const **const i 為例, 例題 1 表明, 其改寫後為 int const *const *const **const i. 現在我們要學習從右到左審視這個宣告. 為了方便閱讀, 我們給出以下記號 :

int const1 *const2 *const3 **const4 i.

如果把 const 全部去掉, 那麼 i 的型別應該是 int ****. 從右邊開始 :

  • const4 標識的是 i, 而 i 是一個指標, 因此 const4 表明了 i 是不可變的. 也就是指標本身的值不能發生改變;
  • const3 標識的是 **i. 此處, 我們將 * 視為解參考運算子, 那麼 **i 是記憶體中的值. 也就是記憶體中的值不能發生改變;
  • const1 const2 分別標識 ***i****i. 將 * 視為解參考運算子的話, ***i****i 都是記憶體中的值, 它們不可以發生改變.

現在我們就可以分辨這些 const 的屬性了. const4 標識的是 i, i 是一個變數, 因此 const4 屬於頂層 const. 剩下的 const 都是標識記憶體中的值, 那麼這些 const 都是底層 const.

如果使用 auto 來推導 i 的型別 : auto i2 {i};, 那麼 i2 的型別應該是 const int *const *const **. 因為在 auto 推導的過程中, 會主動忽略掉頂層 const :

#include <iostream>

using namespace std;
int main(int argc, char *argv[]) {
    const int *const *const **const i {nullptr};
    auto i2 {i};
    cout << is_same_v<decltype(i2), const int *const *const **> << endl;        // 輸出結果 : 1
}

3. 總結

現在, 我們做一個總結 : 在改寫宣告之後, 當一個 const 後面緊跟著的就是變數名稱的話, 這個 const 是用於標識這個變數的, 而且這個 const 是頂層 const; 當 const 後面存在 * 的時候, 說明這個 const 是標識記憶體中某個值的, 屬於底層 const.

還有一個標識符 volatile, 它和 const 具有同等地位, 但是用到的次數卻比 const 少很多. 對於 const *volatile const *volatile **const volatile *const i 這種宣告, 也可以使用類似的方法對 volatile 進行分類.

另外, 值得一提的是, 頂層 const 的全稱應該是 top-level cv-qualifiers, 它在 C++ 20 國際標準中首次出現於 6.7.3 節中. 而底層 const (可能叫 underlying-level cv-qualifiers 或者 low-level cv-qualifiers) 是在 C++ 20 國際標準中沒有出現的, 這個概念應該是由《C++ Primer》這本書創造的.