C++學習筆記:07 類的繼承與派生


課程《C++語言程序設計進階》清華大學 鄭莉老師)

基本概念

繼承派生的區別:

  • 繼承:保持已有類的特性而構造新類的過程稱為繼承。
  • 派生:在已有類的基礎上新增自己的特性(函數方法、數據成員)而產生新類的過程稱為派生

被繼承的已有類稱為基類派生出的新類稱為派生類,直接參與派生出某類的基類稱為直接基類,基類的基類甚至更高層的基類稱為間接基類

繼承與派生的目的

  • 繼承的目的:實現設計與代碼的重用。
  • 派生的目的:當新的問題出現,原有程序無法解決(或不能完全解決)時,需要對
    原有程序的基礎上增加新的特性。

繼承類的定義語法

單繼承

單繼承的直接基類只有一個,定義時需要指定基類的繼承方式,繼承方式包括:private、public、protected三種

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

多繼承

多繼承的直接基類有多個,定義時需要為每個直接基類指定繼承方式。

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

派生類的構成

派生類的類成員包括三個部分:

  1. 吸收基類成員

    默認情況下派生類包含了全部基類中除構造和析構函數之外的所有成員。

  2. 改造基類成員

    如果派生類聲明了一個和某基類成員同名的新成員,派生的新成員就隱藏或
    覆蓋了外層同名成員

  3. 添加新的成員

    派生類中可以增加新數據成員與函數方法。

不同繼承方式的區別

三種繼承方法(private,protected,public)不同繼承方式的影響主要體現在:

  • 派生類成員基類成員的訪問權限
  • 通過派生類對象基類成員的訪問權限

公有繼承(public)

  • 繼承的訪問控制

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

    • 派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
    • 通過派生類的對象:只能訪問public成員.

例如:

#include <iostream>
using namespace std;
class Point {
private:
	float x, y;
public:
	Point(float x = 0, float y = 0) :x(x), y(y) {}
	void move(float offX, float offY) { x += offX; y += offY; }
	float getX() const { return x; }
	float getY() const { return y; }
};

class Rectangle :public Point {
private:
	float w, h; //新增數據成員
public:
    //新增函數成員
	Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
	float getH() const { return h; }
	float getW() const { return w; }
};

int main() {
	Rectangle rect(2, 3, 20, 10);
	rect.move(-2, -3);
	cout << "The date of rect(x,y,w,h):" << endl;
	cout << rect.getX() << endl;
	cout << rect.getY() << endl;
	cout << rect.getW() << endl;
	cout << rect.getH() << endl;
	//cout << rect.x << endl;  /*不可訪問基類私有成員*/
}

私有繼承(private)與保護繼承(protected)

三類繼承方式中,public:派生類以及派生類對象都可以訪問public成員,private:只有基類自身函數可以訪問,派生類以及派生類對象都不可以訪問private成員,protected:派生類成員可以訪問,但派生類對象不可以訪問protected成員。

基類 派生類 派生類對象
public
protected ×
private × ×

私有繼承(private)

  • 繼承的訪問控制

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

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

例如:

#include <iostream>
using namespace std;
class Point {
private:
	float x, y;
public:
	Point(float x = 0, float y = 0) :x(x), y(y) {}
	void move(float offX, float offY) { x += offX; y += offY; }
	float getX() const { return x; }
	float getY() const { return y; }
};

class Rectangle :private Point {
private:
	float w, h;
public:
	Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
	float getH() const { return h; }
	float getW() const { return w; }
};

int main() {
	Rectangle rect(2, 3, 20, 10);
	//rect.move(-2, -3);   /*不可訪問基類私有成員*/
	cout << "The date of rect(x,y,w,h):" << endl;
	//cout << rect.getX() << endl; /*不可訪問基類私有成員*/
	//cout << rect.getY() << endl; /*不可訪問基類私有成員*/
	cout << rect.getW() << endl;
	cout << rect.getH() << endl;
	/*不可訪問基類私有成員*/
}

保護繼承(protected)

  • 繼承的訪問控制
    • 基類的public和protected成員:都以protected身份出現在派生類中;
    • 基類的private成員:不可直接訪問。
  • 訪問權限
    • 派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
    • 通過派生類的對象:不能直接訪問從基類繼承的任何成員
  • protected 成員的特點與作用
    • 對建立其所在類對象的模塊來說,它與private成員的性質相同**。
    • 對於其派生類來說,它與 public 成員的性質相同
    • 既實現了數據隱藏,又方便繼承,實現代碼重用。

基類與派生類之間的類型轉換

公有派生類對象可以被當作基類的對象使用。

  • 派生類的對象可以隱含轉換為基類對象;

  • 派生類的對象可以初始化基類的引用

  • 派生類的指針可以隱含轉換為基類的指針。

通過基類對象名、指針只能使用從基類繼承的成員。

