C++中const以及constexpr
阿新 • • 發佈:2021-11-28
一.const常量與#define比較
- define只是簡單的替換,沒有型別,const可以做到防竄改與型別安全。
- 而且#define會在記憶體中可能(有幾次替換就有幾次拷貝)有多份拷貝,對於字面值常量加不加const都一樣,例如:
const char* arr = “123”;
,儲存在常量區,只有一份拷貝;對於區域性物件,常量存放在棧區,例如:void add(){const char crr[] = “123”;}
,這裡“123”本應儲存在棧上,但編譯器可能會做某些優化,將其放入常量區;對於全域性物件,常量存放在全域性/靜態儲存區;用const會比#define使用更少的空間,效率更高。 - 這裡有一個小例子:
char* brr = "123"; char drr[] = "123";
- 現在C++除了一些特定用法,推薦用const,inline,enum等替換巨集——來自《Effective C++》條款02
二.const修飾
- 修飾普通變數,必須初始化
const int a = 10; 表示int物件a,是一個常量,不可以改變值,從編譯器生成二進位制角度看,生成的a存放在.rodata段,也就是隻讀(readonly)區域。不過並不絕對,有的時間統計優化等級開的高,也不取地址,可能會優化成立即數在.text段中。
- 修飾類變數和成員變數
class cAAA{ public: cAAA(int a) : m_iV(a){} const int GetValue() const {return m_iV;} void AddValueOneTime(){m_iChangeV++;} private: const int m_iV; public: mutable int m_iChangeV; static const int m_iStaticV; }; static const int m_iStaticV = 1000; const cAAA aa(100); aa.GetValue(); aa.m_iChangeV++;
- cAAA類成員m_iV是const變數,必須放到初始化列表中進行初始化,不能進行賦值。
- 對於靜態常成員,與普通靜態成員類似,推薦放到類外.cpp中初始化。
- aa只能呼叫const函式,如aa.GetValue(),不能呼叫非常成員函式aa.AddValueOneTime()。
- 對於這種const物件,又想修改成員,可以在成員宣告加上mutable,這樣const物件aa也可以修改m_iChangeV,這種用法比較少。
3.修飾成員函式
- 表示這個函式可以被const物件呼叫,也可以被普通物件呼叫,不會改變物件的成員,也就是說更像一種只讀不寫型的邏輯運算,所以有些人推薦類成員函式,可以都加上const。有一個小技巧,當const和non-const成員函式有著實質等價的實現
- 有一點要注意,編譯器強制實施bitwase constness,但編寫程式時應該使用conceptual constness,解決編譯器的bitwase constness屬性就用到了上述的mutable。關於介紹bitwase constness的具體表現可以參考《Effective C++》條款03。
4.修飾指標
const char* p1;
char const *p2;
char* const p3;
const char* const p4;
對於初學者來說這大概是很難理解的一個知識點,怎麼區分這四個呢?記住祕訣,直接從右向左讀就一招制敵了。
- p1是一個指標,指向char字元常量,表示p1所指物件內容不可以改,所指地址可以改。
- p2同p1,寫法不同,兩者等價。
- p3是一個常量,且是個指標,指向char字元,表示p3所指物件內容可以改,所指地址不可以改。
- p4是一個常量,且是個指標,指向char字元常量,表示p4所指物件內容不可以改,且所指地址也不可以改。
- 相對來說p1,p2是最常用傳參或者返回值的手段。
5.修飾引用
- 修飾引用和物件差不多,物件內容不可以改變。作為函式引數傳引數,不存在copy開銷,這是比較推薦的寫法,例如:拷貝建構函式,賦值構造,STL裡用於比較的函式或者仿函式,詳情請參閱《Effective C++》條款20。
bool Less(const cAAA& left, const cAAA& right);
float dValue = 1.05f;
const int& a = dValue;
const int iTemp = dValue;
const int& a = iTemp;
- 因為常引用不能改變,這種情況下編譯器會建立一個臨時變數來處理隱式轉換,我們實際是對臨時變數進行了常引用。
三.const轉換
- 一般來說,從T轉換到const T是比較簡單的,且編譯器支援的隱式轉換,也可以顯示的用模板處理,例如我們簡單寫一下RemoveConst模板,最後用using化名一下。但從const T到T就麻煩一些,推薦使用const_cast。
template <typename T>
struct RemoveConst{
typedef T Type;
};
template <typename T>
struct RemoveConst<const T>{
typedef T Type;
};
template <typename T>
using RCType = typename RemoveConst<T>::Type;
四.頂層const與底層const
- 簡單來說const修飾的物件本身不能改變就是頂層const,但如果是指標或者引用的物件不能改變,則稱為底層const。
- const int cV = 10; cV是頂層const,本身不能改變
- char const *p2; p2是底層const,p2本身值可以改變,但所指內容不可以改變
- char* const p3; p3是頂層const,p3的本身值不可以改變
- const char* const p4; p4既是頂層const,又是底層const
- 注:對於上述模板RCType
是無法移除p2這種底層const,如果要移除,請用const_cast<T*>移除,但這種操作可能引起Crash或者未知風險
const char* pA = "sss";
char* pB = const_cast<char*>(pA);
auto pC = RCType<decltype(pA)>(pA);
std::cout << "type is the same: " << std::is_same<decltype(pB), decltype(pC)>::value << std::endl;
std::cout << "pB Type Name: " << typeid(pB).name() << "pc Type Name: " << typeid(pC).name() << std::endl;
//pB[0] = 'A';//error, Segmentation fault
五.C++11新引入的constexpr
- 這個關鍵字表示這是一個常量表達式,是一個編譯期就可以確認的值,最常用於模板中,例如模板遞迴求值。
- 它可不只是變數,例如:
const int iSize1 = sizeof(int);
const int iSize2 = GetSize();
iSize1是個常量,編譯期的,但iSize2就不一定,它雖然不能改變,但要到GetSize()執行結束,才能知道具體值,這與常量一般在編譯期就知道的思想不符,解決這個問題的方法就是改為:constexpr int iSize2 = GetSize();
這樣要求GetSize()一定要能在編譯期就算出值,下面幾個例子中GetSizeError()就會編譯失敗。GetFibo函式,編譯期就已經遞迴計算出值。
constexpr int GetSize(){
return sizeof(int) + sizeof(double);
}
constexpr int GetSizeError(){
return random();
}
constexpr int GetCalc(int N){
return N <= 1 ? 1 : N * GetCalc(N - 1);
}
const int iSize1 = sizeof(int);
constexpr int iSize2 = GetSize();
//constexpr int iSize3() = GetSizeError();
constexpr int iSize4 = GetCalc(10);
std::cout << iSize1 << " " << iSize2 << " " << iSize4 <<std::endl;
- 當然我們可以用模板寫GetCalc函式:
template <int N>
struct TCalc{
static constexpr int iValue = N * TCalc<N-1>::iValue;
};
template <>
struct TCalc<1>{
static constexpr int iValue = 1;
};
std::cout << TCalc<10>::iValue << std::endl;