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
*/
posted @ 2021-09-13 22:41  陈橙橙  阅读(544)  评论(0编辑  收藏  举报