1. 程式人生 > 其它 >C++程式設計進階1(對於單純的常量,用const替換#define;用行內函數替換巨集函式,operator[]與const)

C++程式設計進階1(對於單純的常量,用const替換#define;用行內函數替換巨集函式,operator[]與const)

技術標籤:C++進階c++effective c++

一、對於單純的常量,用const替換#define;用行內函數替換巨集函式

如果想表示一個常量,請使用const而不是#define,因為#define在預編譯期被簡單的替換,沒有型別檢查。而const變數會被編譯,編譯期間會進行型別檢查,更加安全可靠。除了型別檢查之外,由於#define只是簡單的將巨集名替換,所以會導致生成的目的碼更大,而const就不會

因為巨集只是簡單的替換,所以巨集函式不會導致函式呼叫的開銷,但是沒有引數檢查,有可能是不確定的

示例

#define MAX(a,b) (a)>(b)?(a):(b)
int main(int argc, char const *argv[])
{
	int a=5,b=0;
	MAX(++a, b);//執行兩次自增
	cout<<a<<endl;
	MAX(++a, b+10);//執行一次自增
	cout<<a<<endl;
	return 0;
}

因為巨集函式的比較物件不同,導致自增執行的次數也不同,所以就會出現這種不確定的行為

換成行內函數後,因為行內函數會在呼叫處展開,所以也沒有函式呼叫的開銷,但是此時有了引數檢查,所以就不會有上述的不確定行為

inline int MAX(int a,int b)
{
	return (a)>(b)?(a):(b); 
} 

二、operator[]與const

在部落格https://blog.csdn.net/Master_Cui/article/details/109532520中,operator[]的實現分成了兩個版本,但是為啥const版本的operator[]需要返回const物件的引用?此外文中的operator[]實現較短,如果實現程式碼較多,能否用一個呼叫另外一個?

第一個問題:為啥const版本的operator[]需要返回const物件的引用?

如果const版本的operator[]沒有返回const物件的引用,當寫下這樣的程式碼時

int main(int argc, char const *argv[])
{
	mystring t1("1234");
	const mystring t4=t1;
	char *p=&t4[0];
	*p='a';
	cout<<t4<<endl;
	return 0;
}

可見輸出了a234,const物件被修改了,因為如果不對const版本的operator[]的返回值加const,那麼,將返回一個字元的引用,這樣就可以通過指標來修改資料,從而間接修改原來的const物件

當const版本的operator[]的返回值加上const後,返回的是一個const char的引用,此時用char 指標指向const char資料,

發生了下面的轉化

const char t=data[0];
const char &rt=t;
char *p = &rt;

當編譯器編譯到第三行時,發現char *要指向一個const char *,所以報錯,防止間接修改原來的const mystring物件

所以,const版本的operator[]的返回值要返回const資料的引用,防止資料被間接修改

第二個問題:能否用一個operator[]呼叫另外一個operator[]?

https://blog.csdn.net/Master_Cui/article/details/106885048

目前兩個版本的operator[]的實現如下

char & mystring::operator[](size_t n)
{
	cout<<"operator[](size_t n)"<<endl;
	return data_[n];
}

const char & mystring::operator[](size_t n) const
{
	cout<<"operator[](size_t n) const"<<endl;
	return data_[n];
}

程式碼很短,但是如果程式碼較長,能否用其中一個呼叫另一個,防止重複程式碼?

因為const成員函式不會修改成員,所以,不能用const成員你函式呼叫非const成員函式,所以,必須是非const版本調動const版本的operator[]

所以,會寫下如下程式碼

char & mystring::operator[](size_t n)
{
	cout<<"operator[](size_t n)"<<endl;
	return operator[](n);
}

然而實際上並沒有呼叫const版本的operator[],而是無限遞迴

所以直接呼叫不行,就需要想辦法間接呼叫到const版本的operator[],因為const物件可以呼叫operator[],所以就需要構造一個const mystring,然後用構造出來的const物件來呼叫operator[],此時返回的是一個const char&,需要使用const_cast去除底層const,所以可以寫下如下程式碼

char & mystring::operator[](size_t n)
{
	cout<<"operator[](size_t n)"<<endl;
	const mystring t=*this;
	const char &rt=t.operator[](n);
	return const_cast<char&>(rt);
}

int main(int argc, char const *argv[])
{
	mystring c1="1234";
	cout<<c1[1]<<endl;
}

然後並沒有發現要找到的字元2,相反卻出現了一個空格和一個解構函式的呼叫。這是因為構造的const物件是個區域性物件,operator[]執行結束後,區域性物件被釋放,所以區域性物件的引用也就無效了,所以沒有打印出來字元2

修改程式碼如下,將區域性物件變成引用繫結*this,這樣函式退出後,*this不會被釋放,所以引用也有效

char & mystring::operator[](size_t n)
{
	cout<<"operator[](size_t n)"<<endl;
	const mystring &t=*this;//變成引用
	const char &rt=t.operator[](n);
	return const_cast<char&>(rt);
}

將上述程式碼改為一行程式碼如下

char & mystring::operator[](size_t n)
{
	cout<<"operator[](size_t n)"<<endl;
	return const_cast<char&>(static_cast<const mystring &>(*this)[n]);
}

可見這回出現了字元2,並且使用非const版本的operator[]呼叫了const版本的operator[],只不過程式碼醜的一批

所以,如果const版本的operator[]實現並不複雜的話,非const版本的operator[]程式碼重複就重複吧,畢竟可讀性好,否則再考慮這種醜的一批的轉換式寫法

參考

《Effective C++》

歡迎大家評論交流,作者水平有限,如有錯誤,歡迎指出