C++面向對象程序設計(三)——3.類和對象提高

C++面向對象程序設計(三)——3.類和對象提高

本文是中國大學MOOC,北京大學程序設計與算法(三)C++面向對象程序設計第三週筆記。本課程學習的github倉庫歡迎Fork

一 this 指針

C++ 到 C程序的翻譯

//C++
class CCar{
  public:
    	int price;
    	void SetPtice(int p);
};
void CCar::SetPrice( int p ){
    price = p;
}
int main(){
    CCar car;
    car.SetPrice( 20000 );
    return 0;
}
//C
struct CCar{
    int price;
};
void SetPrice( struct CCar * this, int p)
{
    this> price = p;
}
int main(){
    struct CCar car;
    SetPrice( & car, 20000 );
    return 0;
}

對比一下這兩個程序,我們可以看到:

每個類的非靜態成員函數中都隱含包含一個this指針,類型爲當前類類型的指針類型

this 作用就是指向成員函數所作用的對象。在非靜態成員函數中可以直接使用this來代表指向該函數作用的對象的指針。

我們可以看一個案例:

class Complex{
    public:
    	double real,imag;
    	void Print(){
            cout << real << "," << imag;
        }
    Complex( double r, double i ):real( r ),imag( i )
    {}
    Complex AddOne(){
        this -> real ++;		//等價於 real++
        this -> Print();		//等於於 Print
        return * this;
    }
};

int main(){
    Complex c1(1,1),c2(0,0);
    c2 = c1.AddOne();
    return 0;
}//輸出 2,1

需要注意的是:

靜態成員函數中不能使用this指針,因爲靜態成員函數並不具體作用於某個對象。所以,靜態成員函數的真實參數的個數,就是程序中寫出的參數個數。

然而,類的非靜態成員函數,真實的參數比所寫的參數多1,多的這個就是this指針。

二 靜態成員

基本概念

在定義前面加了static關鍵字的成員

class CRectangle
{
    private:
    	int w, h;
    		static int nTotalArea;		//靜態成員變量
    		static int nTotalNumber;
    public:
    		CRectangle(int w_, int h_);
    		~CRectangle();
    		static void PrintTotal();		//靜態成員函數
};

靜態成員變量爲所有對象共享,且sizeof運算符不會計算靜態成員變量

class CMyclass{
    int n;
    static int s;
};
//sizeof(CMyclass) 等於 4

普通成員變量每個對象有各自的一份,而靜態成員變量一共就一份,爲所有對象共享

普通成員函數必須具體作用於某個對象,而靜態成員函數並不具體作用於某個對象。所以實際上靜態成員不需要通過對象就能訪問。

如何訪問靜態成員

1.類型::成員名

CRectangle::PrintTotal();

2.對象名.成員名

CRectangle r;
r.PrintTotal();

3.指針->成員名

CRectangle * p = &r;
p -> PrintTotal();

4.引用.成員名

CRectangle & ref = r;
int n = ref.nTotalNumber;

靜態成員變量本質上是全局變量,哪怕一個對象都不存在,類的靜態成員變量也存在

靜態成員函數本質上是全局函數,主要目的是將和某些類緊密相關的全局變量和函數寫到類裏面,看上去想一個整體,易於維護和理解

class CRectangle
{
    private:
    	int w, h;
    		static int nTotalArea;		//靜態成員變量
    		static int nTotalNumber;
    public:
    		CRectangle(int w_, int h_);
    		~CRectangle();
    		static void PrintTotal();		//靜態成員函數
};

CRectangle::CRectangle(int w_,int h_ )
{
    w = w_;
    h = h_;
    nTotalNmuber ++;
    nTotalArea += w * h;
}

CRectangle::~CRectangle()
{
    nTotalNumber --;
    nTotalArea -= w * h;
}

void CRectangle::PrintTotal()
{
    cout << nTotalNumber << "," <<nTotalArea <<endl;
}

int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalNumber = 0;
//必須在定義類的文件中對靜態函數變量進行以此說明或初始化
//否則編譯能通過,鏈接1不能通過

int main()
{
    CRectangle r1( 3, 3 ), r2(2, 2);
    cout << CRectangle::nTotalNumber;				//error,私有
   	CRectangle::PrintTotal();
    r1.PrintTotal();
    return 0;
}

//輸出:
//2,13
//2,13

要注意的是:在靜態成員函數中,不能訪問非靜態成員變量,也不能調用非靜態成員函數

void CRectangle::PrintTotal()
{
    cout << w << "," << nTotalNumber << "," << nTotalArea << endl;		//error
}
CRectangle::PrintTotal();	//解釋不通,w到底屬於哪個對象?

三 成員對象和封閉類

基本概念

有成員對象的類叫封閉類

class CTyre	{	//輪胎類
    private:
    		int radius;		//半徑
    		int width;		//寬度
    public:
    CTyre( int r, int w ):radius(r),width(w){}//初始化列表,爲成員變量指定初始值
};
class CEngine{		//引擎類
};

