对继承的缠缠绵绵

2024-04-22 05:58
文章标签 继承 缠缠绵绵

本文主要是介绍对继承的缠缠绵绵,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        • 1.继承
        • 2.继承与转换
        • 3.成员函数的重载、 覆盖和隐藏区别
        • 4.派生类成员函数
        • 5.菱形继承和虚继承

1.继承

1.1继承概念:
继承是面向对象重要的复用手段,就是一个类继承另一个类的属性和方法,这个新类包含上一个类的属性和方法,被称为子类或者派生类,被继承的类叫做父类或者基类。
1.2继承的方式及访问属性

1.2.1共有继承(public):除过基类是私有成员无法继承外,其它成员都可以按照基类方式继承,基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。

1.2.2保护继承(protected):除过基类是私有成员外,其它成员继承后都变为保护继承,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。

1.2.3私有继承(private):私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。

1.3三种继承关系

继承方式基类是public基类是protected基类是private继承引起的访问关系变化
public仍为public成员仍为protected不可见基类的非私有成员在子类的访问属性都不变
protected变为protected仍为protected不可见基类的非私有成员在子类中都变为保护成员
private变为private变为privated不可见基类中的私有成员都变为子类的私有成员
eg:
class father//父类
{public:char* _fname;//父亲的名字protected:int _fIdCard;//银行卡账号是需要保护的,子类就是儿子可以知道privateint _fIdPassword;//银行卡密码只能父类知道,子类不能知道,怕你乱取钱花
}
class son:public father//子类
{public:void func(void){char* _sname = _fname;//可以继承,基类的公有成员在派生类中变为公有成员。int _sIdCard = _fIdCard;//可以继承,基类的保护成员在派生类中变为保护成员int _sIdPassword = _fIdPassword;//不能继承,基类的私有成员在派生类中是不可见的}
}

知识点总结:
1.基类中的私有成员不想让基类对象直接访问基类成员,但派生类却可以访问,就定义为保护成员,可见保护成员是为继承而出现的
2.public继承保持is-a原则,每个父类可用的成员在子类中也可用。当然不包括私有成员。
3.protetced/private继承,基类的部分成员并未完全成为子类接口的一部分,是 has-a 的关系原则,所以一般情况下不会使用
这两种继承关系,在绝大多数的场景下使用的都是公有继承
5.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在子类中不可见(不能
访问)
6.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

附:
is-a原则: 全称为is-a-kind-of,显然这里is-a原则指的是子类是父类的一种;例如:人是基类,男人是子类,子类和基类构成继承关系;满足is-a 原则;男人是人的一类
has-a 原则:protected/private继承是实现继承,父类的成员有一部分子类是无法继承的 一个子类中有一个父类,即部分父类的成员是不可用的(has-a),has-a多用于组合关系。
例如:学生是一个类,姓名,学号,电话号都是一个类,这三个类组合成为学生类。


2.继承与转换

2.1赋值兼容规则–public继承

2.1.1 :子类对象可以赋值给父类对象(因为子类是从父类继承下来的,因此父类肯定会包含部分子类成员,在子类进行赋值时,父类成员只要获取自己的部分成员即可,这就叫做切片处理)
2.1.2:父类对象不能赋值给子类对象(因为父类私有成员是不可见的,所以父类给子类赋私有成员时,子类并不知道需要多少空间来接收父类私有成员)
2.1.3 :父类的指针/引用可以指向子类对象(但是只能访问子类从父类继承过来的成员,访问子类其它成员函数或者变量会出错)
2.1.4 :子类的指针/引用不能指向父类对象(因为当你用指针访问父类中子类特有的成员函数或者变量时,父类中没有就会非法访问,但是可以通过强制类型转换完成)

eg:
class base
{public:char* name;int number;
}
class derived: public Base
{publicint IDcard;
}base b;//基类
derived d;//派生类b = d;//基类对象不能赋给派生类对象
d = b;//派生类对象可以赋给基类对象//基类的指针或引用可以指向派生类对象
base *b1 = &d;
base &b2 = d;//派生类的指针或引用不能指向基类对象(但可以通过强制转换)
derived *b3 = (derived*)&d;
derived &b4 = (derived&)d;

这里写图片描述


3.成员函数的重载、 覆盖和隐藏区别

3.1.、重载:
在同一个作用域里,函数名字相同,参数类型不同或者参数个数不同或者返回值不同,会构成重载。
c语言中函数名字相同是不能实现的,但是在C++中却可以,因为 c++ 中底层汇编语言中会将重载函数名映射为返回类型+函数名+参数列表。这样c++有同名函数时,链接时只要参数类型或者顺序不一样就可以调用
深入了解重载点击这里
3.2、重写(覆盖):
1. 不在同一个作用域(即一个父类一个子类)
2. 函数名相同/参数完全相同(包括类型和顺序)/返回值相同(协变除外)
3. 基类函数前必须有virtual。(即基类函数为虚函数)
3.3、重定义(隐藏):
1.在不同作用域里(即父类和子类)
2.子类和父类函数名相同,参数不同,在子类中父类的成员函数被隐藏
3.子类和父类函数名相同,参数完全相同,但父类函数没有virtual(即不是虚函数),父类成员函数被隐藏,切记不是重写
举个栗子:

class B
{
public:void fun(){cout << "B::fun()" << endl;}void fun(int a){cout << "B::fun(a)" << endl;}
};
class D : public B
{
public:void fun(){cout << "D::fun()" << endl;}
};

这里写图片描述

class B
{
public:void fun(){cout << "B::fun()" << endl;}void fun(int a){cout << "B::fun(a)" << endl;}virtual void fun1(){cout << "B::fun1()" << endl;}
};
class D: public B
{
public:using B::fun;//让class B类中所有fun的所有函数在D类中都可见,并且是publicvoid fun(){cout << "D::fun()" << endl;}virtual void fun1(){cout << "D::fun1()"<<endl;}};

这里写图片描述

class B
{
public:void fun(){cout << "B::fun()" << endl;}void fun(int a){cout << "B::fun(a)" << endl;}virtual void fun1(){cout << "B::fun1()" << endl;}
};
class D : private B
{
public:void fun(int a)//转交函数{B::fun(2);//偷偷成为inline内联函数}void fun1(){cout << "D::fun1()" << endl;}}

这里写图片描述
总结:
1.子类继承父类时,它们有重名函数,子类成员函数将屏蔽父类对子类成员函数直接访问。
2.子类对象想要访问同名父类函数时可以通过显示调用,using表达式声和转交函数
3.但在实际使用时子父类最好不要用同名函数,以免出错。

4.派生类成员函数
  1. 子类继承父类,但是并不是父类所以成员函数和变量子类都可以继承,那么那些不能继承呢?

    子类不能从父类继承的有:
    1、构造函数
    原因:子类创建对象时,需要调用父类的构造函数,如果你继承了父类的构造函数,就不能让子类的构造函数去初始化属于父类的那部分成员变量了,也就是说,子类里属于父类部分的成员函数必须由父类的构造函数亲自初始化,所以不能继承
    2.、析构函数
    原因:析构函数继承会构成重写。
    3、赋值操作符=重载函数
    原因:因为赋值操作符重载函数的作用是自己拷贝自己的类,如果继承下来,拷贝类型就对不上号了,就会出错。

  2. 子类的构造函数应该在其初始化列表里显式的调用父类构造函数(除非父类构造函数不能访问)

  3. 如果父类是多态类,那么必须把父类析构函数定义为虚函数,因为这样就可以像其他虚函数一样实现动态绑定了,否则就会产生内存泄漏。
    详细原因戳这里
  4. 在写子类的赋值函数时,注意不要忘记父类的数据成员重新赋值,这可以通过调用父类的赋值函数来实现

那么举个栗子吧

class Base
{
public:Base(const char* name = ""):_name(name){cout << "Base()" << endl;}Base(const Base &b):_name(b._name){cout << "Base(const Base &b)" << endl;}Base& operator = (const Base &b){cout << "Base& operator = (const Base &b)" << endl;if (this != &b){_name = b._name;}return *this;}~Base(){cout << "~Base()" << endl;}string _name;
};class Derived : public Base
{
public:Derived(const char* name = "", int number = 0):Base(name)//显示调用构造函数,_number(number){cout << "Derived(const char* name="", int number=0)" << endl;}Derived(const Derived& d):Base(d)//显示调用拷贝构造,_number(d._number){cout << "Derived(const Derived& d)" << endl;}Derived& operator = (const Derived& d){if (this != &d){Base::operator=(d);//对基类的成员重新赋值_number = d._number;cout << "Derived& operator = (const Derived&d)" << endl;}return *this;}~Derived(){cout << "~Derived()" << endl;}int _number;
};

这里写图片描述
可以看出在派生类构造对象时,先构造基类成员函数再构造派生类成员函数,但析构时,缺失却是先析构派生类再析构父类。


5.菱形继承和虚继承

5.1、菱形继承
:两个子类继承父类,又一个子类同时继承上面两个子类
看图:
这里写图片描述
菱形继承存在问题:
5.1.1:数据冗余
5.1.2:二义性
eg:

class person
{
public:int _name;//为了后续更好验证,把所有成员变量定义为int};class student :public person
{
public:int _number;//学号};class teacher :public person
{
public:int id;//学工号
};class assistant :public student,public teacher
{public:int telephone;};

这里写图片描述
这里写图片描述

解决办法:
1.指定作用域

void Test()
{assistant a;
    a.teacher::_name;
    a.student::_name;
}

2.采用虚继承

class person
{
public:int _name;};class student : virtual public person
{
public:int _number;//学号};class teacher : virtual public person
{
public:int _id;//学工号
};class assistant :public student,public teacher
{public:int _telephone;};

1.虚继承写时要注意: virtual public person这样写,如果写成virtual person,那就默认虚继承的私有继承了,你就不能访问父类成员变量
2.虚继承解决了菱形继承中的数据冗余造成的浪费空间问题


接下来分析虚继承怎么解决掉这些问题的
先看看虚继承后assistant a对象大小的改变
这里写图片描述

普通继承中,assistant继承teacher的8字节和student8字节,再加上自己的4字节,所以大小时20字节,那虚继承多出来的这四个字节是什么?我们来看看内存中的情况

这里写图片描述

可以看出每一次虚继承后,子类都会产生一个指针,指针指向一个虚基表,虚基表里的内容是一个偏移量,是子类对象实例化后通过自身地址加上这个偏移量找到存放继承自父类对象的地址,这样就可以找到里面的内容了。

这篇关于对继承的缠缠绵绵的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/924958

相关文章

JavaSE——封装、继承和多态

1. 封装 1.1 概念      面向对象程序三大特性:封装、继承、多态 。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节 。     比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器, USB 插孔等,让用户来和计算机进行交互,完成日常事务。但实际上:电脑真正工作的却是CPU 、显卡、内存等一些硬件元件。

七、Maven继承和聚合关系、及Maven的仓库及查找顺序

1.继承   2.聚合   3.Maven的仓库及查找顺序

OOP三个基本特征:封装、继承、多态

OOP三个基本特征:封装、继承、多态 C++编程之—面向对象的三个基本特征 默认分类 2008-06-28 21:17:04 阅读12 评论1字号:大中小     面向对象的三个基本特征是:封装、继承、多态。     封装 封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。   封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信

c++ 类的继承详解

在 C++ 中,类的继承是一种面向对象编程(OOP)的特性,允许创建一个新的类(派生类)从一个已有的类(基类)派生。通过继承,派生类可以重用基类的属性和行为,并且可以扩展或修改这些行为。继承是一种代码重用和扩展的机制,使得派生类能够继承基类的特性并添加或修改特性。 1. 继承的基本语法 class Base {// 基类的成员};class Derived : public Base {//

之后是缠缠绵绵的四年恋爱

他和她初次相见是在操场上。她忽然来例假,染红了白裙子,却浑然不觉,还在和同学说笑。他看见后脸红了,脱下自己的上衣让她围在腰间。那一刻,是她一辈子也难忘的。     之后是缠缠绵绵的四年恋爱,她试图帮他,而他不肯;男人哪会用女孩子帮忙?     毕业时,他们本来免不了天各一方,但她死心塌地地跟着他走。家里人反对,几乎与她反目,她却认定这男人是她想要的。     她

C++ 第8章 继承

继承(Inheritance)是面向对象程序设计中软件重用的关键技术。 8.1 类之间的关系 一个大的应用程序,通常由多个类构成,类与类之间互相协同工作。 class Vehicle{int wheels;double weight;double loading;public:void initialize(int in_wheels, double in_weight);int get

C++ 菱形继承与虚拟继承的详解与代码示例

在C++中,多重继承虽然强大,但也会带来不少问题。特别是当继承链中出现菱形继承时,容易导致基类的重复实例化。本文将深入讨论菱形继承的问题,并详细解释如何通过虚拟继承来解决它。同时,给出一个简单的代码示例,并逐步解析输出结果。 什么是菱形继承? 菱形继承是指在多重继承中,同一个基类被多个派生类继承,而这些派生类又被另一个类继承,最终形成菱形结构,如下图所示: A/ \B C\ /D

C++_继承详解

继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象程序设计的层次结构,之前我们接触的复用都是函数复用,今天我们所讨论的继承是类设计层次的复用。 子类继承父类的成员后,父类的 成员函数 和 成员变量 都会变成子类的一部分,其中父类的成员函数与子类的成员函数使用的是同一个函数(构造

【C++】C++的输入输出、循环、条件、字符串、数组、类、继承的使用实例

本文介绍C++的基本使用,用一个程序说明的基本运用,当然C++中的指针与运算符的重载这些特色东西还没有涉及,只是把编程中最基本的东西讲述一次, 与《【Python】Windows版本的python开发环境的配置,Helloworld,Python中文问题,输入输出、条件、循环、数组、类》(点击打开链接)是姊妹篇,据说这堆东西出书的话能写很多很多页,上课的话能上大半学期,真是醉了。 以下程