C++中类的继承

继承的定义:
一个类属于另外一个类的一部分
该类拥有另外一个类的所有的属性和方法
如果一个类继承另外一个类,那么该类拥有另外一个类的所有的属性和方法

在C++中,定义一个类可以继承其他的类
支持多继承,即一个类有多个父类
继承多个类时,每个类的继承方式都可以不一样

继承方式有:
public 公开继承
protected 保护继承
private 私有继承
C++中默认的(缺省的)继承方式为私有继承
在继承中,被继承的类称为父类或基类,继承父类的类称为子类或派生类

继承的意义:
子类拥有父类的属性和方法 子类可以定义自己的属性和方法
代码的复用、功能的扩展

继承的方式:
私有继承 从父类继承来的属性和方法都变成子类私有的

#include <iostream>
using namespace std;

class A{//父类
private://私有的: 只能在本类中访问
	int x;
protected://保护的: 本类和本类的子类中可以访问
	int y;
public://公开的: 任何地方都可以访问
	int z;
};
//私有继承
class B:private A{//从父类中继承来的所有属性和方法都变为子类私有的属性
	//x 继承下来  但在B类中,不能访问x
	//y 保护的  在B类中  y是private
	//z 公开的  在B类中  z是private
public:
	void func(){
		//x = 1;
		y = 1;
		z = 1;
	}
};

class C:private B{
public:
	void bar(){
		//x = 1;
		//y = 1;
		//z = 1;
	}
};

int main(){
	A a;
	//a.x = 1;
	//a.y = 1;
	a.z = 1;
	cout << sizeof(B) << endl;
	B b;
	//b.y = 1;
	//b.z = 1;
	return 0;	
}

保护继承 从父类继承来的属性和方法都变成子类保护的

#include <iostream>
using namespace std;

class A{
private://私有的: 只能在本类中访问
	int x;
protected://保护的: 本类和本类的子类中可以访问
	int y;
public://公开的: 任何地方都可以访问
	int z;
};

class B:protected A{//从父类中继承来的所有属性和方法都变为子类保护的属性
	//x 继承下来  但在B类中,不能访问x
	//y 保护的  在B类中  y是protected
	//z 公开的  在B类中  z是protected
public:
	void func(){
		//x = 1;
		y = 1;//proteced
		z = 1;
	}
};

class C:private B{
public:
	void bar(){
		//x = 1;
		y = 1;
		z = 1;
	}
};

int main(){
	A a;
	//a.x = 1;
	//a.y = 1;
	a.z = 1;
	cout << sizeof(B) << endl;
	B b;
	//b.y = 1;
	//b.z = 1;
	C c;
	//c.y = 1;
	//c.z = 1;
	return 0;	
}

公开继承 子类从父类中继承来的属性和方法类型不变

#include <iostream>
using namespace std;

class A{
private://私有的: 只能在本类中访问
	int x;
protected://保护的: 本类和本类的子类中可以访问
	int y;
public://公开的: 任何地方都可以访问
	int z;
};

class B:public A{//公开继承
	//x 继承下来  但在B类中,不能访问x
	//y 保护的  在B类中  y是protected
	//z 公开的  在B类中  z是public
public:
	void func(){
		//x = 1;
		y = 1;//proteced
		z = 1;
	}
};

class C:private B{
public:
	void bar(){
		//x = 1;
		y = 1;
		z = 1;
	}
};

int main(){
	A a;
	//a.x = 1;
	//a.y = 1;
	a.z = 1;
	cout << sizeof(B) << endl;
	B b;
	//b.y = 1;
	b.z = 1;
	C c;
	//c.y = 1;
	//c.z = 1;
	return 0;	
}

继承方式所表达的含义是:从父类中集成到子类中的属性将怎样的访问控制属性
1.访问控制属性
基类 子类 外部 友元
public OK OK OK OK
protected OK OK NO NO
private OK NO NO NO
2.继承方式对基类中继承下来的成员变量的访问控制属性的影响
基类中不同访问控制属性的变量,通过不同的继承方式到达子类后的访问控制属性
基类 公开继承 保护继承 私有继承
public public protected private
protected protected protected private
private private private private

