【C++】浅谈三大特性之一继承(三)

四,派生类的六个默认成员函数

在继承关系里,如果我们没有显示的定义这六个成员函数,则编译系统会在适合场合为我们自动合成。

继承关系中构造函数和析构函数的调用顺序:

class B
{
public:
	B()
	{
		cout<<"B()"<<endl;
	}
	~B()
	{
		cout<<"~B()"<<endl;
	}
};

class D:public B
{
public:
	D()
	{
		cout<<"D()"<<endl;
	}
	~D()
	{
		cout<<"~D()"<<endl;
	}
};

void Funtest()
{
	D d;
}
int main()
{
	Funtest();
	return 0;
}

非常简单的一段代码,你觉得会打印什么呢?一起来看看

有人看到这里,肯定会说,那明摆着嘛,先调用B类的构造函数再调用D类的构造函数,根据栈空间先进后出的原则,接着先析构B类自己,再析构从基类那继承来的部分,可是,事实真的这么简单吗?当然不。

你想想,你创建的是子类的对象,怎么可能先去调用基类的构造函数呢,既然这样,为什么打印结果显示的确是先调用父类的构造函数呢???这里牵扯到构造函数的调用次序和函数体的执行顺序的问题,注意不要混淆它们。

实际上,调用顺序是这样的:

你可能会有点疑惑,明明先调用了子类的构造函数,可是为什么会去先执行基类的构造函数体,这是因为我们显示定义了父类的缺省构造函数,你想想,既然显示定义了,就必须调用它对吧,如果我们不在子类的构造函数中调用它,那何时调用呢,但是由于我们创建的是子类对象,所以必须先调用子类的构造函数,思来想去,将基类的构造函数放在子类的初始化列表中调用似乎再合适不过了,所以,我们C++的设计者们就是这样做的,是不是很聪明呢?

在这里,还需要注意一点,如果基类的显示的定义了缺省的构造函数,那么基类的构造函数即使不显示定义,编译器也会为我们合成默认的构造函数用来调用基类的构造函数,组合也是如此。

五,继承体系中的作用域

1,继承体系中,子类的作用域和父类的作用域属于两个作用域。(在子类中不能访问父类的私有成员足以说明此点)

2,同名隐藏。如果子类中包含和父类相同名字的成员,则子类成员将屏蔽对父类成员的直接访问,如果想要在子类中访问父类的同名成员,就必须采用作用域限定符。

eg:

class B
{
protected:
	int _a;
};

class D:public B
{
public:
	void test()
	{
		_a = 10;
	}
private:
	int _a;
};

void Funtest()
{
	D d;
	d.test();
}
int main()
{
	Funtest();
	return 0;
}

运行结果:

从监视窗口中,我们清晰的看到了子类对象的_a被改为10,而父类的成员数据_a仍然是一个随机值。怎样做到在子类对象中改变的是父类对象的成员数据呢?

eg2:

class B
{
protected:
	int _a;
};

class D:public B
{
public:
	void test()
	{
		B::_a = 10;
	}
private:
	int _a;
};

void Funtest()
{
	D d;
	d.test();
}
int main()
{
	Funtest();
	return 0;
}

运行结果:

注意:尽量避免父类和子类使用同名成员,不要给自己挖坑哦!

六,赋值与转换----赋值兼容规则

1,子类对象可以直接赋值给父类对象(切片/割片)。

2,父类对象不能直接赋值给子类对象。

3,父类对象的引用或指针可以直接指向子类对象。

4,子类对象的引用或指针不可以直接指向父类对象。(强制类型转换可完成)

对象赋值:

引用或指针:

class B
{
protected:
	int _b;
};

class D:public B
{
private:
	int _d;
};

void Funtest()
{
	D d;
	B *b;
	b = &d;//父类指针指向子类对象

	D *d1;
	B b1;
	d1 = (D*)&b1;//子类对象可通过强制类型转换指向父类对象(尽量避免)

	D &d2 = d;
	B b2;
	b2 = d2;//父类引用指向子类对象

	D d3;
	B &b3 = b1;
    d3 = (D&)b3;//父类引用指向子类对象
}
int main()
{
	Funtest();
	return 0;
}

七,单继承&多继承&菱形继承

<1>单继承:一个子类仅有一个直接的父类。

单继承中类中成员数据的分布与成员变量在类中的定义顺序有关。

<2>多继承:一个子类有两个或两个以上直接的父类。

多继承中派生类成员的分布与继承类的先后次序有关

<3>菱形继承(钻石继承)

菱形继承中成员的分布与最底层类继承的先后次序有关

上图中我们标出了菱形继承中各个类所占字节数,可是C1类和C2类中都继承了B类中的数据成员_b,那么如果我们通过D类的对象对_b进行访问,必然会产生二义性,

class B
{
	int _b;
};
class C1:public B
{
	int _c1;
};
class C2:public B
{
	int _c2;
};
class D:public C1,public C2
{
	int _d;
};
void Funtest()
{
	D d;
	d._b = 10;//错误,访问不明确
	d.C1::_b = 10;//正确
	d.C2::_b = 10;//正确
}

如何避免这种访问不明确呢,是否可以将重复部分_b只在D类中保存一份呢,这将引入虚拟继承的概念。

eg:

class B
{
	int _b;
};
class C1:virtual public B
{
	int _c1;
};
class C2:virtual public B
{
	int _c2;
};
class D:public C1,public C2
{
	int _d;
};
void Funtest()
{
	B b;
	C1 c1;
}

上面这段代码就是一个虚拟继承的例子,注意关键字virtual的位置不要写错哦

当创建好C1类的变量c1时,编译器会为C1合成一个默认的构造函数,这个合成的默认构造函数会做哪些事呢?

