欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++类(构造函数和析构函数、子类继承和调用父类的构造方法)

程序员文章站 2022-05-14 09:06:57
...

为什么要有构造、析构函数

类的数据成员不能在类的声明时候初始化,为了解决这个问题,使用构造函数处理对对象的初始化。构造函数是一种特殊的成员函数,与其他函数不同,不需要用户调用它,而是创建对象的时候自动调用。析构函数是对象不再使用的时候,需要清理资源的时候调用。

为什么类的数据成员不能在类的声明时候初始化?
此处留疑。
我觉得他跟结构体的性质应该是一样的,哈哈。

构造函数
构造函数的调用:一般情况下,C++编译器会自动的调用构造函数,特殊情况下,需要手工的调用构造函数。(这个特殊情况是指继承的时候,你的父类是有参构造函数,则需要子类去显式地调用父类的构造参数)
析构函数
析构函数在对象被销毁的时候自动调用。C++编译器自动调用。

继承父类

构造方法用来初始化类的对象,与父类的其他成员不同,他不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不能继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。

如果没有显示的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

为什么类的构造函数不能被继承
原因之一:其他函数都可以通过类对象来调用,但是构造函数是用来产生对象的函数,它在对象之前。而继承对对象来说是能够调用父类的函数,但是对象都不存在你还调用什么父类的构造函数?所以构造函数无法继承。

构造原则

  1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
  2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
  3. 在创建子类对象的时候,如果子类的构造函数没有显式调用父类的构造函数,则会调用父类的默认无参构造函数。
  4. 在创建子类对象时候,如果子类的构造函数没有显式调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
  5. 在创建子类对象时候,如果子类的构造函数没有显式调用父类的构造函数,且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显式调用此带参构造方法)。
  6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式:
    子类只能在构造对象时才能默认(或者用初始化列表中显式调用“特定父类构造函数”)调用父类的构造函数,在构造完成后不能像调用父类成员函数一样调用父类构造函数,这样保证了父类构造函数只调用一次的原则;
    荔汁:
#include <iotream>
using namespace std;
class game
{
public:
	game(int moeny,int level)
	{
		cout<<"game()"<<endl;
	}
	~game()
	{
		cout<<"~game()"<<endl;
	}
};
class game:public lol
{
public:
	lol():game(200,30)
	{
		cout<<"lol()"<<endl;
	}
	~lol()
	{
		cout<<"~lol()"<<endl;
	}
}

为什么析构函数总是虚函数?

编译器总是根据类型来调用成员函数,但是一个派生类的指针可以安全地转化为一个基类的指针。(上行转换,不知道的可以翻翻之前我的笔记)C++不管这个指针指向一个基类对象还是一个派生类对象,调用的都是基类的析构函数,而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重新析构函数,那么会有内存泄露,子类空间无法回收。也就是说,当你父类指针指向子类对象的时候,你走了父类的构造,子类的构造,而你销毁对象的时候只能走父类的析构。
虚函数过程,当你进行上行转换的时候,当你调用一个函数的时候,先判断这个函数是不是虚函数,如果是虚函数会先看看子类有没有重写,如果重写的话调用重写的,如果没有重写的话,就用父类自己的虚函数。

虚函数

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;

虚函数表
虚函数表,类的虚函数表示一块连续的内存,每个内存单元中记录一个jmp指令的地址。
编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象分享。类的每个虚函数占据虚函数表中的一块。如果类中有N个虚函数,那么其虚函数表将有N*4个字节的大小。
在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚表就显得尤其重要了,他指明了他所应该调用的函数。
编译器应该是保证虚函数表的指针存在于对象实例中的最前面的位置(这是为了保证取到虚函数表的由最高的性能–如果有多层继承或是多重继承的情况下)。这意味着可以通过对象实例的地址得到这张续表,然后就可以遍历其中的函数指针,并调用响应的函数。
有虚函数或虚继承的类实例化后的对象大小至少为4字节(确切的说是一个指针的字节数;说至少是因为还要加上其他非静态数据成员,还要考虑对齐问题);没有虚函数和虚继承的类实例化后的对象大小至少为1字节(没有非静态数据成员的情况下也要有1个字节来记录它的地址)。
有纯虚函数的类称为抽象类,不能定义抽象类的对象,他的子类要么实现他所有的纯虚函数变为一个普通类,要么还是一个抽象类。
重点:

  1. 当存在类继承并且析构函数中有必要进行的操作(如需要释放某些资源,或执行特定的函数)析构函数需要时虚函数,否则若使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,从而造成内存泄露或达不到预期结果。
  2. 内联函数不能为虚函数,内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开。
  3. 构造函数不能为虚函数,构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类的,因此不存在动态绑定的概念;但是构造函数中可以调用虚函数,不过并没有动态效果,只会调用本类中的对应函数。
  4. 静态成员函数不能为虚函数,静态成员函数是以类为单位的函数,与具体对象无关,虚函数是与对象动态绑定的。