基类中的私有属性不管通过什么继承方式,到达子类后都不能访问
基类中的保护成员和公开成员,在子类中是直接可以访问的

公开继承:
基类中保护的成员,通过公开继承的方式,在子类中依然是保护的成员
基类中私有的成员,通过公开继承的方式,在子类中依然是私有的成员
在使用时,基本使用公开继承
保护继承:
基类中保护的、公开的成员,通过保护继承到子类变成子类中保护的成员
私有继承:
基类中保护的、公开的成员,通过私有继承到子类变成子类中私有的成员

子类的构造、子类的构造函数、子类的构造顺序
任何一个子类对象中都包含着它的“基类子对象”
在子类对象中包含一个基类的对象
任何一个子类对象都可以被视为它的基类对象
一个子类对象的引用或者指针,都可以被隐式转换为它的基类类型的引用或者父指针
基类类型的引用,如果引用子类对象,它其实是引用的基类子对象
如果一个类有多个父类,通过不同的父类,引用同一个子类对象,引用的不是同一部分
通过不同父类的指针,指向同一个子类对象,指针的值可能不一样

class A{
	intx;
};
class B{
	int y;
};
class C:public A,public B{
	int z;
};
C c;
A& ra = c;
B& rb = c;//&ra &rb 引用对象的地址值不一样 它们是引用基类子对象
A* pa = &c;
B* pb = &c;//pa pb的值不一样	指向基类子对象
#include <iostream>
using namespace std;
class A{
public:
	int x;
	int y;
public:
	A(){}
	A(const A& a){
		cout << "A拷贝" << endl;	
	}
};
class B{
public:
	int w;
	int z;
	B(){}
	B(const B& b){
		cout << "B拷贝" << endl;	
	}
};
class C:public A,public B{
public:
	int h;

	void show(){
		x = 1;
		y = 1;
		w = 1;
		z = 1;
		cout << x << y << w << z << h << endl;
	}
};
int main(){
	C c;
	A& ra = c;//ra引用 引用A类子对象   子对象对象 可以隐式转换为父类类型的引用
	cout << &ra << endl;
	B& rb = c;//rb引用 引用B类子对象
	cout << &rb << endl;
	cout << "----------" << endl;
	A a1 = c;//c C类型对象  is a A类型的对象	可以调用拷贝构造
	cout << "----------" << endl;
	B b1 = c;//c C类型对象  is a B类型的对象
	//男人也是人类  男人类型的对象 也是 人类类型的对象
	A* pa = &c;
	cout << pa << endl;
	B* pb = &c;
	cout << pb << endl;
	A a;// x y 
	C& rc1 = static_cast<C&>(a);//语法上支持,但是很危险
	C& rc2 = static_cast<C&>(ra); //没问题
	/*
	rc1.h = 1;//内存越界访问  引发不可预测的问题
	rc1.show();
	*/

	C* pc = (C*)pa;
	cout << pc << endl;
	cout << "---------" << endl;
	pc = (C*)pb;//  pc == pb
	cout << pc << endl;
	pc = static_cast<C*>(pb);// pc == &c;  
	cout << pc << endl;
	cout << "---------" << endl;
	return 0;	
}

在这里插入图片描述
如果把一个父类类型对象通过 强制类型转换 或者 static_cast 或者reinterpret_cast转变了子类对象,如果父类类型不是子类对象,则能够转换成功,但是后续可能出来未知的错误

如果父类类型的引用 引用子类对象,或者父类类型的指针指向子类对象
通过强制类型转换和静态类型转换,成功地把父类的指针和引用 指向/引用正确的指针指向
通过reinterpret_cast把父类对象的指针强制转换为子类对象的指针,能够成功,但是可能引发问题

