1. 程式人生 > >北京大學MOOC C++學習筆記(二)

北京大學MOOC C++學習筆記(二)

類成員的可訪問範圍:

 在類的定義中,用下列訪問範圍關鍵字來說明類成員 可被訪問的範圍: – private: 私有成員,只能在成員函式內訪問 – public : 公有成員,可以在任何地方訪問 – protected: 保護成員,以後再說

  • 如過某個成員前面沒有上述關鍵字,則預設地被認為是私有成員。 
  • 在類的成員函式內部,能夠訪問: – 當前物件的全部屬性、函式; – 同類其它物件的全部屬性、函式。
  • 在類的成員函式以外的地方,只能夠訪問該類物件的公有成員。

設定私有成員的機制,叫“隱藏”。 “隱藏”的目的是強制對成員變數的訪問一定要通過成員函式進行,那麼以後成員變數的型別等屬性修改後,只需要更改成員 函式即可。否則,所有直接訪問成員變數的語句都需要修改。

成員函式的過載及引數預設 :

  • 成員函式也可以過載。
  •  成員函式可以帶預設引數。
  • 使用預設引數要注意避免有函式過載時的二義性。

建構函式:

成員函式的一種。 名字與類名相同,可以有引數,不能有返回值(void也不行)。 作用是對物件進行初始化,如給成員變數賦初值。 如果定義類時沒寫建構函式,則編譯器生成一個預設的無引數的建構函式。 預設建構函式無引數,不做任何操作。

  • 如果定義了建構函式,則編譯器不生成預設的無引數的建構函式。
  •  物件生成時建構函式自動被呼叫。物件一旦生成,就再也不能在其上執行建構函式。
  •  一個類可以有多個建構函式,引數個數或型別不同。
  • 建構函式最好是public的,private建構函式不能直接用來初始化物件。

複製建構函式:

  •  只有一個引數,即對同類物件的引用。
  • 形如 X::X( X& )或X::X(const X &), 二者選一。後者能以常量物件作為引數
  • 如果沒有定義複製建構函式,那麼編譯器生成預設複製建構函式。預設的複製建構函式完成複製功能。
  • 不允許有形如 X::X( X )的建構函式。
class Complex {
public :
double real,imag;
Complex(){ }
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//呼叫自己定義的複製建構函式,輸出 Copy Constructor called

複製建構函式起作用的三種情況:

  • 當用一個物件去初始化同類的另一個物件時。
Complex c2(c1);
Complex c2 = c1; //初始化語句,非賦值語句
  • 如果某函式有一個引數是類 A 的物件,那麼該函式被呼叫時,類A的複製建構函式將被呼叫。 
class A
{
public:
A() { };
A( A & a) {
cout << "Copy constructor called" <<endl;
} 
};

void Func(A a1){ }
int main(){
A a2;
Func(a2);
return 0;
}
  • 如果函式的返回值是類A的物件時,則函式返回時,A的複製建構函式被呼叫。 
class A
{
public:
int v;
A(int n) { v = n; };
A( const A & a) {
v = a.v;
cout << "Copy constructor called" <<endl;
}
};

A Func() {
A b(4);
return b;
}
int main() {
cout << Func().v << endl; return 0;
}

注意:物件間賦值並不導致複製建構函式被呼叫

class CMyclass {
public:
int n;
CMyclass() {};
CMyclass( CMyclass & c) { n = 2 * c.n ; }
};
int main() {
CMyclass c1,c2;
c1.n = 5; c2 = c1; CMyclass c3(c1);
cout <<"c2.n=" << c2.n << ",";
cout <<"c3.n=" << c3.n << endl;
return 0;
}
輸出: c2.n=5,c3.n=10

在這裡c2 = c1,是將c1中所有變數的值拷貝給c2,並不是c1和c2指向同一片空間。

常量引用引數的使用void fun(CMyclass obj_ ) { cout << "fun" << endl; } 這樣的函式,呼叫時生成形參會引發複製建構函式呼叫,開銷比較大。 所以可以考慮使用 CMyclass & 引用型別作為引數。 如果希望確保實參的值在函式中不應被改變,那麼可以加上const 關鍵字: void fun(const CMyclass & obj) { // 函式中任何試圖改變 obj 值的語句都將是變成非法 }

型別轉換建構函式:

什麼是型別轉換建構函式? 定義轉換建構函式的目的是實現型別的自動轉換。 只有一個引數,而且不是複製建構函式的建構函式,一般就可以看作是轉換建構函式。 當需要的時候,編譯系統會自動呼叫轉換建構函式,建立一個無名的臨時物件(或臨時變數)。

class Complex {
public:
double real, imag;
Complex( int i) {// 型別轉換建構函式
cout << "IntConstructor called" << endl;
real = i; imag = 0;
}
Complex(double r,double i) {real = r; imag = i; }
};
int main ()
{
Complex c1(7,8);
Complex c2 = 12;
c1 = 9; // 9 被自動轉換成一個臨時Complex 物件
cout << c1.real << "," << c1.imag << endl;
return 0;
}

轉換建構函式的作用是將某種型別的資料轉換為類的物件,當一個建構函式只有一個引數,而且該引數又不是本類的const引用時,這種建構函式稱為轉換建構函式。

#include <iostream>

using namespace std;
class A
{
public:
    int a;
    A(int a) :a(a) {}
    A reta()
    {
        return a;
    }
};
int main()
{
    A a(2);
    A b = a.reta();
    A c = 3;
    cout<<b.a<<"\n"<<c.a<<endl;
    return 0;
}

實際上這是由隱式轉換機制造成的,如果不想要這種效果,可以在建構函式前加上explicit宣告。加上之後上面的程式碼就會編譯出錯,提示無法從“int”轉換為“Complex”。

既然能將資料轉換為型別,型別也能轉換為資料。c++的型別轉換函式可以將一個類的物件轉換為一個指定型別的資料。

型別轉換函式的一般形式為 :

operator 型別名()

{實現轉換的語句}

測試程式碼:

#include <iostream>

using namespace std;
class A
{
public:
    int a;
    A(int a) :a(a) {}
    operator int()
    {
        return a;
    }
};
int main()
{
    A a(2);
    int b = a + 3;
    A c = a + 4;
    cout<<b<<"\n"<<c.a<<endl;
    return 0;
}

解構函式:

什麼是解構函式?

  1. 名字與類名相同,在前面加‘~’, 沒有引數和返回值,一個類最多隻能有一個解構函式。
  2. 解構函式物件消亡時即自動被呼叫。可以定義解構函式來在物件消亡前做善後工作,比如釋放分配的空間等。
  3. 如果定義類時沒寫解構函式,則編譯器生成預設解構函式。預設解構函式什麼也不做。
  4. 如果定義了解構函式,則編譯器不生成預設解構函式
class String{
private :
char * p;
public:
String () {
p = new char[10];
}
~ String () ;
};
String ::~ String()
{
delete [] p;
}

解構函式在物件作為函式返回值返回後被呼叫

class CMyclass {
public:
~CMyclass() { cout << "destructor" << endl; }
};
CMyclass obj;
CMyclass fun(CMyclass sobj ) { // 引數物件消亡也會導致析
// 構函式被呼叫
return sobj; // 函式呼叫返回時生成臨時物件返回
}
int main(){
obj = fun(obj); // 函式呼叫的返回值(臨時物件)被 
return 0; // 用過後,該臨時物件解構函式被呼叫
}


輸出:
destructor
destructor
destructor

建構函式和解構函式呼叫次數

Demo d1(1);
void Func()
{
static Demo d2(2);
Demo d3(3);
cout << "func" << endl;
}
int main () {
Demo d4(4);
d4 = 6;
cout << "main" << endl;
{ Demo d5(5);
}
Func();
cout << "main ends" << endl;
return 0;
}

輸出結果: : id=1 constructed id=4 constructed id=6 constructed id=6 destructed main id=5 constructed id=5 destructed id=2 constructed id=3 constructed func id=3 destructed main ends id=6 destructed id=2 destructed id=1 destructed

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指標和靜態成員函式:靜態成員函式中不能使用 this 指標! 因為靜態成員函式並不具體作用與某個物件! 因此,靜態成員函式的真實的引數的個數,就是程式中寫出的引數個數!

靜態成員

靜態成員:在定義前面加了static關鍵字的成員。

普通成員變數每個物件有各自的一份,而靜態成員變數一共就一份,為所有物件共享。

sizeof 運算子不會計算靜態成員變數。

普通成員函式必須具體作用於某個物件,而靜態成員函式並不具體作用於某個物件。

因此靜態成員不需要通過物件就能訪問。

如何訪問靜態成員 1) 類名::成員名 CRectangle::PrintTotal(); 2) 物件名.成員名 CRectangle r; r.PrintTotal(); 3) 指標->成員名 CRectangle * p = &r; p->PrintTotal(); 4) 引用.成員名 CRectangle & ref = r; int n = ref.nTotalNumber;

