c++笔记 | 类的继承与派生

本文详细介绍了面向对象编程中的继承概念,包括基类和派生类的关系、继承类型以及组合关系。同时,讲解了派生类的构造函数和析构函数的使用规则,包括单继承和多继承的情况。此外,还探讨了子类型的概念以及如何满足赋值兼容规则。最后,提到了虚基类的作用,即解决多继承时的二义性问题。
摘要由CSDN通过智能技术生成

一、基类和派生类

  • 保持已有类的特征而构造新类的过程称为继承;
  • 在已有类的基础上增加自己的特性而产生新类称为派生;
  • 被继承的已有的类称为基类(父类);
  • 派生的新类称为派生类

继承关系:描述派生类和基类的关系;

组合关系:类和类之间的包含关系;

继承的类型有单继承和多继承两种

  • 单继承: 生成的派生类只有一个基类
  • 多继承:生成的派生类有多个基类

三种继承方式:

public (公有);  private (私有);  protected(保护)

  • 基类中的私有成员在通过上述三种继承方式得到的派生类中都不能直接访问;
  • 在public 下,基类中的公有成员和保护成员任然是派生类中的公有成员和保护成员;
  • 在private 下,基类中公有成员和保护成员在派生类中都变成私有成员;
  • 在protected下,基类中公有成员和保护成员在派生类中都会变成保护成员。

派生类的定义格式:

class 派生类名 :继承方式 基类名{

        派生类新增成员说明;

};

eg: class Rectangle: pubilc Point{

                .......

};

#include<iostream>
#include<math.h>
using namespace std;
//基类
class point {
public:
	void InitP(float xx = 0, float yy = 0) {
		X = xx; Y = yy;
	}
	void Move(float xoff, float yoff) {
		X += xoff; Y += yoff;
	}
	float getX() {
		return X;
	}
	float getY() {
		return Y;
	}
private:
	float X, Y;
};
//派生类声明
class Rect :public point {
public:
	//新增公有成员
	void IntR(float x, float y, float z, float h) {
		InitP(x, y);//调用基类中的成员函数
		Z = z;
		H = h;
	}
	float getZ() {
		return Z;
	}
	float getH() {
		return H;
	}
private:
	float Z, H;
};
int main() {
	Rect rect;//派生类对象
	rect.IntR(2, 3, 20, 10);
	rect.Move(3, 2);//通过派生类访问基类中的公有成员
	cout << rect.getX() << "," << rect.getY() << "," << rect.getZ() << "," << rect.getH()<<endl;
	return 0;
}

运行结果:

二、派生类的构造函数和析构函数

构造函数及析构函数不能继承

派生类的构造函数应包含对基类成员的初始化及自身成员的初始化;派生类的析构函数也应包含基类的析构函数

2.1 单继承派生类的构造函数和析构函数

派生类构造函数名 (总参数表) :基类构造函数名 (参数表), 其他初始化项 {

                派生类自身数据初始化

}

派生类构造函数的执行顺序:

  1. 先执行基类构造函数;
  2. 再执行子对象的构造函数
  3. 最后执行派生类的构造函数

析构函数执行顺序与之相反;

#include<iostream>
using namespace std;
class A {
public:
	//基类的构造函数
	A() {//无参数构造函数
		a = 0;
		cout << "ceative 1  " << a << endl;
	}
	A(int i) {
		//有参数构造函数
		a = i;
		cout << "ceative 2  " << a << endl;
	}
	~A() {//析构函数
		cout << "ceative 3   " << a << endl;
	}
	void Print() {
		cout << a << ",";
	}
	int Geta() {
		return a;
	}
private:
	int a;
};
//A的派生类
class B :public A {
public:
	B() {
		//无参数的构造函数
		b = 0;
		cout << "ceative 4  " << b << endl;
	}
	B(int i, int j, int k):A(i),aa(j) {
		//触发基类的构造函数,aa的构造函数
		//有参数的构造函数
		b = k;
		cout << "ceative 5  " << b << endl;
	}
	~B() {
		//析构函数
		cout << "ceative 6  " << b << endl;
	}
	void Print() {
		A::Print();//调用A中成员函数
		cout << b << "," << aa.Geta() << endl;
	}
private:
	int b;
	A aa;
};
int main() {
	B bb[2];
	bb[0] = B(7, 8, 9);//根据有参数的构造函数构造b[0];
	//先触发基类的构造函数
	//在触发aa的构造函数
	//最后触发B的构造函数
	bb[1] = B(12, 13, 14);
	for (int i = 0; i < 2; i++)
		bb[i].Print();
}

运行结果:

 2.2 多继承的构造函数和析构函数

派生类构造函数名 (总参数表):基类名1(参数表),基类名2(参数表),......

