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
的初始化沒問題,用默認構造函數就可以了。
任何生成封閉類對象的語句,都要使得編譯器明白,對象中的成員對象,是如何初始化的。完成這一任務的方法是通過封閉類的構造函數的初始化列表,成員對象初始化列表中的參數可以是任意複雜的表達式,可以包括函數,變量,只要表達式中的函數或變量有定義就行。
封閉類構造函數和析構函數的執行順序
- 封閉類對象生成時,先所有成員對象的構造函數,然後才執行封閉類的構造函數
- 對象成員的構造函數調用次序和對象成員在類中的說明次序一致,與成員初始化列表次序無關
- 封閉類對象消亡時,先執行封閉類析構函數,再執行成員對象的析構函數。次序和構造函數的調用次序相反
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
- 封閉類的對象如果用默認複製構造函數初始化,那麼它包含的成員對象也會用複製構造函數初始化
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();
}
友元類
如果A
是B
的友元類,那麼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;
};