class CCar{		//汽車類
    private:
    		int price;		//價格
    		CTyre yre;
    		CEngine engine;
    public:
    		CCar( int p, int tr, int tw );
};

CCar::CCar( int p, int tr, int w):price(p),tyre(tr,w)
{};


int main()
{
    CCar car( 20000, 17, 225 );
    return 0;
}

上面的例子中,如果CCar類不定義構造函數,那麼下面的語句編譯會出錯

CCar car;

因爲編譯器不明白car.type,該如何初始化。car.engine的初始化沒問題,用默認構造函數就可以了。

任何生成封閉類對象的語句,都要使得編譯器明白,對象中的成員對象,是如何初始化的。完成這一任務的方法是通過封閉類的構造函數的初始化列表,成員對象初始化列表中的參數可以是任意複雜的表達式,可以包括函數,變量,只要表達式中的函數或變量有定義就行。

封閉類構造函數和析構函數的執行順序

  1. 封閉類對象生成時,先所有成員對象的構造函數,然後才執行封閉類的構造函數
  2. 對象成員的構造函數調用次序和對象成員在類中的說明次序一致,與成員初始化列表次序無關
  3. 封閉類對象消亡時,先執行封閉類析構函數,再執行成員對象的析構函數。次序和構造函數的調用次序相反
class CTyre{
    public:
    		CTyre(){cout << " Ctyre contructor "<< endl; }
    		~CTyre(){cout << " Ctyre destructor "<< endl; }
}

class CEngine{
    public:
    		CEngine(){cout << "CEngine contructor" << endl;}
    		~CEngine(){cout << "CEngine destructor" << endl;}
}

class CCar{
		private:
    			CEngine engine;
    			CTyre tyre;
    	public:
    			CCar(){	cout << "CCar constructor" << endl;}
    			~CCar(){ cout << "CCar destructor" << endl;}
}

int main(){
    CCar car;
    return 0;
}


//輸出結果
//CEngine contructor
//Ctyre contructor
//CCar constructor
//CCar destructor
//Ctyre destructor
//CEngine destructor
  1. 封閉類的對象如果用默認複製構造函數初始化,那麼它包含的成員對象也會用複製構造函數初始化
class A{
    public:
    		A(){cout << "default" << endl;}
    		A(A & a){ cout << "Copy" << endl;}
};

class B {A a;};

int main(){
    B b1, b2(b1);
    return 0;
}

//輸出:
//default
//Copy
//b2.a是用類A的複製構造函數初始化的。
//調用複製構造函數的實參是b1.a

四 友元

友元函數

一個類的友元函數可以訪問該類的私有成員

class CCar;
class CDriver{
    public:
    	void ModifyCar(CCar * pCar);
};
class CCar
{
    private:
    		int price;
    friend int MostExpensiveCar( CCar cars[], int total );	//聲明友元
    friend void CDriver::ModifyCar( CCar * pCar );	//聲明友元
}

void CDriver::ModifyCar( CCar * pCar )
{
    pCar->price += 1000 ;	//改裝後變貴了
}

int MostExpensiveCar( CCar cars[],int total )	//最貴汽車價格
{
    int tmpMax = -1;
    for( int i = 0; i < total ; ++ i )
    {
        if(car[i].price > tmpMax)
            tmpMax = cars[i].price;
    }
    return tmpMax;
}

int main(){
    return 0;
}

當然我們還可以把一個類的成員函數(包括析構,構造等)說明爲另一個類的友元

class B{
    public:
    		void function();
};

class A{
    friend void B::function();
}

友元類

如果AB的友元類,那麼A的成員函數可以訪問B的私有成員

class CCar{
    private:
    		int price;
    friend class CDriver;	//聲明CDriver爲友元類
};

class CDriver{
		public:
    		CCar myCar;
    			void ModifyCar(){	//改裝汽車
                    myCar.price += 1000;	//CDriver是CCar的友元類,所以可以訪問CCar的私有成員
                }
};

int main(){
    return 0;
}

友元類之間的關係不能傳遞,不能繼承

五 常量成員函數

如果不希望某個對象的值被改變,那麼可以在對象const關鍵字

class Sample{
    private:
    int value;
    public:
    Sample(){}
    void SetValue(){}
};

const Sample Obj;	//常量對象
Obj.SetValue ();	//錯誤,常量對象只能使用構造函數,析構函數,有const說明的函數

類的成員函數說明後面可以加const關鍵字,該成員函數成爲常量成員函數

常量成員函數內部不能改變屬性的值,也不能調用非常量成員函數

在定義和聲明成員函數時都應該用const關鍵字

如果一個成員函數中沒有調用非常量成員函數,也沒有修改成員函數變量的值,那麼最好將其寫成常量成員函數

如果兩個函數,名字參數表都一樣,但是一個有const,一個沒有,算重載

六 mutable成員變量

可以在const成員函數中修改的成員變量

class CTest{
public:
    	bool GetData()	const
        {
            m_n1++;
            return m_b2;
	}
private:
    mutale int m_n1;
    bool m_b2;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章