提醒自己:
選擇C++很大程度上是因爲C++的高效率和麪向對象,所以要注意每一個影響效率的小細節
1. 數據都在private,並且都在initialization list初始化
2. 參數儘量都是reference
3. 返回值優先考慮reference
4. 類的成員函數,如果可以加const的,要加const(常量成員函數)
第7點轉載鏈接:http://blog.csdn.net/u010003835/article/details/48241913
0. 參考代碼
#ifndef __COMPLEX__
#define __COMPLEX__
#include <cmath>
//前置聲明
class ostream;
class complex //class head
{ //{} class body
public:
complex(double r = 0, double i = 0)
:re(r), im(i)
{}
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
friend complex & __doapl(complex*, const complex&);
int func(const complex ¶m)
{
return param.re + param.im;
}
private:
double re, im;
};
inline complex&
__doapl(complex*ths, const complex&r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator +=(const complex &r)
{
return __doapl(this, r);
}
#endif // !__COMPLEX__
1. 在頭文件”complex.h”中 添加防禦式聲明
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif //__COMPLEX__
這是爲了防止頭文件被多重包含(multiple inclusion),引發的編譯問題
2. inline函數
函數直接在class聲明內定義完成,會自動聲明爲inline函數,例如real(),image()就是inline函數
或者加關鍵字聲明 但是否是按照inline方式編譯,是編譯器決定的,我們只是建議編譯器這做。
inline double
imag(const complex& x)
{
return x.imag();
}
3. constructor(ctor,構造函數)
complex(double r = 0; double i = 0)
:re(r), im(i) //initialization list(初始化列表)
{}
如果不顯示指定class的constructor,C++會默認創建構造函數,是沒有參數的
構造函數沒有返回值,可重載(overloading)
本例中,構造是顯示指定的,並且有帶有默認值的兩個參數
initialization list(初始化列表):構造函數獨有的,建議把成員變量放到這裏初始化
成員變量放在初始化列表中和在函數體內部的區別是
初始化列表是在成員變量初始化的時候就已經改好了
函數體內部是賦值,是成員變量先初始化,然後再把賦值給成員變量,這樣就多了一個步驟
注意構造函數的訪問權限,大多數情況下,都是public的,但是在設計模式中的單例模式下,通常把構造函數設爲private
4. [劃重點]const member functions (常量成員函數)
double real() const {return re;}
在類成員函數名稱後面加上const,限定函數體內部不可改變本類的成員變量,注意,常量成員函數內部不能調用非常量成員函數
double real() { return re; }
double imag() { return im; }
const類型的對象實例只能調用const聲明過的成員函數
如果獲取成員變量的函數不添加const,那按照下列使用是就會出錯,
報錯是因爲 使用const創建 對象實例 c1,那就意味着c1所有的成員變量都是不可改變的,但是real(),image()並沒有聲明稱cosnt常量成員函數,這就說明,在函數體內部是允許改變成員變量的, 存在歧義,故而報錯
但是此種用法是合理的,報錯就是因爲我們設計的不合理,所以,強烈建議把不需要改變成員變量的成員函數使用const修飾
// error
const complex c1(1, 2);
std::cout << c1.real() << std::endl;
std::cout << c1.imag() << std::endl;
/* vs2013的報錯
error C2662: “double complex::real(void)”: 不能將“this”指針從“const complex”轉換爲“complex &”
error C2662: “double complex::imag(void)”: 不能將“this”指針從“const complex”轉換爲“complex &”
*/
5. 參數傳遞(值和引用)
(pass by value[值傳遞] vs pass by reference[引用傳遞](to const))
在函數參數傳遞中,使用引用傳遞,能夠有效的提高傳遞過程中堆棧利用率,所以建議C++中進行函數參數傳遞的時候儘量使用引用,能夠有效地節約堆棧空間
但是爲了避免傳入參數的值被意外修改,可以使用const complex &,const類型的引用
complex & __doapl(complex*, const complex&);
6. friend(友元)
聲明友元的函數可以直接訪問的類的私有成員變量
相同class的各個object互爲friends(友元)
class complex
{ ...
friend complex & __doapl(complex*, const complex&);
int func(const complex ¶m)
{ return param.re + param.im; }
...
};
inline complex&
__doapl(complex*ths, const complex&r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
//相同class的各個object互爲friends(友元)
complex c1(2,3);
complex c2;
c2.func(c1);
7. 操作符重載
1、重載操作符沒必要一定是成員函數,還可以是友元函數。
2、重載操作符函數爲成員函數主要是你需要操作類內部的成員,
必須是成員函數或友元函數纔行。
3、至於由深淺拷貝的原因要使其成爲成員函數,這個不知道。
4、如果運算符被重載爲全局函數,那麼只有一個參數的運算符叫做一元運算符,有兩個參數的運算符叫做二元運算符。
如果運算符被重載爲類的成員函數,那麼一元運算符沒有參數,二元運算符只有一個右側參數,因爲對象自己成了左側參數(cosnt T *this)。
語法上講,運算符既可以定義爲全局函數,也可以定義爲成員函數。文獻[Murray , p44-p47]對此問題作了較多的闡述,並總結了表8-4-1的規則。
運算符 | 規則 |
---|---|
所有的一元運算符 | 建議重載爲成員函數 |
= () [] -> | 只能重載爲成員函數 |
+= -= /= *= &= | = ~= %= >>= <<= |
所有其它運算符 | 建議重載爲全局函數 |
對於賦值操作符(=),這個比較特別,因爲任何類如果不提供顯示的拷貝賦值(即重載=),則編譯器會隱式地提供一個。這樣的話,如果你再通過友元聲明,進行全局的定義會造成調用二義性(即使允許,編譯也會出錯)。
對於操作符(=,[],(),->),只能聲明爲成員函數是爲了避免不合法的書寫通過編譯(這是推測出的原因,更深層的可能要研究 C++ 的設計了)。這涉及到 C++ 中類型的隱式轉換。下面通過代碼例子說明:
#include <iostream>
class X
{
public:
X(){}
X(int){} // int 類型可以被隱式轉換成 X
friend bool operator<(const X& x1, const X& x2) { return true; } // 只是測試,無意義
};
class Y
{
public:
Y(){}
Y(int){} // int 類型可以被隱式轉換成 Y
bool operator<(const Y& y) const { return true; } // 只是測試,無意義
};
int main()
{
X x;
if(1 < x) // 合法,使用友元重載函數,1 可以被隱式地轉換爲 X 類型 --友元函數的第一個參數
{}
Y y;
if(1 < y) // 不合法,使用成員重載函數,函數的第一個參數是 const *this,1 不能被隱式轉換
{}
return 0;
}
// 注:編譯的時候可以通過註釋掉不同的代碼來查看錯誤(即合法性),後面解釋不能作爲友元全局重載的原因
由上面的代碼可以知道,如果將 =,[],(),-> 進行友元全局重載,那麼就會出現 1=x; 1[x]; 1->x; 1(x); 這樣的合法語句(起碼編譯器認爲這些是合法的)--參考代碼中的 if(1<x) 合法的片段,但顯然這些是需要避免的,當然這些不是我們程序員的責任,應該有語言的設計者來實現,所以,就……。
#include <iostream>
class X
{
public:
X(){}
X(int){} // int 類型可以被隱式轉換成 X
friend const X& operator+=(const X& x1, const X& x2) { return x1; } // 只是測試,無意義
};
int main()
{
X x;
1 += x;// 合法,使用友元重載函數,1 可以被隱式地轉換爲 X 類型 --友元函數的第一個參數
return 0;
}