靜態成員變數本質上是全域性變數,哪怕一個物件都不存在,類的靜態成員變數也存在。 靜態成員函式本質上是全域性函式。 設定靜態成員這種機制的目的是將和某些類緊密相關的全域性變數和函式寫到類裡面,看上去像一個整體,易於維護和理解。

 在靜態成員函式中,不能訪問非靜態成員變數,也不能呼叫非靜態成員函式。

成員物件和封閉類:

有成員物件的類叫 封閉(enclosing)類。

任何生成封閉類物件的語句,都要讓編譯器明白,物件中的成員物件,是如何初始化的。 具體的做法就是:通過封閉類的建構函式的初始化列表。

class CTyre // 輪胎類
{
private:
int radius; // 半徑
int width; // 寬度
public:
CTyre(int r,int w):radius(r),width(w) { }
};
class CEngine // 引擎類
{
};
54
class CCar { // 汽車類
private:
int price; // 價格
CTyre tyre;
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;
}

封閉類建構函式和解構函式的執行順序:

  1. 封閉類物件生成時,先執行所有物件成員的建構函式,然後才執行封閉類的建構函式。
  2. 物件成員的建構函式呼叫次序和物件成員在類中的說明次序一致,與它們在成員初始化列表中出現的次序無關。
  3. 當封閉類的物件消亡時,先執行封閉類的解構函式,然後再執行成員物件的解構函式。次序和建構函式的呼叫次序相反。

封閉類的物件,如果是用預設複製建構函式初始化的,那麼它裡面包含的成員物件,也會用複製建構函式初始化。

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

友元

友元分為友元函式和友元類兩種:

1)  友元函式 : 一個類的友元函式可以訪問該類的私有成員. 

2) 友元類 : 如果A是B的友元類,那麼A的成員函式可以訪問B的私有成員。

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

常量成員函式:

如果不希望某個物件的值被改變,則定義該物件的時候可以在前面加 const關鍵字。常量物件只能使用建構函式、解構函式和  有 const  說明的函式(常量方法)。

在類的成員函式說明後面可以加const關鍵字,則該成員函式成為常量成員函式。 常量成員函式內部不能改變屬性的值,也不能呼叫非常量成員函式。

在定義常量成員函式和宣告常量成員函式時都應該使用const 關鍵字。

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

兩個函式,名字和引數表都一樣,但是一個是const,一個不是,算過載。

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

class CTest
{
public:
bool GetData() const
{
m_n1++;
return m_b2;
}
private:
mutable int m_n1;
bool m_b2;
};