{

        派生类构造函数体

}

  • 在多重继承派生类构造函数中,先执行基类的构造函数。多个基类构造函数的执行循序取决于定义派生类时规定的先后顺序。
  • 析构函数的执行顺序与构造函数相反
#include<iostream>
using namespace std;
class A {
public:
	A(int i) {
		a = i;
		cout << "Constructor called.A\n";
	}
	~A() {
		//析构函数
		cout << "Destructor called.A\n";
	}
	void Print() {
		cout << a << endl;
	}
private:
	int a;
};
class B {
public:
	B(int i) {
		b = i;
		cout << "Constructor called.B\n";
	}
	~B() {
		cout << "Destructor called.B\n";
	}
	void Print() {
		cout << b << endl;
	}
private:
	int b;
};
class C {
public:
	C(int i) {
		c = i;
		cout << "Constructor called.C\n";
	}
	~C() {
		cout << "Destructor called.C\n";
	}
	int Getc() {
		return c;
	}
private:
	int c;
};
class D :public A, public B {
	//A类和B类的公有继承
public:
	D(int i, int j, int k, int l) :B(i), A(j), c(l) {
		//先调用A的构造函数,再调用B的构造函数,再调用c的构造函数
		cout << "Constructor called.D\n";
		d = k;
	}
	~D() {
		cout << "Destructor called.D\n";
	}
	void Print() {
		A::Print();//调用A中Print函数
		B::Print();
		cout << d << "," << c.Getc() << endl;
	}
private:
	int d;
	C c;//将C的对象作为D的私有成员
};
int main() {
	D d(5, 6, 7, 8);
	d.Print();//先调用A的Print,输出由D的构造函数中参数j构造的A,再调用B中的Print...
	B b(2);
	b = d;//d赋值给b
	b.Print();
	return 0;
}

运行结果:

 

三、子类型

  • 当一个类型至少包含了另一个类型的所有行为,则称该类型是另一个类型的子类型,在公有继承下,派生类是基类的子类型;
  • 类型适应:指两种类型之间的关系,B类型适应与A类型是指B类型的读心能够用于A类型的队形所能运用的场合 
  • 若类型B是类型A的子类型则称B适应于类型A
  • 子类型关系不可逆

当B类是A类的子类型时,满足下列赋值兼容规则:

  • B类的对象可以赋值给A类的对象
  • B类的对象可以给A类的对象引用赋值
  • B类 的对象地址可以给A类的对象指针赋值
  • #include<iostream>
    using namespace std;
    class A {
    public:
    	A() {
    		//无参数的构造函数
    		a = 0;
    	}
    	A(int i) {
    		//有参数的构造函数
    		a = i;
    	}
    	void Print() {
    		cout << a << endl;
    	}
    	int Geta() {
    		return a;
    	}
    private:
    	int a;
    };
    class B :public A {
    	//B是A的公有继承类
    public:
    	B() {
    		//无参数的构造函数
    		b = 0;
    	}
    	B(int i, int j) :A(i), b(j) {};//将i作为参数传入带参数的A类构造函数中,j作为参数传给b
    	void Print() {
    		cout << b << ",";
    		A::Print();//调用基类中的Print函数
    	}
    private:
    	int b;
    };
    void fun(A& a) {//A 类型对象引用
    	//以A类为参数的函数
    	cout << a.Geta() + 2 << ",";
    	a.Print();
    }
    int main() {
    	A a1(10), a2;//构造有参数的a1,和无参数的a2;
    	B b(10, 20);
    	b.Print();
    	a2 = b;//将b的值赋给a2;
    	a2.Print();
    	A* pa = new A(15);//创建一个A类指针指向一个新的A类空间,参数为15
    	B* pb = new B(15, 25);
    	pa = pb;//pa指针不再指向A(15)
    	pa->Print();//输出为B中的15传到A中的15
    	fun(*pb);//pb是B类的指针,fun()的参数是A类,B是A的继承类,对A对象适用的函数对B也适用
    	delete pa;//pa指向B类,但是是A类指针,触发A类的析构函数
    	return 0;
    }

    运行结果:

 

四、虚基类

虚基类的声明:以 virtual 修饰

eg:class B1:virtual public B

作用:解决多继承时可能发生的同一基类继承多重而产生的二义性

eg:class B { private:int b; }

class B1:virtual public B { private: int b1; };

class B2:virtual public B { private: int b2; };

class C: public B1,public B2 { private: float c; };

若不使用 virtual 声明,C类继承的B1和B2类属于同一种类,在构造时会产生二义性。

在第一继承时就要将共同基类设计为虚基类(只设计B2为虚基类是不对的)

基于上面的代码,下面的访问才是正确的:

        C cobj;

        cobj.b;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_61756086

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值