[Boolan] C++第一週(創建一個不帶指針成員變量的類)[注意事項]


提醒自己:
選擇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 &param)
    {
        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 &param)
    {   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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章