1. 程式人生 > >《C++面向物件程式設計》課程筆記 lessen3

《C++面向物件程式設計》課程筆記 lessen3

1. this 指標

由來:C++原來沒有編譯器,可以將C++程式翻譯為C程式然後使用C的編譯器,翻譯過程中,class轉化為struct,成員變數轉化為結構的域,成員函式轉化為全域性函式,但是成員函式需要增加一個 指向作用物件的指標this作為引數,以方便確定被操作物件並對其進行操作。

比如下面的C++程式碼:

class CCar {    // 類
    public:
        int price;
        void SetPrice(int p);
};

void CCar::SetPrice(int p)      // 成員函式
{
    price = p;
}

int main()
{
    CCar car;
    car.SetPrice(20000);
    return 0;
}

可以被轉化為:

struct CCar {
    int price;      // 成員變數->域
}

void SetPrice(struct CCar * this, int p)    // 成員函式->全域性函式+this指標
{
    this->price = p;
}

int main()
{
    struct CCar car;
    SetPrice(&car, 20000);      // 加入一個引數。物件 car 的地址作為實參

    return 0;
}
  • this 指標的作用:指向成員函式所作用的物件(注意:this 是一個指標,*this 就是 this 所指向的物件)

this 指標使用例項—返回被作用物件:

#include<iostream>

using namespace std;
class Complex {
    public:
        double real, imag;
        void Print();
        Complex(double r, double i):real(r), imag(i) {}
        Complex AddOne();
};

Complex Complex::AddOne()
{
    this->real++;
    this->Print();
    return * this;      // 返回被作用物件的值
}

void Complex::Print()
{
    cout << real << "," << imag;
}

int main()
{
    Complex c1(1, 1), c2(0, 0);
    c2 = c1.AddOne();

    return 0;
}

//輸出:2,1

使用空指標呼叫成員函式:

這樣的呼叫是有條件的,條件就是:成員函式內部不使用被作用物件的成員變數或成員函式

如下程式碼,是正確的:

#include<iostream>

using namespace std;

class A {
    int i;
    public:
        void Hello() { cout << "hello" << endl;}    // 函式執行和物件沒有任何關係
        //等價於 void Hello(A * this) {cout << "hello" << endl;} (翻譯成C語言)
};

int main()
{
    A * p = NULL;
    p->Hello();         // 等價於 Hello(p), 空指標也可呼叫成員函式(函式體裡並未使用該空指標)

    return 0;
}

但如下程式碼是錯誤的,其成員函式中使用了物件,可以通過編譯,但是執行時會非正常終止。

#include<iostream>

using namespace std;

class A {
    int i;
    public:
        void Hello() { cout << i << "hello" << endl;}    // 函式執行使用了物件
        ////等價於 void Hello(A * this) {cout << this->i << "hello" << endl;}
};

int main()
{
    A * p = NULL;
    p->Hello();         // 空指標不可呼叫成員函式 Hello(p)  函式體裡呼叫了空指標

    return 0;
}

this 指標和靜態成員函式:

靜態成員函式中不能使用 this 指標!(靜態成員函式並不具體作用於某個物件!)

靜態成員函式的真實的引數個數就是程式中寫出的引數個數。普通成員函式的真實引數個數比寫出的對一個(this 指標)。

2. 靜態成員函式和靜態成員變數

靜態成員:在說明前加了 static 關鍵字的成員

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

基本概念:

  • 普通成員變數每個物件有各自的一份,而靜態成員變數一共就一份,為所有物件共享。
  • sizeof 運算子不會計算靜態成員變數。(計算一個類的位元組數時忽略靜態成員變數)
  • 普通成員變數必須具體作用於某個物件,而靜態成員變數並不具體作用於某個物件。(普通成員變數呼叫時:myclass.a 或 myclass->a 。而靜態成員變數呼叫時不需指定物件)。
  • 靜態成員不需要通過物件就能訪問。
  • 靜態成員變數本質上是全域性變數,沒有物件,類的靜態成員變數也存在
  • 靜態成員函式本質上是全域性函式
  • 這種機制使得和某些類相關的全域性變數和全域性函式寫到類裡面,易於理解和維護

