1. 程式人生 > >聽課筆記---程式設計與演算法(三)C++面向物件程式設計(郭偉老師)---第三週

聽課筆記---程式設計與演算法(三)C++面向物件程式設計(郭偉老師)---第三週

Week3

目錄

this 指標

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

成員物件和封閉類

常量物件和常量函式

友元

內容與思考

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 CCaar {
    int price;      // 成員變數->域
}

void SetPrice(struct CCar * this, int p)    // 成員函式->全域性函式+this指標
{
    this
->price = p; } int main() { struct CCar car; SetPrice(&car, 20000); // 加入一個引數 return 0; }

可以理解為C++語言的機器級別程式碼(真正執行的程式碼)事實上就加入了this 指標

理解含有this 指標的程式碼可以嘗試將其翻譯為等價的C語言程式碼

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;}    // 函式執行和物件沒有任何關係
};

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

    return 0;
}

輸出:

hello

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

#include<iostream>

using namespace std;

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

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

    return 0;
}

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

定義方式

在宣告前加入static 關鍵字

例項

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

靜態成員的特徵與本質

  • 靜態成員變數被所有物件共享。sizeof 不計算靜態成員變數的空間
  • 靜態成員函式被所有物件共享,不具體作用於某一個物件。
  • 不需要通過物件就可以訪問,但可以通過物件訪問
  • 靜態成員變數必須在使用前進行宣告或初始化,否則編譯通過,連結不通過
  • 靜態成員變數本質上是全域性變數,沒有物件也會存在
  • 靜態成員函式本質上是全域性函式
  • 這種機制使得和某些類相關的全域性變數和全域性函式寫到類裡面,易於理解和維護

靜態成員的訪問方式

  1. 類名::成員名

    Crectangle::PrintTotal()
  2. 物件名.成員名

    Crectangle r; r.PrintTotal();    // 不作用在r上
  3. 物件指標->成員名

    Crectangle * p = &r; p->PrintTotal();        // 不作用在r上
  4. 引用.成員名

    Crectangle & ref = r; int n = ref.nTotalNumber;      // 不屬於 r

靜態成員例項

#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;
}

注意事項

  • 靜態成員函式不可以使用非靜態成員變數,包括this 指標
  • 靜態成員函式不可以呼叫非靜態成員函式

成員物件和封閉類

定義

  • 是另一個類的成員變數的物件為成員物件
  • 有成員物件的類為封閉類

例項

#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 沒有無參建構函式,初始化需要引數

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

    如:

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

    這段程式碼仍然正確,因為price 是普通成員變數而非成員物件。

    但是,這段程式碼出錯

    CCar::CCar(int p, int tr, int w):
    {
      tyre.r = tr;      // 出錯
      tyre.w = w;           // 出錯
      price = p;
    }

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

  • 先構造成員物件,後構造封閉類物件(構造封閉類物件可能使用成員物件)

  • 先析構封閉類物件,後析構成員物件(封閉類解構函式可能使用成員物件)

  • 例項

    
    #include<iostream>
    
    
    using namespace std;
    
    class CTyre {
      public:
          CTyre() { cout << "CTyre Constructor called." << endl;}
          ~CTyre() { cout << "CTyre Destructor called." << endl;}
    };
    
    class CEngine {
      public:
          CEngine() {cout << "CEngine Constructor called." << endl;}
          ~CEngine() { cout << "CEngine Destructor called." << endl;}        
    };
    
    class CCar
    {
      CEngine engine;
      CTyre tyre;
      public:
          CCar() {cout << "CCar Constructor called." << endl;}
          ~CCar() { cout << "CCar Destructor called." << endl;}
    };
    
    int main()
    {
      CCar car;
    
      return 0;
    }

    輸出:

    CEngine Constructor called.
    CTyre Constructor called.
    CCar Constructor called.
    CCar Destructor called.
    CTyre Destructor called.
    CEngine Destructor called.

封閉類的複製建構函式

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

例項

#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.

常量物件和常量成員函式

定義方式

  • 常量物件定義方式

    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()
    {
      return 0;
    }