子类对象可以直接赋值给父类对象 调用父类的拷贝赋值函数
子类对象可以构造新的父类对象 调用父类的拷贝构造函数

#include <iostream>
using namespace std;
class A{
public:
	int x;
};
class B{
public:
	int y;
};
class C:public A,public B{
public:
	int z;
};
int main(){
	C c;
	cout << &c << endl;
	A* pa = &c;
	B* pb = &c;//会根据其类子对象在子类对象中的位置进行调整
	C* pc = &c;
	cout << "-------------" << endl;
	cout << pa << endl;
	cout << pb << endl;
	cout << pc << endl;
	cout << "-------------" << endl;
	pc = (C*)pb;
	cout << pc << endl;
	pc = static_cast<C*>(pb);//会根据基类子对象进行调整
	cout << pc << endl;
	pc = reinterpret_cast<C*>(pb);
	cout << pc << endl;
	return 0;	
}

在这里插入图片描述
子类中的构造函数,会默认调用父类的无参构造函数进行构造基类子对象
构造函数的执行顺序:
1.按照继承顺序依次调用基类的构造函数(如果一个基类还有基类,则会一直去调用其基类的基类构造函数)
2.按照成员属性的定义顺序,依次调用各个类类型属性的构造函数
3.执行本类的构造函数体
析构函数的执行顺序和构造函数正好相反
如果用一个父类类型指针指向一个new的子类对象,在delete这个父类类型指针时,只会调用父类的析构函数,会产生内存泄漏

#include <iostream>
using namespace std;
class A{
public:
	A(int z){//A的有参构造
		cout << "A()" << endl;	
	}
	A(const A& a){//A的拷贝构造
		cout << "A(const A& a)" << endl;	
	}
	A& operator=(const A& a){//A的拷贝赋值
		cout << "A& operator=(const A& a)" << endl;
		return *this;	
	}
	~A(){//A的析构
		cout << "~A()" << endl;	
	}
};
class B{
public:
	B(){//B的无参构造
		cout << "B()" << endl;	
	}
	B(const B& a){//B的拷贝构造
		cout << "B(const B& a)" << endl;	
	}
	B& operator=(const B& a){//B的拷贝赋值
		cout << "B& operator=(const B& a)" << endl;
		return *this;	
	}
	~B(){//B的析构
		cout << "~B()" << endl;	
	}
};
class C:public A,public B{
public:
	C():A(10){//初始化列表
		cout << "C()" << endl;	
	}
	C(const C& a):A(a),B(a){//C的拷贝构造
		cout << "C(const C& a)" << endl;	
	}
	C& operator=(const C& a){//C的拷贝赋值
		A::operator=(a);
		B::operator=(a);
		cout << "C& operator=(const C& a)" << endl;
		return *this;	
	}
	~C(){//C的析构
		cout << "~C()" << endl;	
	}
};
class D:public C{
};
int main(){
	C c;//调用有参/无参构造函数
	C c1(c);//调用拷贝构造函数
	c1 = c;//调用拷贝赋值函数
	return 0;	
}

在这里插入图片描述
在构造函数的初始化列表中,会依次调用父类的无参构造函数和成员类型的无参构造函数
如果父类中没有无参构造函数,子类将必须在初始化列表中显式调用父类的有参构造函数

classA:public B,public C{
private:
	D d;
	E e;
public:
	A(形参列表,...):B(实参列表),C(实参列表),e(实参列表){}
	//默认的拷贝构造 如果初始化列表不写,默认调用无参构造函数
	A(const A& a):B(a),C(a),d(a),e(a){}
	//拷贝赋值
	A& operator=(const A& a){
		B::operator(a);//调用B类中的拷贝赋值
		C::operator(a);//调用C类中的拷贝赋值
		d=a;//d.operator=(a);//调用D类的拷贝赋值
		e=a;//e.operator=(a);//调用E类的拷贝赋值
	}
	//析构函数
	~A(){
		//析构子类资源 自动调用父类的析构
	}
};

在初始化列表中,调用基类的构造函数:基类名(实参列表)
在初始化列表中,调用成员类型的构造函数:成员属性名(实参列表)
一般需要在初始化列表中调用基类和类类型成员的拷贝构造函数

什么时候必须使用初始化列表?
1.常属性和引用属性
2.显式调用父类或者成员属性的有参构造

子类中的成员变量和父类中的成员变量重名了,会隐藏父类中的成员变量

基类类型引用、基类类型指针
可以用基类类型引用引用子类对象,本质上是引用子类对象中“基类子对象”
可以用基类类型指针指向子类对象,本质上是指向子类对象中“基类子对象”
通过上面的引用和指针 只能访问父类中的属性和方法,不能访问子类中的成员
基类类型 *指针 = new 子类(实参列表)
delete指针;//只调用基类的析构函数 子类资源无法得到释放 内存泄漏
基类类型& r = 子类对象;//引用 不会调用构造函数
基类类型 obj = 子类对象;//基类的拷贝构造 用子类对象中的基类子对象来构造基类类型
基类类型 *指针 = &子类对象;
指针变量 和 &子类对象 两个地址可能会不相等
引用变量.func();通过引用变量调用方法时,调用哪个类的方法取决于引用变了的类型而非具体引用的那个对象的类型

多继承与多重继承
多继承语法上和语义上与单继承没有区别,只是子类对象中包含更多的基类子对象
这些基类子对象在内存中按照继承表的先后顺序从低地址到高地址依次排列
子类对象可以用任意类型的基类引用变量来引用
子类对象的指针可以隐式转换为任意基类类型的指针
无论是隐式转换还是静态转换,编译器都能保证特定类型的基类指针指向相应的基类子对象
通过父类类型指针指向子类对象,通过强制类型转换和静态类型转换
编译器能够保证转换之后指针指向的是子类对象的地址
但是重解释类型转换无法保证上面两种情况
重解释转换之后,地址值不会发生变化
强制类型转换 和 静态类型转换 隐式类型转换 地址值可能发生变化

成员属性和方法尽量避免重名
如果要访问重名的基类属性时: 基类名::属性名
子类对象名.基类名::属性名

钻石继承和虚继承:

class A{};
class B:public A{};
class C:public A();
class D:public B,public C();
钻石继承:如果一个类有多个基类,而这个基类又有公共的基类,沿着不同的继承路径,公共的基类子对象会在最终子类对象中有多份实例
通过子类对象去访问时,可能造成数据不一致的问题

虚继承 virtual
在继承表中通过virtual关键字指定从公共基类中虚继承,这样就可以保证公共的基类在最终的子类对象中,仅存在一份公共基类子对象实例
避免了沿着不同的继承路径访问公共基类子对象成员时,所引发的不一致问题

只有当所创建对象的类型回溯中存在钻石结构,虚继承才起作用,否则编译器会自动忽略virtual关键字

#include <iostream>
using namespace std;
class A{
public:
	int x;
public:
	A(int x = 0):x(x){
		cout << "A()" << endl;	
	}
};
class B:virtual public A{
public:
	B():A(1024){}
	void set(int x){
		this->x = x;	
	}
};
class C:virtual public A{
public:
	C():A(9527){}
	int get(){
		return x;	
	}
};
class D:public B,public C{};
int main(){
	D d;
	cout << sizeof(d) << endl;// 公共的基类子对象会在最终子类对象中有多份实例
	d.set(110);
	cout << d.get() << endl;
	cout << d.B::x << endl;
	cout << d.C::x << endl;
	return 0;	
}

在这里插入图片描述
用父类指针指向子类对象,只能调用父类中的方法
用父类的指针指向new的子类对象,delete只调用父类的析构函数,造成内存泄漏
调用哪个类的方法和析构函数,取决于引用的类型而非目标类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值