1. 程式人生 > 實用技巧 >《c++入門經典》筆記16

《c++入門經典》筆記16

第十六章 使用繼承擴充套件類

16.1什麼是繼承

如果一個類在現有類的基礎上添加了新功能,那麼這個類就被稱為從原來的類派生而來的派生類(子類),而原來的類稱為新類的基類(父類)。

基類可以有多個派生類

在c++中,要從一個類派生出另一個類,可在類宣告中的類名後加上冒號,再指定類的訪問控制符(public、protected或private)以及基類。

程式清單16.1 Mammal1.cpp

#include <iostream>

enum BREED
{
    YORKIE,
    CAIRN,
    DANDIE,
    SHETLAND,
    DOBERMAN,
    LAB
};

class Mammal
{
protected:
    int age;
    int weight;

public:
    Mammal();
    ~Mammal();
    int getAge() const;
    void setAge(int);
    int getWeight() const;
    void setWeight();
    void speak();
    void sleep();
};

class Dog : public Mammal
{
protected:
    BREED itsBreed;

public:
    Dog();
    ~Dog();

    BREED getBreed() const;
    void setBreed(BREED);
    void wagTail();
    void begForBreed();
};

int main()
{
    return 0;
}

這個程式能夠通過編譯,但是沒有任何輸出。它只包含類宣告,而沒有實現。

​ 若希望資料對當前類和它的派生類可見,為此可使用protected。受保護的資料成員和函式對派生類來說是可見的,但其他方面與私有成員完全相同。

​ 有三個訪問限定符:public、protected和private。只要有類的物件,其成員函式就能夠訪問該類的所有成員資料和成員函式。成員函式可訪問基類的所有私有資料和函式。

16.2 私有和保護

程式清單16.2 Mammal2.cpp

#include <iostream>

enum BREED
{
    YORKIE,
    CAIRN,
    DANDIE,
    SHETLAND,
    DOBERMAN,
    LAB
};

class Mammal
{
protected:
    int age;
    int weight;

public:
    Mammal() : age(2), weight(5) {}
    ~Mammal() {}
    int getAge() const { return age; }
    void setAge(int newAge) { age = newAge; }
    int getWeight() const { return weight; }
    void setWeight(int newWeight) { weight = newWeight; }
    void speak() const { std::cout << "Mammal sound!\n"; }
    void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};

class Dog : public Mammal
{
private:
    BREED breed;

public:
    Dog() : breed(YORKIE) {}
    ~Dog() {}

    BREED getBreed() const { return breed; }
    void setBreed(BREED newBreed) { breed = newBreed; }
    void wagTail() { std::cout << "Tail wagging ...\n"; }
    void begForBreed() { std::cout << "Begging for food ...\n"; }
};

int main()
{
    Dog fido;
    fido.speak();
    fido.wagTail();
    std::cout << "Fido is " << fido.getAge() << " years oid\n";
    return 0;
}

16.3建構函式和解構函式

建立派生類物件時,將呼叫多個建構函式。

所以建立一個子類物件時,將先呼叫基類的建構函式,再呼叫子類的建構函式;銷燬物件時,將先呼叫子類的解構函式,再呼叫基類的解構函式。

程式清單16.3 Mammal3.cpp

#include <iostream>

enum BREED
{
    YORKIE,
    CAIRN,
    DANDIE,
    SHETLAND,
    DOBERMAN,
    LAB
};

class Mammal
{
protected:
    int age;
    int weight;

public:
    Mammal() : age(2), weight(5) { std::cout << "Mammal constructor ..."; }
    ~Mammal() { std::cout << "Mammal destructor ..."; }
    int getAge() const { return age; }
    void setAge(int newAge) { age = newAge; }
    int getWeight() const { return weight; }
    void setWeight(int newWeight) { weight = newWeight; }
    void speak() const { std::cout << "Mammal sound!\n"; }
    void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};

class Dog : public Mammal
{
private:
    BREED breed;

public:
    Dog() : breed(YORKIE) { std::cout << "Dog constructor ..."; }
    ~Dog() { std::cout << "Dog destructor ..."; }

    BREED getBreed() const { return breed; }
    void setBreed(BREED newBreed) { breed = newBreed; }
    void wagTail() { std::cout << "Tail wagging ...\n"; }
    void begForBreed() { std::cout << "Begging for food ...\n"; }
};

int main()
{
    Dog fido;
    fido.speak();
    fido.wagTail();
    std::cout << "Fido is " << fido.getAge() << " years oid\n";
    return 0;
}

16.4將引數傳遞給基類建構函式

要在派生類的初始化階段進行基類初始化,可指定基類名稱,並在後面跟基類建構函式需要的引數。

程式清單16.4 Mammal4.cpp

#include <iostream>

enum BREED
{
    YORKIE,
    CAIRN,
    DANDIE,
    SHETLAND,
    DOBERMAN,
    LAB
};

