《C++语言程序设计基础》学习之继承与派生

 继承与派生概述:继承与派生是同一过程从不同的角度看,保持已有类的特性而构造新类的过程称为继承,在已有类的基础上新增自己的特性而产生新类的过程称为派生
被继承的已有类称为基类(或父类)派生出的新类称为派生类(或子类)直接参与派生出某类的基类称为直接基类基类的基类甚至更高层的基类称为间接基类,先有继承才有派生。
继承与派生的目的:继承的目的:实现设计与代码的重用。派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。需要先继承才有派生,继承原先的代码和类,派生出新的类。
单继承时派生类的定义:
class 派生类名:继承方式 基类名
{
        成员声明;
}

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

多继承时派生类的定义:
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...{
          成员声明;
} 注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。

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

派生类:吸收基类成员,改造基类成员,添加新的成员。
吸收基类成员:
默认情况下派生类包含了全部基类中除构造和析构函数之外的所有成员,c++11规定可以用using语句继承基类构造函数
改造基类成员:如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或覆盖了外层同名成员
添加新的成员:增加新的功能 成员数据和成员函数

不同继承方式的影响主要体现在:派生类成员对基类成员的访问权限,通过派生类对象对基类成员的访问权限
三种继承方式:公有继承,私有继承,保护继承,用的多的是公有继承

 公有继承(public)
继承的访问控制:基类的public和protected成员:访问属性在派生类中保持不变;基类的private成员:不可直接访问。
访问权限:派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;通过派生类的对象:只能访问public成员。
Point.h

#pragma once
#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

#pragma once
#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 "pch.h"
#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;
}

私有继承(private):
继承的访问控制:基类的publicprotected成员:都以private身份出现在派生类中;基类的private成员:不可直接访问
访问权限:派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;通过派生类的对象:不能直接访问从基类继承的任何成员。
上述代码中将Rectangle.h修改为:

#pragma once
#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

 保护继承(protected):
继承的访问控制:基类的publicprotected成员:都以protected身份出现在派生类中;基类的private成员:不可直接访问
访问权限:派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;通过派生类的对象:不能直接访问从基类继承的任何成员。
protected 成员的特点与作用
对建立其所在类对象的模块来说,它与 private 成员的性质相同。
对于其派生类来说,它与 public 成员的性质相同。
既实现了数据隐藏,又方便继承,实现代码重用。
如果派生类有多个基类,也就是多继承时,可以用不同的方式继承每个基类。

class A {
protected:
    int x;
};

int main() {
    A a;
    a.x = 5;//错误
}
class A {
protected:
    int x;
};
class B: public A{
public:
    void function();
};
void B:function() {
    x = 5;   //正确
}

多继承举例:

class A {
public:
	void setA(int);
	void showA() const { cout << "A" << endl; };
private:
	int a;
};
class B {
public:
	void setB(int);
	void showB() const { cout << "B" << endl; };
private:
	int b;
};
class C : public A, private B {
public:
	void setC(int, int, int);
	void showC() const { cout << "C"<<endl; };
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;
}

 类型转换:如果参数类型是基类的指针,那么实参可以是派生类的地址,派生类的地址可以初始化公有基类的指针
公有派生类对象可以被当作基类的对象使用,反之则不可。
          派生类的对象可以隐含转换为基类对象;
          派生类的对象可以初始化基类的引用;
派生类的指针可以隐含转换为基类的指针。
通过基类对象名、指针只能使用从基类继承的成员。

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()

调用的都是基类的display函数,非虚函数不要使用相同名字的函数,不要重新定义继承而来的非虚函数!

派生类的构造函数:
默认情况:基类的构造函数不被继承;派生类需要定义自己的构造函数。
C++11规定:
可用using语句继承基类构造函数。
但是只能初始化从基类继承的成员。派生类新增成员可以通过类内初始值进行初始化。
语法形式:using B::B; 不常用的形式

建议:如果派生类有自己新增的成员,且需要通过构造函数初始化,则派生类要自定义构造函数。
若不继承基类的构造函数:
派生类新增成员:派生类定义构造函数初始化;
继承来的成员:自动调用基类构造函数进行初始化;
派生类的构造函数需要给基类的构造函数传递参数。
单继承:派生类只有一个直接基类的情况,是单继承。单继承时,派生类的构造函数只需要给一个直接基类构造函数传递参数。单继承时构造函数的定义语法

