詳解C++中的const和constexpr
一.const常量與#define比較
- define只是簡單的替換,沒有類型,const可以做到防竄改與類型安全。
- 而且#define會(huì)在內(nèi)存中可能(有幾次替換就有幾次拷貝)有多份拷貝,對于字面值常量加不加const都一樣,例如:
const char* arr = “123”;,
儲(chǔ)存在常量區(qū),只有一份拷貝;對于局部對象,常量存放在棧區(qū),例如:void add(){const char crr[] = “123”;},
這里“123”本應(yīng)儲(chǔ)存在棧上,但編譯器可能會(huì)做某些優(yōu)化,將其放入常量區(qū);對于全局對象,常量存放在全局/靜態(tài)存儲(chǔ)區(qū);用const會(huì)比#define使用更少的空間,效率更高。 - 這里有一個(gè)小例子:
char* brr = "123"; char drr[] = "123";
前者字符串123存在常量區(qū),不能通過brr去修改"123"的值;后者"123"保存在棧區(qū),可以通過drr去修改。 - 現(xiàn)在C++除了一些特定用法,推薦用const,inline,enum等替換宏——來自《Effective C++》條款02
二.const修飾
1.修飾普通變量,必須初始化
const int a = 10; 表示int對象a,是一個(gè)常量,不可以改變值,從編譯器生成二進(jìn)制角度看,生成的a存放在.rodata段,也就是只讀(readonly)區(qū)域。不過并不絕對,有的時(shí)間統(tǒng)計(jì)優(yōu)化等級(jí)開的高,也不取地址,可能會(huì)優(yōu)化成立即數(shù)在.text段中。
2.修飾類變量和成員變量
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變量,必須放到初始化列表中進(jìn)行初始化,不能進(jìn)行賦值。
- 對于靜態(tài)常成員,與普通靜態(tài)成員類似,推薦放到類外.cpp中初始化。
- aa只能調(diào)用const函數(shù),如aa.GetValue(),不能調(diào)用非常成員函數(shù)aa.AddValueOneTime()。
- 對于這種const對象,又想修改成員,可以在成員聲明加上mutable,這樣const對象aa也可以修改m_iChangeV,這種用法比較少。
3.修飾成員函數(shù)
- 表示這個(gè)函數(shù)可以被const對象調(diào)用,也可以被普通對象調(diào)用,不會(huì)改變對象的成員,也就是說更像一種只讀不寫型的邏輯運(yùn)算,所以有些人推薦類成員函數(shù),可以都加上const。有一個(gè)小技巧,當(dāng)const和non-const成員函數(shù)有著實(shí)質(zhì)等價(jià)的實(shí)現(xiàn)時(shí),令non-const版本調(diào)用const版本可避免代碼重復(fù);但反過來不行,const函數(shù)內(nèi)部也必須只能調(diào)用const函數(shù)—— 《Effective C++》條款03
- 有一點(diǎn)要注意,編譯器強(qiáng)制實(shí)施bitwase constness,但編寫程序時(shí)應(yīng)該使用conceptual constness,解決編譯器的bitwase constness屬性就用到了上述的mutable。關(guān)于介紹bitwase constness的具體表現(xiàn)可以參考《Effective C++》條款03。
4.修飾指針
const char* p1; char const *p2; char* const p3; const char* const p4;
對于初學(xué)者來說這大概是很難理解的一個(gè)知識(shí)點(diǎn),怎么區(qū)分這四個(gè)呢?記住秘訣,直接從右向左讀就一招制敵了。
- p1是一個(gè)指針,指向char字符常量,表示p1所指對象內(nèi)容不可以改,所指地址可以改。
- p2同p1,寫法不同,兩者等價(jià)。
- p3是一個(gè)常量,且是個(gè)指針,指向char字符,表示p3所指對象內(nèi)容可以改,所指地址不可以改。
- p4是一個(gè)常量,且是個(gè)指針,指向char字符常量,表示p4所指對象內(nèi)容不可以改,且所指地址也不可以改。
- 相對來說p1,p2是最常用傳參或者返回值的手段。
5.修飾引用
- 修飾引用和對象差不多,對象內(nèi)容不可以改變。作為函數(shù)參數(shù)傳參數(shù),不存在copy開銷,這是比較推薦的寫法,例如:拷貝構(gòu)造函數(shù),賦值構(gòu)造,STL里用于比較的函數(shù)或者仿函數(shù),詳情請參閱《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;
- 因?yàn)槌R貌荒芨淖?,這種情況下編譯器會(huì)創(chuàng)建一個(gè)臨時(shí)變量來處理隱式轉(zhuǎn)換,我們實(shí)際是對臨時(shí)變量進(jìn)行了常引用。
三.const轉(zhuǎn)換
- 一般來說,從T*轉(zhuǎn)換到const T*是比較簡單的,且編譯器支持的隱式轉(zhuǎn)換,也可以顯示的用模板處理,例如我們簡單寫一下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本身值可以改變,但所指內(nèi)容不可以改變
- char* const p3; p3是頂層const,p3的本身值不可以改變
- const char* const p4; p4既是頂層const,又是底層const
- 注:對于上述模板RCType
是無法移除p2這種底層const,如果要移除,請用const_cast<T*>移除,但這種操作可能引起Crash或者未知風(fēng)險(xiǎn)
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
- 這個(gè)關(guān)鍵字表示這是一個(gè)常量表達(dá)式,是一個(gè)編譯期就可以確認(rèn)的值,最常用于模板中,例如模板遞歸求值。
- 它可不只是變量,例如:
const int iSize1 = sizeof(int); const int iSize2 = GetSize();
iSize1是個(gè)常量,編譯期的,但iSize2就不一定,它雖然不能改變,但要到GetSize()執(zhí)行結(jié)束,才能知道具體值,這與常量一般在編譯期就知道的思想不符,解決這個(gè)問題的方法就是改為:constexpr int iSize2 = GetSize();?
這樣要求GetSize()一定要能在編譯期就算出值,下面幾個(gè)例子中GetSizeError()就會(huì)編譯失敗。GetFibo函數(shù),編譯期就已經(jīng)遞歸計(jì)算出值。
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;
當(dāng)然我們可以用模板寫GetCalc函數(shù):
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;
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C++ 非遞歸實(shí)現(xiàn)二叉樹的前中后序遍歷
本文將結(jié)合動(dòng)畫和代碼演示如何通過C++ 非遞歸實(shí)現(xiàn)二叉樹的前中后序的遍歷,代碼具有一定的價(jià)值,感興趣的同學(xué)可以學(xué)習(xí)一下2021-11-11C++中拷貝構(gòu)造函數(shù)的應(yīng)用詳解
這篇文章主要介紹了C++中拷貝構(gòu)造函數(shù)的應(yīng)用,需要的朋友可以參考下2014-07-07VisualStudio2022 cmake配置opencv開發(fā)環(huán)境
本文主要介紹了VisualStudio2022 cmake配置opencv開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08詳解C++中const_cast與reinterpret_cast運(yùn)算符的用法
這篇文章主要介紹了C++中const_cast與reinterpret_cast運(yùn)算符的用法,經(jīng)常被用于表達(dá)式中的類型轉(zhuǎn)換,需要的朋友可以參考下2016-01-01