c++:-4

PamShao發表於2022-05-05

上一節學習了C++的陣列,指標和字串,c++:-3。本節學習C++的繼承與派生:

繼承

繼承和派生的關係

  • 繼承與派生是同一過程從不同的角度看
    保持已有類的特性而構造新類的過程稱為繼承
    在已有類的基礎上新增自己的特性而產生新類的過程稱為派生
  • 被繼承的已有類稱為基類(或父類)
  • 派生出的新類稱為派生類(或子類)
  • 直接參與派生出某類的基類稱為直接基類
  • 基類的基類甚至更高層的基類稱為間接基類

繼承與派生的目的

繼承的目的:實現設計與程式碼的重用。
派生的目的:當新的問題出現,原有程式無法解決(或不能完全解決)時,需要對原有程式進行改造。

繼承定義

單繼承

(1)語法:

class 派生類名:繼承方式 基類名
{
    成員宣告;
}

(2)例:

class Derived: public Base
{
    public:
    Derived ();
    ~Derived ();
};

多繼承

(1)語法

class 派生類名:繼承方式1 基類名1,繼承方式2 基類名2,...
{
    成員宣告;
}

注意:每一個“繼承方式”,只用於限制對緊隨其後之基類的繼承。
(2)例:

class Derived: public Base1, private Base2
{
    public:
        Derived ();
        ~Derived ();
};

繼承方式

(1)不同繼承方式的影響主要體現在:

  • 派生類成員對基類成員的訪問許可權
  • 通過派生類物件對基類成員的訪問許可權

(2)有三種:公有繼承、私有繼承、保護繼承

公有繼承(public)

(1)繼承的訪問控制

  • 基類的public和protected成員:訪問屬性在派生類中保持不變;
  • 基類的private成員:不可直接訪問。

(2)訪問許可權

  • 派生類中的成員函式:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
  • 通過派生類的物件:只能訪問public成員。

(3)舉例

//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point {
    //基類Point類的定義
public:
    //公有函式成員
    void initPoint(float x = 0, float y = 0){
        this->x = x;
        this->y = y;
    }
    void move(float offX, float offY){
        x += offX;
        y += offY;
    }
    float getX() const { return x; }
    float getY() const { return y; }
private:
    //私有資料成員
    float x, y;
};
#endif //_POINT_H

//Rectangle.h
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle: public Point {
//派生類定義部分
public:
//新增公有函式成員
    void initRectangle(float x, float y, float w, float h) {
        initPoint(x, y);              //呼叫基類公有成員函式
        this->w = w;
        this->h = h;
    }
    float getH() const { return h; }
    float getW() const { return w; }
private:
//新增私有資料成員
    float w, h;
};
#endif //_RECTANGLE_H

//main.cpp
#include <iostream>
#include "Rectangle.h"
using namespace std;

int main() {
    Rectangle rect; //定義Rectangle類的物件
    //設定矩形的資料
    rect.initRectangle(2, 3, 20, 10);
    rect.move(3,2); //移動矩形位置
    cout << "The data of rect(x,y,w,h): " << endl;
    //輸出矩形的特徵引數
    cout << rect.getX() <<", "
         << rect.getY() << ", "
         << rect.getW() << ", "
         << rect.getH() << endl;
    return 0;
}
輸出:
The data of rect(x,y,w,h):
5, 5, 20, 10

私有繼承(private)

(1)繼承的訪問控制

  • 基類的public和protected成員:都以private身份出現在派生類中;
  • 基類的private成員:不可直接訪問。

(2)訪問許可權

  • 派生類中的成員函式:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
  • 通過派生類的物件:不能直接訪問從基類繼承的任何成員。

(3)舉例:

//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point {   //基類Point類的定義
public: //公有函式成員
    void initPoint(float x = 0, float y = 0)
    { this->x = x; this->y = y;}
    void move(float offX, float offY)
    { x += offX; y += offY; }
    float getX() const { return x; }
    float getY() const { return y; }
private:    //私有資料成員
    float x, y;
};
#endif //_POINT_H