常量成員函式呼叫規則

  • 常量物件只能呼叫常量成員函式

  • 非常量物件可以呼叫常量成員函式和非常量成員函式

  • 同樣引數表,同樣返回值型別的成員函式,一個是常量成員函式,另一個不是,這兩個函式就形成過載關係,在呼叫時,非常量物件雖然可以呼叫任何一個,但預設呼叫非常量成員函式。

    例項:

    
    #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;
    }

    輸出:

    1
    2

友元

定義

  • 友元函式:一個類的友元函式(非成員函式)可以訪問該類的私有成員
  • 友元類:若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;
}

友元類:

#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;
}

注意事項

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

練習題原始碼與分析

BigAndBase

封閉類與成員物件的建構函式與複製建構函式

Code:

#include<iostream>

using namespace std;

class Base {
    public:
        int k;
        Base(int a):k(a) {}
};

class Big {
    public:
    int v;
    Base b;
    Big(int i):v(i),b(i) {}
    Big(const Big & a):b(a.b.k) // b can only be initialized by the list
    {
        v = a.v;
    }
};

int main()
{
    int n;
    while (cin >> n) {
        Big a1(n);
        Big a2 = a1;
        cout << a1.v << "," << a1.b.k << endl;
        cout << a2.v << "," << a2.b.k << endl;
    }

    return 0;
}

Pointer

常量物件與常量成員函式,常量成員函式的呼叫規則,this 指標

Code:

#include<iostream>
using namespace std;
struct A {
    int v;
    A(int vv):v(vv){}
    const A * getPointer() const
    {
        return this;
    }
};

int main()
{
    const A a(10);
    const A * p = a.getPointer();
    cout << p->v << endl;
    return 0;
}

ProgramCompletion

引用,this 指標

#include<iostream>
using namespace std;

class A {
    public:
        int val;
        A(int i = 123)
        {
            val = i;
        }
        A & GetObj()
        {
            return *this;
        }
};

int main()
{
    int m, n;
    A a;
    cout << a.val << endl;
    while (cin >> m >> n) {
        a.GetObj() = m;
        cout << a.val << endl;
        a.GetObj() = A(n);
        cout << a.val << endl;
    }

    return 0;
}

Zoo

多型與繼承,暫且不表,來日再說

Code:

#include<iostream>

using namespace std;

class Animal {
    public:
        static int number;
        virtual ~Animal() {}
};

class Dog :public Animal
{
    public:
        static int number;
        Dog()
        {
            Dog::number++;
            Animal::number++;
        }
        ~Dog()
        {
            Dog::number--;
            Animal::number--;
        }
};

class Cat :public Animal
{
    public:
        static int number;
        Cat()
        {
            Cat::number++;
            Animal::number++;
        }
        virtual ~Cat()
        {
            Cat::number--;
            Animal::number--;
        }
};

int Cat::number = 0;
int Dog::number = 0;
int Animal::number = 0;

void print()
{
    cout << Animal::number << " animals in the zoo, " << Dog::number << " of them are dogs, " << Cat::number << "of them are cats" << endl;
}

int main()
{
    print();
    Dog d1, d2;
    Cat c1;
    print();
    Dog *d3 = new Dog();
    Animal* c2 = new Cat;
    Cat * c3 = new Cat;
    print();
    delete c2;
    delete c3;
    delete d3;
    print();
    return 0;
}

WarCraft1

綜合測試,使用了友元,靜態成員等等

Code:

#include<iostream>
#include<cstring>
#include<iomanip>

#define TYPENUM 5

using namespace std;
class Headquater;

class Warrior {
    private:
        char Type[10];      // Name
        int strength;       
        int number;
    friend Headquater;
};

