构造函数到底是什么,它在C++对象创建过程到底起什么样的作用呢 《C++ Primer》中文版(第五版)中是这么定义的:
每一个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
看完书中的定义其实我们还是什么似懂非懂,我们都知道C++中一个对象的创建分两步:
为对象分配空间; 对对象进行初始化工作; 而我们的构造函数其实就是做的第二步的工作,所以构造函数的实质其实就是一个对对象进行初始化的工作。那么到底如何进行初始化呢,或者说如何进行构造函数的编写和使用呢 构造函数分为默认构造函数,拷贝构造函数以及其他形式的构造函数。至于调用哪一个构造函数则根据函数重载机制进行选择。
class Test { public: Test(); Test(const Test ©_test); Test(const std::string &arg_s, int arg_x, int arg_y); private: std::string s; int x = 0; int y; };对于上面的Test类, 1. Test()就是一个简单的默认构造函数,当然这是一种最简单的形式; 2. Test(const Test ©_test)是拷贝构造函数; 3. Test(const std::string &arg_s, int arg_x, int arg_y)是其他形式的构造函数; 4. Test() : Test("", 0, 0) 1是委托构造函数。即委托它所属类中其他构造函数执行自己的初始化过程。委托函数执行完后会返回该构造函数的函数体继续执行。
而对于Test(const std::string &arg_s = "", int new_x = 0, int new_y = 0); 这样含有默认参数的构造函数,当所有的参数都有默认值时,则其实质上相当于也定义了默认构造函数。
这种方式效率最低,不建议大家使用。至于为什么,后面会讲到。
在构造函数后面加:成员变量名(值),成员变量名(值)这种形式就是参数化列表。 使用参数化列表初始化效率高于函数体内赋值。建议使用这种方法进行初始化。
为什么说参数化列表的效率比函数体内赋值要高呢?
构造函数在执行函数体之前,会使用初始化列表对数据成员进行初始化;对没有初始化列表的数据成员,若有类内参数值,则会采用类内参数进行初始化;其他的则采用默认初始化。也就是说,如果你在初始化列表中初始化了,就会采用你的初始化值,否则就是编译器执行默认初始化操作。像本例子中,根据声明顺序s,x,y(类定义中数据成员排列的顺序),先初始化s,采用默认初始化;在初始化x,y,采用自己的初始化值5进行初始化。
初始化的顺序只与声明顺序有关,与初始化列表中的顺序没有关系。也就是说,你最好不要打乱顺序,且不要用一个成员来初始化另一个成员。例如:Test(int value) : y(value), x(y) { },由于初始化顺序只与声明顺序有关,因此先初始化s,在初始化x,然后是y,造成x在y之前初始化,此时由于y没有初始化,所有x的值是未知的。
那么默认初始化究竟是执行一个什么样的过程呢?
对于类成员,则会调用类的默认构造函数进行初始化(当然,如果没有默认构造函数的话,编译器就会报错);对于内置类型(int,char,double等),若在函数体之外,则被初始化为0;在函数体内,则值为未定义(即不被初始化)。没有定义任何一个构造函数时,编译器就会自动合成默认构造函数。
下列情况下,编译器不能合成默认的构造函数:
当类中有其他的构造函数;(C++认为如果类在某种情况下需要控制对象初始化,即调用自定义的构造函数,它就会认为你会在所有的情况下都调用自定义的构造函数,即控制对象初始化) 当类中的成员没有默认构造函数。使用自定义默认构造函数,而不用编译器合成的的场景
某些情况下,假如我们的成员中有指针,我们可能需要初始化指针所指空间中的所有内容,而不仅仅是拷贝指针的值。使用编译器合成的默认构造函数会造成浅拷贝。(详情请查看浅拷贝与深拷贝的区别,string类的实现);含有内置类型或者复合类型,由于它们在函数内默认初始化为未定义的值,所以最好使用自定义的默认构造函数进行初始化;当该类继承自父类时,调用构造函数时,如果没有显示在参数列表中列出父类初始化(形式为父类构造函数调用),则会先调用父类的默认构造函数进行父类对象的初始化,然后才会进行子类成员的初始化。
当类中含有虚函数时,在调用构造函数初始化时,会首先初始化对象的虚表指针,在初始化其他的成员,然后进入函数体内执行某些操作。
构造函数是回调函数,当C++创建对象时,会首先使用内存分配函数对对象进行空间的分配(类似于malloc,::operator new),当分配空间完成后(类似于做GUI的时候双击按钮),会回调构造函数(类似于点完按钮,然后会执行某些操作),构造函数完成后,又会返回回来,最后返回内存分配函数分配的空间地址(malloc,::operator new最后都会返回开辟内存的地址)。
构造函数是没有返回值的,而创建对象会返回创建的对象的地址。(这是分配空间完成的操作,当构造函数回调完成之后返回对象的地址)。