class Mammal
{
protected:
    int age;
    int weight;

public:
    Mammal() : age(1), weight(5) { std::cout << "Mammal constructor ...\n"; }
    Mammal(int age) : age(age), weight(5) { std::cout << "Mammal(int) constructor ...\n"; }
    ~Mammal() { std::cout << "Mammal destructor ...\n"; }
    int getAge() const { return age; }
    void setAge(int newAge) { age = newAge; }
    int getWeight() const { return weight; }
    void setWeight(int newWeight) { weight = newWeight; }
    void speak() const { std::cout << "Mammal sound!\n"; }
    void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};

class Dog : public Mammal
{
private:
    BREED breed;

public:
    Dog() : Mammal(), breed(YORKIE) { std::cout << "Dog constructor ...\n"; }
    Dog(int age) : Mammal(age), breed(YORKIE) { std::cout << "Dog(int) constructor ...\n"; }
    Dog(int age, int newWeight) : Mammal(age), breed(YORKIE)
    {
        weight = newWeight;
        std::cout << "Dog(int,int) constructor ...\n";
    }
    Dog(int age, int newWeight, BREED breed) : Mammal(age), breed(breed)
    {
        weight = newWeight;
        std::cout << "Dog(int,int,BREED) constructor ...\n";
    }
    Dog(int age, BREED newBreed) : Mammal(age), breed(newBreed) { std::cout << "Dog(int,BREED) constructor ...\n"; }
    ~Dog() { std::cout << "Dog destructor ...\n"; }

    BREED getBreed() const { return breed; }
    void setBreed(BREED newBreed) { breed = newBreed; }
    void wagTail() { std::cout << "Tail wagging ...\n"; }
    void begForBreed() { std::cout << "Begging for food ...\n"; }
};

int main()
{
    Dog fido;
    Dog rover(5);
    Dog buster(6, 8);
    Dog yorkie(3, YORKIE);
    Dog dobbie(4, 20, DOBERMAN);
    fido.speak();
    rover.wagTail();
    std::cout << "Yorkie is " << yorkie.getAge() << " years old\n";
    std::cout << "Dobbie weights: " << dobbie.getWeight() << " pounds\n";
    return 0;
}

16.5重寫函式

如果派生類建立了一個返回型別和簽名都與基類成員函式相同的函式,但是提供了新的實現,就稱之為重寫(覆蓋)該函式。

重寫函式時,返回型別和簽名必須與基類函式相同。簽名指的是除返回型別外的函式原型,這包括函式名、引數列表及關鍵字const(如果被重寫的函式使用了const)

函式的簽名由其名稱以及引數的數量和型別組成,但不包括返回型別。

程式清單16.5 Mammal5.cpp

#include <iostream>

enum BREED
{
    YORKIE,
    CAIRN,
    DANDIE,
    SHETLAND,
    DOBERMAN,
    LAB
};

class Mammal
{
protected:
    int age;
    int weight;

public:
    Mammal() : age(2), weight(5) { std::cout << "Mammal constructor ...\n"; }
    ~Mammal() { std::cout << "Mammal destructor ...\n"; }

    void speak() const { std::cout << "Mammal sound!\n"; }
    void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};

class Dog : public Mammal
{
private:
    BREED breed;

public:
    Dog() : breed(YORKIE) { std::cout << "Dog constructor ...\n"; }
    ~Dog() { std::cout << "Dog destructor ...\n"; }

    void wagTail() { std::cout << "Tail wagging ...\n"; }
    void begForBreed() { std::cout << "Begging for food ...\n"; }
    void speak() const { std::cout << "Woof!\n"; }
};

int main()
{
    Mammal mammal;
    Dog dog;
    mammal.speak();
    dog.speak();
    return 0;
}

重寫與過載不同,過載成員函式實際上是建立了多個名稱相同但簽名不同的函式。但重寫成員函式時,在派生類中建立的是一個名稱與簽名都與基類函式相同的函式。

如果Mammal有三個move()的過載版本,一個不接受任何引數,一個接受一個int型引數,一個接受int型和其他引數,而Dog只重寫了無參版本的函式,那麼使用Dog物件將難以訪問其他兩個版本。

程式清單16.6 Mammal6.cpp

#include <iostream>

class Mammal
{
protected:
    int age;
    int weight;

public:
    void move() const { std::cout << "Mammal moves one step\n"; }
    void move(int distance) const { std::cout << "Mammal moves " << distance << " steps\n"; }
};

class Dog : public Mammal
{
public:
    void move() const { std::cout << "Dog moves 5 steps\n"; }
};

int main()
{
    Mammal mammal;
    Dog dog;
    mammal.move();
    mammal.move(2);
    dog.move();
    //dog.move(10);//報錯
    return 0;
}

即便重寫了基類的成員函式,仍可使用全限定名來呼叫它。為此,可指定基類名、冒號和函式名

程式清單16.7 Mammal7.cpp

#include <iostream>

class Mammal
{
protected:
    int age;
    int weight;

public:
    void move() const { std::cout << "Mammal moves one step\n"; }
    void move(int distance) const { std::cout << "Mammal moves " << distance << " steps\n"; }
};

class Dog : public Mammal
{
public:
    void move() const
    {
        std::cout << "Dog moves ...\n";
        Mammal::move(3);
    }
};

int main()
{
    Mammal mammal;
    Dog dog;
    mammal.move(2);
    dog.move();
    dog.Mammal::move(6); //顯式呼叫
    return 0;
}