class Headquater {
    private:
        static Warrior Types[TYPENUM + 1];      // 五種戰士,所有基地共享
        int WarriorSeq[TYPENUM + 1];            // 出兵順序
        int WarriorNum[TYPENUM + 1];            // 各兵種數量,與WarriorSeq對應
        char Color[10];
        int WarriorTotal;                       // 兵力總數,即當前兵編號
        int NexttoPro;                          // 下一個訓練兵種的下標
        int LifeTotal;                          // 生命元
        bool Stop;                              // 是否已停止製造兵
    public:
        Headquater(const char Color[], int LifeTotal);  // 建構函式,確定顏色,生命元
        void SetWarSeq(int a1, int a2, int a3, int a4, int a5); // 設定出兵順序
        static void WarTypeInit(int strength[]);        // 各兵種strength值設定
        void Event(int time);                   // 某一時間點的動作
        bool StopOrNot() {return Stop;}
};

Warrior Headquater::Types[6];

void Headquater::WarTypeInit(int strength[])        // 設定strength值
{
    for (int i = 0; i < TYPENUM; i++) {
        Types[i].strength = strength[i];
    }
    strcpy(Headquater::Types[0].Type, "dragon");
    strcpy(Headquater::Types[1].Type, "ninja");
    strcpy(Headquater::Types[2].Type, "iceman");
    strcpy(Headquater::Types[3].Type, "lion");
    strcpy(Headquater::Types[4].Type, "wolf");
}

Headquater::Headquater(const char Col[], int Life)  // 建構函式
{
    strcpy(Color, Col);
    LifeTotal = Life;
    Stop = false;
    NexttoPro = 0;
    WarriorTotal = 0;
    for (int i = 0; i < TYPENUM; i++) {
        WarriorNum[i] = 0;
    }
}

void Headquater::SetWarSeq(int a1, int a2, int a3, int a4, int a5)      // 設定出兵順序
{
    WarriorSeq[0] = a1;
    WarriorSeq[1] = a2;
    WarriorSeq[2] = a3;
    WarriorSeq[3] = a4;
    WarriorSeq[4] = a5;
}

void Headquater::Event(int time)        // 某一時刻動作
{
    if (Stop) {     // 已停止出兵
        return;
    }

    cout << setw(3) << setfill('0') << time << " " << Color << " "; // 輸出時間
    if (LifeTotal < Types[WarriorSeq[NexttoPro]].strength) {        // 無法制造當前應制造兵種
        int index = (NexttoPro + 1) % TYPENUM;
        while (LifeTotal < Types[WarriorSeq[index]].strength && index != NexttoPro) {   // 查詢下一可製造兵種
            index = (index + 1) % TYPENUM;
        }
        if (index == NexttoPro) {       // 無可製造兵種,停止造兵
            cout << "headquarter stops making warriors" << endl;
            Stop = true;
            return;
        }
        else {  // 找到可製造兵種
            NexttoPro = index;
        }
    }

    cout << Types[WarriorSeq[NexttoPro]].Type << " " << ++WarriorTotal << " born with strength " << Types[WarriorSeq[NexttoPro]].strength
         << "," << ++WarriorNum[NexttoPro] << " " << Types[WarriorSeq[NexttoPro]].Type << " in " << Color << " headquarter" << endl;    // 輸出造兵行為
    LifeTotal -= Types[WarriorSeq[NexttoPro]].strength; // 減少生命元
    NexttoPro = (NexttoPro + 1) % TYPENUM;  // 指向下一應制造兵種
}

int main()
{
    int strength[TYPENUM + 1];
    int N, LifeUnit;

    cin >> N;
    for (int i = 1; i <= N; i++) {
        cin >> LifeUnit;    // 讀入生命元
        Headquater Blue("blue", LifeUnit);
        Headquater Red("red", LifeUnit);
        Blue.SetWarSeq(3, 0, 1, 2, 4);
        Red.SetWarSeq(2, 3, 4, 1, 0);   // 初始化兩大基地
        for (int j = 0; j < TYPENUM; j++) { // 讀入力量值
            cin >> strength[j];
        }
        Headquater::WarTypeInit(strength);  // 設定力量值
        cout << "Case:" << i << endl;
        int time = 0;
        while (!Blue.StopOrNot() || !Red.StopOrNot()) { // 有一方未停止造兵,即繼續行為
            Red.Event(time);
            Blue.Event(time);
            time++;
        }
    }

    return 0;
}