靜態成員的訪問方式:

  • 類名::成員名
Crectangle::PrintTotal()
  • 物件名.成員名
Crectangle r; r.PrintTotal();    // 只是一種形式,並不作用在物件 r 上
  •  物件指標->成員名
Crectangle * p = &r; p->PrintTotal();        // 不作用在物件 r 上
  • 引用.成員名
Crectangle & ref = r; int n = ref.nTotalNumber;      // 不屬於 r。共享的 nTotalNumber
  • 靜態成員變數必須在使用前在類的定義之外進行宣告或初始化,否則編譯通過,連結不通過
  • 在靜態成員函式中,不能訪問非靜態成員變數,也不能呼叫非靜態成員函式。

靜態成員例項:

#include<iostream>

using namespace std;

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_;
    nTotalNumber++;
    nTotalArea += w * h;
}

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

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

int Crectangle::nTotalNumber = 0;
int Crectangle::nTotalArea = 0;         // 靜態成員在使用前進行一次宣告或初始化,否則編譯能通過,連結不能通過

int main()
{
    Crectangle r1(3,3), r2(2,2);
    // cout << Crectangle::nTotalNumber; // 錯誤,私有變數
    Crectangle::PrintTotal();
    r1.PrintTotal();    // 兩個PrintTotal等價

    return 0;
}

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

例項的改進:

注意到例項中沒有對複製建構函式進行重新定義,使用複製建構函式生成的物件沒有修改兩個靜態成員變數,但消亡時呼叫了解構函式,會導致兩個靜態成員變數比正確值小(一旦有呼叫複製建構函式的物件生成就比正確值小。),可以通過自定義複製建構函式來改進。

#include<iostream>

using namespace std;

class Crectangle {
    private:
        int w, h;
        static int nTotalArea;          // 靜態成員變數
        static int nTotalNumber;        // 靜態成員變數,別的類無法訪問
    public:
        Crectangle(int w_, int h_);
        Crectangle(Crectangle & c);     // 自定義複製建構函式
        ~Crectangle();
        static void PrintTotal();       // 靜態成員函式,別的類無法訪問
};

Crectangle::Crectangle(Crectangle & c)  //增加自定義的複製建構函式
{
    w = c.w;
    h = c.h;
    nTotalNumber++;
    nTotalArea += w * h;
}

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

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

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

int Crectangle::nTotalNumber = 0;
int Crectangle::nTotalArea = 0;         // 進行一次宣告或初始化,否則編譯能通過,連結不能通過

int main()
{
    Crectangle r1(3,3), r2(2,2);
    // cout << Crectangle::nTotalNumber; // 錯誤,私有變數
    Crectangle::PrintTotal();
    r1.PrintTotal();    // 兩個PrintTotal等價

    return 0;
}

3. 成員物件和封閉類

  • 是另一個類的成員變數的物件為成員物件
  • 有成員物件的類為封閉類
#include<iostream>

using namespace std;

class CTyre {
    int radius;
    int width;
    public:
        CTyre (int r, int w):radius(r), width(w) {} //初始化列表
};

class CEngine {

};