首先,如果基类有缺省构造函数,它会去调它,其次,它会将偏移量的地址指针(虚指针)放在c1对象的前4个字节处。

再来看一下如果是D类的对象,又是怎么存储的呢?

动脑筋想一下,如果B,C1,C2,D均为空类,每个类所占字节大小又是多少呢?

最后,需要注意的几点:

1、友元关系不能继承,因为友元关系不属于类的成员(就好比你朋友的女朋友并不是你的女朋友)。

2、如果类中包含静态成员,无论继承了多少派生类,静态成员都只保存一份。

3、析构函数和构造函数不能被继承下来。原因:派生类除了继承基类的成员外,还可以添加只属于自己的新成员,如果用继承来的构造函数初始化,只能初始化从基类继承来的那部分,而派生类本身新添加的那部分成员初始化不了。析构函数也是一样的,初始化不到派生类新添加的成员,导致内存泄漏。

时间: 2024-10-19 03:13:28

【C++】浅谈三大特性之一继承(三)的相关文章

【C++】浅谈三大特性之一继承(一)

一,为什么要引入继承? 继承是一个非常自然的概念,现实世界中的许多事物也都是具有继承性的. 例如,爸爸继承爷爷的特性,儿子又继承爸爸的特性等都属于继承的范畴.下面是一个简单的汽车分类图: 在这个分类图中建立了一种层次结构,最高层是最普遍,最一般的,每一次都比它上一层的更详细,更具体. 其中把上一层的叫做基类(或父类),紧接着基类的下一层叫做派生类(或子类). 所谓继承,就是从先辈处得到属性和特征. 类的继承就是新类从已有类得到已有的特性,新类被称为派生类,已有类被称为基类.可抽象为派生类是基类的

【C++】浅谈三大特性之一继承(二)

三,继承方式&访问限定符 派生类可以继承基类中除了构造函数和析构函数之外的所有成员,但是这些成员的访问属性是由继承方式决定的. 不同的继承方式下基类成员在派生类中的访问属性: 举例说明: (1)public继承 eg1: #include <iostream> using namespace std; class Person { public://公有数据成员 int length;//身高 int weight;//体重 }; class Student:public Person

【转】java提高篇(二)-----理解java的三大特性之继承

[转]java提高篇(二)-----理解java的三大特性之继承 原文地址:http://www.cnblogs.com/chenssy/p/3354884.html 在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句话中最引人注目的是"复用代码",尽可能的复用代码使我们程序员一直在追求的,现在我来介绍一种复用代码的方式,也是java三大

java随笔:三大特性之继承

Java三大特性之继承 一.介绍 笔记重点:构造器.protected关键字(这个自行查阅).向上转型.private关键字(继承非常重要的要点)  复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句话中最引人注目的是"复用代码",尽可能的复用代码使我们程序员一直在追求的. 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继

(1) 深入理解Java面向对象三大特性 封装 继承 多态

转眼已经工作快6年了,最开始做了2年J2EE:然后整了2年的数据仓库,主要是Cognos的报表开发:现在从事4G LTE核心网的开发,用的语言任然是Java,但写代码的机会不多,基本都是看代码找BUG,偶尔做点new feature也是在比较成熟的框架上复制.粘贴.修改,大部分时间还是在理解业务,钱多.事少.离家近,当时来这家公司图的是后面2点,2年过去了,英文水平有所提升,对敏捷开放也有一定的了解,但技术方面明显退步了或者说没有进步吧,本来以前也不怎么好,因为工作上用不到,自己也没怎么学习,所

[转载]CSS三大特性(继承、优先级、层叠)之个人见解

首先声明一下CSS三大特性——继承.优 先级和层叠.继承即子类元素继承父类的样式,比如font-size,font-weight等f开头的css样式以及text-align,text- indent等t开头的样式以及我们常用的color.简单的就不演示了,强调一下font-size这个东东(虽然也有继承,但是标签不同继承的效果也 不一样),比如下面的代码: <!DOCTYPE html> <html lang="en"> <head> <met

Python面向对象之:三大特性:继承,封装,多态。

前言: python面向对象的三大特性:继承,封装,多态. 1. 封装: 把很多数据封装到?个对象中. 把固定功能的代码封装到?个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了?个很?B的函数. 那这个也可以被称为封装. 在?向对象思想中. 是把?些看似?关紧要的内容组合到?起统?进?存储和使?. 这就是封装. 2. 继承: ?类可以?动拥有?类中除了私有属性外的其他所有内容. 说?了, ??可以随便?爹的东?. 但是朋友们, ?定要认清楚?个

19.Python面向对象之:三大特性:继承,封装,多态。

前言: python面向对象的三大特性:继承,封装,多态. 1. 封装: 把很多数据封装到?个对象中. 把固定功能的代码封装到?个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了?个很?B的函数. 那这个也可以被称为封装. 在?向对象思想中. 是把?些看似?关紧要 的内容组合到?起统?进?存储和使?. 这就是封装. 2. 继承: ?类可以?动拥有?类中除了私有属性外的其他所有内容. 说?了, ??可以随便?爹的东?. 但是朋友们, ?定要认 清楚

面向对象的三大特性之继承-基础知识总结------彭记(05)

面向对象的三大特性之继承: js中的某些对象没有一些需要的属性和方法,但是另外的对象有,那么拿过使用,就是继承. js中继承的实现方式: 1.混入式继承:通过循环将一个对象中的所有属性和方法混入到另外一个对象中: var me={ work:function(){ console.log('敲代码'): } }: var mayun={ money:9999999, car:'000', manager:function(){ console.log('管理巴巴...'); } } /*遍历,让