派生类名::派生类名(基类所需的形参,本类成员所需的形参):
基类名(参数表), 本类成员初始化列表
{
	//其他初始化;
};
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初始化参数表), 
基类名2(基类2初始化参数表), 
...
基类名n(基类n初始化参数表), 
本类成员初始化列表
{
        //其他初始化;
};

 派生类与基类的构造函数:
当基类有默认构造函数时:派生类构造函数可以不向基类构造函数传递参数;构造派生类的对象时,基类的默认构造函数将被调用。
如需执行基类中带参数的构造函数,派生类构造函数应为基类构造函数提供参数。
多继承且有对象成员时派生的构造函数定义语法:即有多继承也有组合类

派生类名::派生类名(形参表):
基类名1(参数), 基类名2(参数), ..., 基类名n(参数), 
本类成员(含对象成员)初始化列表
{
        //其他初始化
};

构造函数的执行顺序:
调用基类构造函数。顺序按照它们被继承时声明的顺序(从左向右)。
对初始化列表中的成员进行初始化。顺序按照它们在类中定义的顺序。对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。
执行派生类的构造函数体中的内容。

#include"pch.h"
#include <iostream>
using namespace std;
class Base1 {//基类Base1,构造函数有参数
public:
	Base1(int i) { cout << "Constructing Base1 " << i << endl; }
	~Base1() { cout << "Destructor Base1." << endl; }
};
class Base2 {//基类Base2,构造函数有参数
public:
	Base2(int j) { cout << "Constructing Base2 " << j << endl; }
	~Base2() { cout << "Destructor Base2." << endl; }
};
class Base3 {//基类Base3,构造函数无参数
public:
	Base3() { cout << "Constructing Base3 *" << endl; }
	~Base3() { cout << "Destructor 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)
		//此处的次序与构造函数的执行次序无关
	{ }
	~Derived() { cout << "Destructor Derived." << endl; }
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 *   //按照派生类私有数据成员声明的顺序调用构造函数
Destructor Derived.      //调用Derived的析构函数
Destructor Base3.       
Destructor Base2.
Destructor Base1.    //依次析构Derived类中的私有数据成员
Destructor Base3.
Destructor Base1.
Destructor Base2.    //依次析构继承时的基类

派生类的复制构造函数:
从基类继承过来的成员,它们的复制构造是由基类的复制构造函数完成,而派生类的新增成员,它们的复制构造由派生类的复制构造函数完成,但还是有传参数的问题。

派生类未定义复制构造函数的情况:
编译器会在需要时生成一个隐含的复制构造函数;
先调用基类的复制构造函数;
再为派生类新增的成员执行复制。
派生类定义了复制构造函数的情况:派生类的对象可以充当基类的对象
一般都要为基类的复制构造函数传递参数。
复制构造函数只能接受一个参数(类的引用),既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。
基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用
例如: C::C(const C &c1): B(c1) {…}

派生类的析构函数:可以查看上面的运行结果
析构函数不被继承,派生类如果需要,要自行声明析构函数。
声明方法与无继承关系时类的析构函数相同。
不需要显式地调用基类的析构函数,系统会自动隐式调用。
先执行派生类析构函数的函数体,再调用基类的析构函数。

访问从基类继承的成员:
作用域限定

当派生类与基类中有相同成员时:若未特别限定,则通过派生类对象使用的是派生类中的同名成员。如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符(::)来限定。

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

 二义性问题:
如果从不同基类继承了同名成员,但是在派生类中没有定义同名成员,“派生类对象名或引用名.成员名”、“派生类指针->成员名”访问成员存在二义性问题。解决方式:用类名限定

class A {
public:
    void  f();
};
class B {
public:
    void f();
    void g()
};
class C: public A, piblic B {
public:
    void g();
    void h();
};
如果定义:C  c1;
则 c1.f() 具有二义性
而 c1.g() 无二义性(同名隐藏)
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
作用:主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题。为最远的派生类提供唯一的基类成员,而不重复产生多次复制
注意:在第一级继承时就要将共同基类设计为虚基类。

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;
}

虚基类及其派生类构造函数:虚基类用的少,不适用于公共类库、软件包。
建立对象时所指定的类称为最远派生类
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数。
在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。

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 {
	//定义派生类Derived
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;
}

Derived如果不是虚继承,只需要给自己的直接基类Base1、Base2构造函数传递参数,由于使用虚基类,Derived类还需要负责给最远基类即虚基类的构造函数传递参数。Base0的构造函数只调用一次,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。

猜你喜欢

转载自blog.csdn.net/shiheyingzhe/article/details/82943743