C++面向对象特性之继承

继承是类之间的一种层次关系,根部的类称为基类,其他由基类直接间接继承而来称为派生类。基类的成员是所有类共有的成员,派生类定义其特有的成员。作用---代码重用 ,代码扩充
一种是基类希望其派生类进行覆盖的函数:另一种是基类希望派生类直接继承而不改变的函数。对于前者,基类通常将其定义为虛函数( virtual)。当使用指针或引用调用虚函数时,该调用将被动态绑定。根据引用或指针所绑定的对象类型不同,该调用可能执行基类的版本,也可能执行某个派生类的版本。
基类通过在其成员函数的声明语句之前加上关键字virtual使得该函数执行动态绑定。任何构造函数之外的非静态函数都可以是虚函数。关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果基类把一个函數声明成虚函数,则该函数在派生类中隐式地也是虛函数。

实现多态的基类析构函数一般被声明成虚函数,如果不设置成虚函数,在析构的过程中只会调用基类的析构函数而不会调用派生类的析构函数,从而可能造成内存泄漏。

正如我们可以将一个派生类的普通指针转换成基类指针一样,在容器中放置有继承关系的对象时也能把一个派生类的智能指针转换成基类的智能指针。P558

//print_info()是虚函数univer_student是派生类 student是基类
univer_student stu1("张栋才" ,'M',23,"电子工程");
student base_student1;//测试赋值类型转换值转换
base_student1=stu1;
base_student1.print_info();//此时调用的是基类的print_info

student *base_student2=&stu1;//测试赋值类型转换赋给基类指针
base_student2->print_info();//此时调用派生类的print_info

student &base_student3=stu1;//测试赋值类型转换赋给基类引用
base_student3.print_info();//此时调用派生类的print_info

重写(覆盖):函数名相同 参数相同 返回值相同,派生类中存在的重新定义的函数,派生类调用时会调用派生类的重写函数,不会调用被重写的基类中的函数。基类中被重写的函数必须有virtual来修饰。
隐藏:函数名相同 参数可同可不同 返回值可同可不同,派生类中的函数屏蔽了与其同名的基类中的函数。当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是重写。可以把重写理解成隐藏的特殊情况。

很多时候基类生成对象是不合理的,因为基类只是代表一个抽象的概念,这时候基类中的虚函数就不能给出有意义的实现。比如以形状为基类可以衍生出圆形矩形等,这样在基类定义求面积功能的虚函数就不知道怎么定义。解决方法是引入纯虚函数,在基类中只声明不定义,在尾部加‘=0’,在派生类中给出所有纯虚函数的实现。含有纯虚函数的类是抽象基类,这种类不能声明对象,相当于在抽象基类中只定义了接口。

虚函数与纯虚函数的区别在于1)纯虚函数只有定义没有实现,虚函数既有定义又有实现;
2)含有纯虚函数的类不能定义对象,含有虚函数的类能定义对象;

虚函数表

  编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4(x64下是N*8)的大小。

  派生类的虚函数表存放重写的虚函数,当基类的指针指向派生类的对象时,调用虚函数时都会根据vptr(虚表指针)来选择虚函数,而基类的虚函数在派生类里已经被改写或者说已经不存在了,所以也就只能调用派生类的虚函数版本了.

  类对象中,每个同类对象中都有个vptr,指向内存中的vtable,所有同类对象,共享一个vtable,但是每个对象都自带一个vptr指向这个vtable,否则调用虚函数的时候会找不到正确的函数入口,虚表指针是对象的第一个数据成员。

 

 

 

虚函数的优点主要是实现了C++的多态,提高代码的复用和接口的规范化,更加符合面向对象的设计理念,其缺点主要包括:编译器借助于虚表指针和虚表实现时,导致类对象占用的内存空间更大,这种情况在子类无覆盖基类的多继承场景下更加明显;虚函数表可能破坏类的安全性,可以根据地址偏移来访问Private成员;执行效率有损耗,因为涉及通过虚函数表寻址真正执行函数

哪些函数不能是虚函数
1)构造函数,构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类有一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;
2)内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数;
3)静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。
4)友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。

虚函数的代价?
1)带有虚函数的类,每一个类会产生一个虚函数表,用来存储指向虚成员函数的指针,增大类;
2)带有虚函数的类的每一个对象,都会有有一个指向虚表的指针,会增加对象的空间大小;
3)不能是内联函数,因为内联函数在编译阶段进行替代,而虚函数在运行阶段才能确定到底是采用哪种函数。

构造函数和析构函数可以调用虚函数吗,为什么
1)在C++中,提倡不在构造函数和析构函数中调用虚函数;
2)构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本;
3)因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化,因此调用子类的虚函数时不安全的,故而C++不会进行动态联编;
4)析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经销毁,这个时候再调用子类的虚函数没有任何意义。

析构函数什么时候必须是虚函数
C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么派生类中申请的空间就得不到释放从而产生内存泄漏。为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。

类如何实现只能静态分配和只能动态分配
1)前者是把new、delete运算符重载为private属性。后者是把构造、析构函数设为protected属性,再用子类来动态创建
2)建立类的对象有两种方式:
①静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;
②动态建立,A *p = new A();动态建立一个类对象,就是使用new运算符为对象在堆空间中分配内存。这个过程分为两步,第一步执行operator new()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象;

 

多继承时,比如菱形继承,由于多个基类成员命名重复会导致产生二义性。 使用虚继承可以使派生基类只保留间接基类的一份成员。继承将共同基类设置为虚基类,从不同途径继承来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。从而解决了二义性问题、节省了内存,避免了数据不一致的问题。

假如类A和类B各自从类X派生,且类C同时多继承自类A和B,那么C的对象就会拥有两套X的实例数据。 但是如果类A与B各自虚继承了类X,那么C的对象就只包含一套类X的实例数据。这一特性在多重继承应用中非常有用,可以使得虚基类对于由它直接或间接派生的类来说,拥有一个共同的基类对象实例,避免由菱形继承问题。

声明虚基类只需要在派生列表加virtual即可。

继承与组合

一个类里面的数据成员是另一个类的对象, 也叫封闭类
继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。
组合是has a 的关系 比如Student组合Techer 则说明Student has a teacher
继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。

缺点:

1:父类的内部细节对子类是可见的。2:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。

组合的优点:①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象是不可见的。②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。

缺点:
①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进行定义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值