通常一个类,其内部包含有变量和函数,当我们想要使用类的时候,总是会不得不面临这样一个问题,需要对类进行初始化,否则内部这些变量就会是随机值,导致程序出现异常。
为此,我们需要在使用类之前对它进行初始化,C++就提供了这样一类特殊的函数——构造函数,它在创建类的时候会被自动调用,对类进行初始化。
析构函数和构造函数类似,它会在类对象被销毁时自动调用,主要负责一些清理工作。通常在函数结束后,在当前函数内生成的那些类就会被调用。
如果构造函数没有使用new来创建堆内存对象的话,一般是不需要析构函数做任何处理的,否则需要在析构函数内使用delete来释放这些堆内存,以避免出现内存泄漏的风险。
举个例子,测试类当中有三个变量a、b、c需要初始化,可以这样写。
基本格式是 classname() : {} 或 classname() {}
由以下几个特征:
1.没有返回类型,同时也不能有返回值;
2.括号()中间是函数的输入变量,可以在后面赋值,这样输入就会在没有输入的情况下赋默认值(这一条适用于所有函数,但是必须保证默认赋值变量的顺序是从后往前);
3.冒号:后面的内容是初始化列表,使用A(B)的方式,将B赋值给A,用逗号,隔开,但最后一个变量不能有逗号,这段内容写在()和{}中间。需要注意的是,初始化列表不是必须的,可以完全不使用初始化列表,全部都在{}内赋值也可以。
4.花括号{}中间的内容,就像正常的函数实现一样,在初始化时,会执行一次内部的程序。
因此,最后输出的结果如注释写的那样:m_a = 100, m_b = 2, m_c = 0。
构造函数是运行重载的,下面这些构造函数本质和其它构造函数没有任何区别,只是这些用法比较多,从而有了一些特殊的名称而已。
默认构造函数:
当我们没有写任何构造函数的时候,系统也会隐含存在一个构造函数,只不过它的输入变量、初始化列表,以及函数内容都是空的,不进行任何初始化操作。如果我们写一个构造函数不带任何输入,那么就会覆盖掉默认的构造函数,使用人工编写的构造函数。
注意:默认构造函数是公有的
拷贝构造函数:
以相同的类来作为当前类的唯一输入的构造函数。这里有两个关键点,相同的类和唯一输入,说大白话就是,将同样的类A拷贝到B。至于如何进行初始化,那就要针对不同的类来考虑了,通常都需要手动对类的内部变量进行一一拷贝赋值。
转换构造函数:
和拷贝构造函数类似,只不过是将另外一个类,转化为当前类,那么情况就更为复杂,需要依据使用场景和类的具体内容,进行初始化赋值。
例如这个例子,需要用test_a给test_b赋值,但是又无法访问a的私有成员,就需要使用get_a函数,且我们不希望更改test_a的内容加上了const,但是赋值时又需要去掉const,从而使用const_cast修饰。
基本格式是 ~classname() {}
由以下几个特征:
1.没有返回类型,同时也不能有返回值;
2.类名前面需要加一个~,代表析构函数名;
3.花括号{}中间的内容,就像正常的函数实现一样,在类被销毁时,会执行一次内部的程序。
注意:通常情况下,我们不会主动调用析构函数,都是让系统自动去调用。
析构函数使用场景一般像下面这样,用于释放构造时占用的堆内存:
在初始化赋值时,我们通常还习惯这样一种用法:
这里实际上包含有两个步骤:
1.将(2.5)由类型float隐式转化为类型int2.再将转化后的int类型值,赋值给a
例如下面这个例子,类的隐式构造当中,就会先调用Implicit(int num = 0)函数,将1转化为Implicit类,然后赋值给Implicit a。
而在构造函数前加上explicit关键字,代表只允许进行显式转换,因此自动调用Implicit(int num = 0)将1转化为Implicit类的操作就会变成非法的,所以我们得想想其他办法。
例如,显式将1转化为Implicit,或者创建中间变量等。
私有构造函数有以下几个特性:
通常利用这种特性,可以屏蔽某些不想要开放的构造函数。例如,禁止拷贝类的时候,可以私有化拷贝构造函数。
如果重载了多个构造函数,只要存在公有的构造函数,就可以直接调用那些公有的构造函数来构造这个类,而不会出现无法构造的情况。
例如单例模式当中,内部含有一个指向自身类型的静态指针,通过公有的函数来调用构造函数,并将类赋值给这个指针并返回,来实现类的构造和获取。
原则上,只有析构函数是公有的,两种方式都可以析构,并释放内存。
私有析构函数有以下几个特性:
可以通过这种方式,来创建类的指针对象。
因为此时的析构函数为私有,外部的delete方法也相当于从外部直接调用析构函数。