//Rectangle.h
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle: private Point {    //派生類定義部分
public: //新增公有函式成員
    void initRectangle(float x, float y, float w, float h) {
        initPoint(x, y); //呼叫基類公有成員函式
        this->w = w;
        this->h = h;
    }
    void move(float offX, float offY) {   Point::move(offX, offY);  }
    float getX() const { return Point::getX(); }
    float getY() const { return Point::getY(); }
    float getH() const { return h; }
    float getW() const { return w; }
private:    //新增私有資料成員
    float w, h;
};
#endif //_RECTANGLE_H

//main.cpp
#include <iostream>
#include <cmath>
#include "Rectangle.h"
using namespace std;

int main() {
    Rectangle rect; //定義Rectangle類的物件
    rect.initRectangle(2, 3, 20, 10);   //設定矩形的資料
    rect.move(3,2); //移動矩形位置
    cout << "The data of rect(x,y,w,h): " << endl;
    cout << rect.getX() <<", "  //輸出矩形的特徵引數
         << rect.getY() << ", "
         << rect.getW() << ", "
         << rect.getH() << endl;
    return 0;
}

保護繼承(protected)

(1)繼承的訪問控制

  • 基類的public和protected成員:都以protected身份出現在派生類中;
  • 基類的private成員:不可直接訪問。

(2)訪問許可權

  • 派生類中的成員函式:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private員;
  • 通過派生類的物件:不能直接訪問從基類繼承的任何成員。

(3)protected 成員的特點與作用

  • 對建立其所在類物件的模組來說,它與 private 成員的性質相同。
  • 對於其派生類來說,它與 public 成員的性質相同。
  • 既實現了資料隱藏,又方便繼承,實現程式碼重用。
  • 如果派生類有多個基類,也就是多繼承時,可以用不同的方式繼承每個基類。

(4)舉例:單繼承

class A {
protected:
    int x;
};
class B: public A{
public:
    void function();
};
void B::function() {
    x = 5;   //正確,派生類成員函式可以訪問
}

int main() {
    A a;
    a.x = 5;//錯誤,派生類物件可以訪問
}

(5)舉例:多繼承

class A {
public:
    void setA(int);
    void showA() const;
private:
    int a;
};
class B {
public:
    void setB(int);
    void showB() const;
private:
    int b;
};
class C : public A, private B {
public:
    void setC(int, int, int);
    void showC() const;
private:
    int c;
};

void A::setA(int x) {
    a=x;
}
void B::setB(int x) {
    b=x;
}
void C::setC(int x, int y, int z) {
    //派生類成員直接訪問基類的
    //公有成員
    setA(x);
    setB(y);
    c = z;
}

//其他函式實現略
int main() {
    C obj;
    obj.setA(5);
    obj.showA();
    obj.setC(6,7,9);
    obj.showC();
// obj.setB(6);  錯誤,private繼承的派生類物件類外不能訪問
// obj.showB(); 錯誤
    return 0;
}

基類與派生類轉換

(1)公有派生類物件可以被當作基類的物件使用,反之則不可。

  • 派生類的物件可以隱含轉換為基類物件;
  • 派生類的物件可以初始化基類的引用;
  • 派生類的指標可以隱含轉換為基類的指標。

(2)通過基類物件名、指標只能使用從基類繼承的成員。

(3)舉例

#include <iostream>
using namespace std;
class Base1 { //基類Base1定義
public:
    void display() const {
        cout << "Base1::display()" << endl;
    }
};
class Base2: public Base1 { //公有派生類Base2定義
public:
    void display() const {
        cout << "Base2::display()" << endl;
    }
};
class Derived: public Base2 { //公有派生類Derived定義
public:
    void display() const {
        cout << "Derived::display()" << endl;
    }
};

void fun(Base1 *ptr) {  //引數為指向基類物件的指標
    ptr->display();     //"物件指標->成員名"
}
int main() {    //主函式
    Base1 base1;    //宣告Base1類物件
    Base2 base2;    //宣告Base2類物件
    Derived derived;    //宣告Derived類物件

    fun(&base1);    //用Base1物件的指標呼叫fun函式
    fun(&base2);    //用Base2物件的指標呼叫fun函式
    fun(&derived); //用Derived物件的指標呼叫fun函式
    return 0;
}
輸出:
Base1::display()
Base1::display()
Base1::display()

派生類

建構函式

(1)預設情況:

  • 基類的建構函式不被繼承;
  • 派生類需要定義自己的建構函式。

(2)C++11規定:

using B::B;
  • 派生類新增成員可以通過類內初始值進行初始化。

  • 可用using語句繼承基類建構函式。

  • 但是隻能初始化從基類繼承的成員。

    (3) 建議:
    如果派生類有自己新增的成員,且需要通過建構函式初始化,則派生類要自定義建構函式。

(4)若不繼承基類的建構函式
派生類新增成員:派生類定義建構函式初始化;
繼承來的成員:自動呼叫基類建構函式進行初始化;
派生類的建構函式需要給基類的建構函式傳遞引數。

單繼承

派生類只有一個直接基類的情況,是單繼承。單繼承時,派生類的建構函式只需要給一個直接基類建構函式傳遞引數。
(1)語法

派生類名::派生類名(基類所需的形參,本類成員所需的形參):基類名(參數列), 本類成員初始化列表
{
	//其他初始化;
};

(2)舉例

#include<iostream>
using namespace std;
class B {
public:
    B();
    B(int i);
    ~B();
    void print() const;
private:
    int b;
};
B::B() {
    b=0;
    cout << "B's default constructor called." << endl;
}
B::B(int i) {
    b=i;
    cout << "B's constructor called." << endl;
}
B::~B() {
    cout << "B's destructor called." << endl;
}
void B::print() const {
    cout << b << endl;
}

class C: public B {
public:
    C();
    C(int i, int j);
    ~C();
    void print() const;
private:
    int c;
};
C::C() {
    c = 0;
    cout << "C's default constructor called." << endl;
}
C::C(int i,int j): B(i), c(j){
    cout << "C's constructor called." << endl;
}
C::~C() {
    cout << "C's destructor called." << endl;
}
void C::print() const {
    B::print();
    cout << c << endl;
}

int main() {
    C obj(5, 6);
    obj.print();
    return 0;
}
輸出:
B's constructor called.
C's constructor called.
5
6
C's destructor called.
B's destructor called.

多繼承

多繼承時,有多個直接基類,如果不繼承基類的建構函式,派生類建構函式需要給所有基類建構函式傳遞引數。
(1)語法

派生類名::派生類名(參數列) : 基類名1(基類1初始化參數列), 基類名2(基類2初始化參數列), ...基類名n(基類初始化參數列), 本類成員初始化列表
{
        //其他初始化;
};

(2)派生類與基類的建構函式

  • 當基類有預設建構函式時:
    派生類建構函式可以不向基類建構函式傳遞引數。
    構造派生類的物件時,基類的預設建構函式將被呼叫。

  • 如需執行基類中帶引數的建構函式
    派生類建構函式應為基類建構函式提供引數。
    (3)多繼承且有物件成員時派生的建構函式定義

派生類名::派生類名(形參表):基類名1(引數), 基類名2(引數), ..., 基類名n(引數), 本類成員(含物件成員)初始化列表
{
        //其他初始化
};

執行順序

(1)呼叫基類建構函式。

  • 順序按照它們被繼承時宣告的順序(從左向右)。

(2)對初始化列表中的成員進行初始化。

  • 順序按照它們在類中定義的順序
  • 物件成員初始化時自動呼叫其所屬類的建構函式。由初始化列表提供引數。

(3)執行派生類的建構函式體中的內容。

舉例

#include <iostream>
using namespace std;
class Base1 {//基類Base1,建構函式有引數
public:
    Base1(int i)
    { cout << "Constructing Base1 " << i << endl; }
};
class Base2 {//基類Base2,建構函式有引數
public:
    Base2(int j)
    { cout << "Constructing Base2 " << j << endl; }
};
class Base3 {//基類Base3,建構函式無引數
public:
    Base3()
    { cout << "Constructing Base3 *" << endl; }
};

class Derived: public Base2, public Base1, public Base3 {
public:
    Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b)
    //此處的次序與建構函式的執行次序無關
    { }
