资讯专栏INFORMATION COLUMN

C++之继承

不知名网友 / 888人阅读

摘要:基类中的构造函数和析构函数不能被继承,在派生类中需要定义新的构造函数和析构函数,私有成员不能被继承。对象访问在派生类外部,通过派生类的对象对从基类继承来的成员的访问。

1、类与类之间的关系有哪些?
与类之间的关系分为纵向和横向两种:
纵向就是继承;横向包括:依赖、关联、聚合和组合。(这里不进行解释,详解链接:https://blog.csdn.net/u014694510/article/details/88316605.
2、什么是继承?继承有什么作用?
所谓继承就是从先辈出得到属性和行为特征。类的继承就是新的类从已有类那里得到已有的特征;从另一个角度来看,类的继承和派生机制使程序员无需修改已有类,只需在此类的基础上,通过少量代码或修改少量代码的方法得到新的类,从而很好的解决了代码重用的问题。由已有类产生新类时,新类便包含了已有类的特征,同时也可以加入自己的新特征。已有类被称为基类或者父类,产生的新类被称为派生类或者子类。
3、继承有哪些分类?
派生类的继承方式有私有继承(private),公有继承(public),保护继承(protect)
4、基类成员在派生类的访问属性是怎样的?

基类中的成员在公有派生类的访问属性在私有派生类中的访问属性在保护派生类中的访问属性
私有成员不可直接访问不可直接访问不可直接访问
公有成员公有私有保护
保护成员保护私有保护

5、基类的成员函数都能被继承吗?
不是。基类中的构造函数和析构函数不能被继承,在派生类中需要定义新的构造函数和析构函数,私有成员不能被继承。
6、派生类对基类成员的访问规则是怎样的?
内部访问:由派生类中新增的成员函数对基类继承来的成员的访问。
对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
私有继承的访问规则:

基类中的成员私有成员公有成员保护成员
内部访问不可访问可访问可访问
对象访问不可访问不可访问不可访问

公有继承的访问规则:

基类中的成员私有成员公有成员保护成员
内部访问不可访问可访问可访问
对象访问不可访问可访问不可访问

保护成员的访问规则:

基类中的成员私有成员公有成员保护成员
内部访问不可访问可访问可访问
对象访问不可访问不可访问不可访问

先看个简单的代码了解一下:

class Person{public:	void work()	{		cout << "work()" << endl;	}	void eat()	{		cout << "eat()" << endl;	}protected:private:	string _name;	int _age;	string _sex;};class Student :public Person{public:	void show()	{		cout << _name << endl;		cout << _age << endl;		cout << _sex << endl;	}};

这里会报错:

但如果把这些私有属性改为保护或者公有的话就不会报错:

class Person{public:	void work()	{		cout << "work()" << endl;	}	void eat()	{		cout << "eat()" << endl;	}protected:    string _name;	int _age;	string _sex;private:};class Student :public Person{public:	void show()	{		cout << _name << endl;		cout << _age << endl;		cout << _sex << endl;	}};


这就是因为私有成员不可以被继承。
总结:子类继承的父类成员,在自身中的权限不能高于继承权限()

再看这个代码:

class Person{public:    Person(string name,int age,string sex,string wife=string())    {       _name=name;       _age=age;       _sex=sex;       _wife=wife;    }    ~Person()    {       cout<<"~Person()"<<endl;    }	void work()	{		cout << "work()" << endl;	}	void eat()	{		cout << "eat()" << endl;	}protected:    string _name;	int _age;	string _sex;private:    string wife;};class Student :public Person{public:	void show()	{		cout << _name << endl;		cout << _age << endl;		cout << _sex << endl;	}};int main(){	Student s;//报错,无法引用默认的构造函数	s.show();}

这样改正之后错误消失(给派生类定义构造函数):

class Student :public Person{public:	Student(string name, int age, string sex, string num, string wife = string())	:Person(name,age,sex,wife)	{		cout << "Student(string name, int age, string sex, string num, string wife = string())" << endl;		_num = num;	}	void show()	{		cout << _name << endl;		cout << _age << endl;		cout << _sex << endl;		cout << _num << endl;	}private:	string _num;};int main(){	Student s("zjh",11,"man","1111");	s.show();}

错误原因:C++规定,当基类的构造函数没有参数,或没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给构造函数的途径。
7、派生类构造函数和析构函数的执行顺序是怎样的?
通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤销派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
8、派生类构造函数的参数列表是怎样构成的?

	Student(string name, int age, string sex, string num, string wife = string())	:Person(name,age,sex,wife)

从上面列出的派生类Student构造函数首行中可以看到,派生类构造函数名后边括号内的总参数表中包括了参数的类型和参数名,而基类构造函数参数表中只有参数名而不包括参数类型,因为在这里不是定义基类构造函数,而是调用基类构造函数(这里的调用和在主函数中的调用是一样的,只是为了说明这部分参数需要用基类的构造函数初始化),因此这些参数是实参而不是形参。它们可以是派生类构造函数总参数列表中的参数,也可以是常量和全局变量。
9、如果有多层继承,参数列表又怎样构成?
这里我们给上边的Student类再写一个派生类来看看

class High_Student :public Student{public:	High_Student(string name, int age, string sex, string num, string high, string wife = string())		:Student(name, age, sex, num,wife)	{		cout << "High_Student()" << endl;		_high = high;	}	~High_Student()	{	  cout<<"High_Student()"<<endl;	}protected:private:	string _high;};int main(){	High_Student a = { "zjh",21,"nan","1010","sss","aaa"};	a.eat();	return 0;}

我们可以看出,这里依旧是类名后边是总参数列表,但是冒号后边是前两个父类的实参,我们可以将Student(name, age, sex, num,wife)理解为嵌套调用,即执行该语句之后,还是先调用Person类的构造函数初始化name、sge、sex、wife四个参数,再调用Student类的构造函数初始化num.

10、C++中的隐藏是怎样的?
还是先看代码:

class Base{public:	void fun1(int a)	{		cout << "Base::void fun1()" << endl;	}protected:private:	int _a;};class Derive :public Base{public:	void fun1()	{	    fun1(10);//这里会报错		cout << "Derive::void fun1()"<<endl;	}	void fun1(int a,int b)	{		cout << "Derive::void fun1(int a)" << endl;	}protected:private:	int _b;};int main(){	Derive d;	d.fun1(10);//这里会报错,显示没有匹配的函数}

问题:明明子类继承了父类只有一个参数的构造函数,为什么还不能用?
C++中规定,当父类和子类有同名参数时,子类会隐藏父类的同名函数,导致子类对象和子类成员函数不能调用。
解决办法:d.Base::fun1(); Base::fun1()给函数加作用域

接下来我们再看一段代码,通过这段代码引出虚基类的概念:

class Base{public:	Base()	{		a = 5;		cout << "Base()" << endl;	}protected:	int a;};class Base1 :public Base{public:	int b1;	Base1()	{		a = a + 10;		cout << "Base1()" << endl;	}};class Base2 :public Base{public:	int b2;	Base2()	{		a = a + 20;		cout << "Base2()" << endl;	}};class Derive :public Base1, public Base2{public:	int d;	Derive()	{	    cout<<"Derive a="<<a<<endl;//会报错	}};int main(){	Derive d;	return 0;}

上边报错语句需要改成这样才能成功运行:

        cout << "Base1::a=" << Base1::a << endl;		cout << "Base2::a=" << Base2::a << endl;

执行结果如下:

11、为什么这里加上作用域就能成功运行?
在上述程序中,类Derive是从类Base1和Base2公有派生来的,而类Base1和Base2又都是从类Base公有派生而来的。虽然在类Base1和Base2中没有定义数据成员a,但是它们分别都从类Base继承了数据成员a,这样在类Base1和Base2中同时存在着数据成员a,它们都是类Base成员的复制。但是类Base1和Base2中的数据成员a具有不同的存储单元,可以存放不同的数据。在程序中可以通过类Base1和Base2去调用基类Base的构造函数,分别对类Base1和Base2的数据成员a初始化。因此在Derive的构造函数中输出a的值,必须加上类名,指出是哪一个数据成员a,否则就会出现二义性。(即类中的数据成员a的值可能是Base1中的a,也可能是Base2中的a)。
图解:

为了解决这个问题,从而有了虚基类:
先看图解,了解虚基类是怎样解决的

虚基类的本质其实就是:当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。
下面来看代码:

class Base{public:	Base()	{		a = 5;		cout << "Base()a=" <<a<< endl;	}protected:	int a;};class Base1 :virtual public Base{public:	int b1;	Base1()	{		a = a + 10;		cout << "Base1()a=" <<a<< endl;	}};class Base2 :virtual public Base{public:	int b2;	Base2()	{		a = a + 20;		cout << "Base2()a=" <<a<< endl;	}};class Derive :public Base1, public Base2{public:	int d;	Derive()	{		cout << "Derive a=" << a << endl;	}};int main(){	Derive d;	return 0;}

执行结果如下:

关于虚基类初始化的几点说明:

  • 建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数书进行初始化的。虚基类的其它派生类对虚基类构造函数的调用可以忽略
  • 若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后再调用派生类的构造函数。
  • 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
  • 对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下。
  • 若虚基类是由非虚基类派生而来,则仍然是先调用基类构造函数,再调用派生类的构造函数。
    如:将上述程序改成这样:

    则执行结果为:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/125337.html

相关文章

  • [初识C++] 何为最:心酸历史

    摘要:上面需要了解的是这俩个版本都是破蛹成蝶的版本世界挑战榜咋才前三还没挤进去呀,你想想世界上有几千中编程语言,在其中脱颖出来,可以说是天之娇子,凤毛麟角了。支持正版图灵上面买吧,如果没钱买盗版吧学完以后买本正版支持一下,创作不易是吧 ...

    forrest23 评论0 收藏0
  • 谈谈Java的面向对象

    摘要:也就是说,一个实例变量,在的对象初始化过程中,最多可以被初始化次。当所有必要的类都已经装载结束,开始执行方法体,并用创建对象。对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分。 类的拷贝和构造 C++是默认具有拷贝语义的,对于没有拷贝运算符和拷贝构造函数的类,可以直接进行二进制拷贝,但是Java并不天生支持深拷贝,它的拷贝只是拷贝在堆上的地址,不同的变量引用的是堆上的...

    ormsf 评论0 收藏0
  • 系统地学习C++

    摘要:本书主要围绕一系列逐渐复杂的程序问题,以及用以解决这些问题的语言特性展开讲解。你不只学到的函数和结构,也会学习到它们的设计目的和基本原理。因此我们把精力集中在最有价值的地方。本书不仅是对模板的权威解释,而且本书还深入地介绍了其他一般的思想。 C++ 入门教程(41课时) - 阿里云大学 C+...

    joyqi 评论0 收藏0
  • C++重温笔记(四): 继承和派生

    摘要:继承继承,就是子类继承父亲的特征和行为,使得子类具有父类的成员变量和方法。此时,被继承的类称为父类或基类,而继承的类称为子类或派生类。,如果存在继承关系的时候,和就不一样了基类中的成员可以在派生类中使用,但是基类中的成员不能再派生类中使用。 ...

    DevWiki 评论0 收藏0
  • 2021年游戏项目的十大编程语言:C++、Java、C#均上榜

    摘要:月日,发布文章,介绍了年游戏项目的十大编程语言。无疑是游戏项目的最佳编程语言之一。是和等游戏引擎所使用的主要编程语言。对于游戏开发者来说,是最友好最灵活的编程语言之一。作为游戏项目的最佳视频游戏编程语言之一,正在赢得属于自己的一份荣耀。 ...

    不知名网友 评论0 收藏0

发表评论

0条评论

不知名网友

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<