c++:继承(超详解)

这篇具有很好参考价值的文章主要介绍了c++:继承(超详解)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一:什么是继承

二:继承的格式

继承的总结:

二:子类和父类(基类和派生类)

1.子类和父类的相互赋值:

2.同名的成员变量

3.同名成员函数

三:子类中默认的成员函数

1.构造函数

2.析构函数

3.拷贝构造

4.赋值运算符重载

 四:单继承和多继承

单继承:

 多继承:

菱形继承

解决方法一:

解决方法二:

单继承和多继承的总结:


一:什么是继承

定义:

继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类,称派生类(或子类),被继承的类称基类(或父类)。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前接触的复用都是函数复用,继承是类设计层次的复用。

好吧,光看也看不出个啥,还是直接上代码吧

代码:

class human {//定义了一个父类,名字叫human
public:
	string name = "小明";//父类里面定义了一个string类型的和一个int类型
	int age = 18;
};
class student:public human {//定义了一个以public方式继承父类的子类student
public:
	int schoolnum = 666;//在父类的name和age的基础上增加了一个schoolnum
	void print()
	{
		cout << name << endl << age << endl << schoolnum << endl;//输出
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

结果如下:

c++继承,c++学习,c++,开发语言

 好吧是不是还是看不懂,那让我们把这个代码分成两半

第一部分:

class human {
public:
	string name = "小明";
	int age = 18;
};

第二部分:

class student:public human {
public:
	int schoolnum = 666;
	void print()
	{
		cout << name << endl << age << endl << schoolnum << endl;
	}
};

这样我们发现,其实第一部分的代码就是我们平时使用的class。

对于第二部分的解读我们先举一个例子

如果我们要设计一个学校系统,那么对于学生,老师.....一系列的人,我们都是需要将姓名和年龄等必要信息录入,但是单独针对到某一类人,比如学生,除了必要信息外,还单独有一个学号。比如老师除了必要信息外,还有一个单独的职工号。

c++继承,c++学习,c++,开发语言

 所以为了偷懒提高效率,我们这里就可以把姓名和年龄封装到一个class类里面,也就是我们第一部分的代码,然后再新创一个类,继承原有姓名和年龄类的基础上,再新增学号/职工号,也就是我们第二类。

所以说怎么继承呢?

二:继承的格式

class 新类的名字:继承方式 继承类的名字{};

以我刚才的例子为例

class student:public human{};
//student是新类的名字,public是继承方式,human是要继承的类
//意思就是说我定义了一个名叫 student的类 以public的方式 来继承你human

我们这里对于student和human就有两种叫法。

一种是教科书里面的基类(human)和派生类(student)。

我本人喜欢第二种父类(human)和子类(student)。毕竟感觉就像继承家产一样。

三:继承后的子类成员访问权限

这里我先丢一张图在,这是教科书里面老师铁定要求背诵的

c++继承,c++学习,c++,开发语言

 我这里分享一个很巧妙的方式

我们假设

public>protectd>private

我们取x和y中,两个较小的。

c++继承,c++学习,c++,开发语言

 最后一个private就都不可。这样我们就很轻松的记忆了下来

继承的总结:

1.基类private成员无论以什么方式继承到派生类中都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2.基类private成员在派生类中不能被访问,如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。

二:子类和父类(基类和派生类)

1.子类和父类的相互赋值:

代码:

class human {//父类
public:
	string name = "小明";
	int age = 18;
};
class student:public human {//子类
public:
	int schoolnum = 666;
};
int main()
{
	student st;
	human hm;
	hm = st;//将子类赋值给父类
	st = hm;//将父类赋值给子类
	return 0;
}

就会出现这样的结果:

c++继承,c++学习,c++,开发语言

 在这里我们引入一个叫做切片原则的东西

c++继承,c++学习,c++,开发语言

 因为父类中没有schoolnum,所以父类接收子类传过来的name和age之后,多余的schoolnum就不管了。但是如果父类传给子类,少传一个,所以会报错。

同时我们给出三种赋值方式

三种方式的赋值:

一:=符号

student st;//子类
	human hm;//父类
	hm = st;

 c++继承,c++学习,c++,开发语言

二:引用

student st;//子类
	human& hm=st;父类

c++继承,c++学习,c++,开发语言

三:指针

student st;//子类
	human* hm=&st;//父类

c++继承,c++学习,c++,开发语言

2.同名的成员变量

在有些时候,父类和子类中出现了同一个成员变量,如下name

class human {
public:
	string name = "小明";
};
class student:public human {
public:
	string name = "小红";
	void print()
	{
		cout << name << endl;
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

这个时候编译器是以子类为优先

结果如下

c++继承,c++学习,c++,开发语言

但是如果我们就是想要访问父类的该成员变量,就需要加上修饰

void print()
	{
		cout << human::name << endl;
	}

 其实也很好理解,默认子类,父类就修饰限定

3.同名成员函数

如下,同样一个函数print在父类和子类中都存在

class human {
public:
	string name = "小明";
	void print()
	{
		cout << name << endl;
	}
};
class student :public human {
public:
	string name = "小红";
	void print()
	{
		cout << name << endl;
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

这就构成了隐藏。(函数重载是在同一个作用域,这里父类和子类是两个作用域)

函数的隐藏,编译器会默认调用子类中匹配的函数,如果没有编译器就会报错

上面的结果如下

c++继承,c++学习,c++,开发语言

虽然成员函数的隐藏,只需要函数名相同就构成隐藏,对参数列表没有要求。

 但是我们修改一下子类的函数

void print(int x)//我们对子类的print函数加入一个参数
	{
		cout << name << endl;
	}

c++继承,c++学习,c++,开发语言

 这是因为编译器默认调用子类中print函数,但是子类中唯一的print函数有一个默认的参数,所以编译器无法找到匹配的print函数,所以就会报错。

三:子类中默认的成员函数

1.构造函数

编译器会默认先调用父类的构造函数,再调用子类的构造函数,如下

class human {
public:
	human(string name = "小明")//先调用:父类默认构造调用一个print打印name
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};


class student :public human {//后调用:子类默认构造调用一个print打印name和age
public:
	student(string name,int age)
		:_age(age)
	{
		cout << name << endl<<age<<endl;
	}
protected:
	int _age;
};


int main()
{
	student st("小红", 18);
	return 0;
}

 结果如下

c++继承,c++学习,c++,开发语言

 可以看到,编译器先调用了父类的,打印出了小明,然后再次调用了子类的打印出了小红和age。

所以说请务必保证父类构造有效,假如父类失效

human(string name)//你这里不传值,那么就不能完成初始化,相当于父类失效
		:_name(name)
	{
		cout << name << endl;
	}

那么就必须在子类中给父类构造赋值

student(string name,int age)
		:_age(age)
		, human(name)//新增,子类以自己的name给父类的析构中的name赋值,age和name的顺序随意变动

结果如下

c++继承,c++学习,c++,开发语言

 实在不行就把父类的构造删了,反正编译器也默认会生成的

2.析构函数

析构函数和构造函数相反,编译器默认先调用子类的析构函数,再调用父类的析构函数。

验证如下:

我们在原有的代码上,加入两个析构函数

class human {
public:
	human(string name = "小明")
		:_name(name)
	{}
	~human()
	{
		cout << "我是父类" << endl;
	}
protected:
	string _name;
};
class student :public human {
public:
	student(string name,int a = 20)
		:age(a)
	{}
	~student()
		
	{
		cout <<"我是子类"<< endl;
	}
protected:
	int age;
};
int main()
{
	student st("小明", 18);
	return 0;
}

结果如下:

c++继承,c++学习,c++,开发语言

 所以说

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

如果是指针类型,那么同一块区域被析构两次就会造成野指针的问题。

3.拷贝构造

子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。

class human {
public:
	human(string name="小明")
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};
class student:public human {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
	student(student& s)
		:human(s)//直接将st传过来通过切片拿到父类中的值
		,_age(s._age)//拿除了父类之外的值
	{
		cout << s._age << endl<<s._name<<endl;
	}
protected:
	int _age;
};
int main()
{
	student st("小红",18);
	student st2(st);
	return 0;
}

结果如下:

c++继承,c++学习,c++,开发语言

4.赋值运算符重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。

因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,所以这里的赋值要直接修饰限定父类

class human {
public:
	human(string name = "小明")
		:_name(name)
	{
	}
	human& operator=(const human& p)
	{
		if (this != &p)
		{
			cout << "调用父类" << endl;
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};
class student :public human {
public:
	student(string name, int age)
		:_age(age)
	{
	}
	student(student& s)
		:human(s)
		, _age(s._age)
	{
	}
	student& operator=(const student& s)
	{
		if (this != &s)
		{
			cout << "调用了子类" << endl;
			human::operator=(s);//必须调用父类运算符
			_age = s._age;
			_name = s._name;
		}
		return *this;
	}
protected:
	int _age;
};
int main()
{
	student st("小红", 18);
	student st2(st);
	student st3("小刚", 16);
	st = st3;
	return 0;
}

 结果如下:

c++继承,c++学习,c++,开发语言

 四:单继承和多继承

单继承:

一个子类只有一个直接父类的继承关系。

c++继承,c++学习,c++,开发语言

 多继承:

一个子类有两个或以上直接父类的继承关系。

c++继承,c++学习,c++,开发语言

 由以上两点,我们就会发现一个很蛋疼厉害的继承,

菱形继承

c++继承,c++学习,c++,开发语言

好,我们先上一段经典菱形继承代码 

这是个代码是有问题的

class A {
public:
	string name;
};
class B :public A {
public:
	int age;
};
class C :public A {
public:
	string sex;
};
class D :public B, public C {
public:
	int id;
};
int main()
{
	D student;
	student.name = "小明";
	student.age = 18;
	student.sex = "男";
	student.id = 666;
	return 0;
}

啪的一下,很快啊,报错就出来了 

c++继承,c++学习,c++,开发语言

 因为这里的name,同时存在B和C中,所以D不知道继承B的name还是C中的name

这也就是引出了代码冗余和二义性的问题。

所以我们有两种解决方法

解决方法一:

加修饰限定

student.B::name = "小明";

这里我们指定继承B中的name,就不会冲突了

解决方法二:

虚继承:在继承方式前加上virtual。

class B :virtual  public A {
public:
	int age;
};
class C :virtual public A {
public:
	string sex;
};

单继承和多继承的总结:

别用菱形继承就完了

多继承是C++复杂的一个体现。有了多继承,就存在菱形继承,为了解决菱形继承,又出现了菱形虚拟继承,其底层实现又很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。文章来源地址https://www.toymoban.com/news/detail-781047.html

到了这里,关于c++:继承(超详解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】继承 -- 详解

    1、 继承的概念 继承   (inheritance) 机制是面向对象程序设计 使代码可以 复用 的最重要的手段,它允许程序员在 保 持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称 派生类 。 继承 呈现了面向对象 程序设计的层次结构 ,体现了由简单到复杂的认知过程。以

    2024年02月07日
    浏览(26)
  • C++语法——详解虚继承

    目录 一.什么是虚继承 二.虚继承原理 三.虚继承使用注意事项 所谓虚继承(virtual)就是子类中只有一份间接父类的数据。该技术用于解决多继承中的父类为非虚基类时出现的数据冗余问题,即菱形继承问题。 小编用一张图来表述一下: 如果是下图这种非虚继承 ,那么 D类中

    2024年01月16日
    浏览(30)
  • C++之继承详解(万字讲解)

    今天我们要讲述C++内更重要的一部分知识——继承,对于面向对象的一门语言来说,这部分知识特别重要,同时其中也会有非常多的细节,难度也是不小的,但是它是我们必须要翻越的一座大山,到底什么是继承呢?一起来看看吧。 继承 (inheritance)机制是面向对象程序设计 使

    2024年02月14日
    浏览(27)
  • 【C++进阶】继承、多态的详解(多态篇)

    作者:爱写代码的刚子 时间:2023.8.16 前言:本篇博客主要介绍C++中多态有关的知识,是C++中的一大难点,刚子将带你深入C++多态的知识。(该博客涉及到的代码是在x86的环境下,如果是在x86_64环境下指针的大小可能需要变成8bytes) 多态的概念 多态的概念:通俗来说,就是多

    2024年02月12日
    浏览(31)
  • 【c++】“谁想继承我的花呗-.-“继承的学习

        文章目录 前言 一、继承的语法 二、基类和派生类对象赋值转换 1.例子 2.继承中的作用域 3.派生类的默认成员 4.继承与友元 5.继承与静态成员 6.复杂的菱形继承和菱形虚拟继承 总结   继承 (inheritance)机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序

    2023年04月16日
    浏览(24)
  • 【C++学习】继承

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! C++是面向对象的编程语言,它有很多的特性,但是最重要的就是封装,继承,多态三大特性,封装本喵就不介绍了,前面我们一直都在使用,这里本喵来详细介绍 继承 。 继承:是面向

    2023年04月09日
    浏览(26)
  • C++学习:类继承

    面相对象的主要目的之一就是提供可重用的代码。 类继承就是从已有的类派生出新的类,而派生类继承了原有类,也就是基类的特征和成员函数。 继承一笔财富比自己白手起家要简单的多,写代码也是一样。 下面是可通过继承来外城的工作: 1、可以在已有类的基础上添加

    2024年02月08日
    浏览(33)
  • 【C++学习手札】一文带你初识C++继承

                                                                                      食用指南:本文在有C基础的情况下食用更佳                                           🍀 本文前置知识:  C++类                           

    2024年02月12日
    浏览(26)
  • 【C++学习手札】一文带你认识C++虚继承​​

                                            食用指南:本文在有C++基础的情况下食用更佳                                           🍀 本文前置知识:C++虚函数(很重要,内部剖析)                                        ♈️ 今日夜电波 : 僕

    2024年02月12日
    浏览(32)
  • 【浅尝C++】继承机制=>虚基表/菱形虚继承/继承的概念、定义/基类与派生类对象赋值转换/派生类的默认成员函数等详解

    🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。 🎯每日格言:每日努力一点点,技术变化看得见。 我们生活中也有继承的例子,例如:小明继承了孙老师傅做拉面的手艺。继承就是一种延续、复用的方式。C++为了提高代码的可复用性,引入了继承机制,

    2024年04月10日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包