private:
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

int main() {
    Derived obj(1, 2, 3, 4);
    return 0;
}
輸出:
//先呼叫基類建構函式
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
//再對初始化列表中的成員進行初始化。
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *

複製建構函式

(1)派生類未定義複製建構函式的情況

  • 編譯器會在需要時生成一個隱含的複製建構函式;
  • 先呼叫基類的複製建構函式;
  • 再為派生類新增的成員執行復制。

(2)派生類定義了複製建構函式的情況

  • 一般都要為基類的複製建構函式傳遞引數。
  • 複製建構函式只能接受一個引數,既用來初始化派生類定義的成員,也將被傳遞給基類的複製建構函式。
  • 基類的複製建構函式形參型別是基類物件的引用,實參可以是派生類物件的引用
  • 例如: C::C(const C &c1): B(c1) {…}

解構函式

  • 解構函式不被繼承,派生類如果需要,要自行宣告解構函式。
  • 宣告方法與無繼承關係時類的解構函式相同。
  • 不需要顯式地呼叫基類的解構函式,系統會自動隱式呼叫。
  • 先執行派生類解構函式的函式體,再呼叫基類的解構函式。

呼叫順序

#include <iostream>
using namespace std;
class Base1 {
public:
    Base1(int i)
    { cout << "Constructing Base1 " << i << endl; }
    ~Base1() { cout << "Destructing Base1" << endl; }
};
class Base2 {
public:
    Base2(int j)
    { cout << "Constructing Base2 " << j << endl; }
    ~Base2() { cout << "Destructing Base2" << endl; }
};
class Base3 {
public:
    Base3() { cout << "Constructing Base3 *" << endl; }
    ~Base3() { cout << "Destructing Base3" << endl; }
};

class Derived: public Base2, public Base1, public Base3 {
public:
    Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b)
    { }
private:
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

int main() {
    Derived obj(1, 2, 3, 4);
    return 0;
}
輸出:
//建構函式
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *
//解構函式
Destructing Base3
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base1
Destructing Base2

虛基類

訪問從基類繼承的成員

(1)當派生類與基類中有相同成員時:

  • 若未特別限定,則通過派生類物件使用的是派生類中的同名成員。
  • 如要通過派生類物件訪問基類中被隱藏的同名成員,應使用基類名和作用域操作符(::)來限定。

(2)舉例

#include <iostream>
using namespace std;

class Base1 {
public:
    int var;
    void fun() { cout << "Member of Base1" << endl; }
};
class Base2 {
public:
    int var;
    void fun() { cout << "Member of Base2" << endl; }
};
class Derived: public Base1, public Base2 {
public:
    int var;
    void fun() { cout << "Member of Derived" << endl; }
};
int main() {
    Derived d;
    Derived *p = &d;
    //訪問Derived類成員
    d.var = 1;
    d.fun();
    //訪問Base1基類成員
    d.Base1::var = 2;
    d.Base1::fun();
    //訪問Base2基類成員
    p->Base2::var = 3;
    p->Base2::fun();
    return 0;
}
輸出:
Member of Derived
Member of Base1
Member of Base2

二義性問題

如果從不同基類繼承了同名成員,但是在派生類中沒有定義同名成員,“派生類物件名或引用名.成員名”、“派生類指標->成員名”訪問成員存在二義性問題

  • 解決方式:用類名限定

舉例

//7_7.cpp
#include <iostream>
using namespace std;

class Base0 {   //定義基類Base0
public:
    int var0;
    void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: public Base0 { //定義派生類Base1 
public: //新增外部介面
    int var1;
};
class Base2: public Base0 { //定義派生類Base2 
public: //新增外部介面
    int var2;
};

class Derived: public Base1, public Base2 {
public:
    int var;
    void fun()
    { cout << "Member of Derived" << endl; }
};

int main() {    //程式主函式
    Derived d;
    d.Base1::var0 = 2;
    d.Base1::fun0();
    d.Base2::var0 = 3;
    d.Base2::fun0();
    return 0;
}

虛基類

需要解決的問題

當派生類從多個基類派生,而這些基類又共同基類,則在訪問此共同基類中的成員時,將產生冗餘,並有可能因冗餘帶來不一致性

宣告

以virtual說明基類繼承方式
例:class B1:virtual public B

作用