#include <iostream>
using namespace std;
class Base1 {
public:
	void display() const { cout << "Base1::display()" << endl; }
};
class Base2 :public Base1 {
public:
	void display() const { cout << "Base2::display()" << endl; }
};
class Derived :public Base2 {
public:
	void display() const { cout << "Derived::display()" << endl; }
};
void fun(Base1* ptr) { ptr->display(); }
int main() {
	Base1 base1;
	Base2 base2;
	Derived derived;
	fun(&base1);
	fun(&base2);
	fun(&derived);
	return 0;
}
/*
Base1::display()
Base1::display()
Base1::display()
*/

void fun(Base1* ptr)函數中傳入參數為基類指針,派生類的指針可以隱含轉換為基類的指針。但是以上程序結果最終只調用基類display()函數。可以使用virtual定義虛函數,實現多態特性。

派生類的構造函數

繼承時,默認規則

  • 基類的構造函數不被繼承
  • 派生類需要定義自己的構造函數

如果派生類有自己新增的成員,且需要通過構造函數初始化,則派生類要自定義構造函數

派生類成員初始化:

派生類新增成員:派生類定義構造函數初始化

繼承來的成員:自動調用基類構造函數進行初始化

派生類的構造函數需要給基類的構造函數傳遞參數

例如:

先前的Point、Rectangle例子中,Rectangle類的構造函數,傳遞參數至Point類構造函數Point(x, y),對基類成員進行初始化

Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}

構造函數的執行順序

  1. 調用基類構造函數。

    順序按照它們被繼承時聲明的順序(從左向右)。

  2. 對初始化列表中的成員進行初始化。
    順序按照它們在類中定義的順序
    對象成員初始化時自動調用其所屬類的構造函數。由初始化列表提供參數。

  3. 執行派生類的構造函數體中的內容。

派生類復制構造函數

派生類未定義復制構造函數的情況

編譯器會在需要時生成一個隱含的復制構造函數先調用基類的復制構造函數

再為派生類新增的成員執行復制。

例如:

以下例子中的復制構造函數傳參

Rectangle(const Rectangle& rect) :Point(rect) { this->w = rect.w; this->h = rect.h; }
#include <iostream>
using namespace std;
class Point {
private:
	float x, y;
public:
	Point(float x = 0, float y = 0) :x(x), y(y) {}
	Point(const Point& p) { this->x = p.x; this->y = p.y; }//復制構造函數
	void move(float offX, float offY) { x += offX; y += offY; }
	float getX() const { return x; }
	float getY() const { return y; }
};

class Rectangle :public Point {
private:
	float w, h; 
public:
	Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
	Rectangle(const Rectangle& rect) :Point(rect) { this->w = rect.w; this->h = rect.h; }//復制構造函數
	float getH() const { return h; }
	float getW() const { return w; }
	friend ostream& operator << (ostream& os, const Rectangle& rect) {
		os << "Center:(" << rect.getX() << "," << rect.getY() << ")" << endl;
		os << "h=" << rect.getH() << endl
			<< "w=" << rect.getW() << endl;
		return os;
	}
};
int main() {
	Rectangle rect(2, 3, 20, 10);
	Rectangle rect1(rect);
	rect.move(-2, -3);
	cout << "rect" << endl << rect;
	cout << "rect1" << endl << rect1;
}
/*
rect
Center:(0,0)
h=10
w=20
rect1
Center:(2,3)
h=10
w=20
*/

派生類析構函數

析構函數不被繼承,派生類如果需要,要自行聲明析構函數。

聲明方法與無繼承關系時類的析構函數相同。

不需要顯式地調用基類的析構函數,系統會自動隱式調用。

先執行派生類析構函數的函數體,再調用基類的析構函數。

例如:

以下例子中,派生類Derived的對象obj,按照繼承順序,依次調用構造函數,最后是自己的構造函數,而銷毀對象按照相反的順序執行析構函數。

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

派生類成員二義性問題

二義性指的是在派生類中存在與基類擁有相同名字的成員。可以通過使用類名限定的方式進行特定訪問

例如:

#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
*/
image-20210913221501411

虛基類(個人一般不常用)

需要解決的問題

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

虛基類聲明

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

作用:主要用來解決多繼承時可能發生的對同一基類繼承多次而產生的二義性問題,為最遠的派生類提供唯一的基類成員,而不重復產生多次復制

注意:在第一級繼承時就要將共同基類設計為虛基類

類的存儲

參考B站的一個視頻:鏈接

image-20210913222000664
#include <iostream>
using namespace std;
class Base {
public:
    //int var; //4字節
    float x; //4字節
    double y;//8字節
    static int a; //靜態成員不占用類空間
    void fun() { cout << "Member of Base1" << endl; } //函數不占字節
    void virtual fun1() {};//虛函數,相當於一個指向虛表的指針 4字節
};
int main() {
    Base base;
    cout << sizeof(base);
    return 0;
}
/*
24
*/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM