C++基础知识(六)--- 继承

本文详细介绍了面向对象编程中的继承概念,包括公有、私有和保护继承的特性,以及继承中的构造和析构函数调用顺序。探讨了同名成员处理、静态成员的继承规则,非自动继承的函数如构造和析构函数。此外,还讲解了多继承、菱形继承及其解决的二义性问题,特别是虚继承的原理和作用。动态联编和静态联编的概念以及虚函数在实现动态联编中的关键角色也被阐述。
摘要由CSDN通过智能技术生成

目录

一. 继承

1. 三种继承方式:

 (1)公有继承

 (2)私有继承

 (3)保护继承

 2. 继承中的构造和析构

调用顺序:

3. 继承中同名成员的处理

4. 继承中的静态成员

5. 非自动继承的函数

6. 多继承

7. 菱形继承

虚继承的原理:

 8. 静态联编、动态联编

静态联编:

虚函数:

动态联编:


一. 继承

利用已有的数据类型来定义新的数据类型。

一个 B 类继承于 A 类,或称从类 A 派生类 B。则,类A称为基类(父类),类B称为派生类(子类)。

1. 三种继承方式:

 (1)公有继承

 (2)私有继承

 (3)保护继承

 2. 继承中的构造和析构

调用顺序:

先调用父类的构造函数,再调用子类的构造函数。析构函数反之。

如果是继承与组合混搭的构造和析构,则:

先调用父类的构造,然后调用成员对象的构造,最后调用子类本身的构造。析构函数反之。

class A
{
public:
	A()
	{
		cout << "A构造函数" << endl;
	}
	~A()
	{
		cout << "A析构函数" << endl;
	}
};

class B
{
public:
	B()
	{
		cout << "B构造函数" << endl;
	}
	~B()
	{
		cout << "B析构函数" << endl;
	}
};

class C :public A
{
public:
	C()
	{
		cout << "C构造函数" << endl;
	}
	~C()
	{
		cout << "C析构函数" << endl;
	}
public:
	B b;
};

void test()
{
	C c;
}
A构造函数
B构造函数
C构造函数
C析构函数
B析构函数
A析构函数

3. 继承中同名成员的处理

(1)当子类和父类有同名成员时,子类的同名成员会隐藏父类的同名成员;

(2)当子类和父类有同名函数时,父类的所有函数重载都会被隐藏;

class Father
{
public:
	Father()
	{
		a = 10;
	}
	void func()
	{
		cout << "Ffunc" << endl;;
	}
	void func(int a)
	{
		cout << "Ffunc(int a)" << endl;;
	}
public:
	int a;
};

class Son :public Father
{
public:
	Son()
	{
		a = 20;
	}
	void func()
	{
		cout << "Sfunc" << endl;;
	}
public:
	int a;
};

void test()
{
	Son s;
	cout << s.a << endl;
	cout << sizeof(s) << endl;
	//通过父类名加作用域来访问
	cout << s.Father::a << endl;

	s.func();
	//通过作用域来访问隐藏的父类函数
	s.Father::func();
	s.Father::func(1);
}
20
8
10
Sfunc
Ffunc
Ffunc(int a)

4. 继承中的静态成员

静态成员可以被继承;

继承中的静态成员变量会被同名的子类成员变量隐藏;

继承的静态成员函数中,当子类有和父类同名静态函数时,父类的所有重载静态函数都会被隐藏;

改变从基类继承过来的静态函数的某个特征、返回值或者参数个数,将会隐藏基类重载的函数;

静态成员函数不能是虚函数。

class Father
{
public:
	static int a;
};
int Father::a = 1;

class Son :public Father
{
public:
	static int a;  //子类的静态成员变量a会把父类的同名静态成员变量a隐藏
};
int Son::a = 2;

5. 非自动继承的函数

构造函数、析构函数只知道对它们特定的对象做什么,所以不能被继承。

6. 多继承

我们可以从一个类继承,也可以同时从多个类继承,这就是多继承。

问题是:当父类中有同名成员时,子类中会产生二义性问题。

7. 菱形继承

class Animal  //祖类
{
public:
	Animal()
	{
		a = 10;
	}
public:
	int a;
};
//被虚继承的基类叫虚基类
//用虚继承的方法解决菱形继承中的二义性问题
class Sheep :virtual public Animal    //父类
{};

class Camel :virtual public Animal    //父类
{};

class SheepCamel :public Sheep, public Camel    //子类
{};

两个子类继承同一个基类而又有某个类同时继承这两个子类,这种继承称为菱形继承

虚继承:父类虚继承祖类,用 virtual 关键字。

虚基类:被虚继承的基类。

问题:

两个父类中有祖类的数据,然后子类会继承两个父类的数据,产生二义性的问题。

虚继承的原理:

两个父类通过虚继承的方式继承自祖类。从这两个对象的布局图汇总可以看出,编译器为我们的对象添加了一个指针,它指向一个类似于表的组织,该表记录了当前的虚指针相对于虚基类的首地址的偏移量;

当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象;

当子类继承两个父类(如下图所示的SheepCamel继承Sheep和Camel),同时也继承了两个父类的虚指针,那么只有一份成员变量,所以不会产生二义性。

下图所示为子类的对象布局图:

 

 8. 静态联编、动态联编

静态联编:

预处理--编译--汇编--链接。

 编译器会根据函数调用的对象类型,在编译阶段就确定函数的调用地址。C语言全都是这样子的。

class Animal
{
public:
	void speak()
	{
		cout << "Animal speak " << endl;
	}
};

void test()
{
    Animal an;
    an.speak();  //在编译阶段就确定了调用speak这个函数
}

虚函数:

在普通函数前面加virtual,该函数变为虚函数,告诉编译器这个函数要晚绑定。

virtual void speak()
{
	cout << "Animal speak " << endl;
}

动态联编:

在运行阶段才确定调用哪个函数(晚绑定)。

作用:可以晚绑定函数调用地址,这样可以扩展功能,在不修改前面代码的基础上进行项目的扩充。

(1)设置虚函数之前:

class Animal
{
public:
	void speak()
	{
		cout << "Animal speak " << endl;
	}
};

class Dog :public Animal
{
public:
	void speak()
	{
		cout << "Dog speak " << endl;
	}
};

void doLogin(Animal* animal) //强转,将Dog*类型转换为Animal*类型
{
	animal->speak();  //在编译阶段就绑定好了animal的speak()函数
}

void test()
{
	Dog* dog = new Dog;
	doLogin(dog);
}
Animal speak

(2)设置虚函数之后:

class Animal
{
public:
	virtual void speak()
	{
		cout << "Animal speak " << endl;
	}
};

class Dog :public Animal  //扩展技能,利于扩展,并且不影响原有代码
{
public:
	virtual void speak()
	{
		cout << "Dog speak " << endl;
	}
};

class Dog2 :public Animal
{
public:
	virtual void speak()
	{
		cout << "增加技能" << endl;
	}
};

void doLogin(Animal* animal) //强转,将Dog*类型转换为Animal*类型
{
	animal->speak();
}

void test()
{
	Dog* dog = new Dog;
	doLogin(dog);

	Dog2* dog2 = new Dog2;
	doLogin(dog2);
}
Dog speak
增加技能

(3)类型转换问题:

子类转换成父类(向上转换):编译器认为指针的寻址范围缩小了,所以是安全的;

父类转换成子类(向下转换):编译器认为指针的寻址范围扩大了,所以不安全。

解释:

子类继承时会继承父类的所有东西,所以子类的空间是会比父类大的。所以当把子类强转为父类时,就缩小了指针的寻址范围,没有越界,编译器就认为是安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值