class CCar {            // 封閉類
    int price;
    CTyre tyre;         // 成員物件
    CEngine engine;     // 成員物件
    public:
        CCar(int p, int ty, 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 的自定義建構函式是必須的,否則編譯器的無參建構函式不知道如何初始化成員物件 tyre 因為 CTyre 沒有無參建構函式,初始化需要引數

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

封閉類中的成員物件初始化必須使用建構函式的初始化列表(擁有無參建構函式的成員物件除外),不能使用建構函式體,列表中的引數可以是任意有定義的表示式

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

  • 先執行所有成員物件的建構函式,然後才執行封閉類物件的建構函式(構造封閉類物件可能使用成員物件)。

  • 成員物件的建構函式呼叫次序和成員物件在類中的說明次序一致,與它們在初始化列表中出現的次序無關。

  • 先執行封閉類物件的解構函式,然後執行成員物件的解構函式(封閉類解構函式可能使用成員物件)。次序和建構函式的呼叫次序相反。

封閉類的複製建構函式:

會使得封閉類的成員物件使用複製建構函式而非普通建構函式初始化,成員物件的複製建構函式的實參即為封閉類複製建構函式的實參的成員物件。

#include<iostream>

using namespace std;

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

  • b1 沒有自定義建構函式或複製建構函式,均使用預設的函式。
  • 預設的建構函式初始化了 b1 ,呼叫了 a 的無參建構函式,輸出 default
  • 預設的複製建構函式初始化了 b2, 呼叫了 a 的複製建構函式,其實參為 b1.a,輸出copy.

4. 常量物件和常量成員函式

定義方式:

  • 常量物件定義方式

    const 類名 物件名

  • 常量成員函式定義方式

    返回值型別 類名::函式名(引數表) const {函式體}             

       注:定義常量成員函式時把 const 關鍵字放在後面。

常量成員函式定義規則:

  • 常量成員函式執行期間不應修改其所作用的物件。
  • 不能修改成員變數的值(靜態成員變數除外)。
  • 也不能呼叫同類的非常量成員函式(靜態成員函式除外)。

例項:

#include<iostream>

using namespace std;

class Sample {
  int value;
  public:
      void GetValue() const; //常量成員函式
      void func() {};
      Sample() {};
};

void Sample::GetValue() const
{
  value = 0;  // 出錯。修改了成員變數的值
  func();     // 出錯。呼叫了非常量成員函式
}

int main()
{
  const Sample o;  //定義成員物件
  o.value = 100; //err。常量物件不可被修改。
  o.func();  //err。常量物件上面不能執行非常量成員函式。
  o.GetValue();  //ok。常量物件上可以執行常量成員函式。 

  return 0;
}

常量成員函式的過載:

  • 兩個成員函式,名字和引數表都一樣。但是一個是 const ,一個不是。這算是成員函式的過載,不是重複定義。
  • 在呼叫時,常量物件只能呼叫常量成員函式;非常量物件雖然可以呼叫任何一個,但預設呼叫非常量成員函式。
#include<iostream>
using namespace std;

class CTest {
  int n;
  public:
      CTest() {n = 1;}
      int GetValue() const { return n; }  //常量成員函式
      int GetValue() { return 2 * n; }    //非常量成員函式
      //返回值,函式名,引數都相同,但一個是常量,一個是非常量,屬於過載。
};

int main()
{
  const CTest objTest1; //常量物件
  CTest objTest2;       //非常量物件

  cout << objTest1.GetValue() << endl;    // 呼叫常量成員函式
  cout << objTest2.GetValue() << endl;    // 呼叫非常量成員函式

  return 0;
}

5. 友元

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

定義:

  • 友元函式:一個類的友元函式(非成員函式)可以訪問該類的私有成員
  • 友元類:若A是B的友元類,則類A的成員函式可以訪問類B的私有成員

友元函式例項:

#include<iostream>
using namespace std;

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 (cars[i].price > tmpMax) {
            tmpMax = cars[i].price;            // 訪問類的私有變數(而不是該類的成員函式)
        }
    }
    return tmpMax;
}

int main()
{
    return 0;
}

可以將一個類的成員函式(包括構造、解構函式)說明為另一個類的友元。形如:

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

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

 友元類例項:

#include<iostream>

using namespace std;

class CDriver;  // 提前宣告,便於使用
class CCar {
    private:
        int price;
    friend CDriver;           // 宣告友元類
};
    
class CDriver {
    CCar myCar;   //定義物件
    public:
        void ModifyCar(CCar * pCar);
};

void CDriver::ModifyCar(CCar * pCar)
{
    pCar->price += 1000;        // 操作私有變數
}

int main()
{
    return 0;
}

注意事項:

  • 友元關係不能傳遞,不能繼承