c++:-4

上一節學習了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;
}
Tags: