C++中,并不是所有的成员函数都能被子类继承,有三类成员函数不能被子类继承,分别是:构造函数(包括拷贝构造)、析构函数、赋值运算符重载函数。
一,构造函数
构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
二,析构函数
析构函数也不会被子类继承,只是在子类的析构函数中会调用父类的析构函数。
三,赋值运算符重载函数
赋值运算符重载函数也不会被子类继承,只是在子类的赋值运算符重载函数中会调用父类的赋值运算符重载函数。
C++中基类采用virtual虚析构函数是为了防止内存泄漏
具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
ps: 内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
在C++,有五种函数不能被声明成虚函数,分别是:非成员函数、构造函数、静态成员函数、内联成员函数、友元函数这五种,下面分别解释为什么这五种函数不能被声明成虚函数。
1. 普通函数(非成员函数)
非成员函数只能被重载(overload),不能被继承(override),而虚函数主要是通过继承方式来体现多态作用,非成员函数早在编译期间就已经绑定函数了,是不能被继承的。
2. 构造函数
构造函数是用来初始化对象的,虚函数依赖虚函数能产生地址,存储在虚函数表当中,对象必须存在/实例化(vfptr->vftable->虚函数地址),虚函数是在不同类型的对象产生不同的动作,现在对象还没有产生,自然就没法调用虚函数了。
3. 静态成员函数
静态成员函数对于每个类来说只有一份,所有的对象都共享这一份代码,它是属于类的而不是属于对象,编译时确定的,不能被继承,只属于该类。虚函数必须根据对象类型才能知道调用哪一个虚函数,故虚函数是一定要在对象的基础上才可以的,两者一个是与实例相关,一个是与类相关。
4. 内联成员函数
内联函数是为了在代码中直接展开,减少函数调用花费的代价,并且inline函数在编译时被展开,虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
5. 友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
多态
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用基类的指针或引用指向子类的对象。
C++的多态性具体体现在运行和编译两个方面:
(1)在程序运行时的多态性通过继承和虚函数来体现;
(2)在程序编译时多态性体现在函数和运算符的重载上;
虚函数
使用virtual关键字声明的是虚函数,虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的函数。它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。由多态方式调用的时候动态绑定。
纯虚函数
函数声明的时候=0 没有方法实体的是纯虚函数
当类中有一个纯虚函数,这个类也称为抽象类。
抽象类特点:
1、对象的静态类型(static type):就是它在程序中被声明时所采用的类型(或理解为类型指针或引用的字面类型),在编译期确定;
2、对象的动态类型(dynamic type):是指“目前所指对象的类型”(或理解为类型指针或引用的实际类型),在运行期确定;
3、静态绑定(statically bound):又名前期绑定(eraly binding),绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
4、动态绑定(dynamically bound):又名后期绑定(late binding),绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
即所谓动态绑定,就是基类的指针或引用有可能指向不同的派生类对象,对于非虚函数,执行时实际调用该函数的对象类型即为该指针或引用的静态类型(基类类型);而对于虚函数,执行时实际调用该函数的对象类型为该指针或引用所指对象的实际类型
只有虚函数才使用的是动态绑定,其他的全部是静态绑定
指针和引用存放的都是所指对象的地址
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已,引用变量内存单元保存的是被引用变量的地址。
(2)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(3)指针常量本身(以p为例)允许寻址,即&p返回指针常量(常变量)本身的地址,被引用对象用*p表示;引用变量本身(以r为例)不允许寻址,&r返回的是被引用对象的地址,而不是变量r的地址(r的地址由编译器掌握,程序员无法直接对它进行存取),被引用对象直接用r表示
(4)可以有const指针,但是没有const引用;
(5)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(6)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(7)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(8)指针和引用的自增(++)运算意义不一样;引用自增被引用对象的值,指针自增内存地址。
5、malloc开辟空间类型大小需手动计算,new是由编译器自己计算;
6、malloc返回类型为void*,必须强制类型转换对应类型指针,new则直接返回对应类型指针;
8、对于malloc分配内存后,若在使用过程中内存分配不够或太多,这时可以使用realloc函数对其进行扩充或缩小,但是new分配好的内存不能这样被直观简单的改变
对于内建基本数据类型,delete和delete[]功能是相同的。
对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,为每个数组元素调用析构函数;delete删除单个对象,只会调用一次析构函数。
简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。