  • 主要用來解決多繼承時可能發生的對同一基類繼承多次而產生的二義性問題
  • 為最遠的派生類提供唯一的基類成員,而不重複產生多次複製
    注意:
    在第一級繼承時就要將共同基類設計為虛基類。

舉例

#include <iostream>
using namespace std;
class Base0 {
public:
    int var0;
    void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
public:
    int var1;
};
class Base2: virtual public Base0 {
public:
    int var2;
};


class Derived: public Base1, public Base2 {
//定義派生類Derived
public:
    int var;
    void fun() {
        cout << "Member of Derived" << endl;
    }
};

int main() {
    Derived d;
    d.var0 = 2; //直接訪問虛基類的資料成員
    d.fun0();     //直接訪問虛基類的函式成員
    return 0;
}

建構函式

  • 建立物件時所指定的類稱為最遠派生類
  • 虛基類的成員是由最遠派生類的建構函式通過呼叫虛基類的建構函式進行初始化的。
  • 在整個繼承結構中,直接或間接繼承虛基類的所有派生類,都必須在建構函式的成員初始化表中為虛基類的建構函式列出引數。如果未列出,則表示呼叫該虛基類的預設建構函式。
  • 在建立物件時,只有最遠派生類的建構函式呼叫虛基類的建構函式,其他類對虛基類建構函式的呼叫被忽略。

舉例:

#include <iostream>
using namespace std;

class Base0 {
public:
    Base0(int var) : var0(var) { }
    int var0;
    void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
public:
    Base1(int var) : Base0(var) { }
    int var1;
};
class Base2: virtual public Base0 {
public:
    Base2(int var) : Base0(var) { }
    int var2;
};

class Derived: public Base1, public Base2 {
public:
    Derived(int var) : Base0(var), Base1(var), Base2(var)
    { }
    int var;
    void fun()
    { cout << "Member of Derived" << endl; }
};

int main() {    //程式主函式
    Derived d(1);
    d.var0 = 2; //直接訪問虛基類的資料成員
    d.fun0();   //直接訪問虛基類的函式成員
    return 0;
}
舉例:
Member of Base0

程式

(1)

#include <iostream>
using namespace std;

class Animal {
	//int age;
public:
	int age;
};

class Dog : Animal {
public:
	void SetAge(int n) {age = n;}
};

int main()
{
	Dog d;
	d.SetAge(2);
	return 0;
}

(2)

#include <iostream>
using namespace std;

class BaseClass {
public:
	int Number;
	int getNumber() {return Number;}
	BaseClass(){ cout<<"BaseClass construct"<<endl;}
	~BaseClass(){ cout<<"BaseClass destruct"<<endl;}
};

class DerivedClass : BaseClass {
public:
	DerivedClass() {
		Number = 0;
		Number ++;
		cout << "DerivedClass Construction. Number = " << getNumber() << endl;
	}
	~DerivedClass() {
		Number --;
		cout << "DerivedClass Destruction. Number = " << getNumber() << endl;
	}
};

int main()
{
	DerivedClass d;
	return 0;
}
說明:
BaseClass construct
DerivedClass Construction. Number = 1
DerivedClass Destruction. Number = 0
BaseClass destruct

(3)

#include <iostream>
using namespace std;

class Vehicle
{
public:
	int MaxSpeed;
	int Weight;
	void Run(){cout<<"Vehicle run"<<endl;};
	void Stop(){cout<<"Vehicle stop"<<endl;};

};

class Bicycle : virtual public Vehicle
{
public:
	int Height;
};

class Motorcar : virtual public Vehicle
{
public:
	int SeatNum;
};

class Motorcycle : public Bicycle, public Motorcar
{
};

int main()
{
	Motorcycle m;

	m.Height=1;
	m.SeatNum=2;
	m.Weight=100;
	m.MaxSpeed=60;
	
	m.Run();
	m.Stop();
	return 0;
}

相關文章