C++独孤九剑第四式——菩提无树(构造函数语意)

来源:互联网 发布:淘宝vr资源怎么搜索 编辑:程序博客网 时间:2024/04/27 15:13

菩提本无树,明镜亦非台。本来无一物,何处惹尘埃!

如果没有构造函数的辛勤劳作我们的编程世界也是空无一物(当然是站在面向对象的角度,也排除了一些特殊情况)。那么构造函数又是如何帮我们构造“万物”的呢?嘻嘻,人家待会儿再告诉你。

先说人生三大错觉:股票要涨,房价要跌,她还爱我。使用构造函数也经常有错觉,比如:当我们没有定义任何构造函数时,编译器总是会帮我们生成默认构造函数;编译器合成的默认构造函数会设定其构造对象的成员变量初值。

我们知道,构造函数是特殊的成员函数,只要创建类类型的对象,都要执行相应的构造函数(如果需要话)。构造函数的工作是保证每个对象的数据成员具有合适的初始值,但是这是由程序员自己来保证的。注意:构造函数只是在已分配的内存中做初始化的工作,而分配内存并不是构造函数的工作。

好了,不卖关子了,开始!

1.    默认构造函数

首先要说明的是,默认构造函数包括构造函数的参数都是默认值的情况和构造函数根本就没有参数的情况这两种,这一节主要讲后者。

上面提到编译器合成默认构造函数的错觉。编译器合成构造函数只在编译器自身需要的时候才会这么做,而不是我们的程序逻辑需要的时候。

例如,我们定义如下的类:

class A{public:static  int s;int a;int  getA(){return a;}};

此时编译器并不会给我们合成出构造函数,因为对于编译器来说,这个时候有没有构造函数是一样的。看过第二式的朋友知道,其实类的成员函数在编译时会被改写为外部函数;static成员变量会被mangling之后成为外部变量;剩下的只有成员变量,其实和C语言中的结构体(struct)并没有什么质的区别,所以根本就不需要劳“构造函数”的大驾。

那么如何证明以上结论的正确性呢?当我们调用:A  a;语句时,反汇编的情况如下所示:


可以清楚地看到A  a;语句调用时,根本就不存在其它函数调用的痕迹,也不可能有构造函数被调用。

也可以换个角度来证明,如果我们给上述类定义加上一个默认构造函数,则,其反汇编的情况如下图所示:


此时,才是真的有调用默认构造函数。

那么究竟是什么时候才需要合成的默认构造函数亲自出马呢?以下四种情况编译器就会需要默认构造函数为其提供某些服务,你懂得。

一、     类中有一个成员对象,而该对象拥有默认构造函数,且类中未定义任何构造函数。

此时需要在编译器合成的默认构造函数中调用成员对象的默认构造函数。

二、     该类的某个基类拥有默认构造函数,且类中未定义任何构造函数。

此时需要在编译器合成的默认构造函数中调用基类的默认构造函数。

三、     该类中存在虚函数(无论是继承所得还是自身定义),且类中未定义任何构造函数。

此时需要在编译器合成的默认构造函数中设置虚函数表指针。

四、     该类的某个基类是虚基类(无论是直接还是间接),且类中未定义任何构造函数。

此时需要在编译器合成的默认构造函数中设置虚基类表指针。

这里就不一一证明了,感兴趣的同学可以自己试一试。

那么通过默认构造函数构造的对象是否会被初始化呢?看如下的代码:

Aa1;

//main------------------------------------------------------------------------------

     static A a2;

     A a3;

     A *a4 = new A();

     cout<<a1.a<<"\t"<<a2.a<<"\t"<<a3.a<<"\t"<<a4->a<<endl;

可以看到a1和a2是被初始化了的,但是那是默认构造函数的功劳吗?恐怕不是,因为即使你把上面的A改为int,相应的变量依然会被初始化。这是由它们分配的内存位置决定的,构造函数根本就没做任何事情(甚至就不存在所谓的构造函数)。

既然说到了这里,顺便和大家一起分享一下各种作用域对象的生存周期

全局对象(a1)程序一开始,其构造函数先被执行(比程序进入点更早);程序即将结束前其析构函数被执行。

静态对象(a2)当对象诞生时,其构造函数被执行;当程序将结束时,其析构函数才被执行,但比全局对象析构函数早一步。

局部对象(a3)当对象诞生时,其构造函数被执行;当程序流程将离开该对象的存活范围时,其析构函数被执行。

new方式产生的局部对象(*a4)当对象诞生时,其构造函数被执行;析构函数则在对象被delete时执行。


2.    带参构造函数

对于带参构造函数的情况就不存在编译器会不会自动合成的疑问了,相信怎么调用大家也很清楚了,我们主要就看隐式转换的情况。

用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。何时会触发这种隐式转换?在程序中需要该类类型,但我们给的却是构造函数形参类型数据的时候。

例如,有如下的定义:

class A{public:int _a;A (int a){_a = a;}};

在main函数中如此调用:

Aa = 4;

此时a对象需要的其实是一个A类型的对象,但‘=’右边却是一个整形,恰巧是构造函数的形参类型。那么会发生什么?4处将会调用A(int)构造函数,隐式转换为A类的一个匿名对象,然后将该匿名对象直接指定为a(此时即没有调用复制构造函数,也没有调用等号操作符)。具体复制控制的细节将在第五式中详细讨论。

这种构造函数的使用很容易在没有察觉的时候进行对象的转换,有时候可能并不是我们所希望的。那么如何避免这种隐式转换呢?C++提供了explicit关键字用来抑制这种存在隐患的转换。

例如上面的构造函数可以如下定义:

explicit  A (int _a){a = _a;}

此时再调用:A  a = 4;将会报错,但是可以这样显示转换:A  a = A(4);

 

3.    复制构造函数

关于复制构造函数的部分我们将在第五式中探讨,此处为了知识点的完整性顺便提一下。


4.    构造函数的初始化列表

      与其它函数不同,构造函数还可以包含一个初始化列表。构造函数的初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。

      那么构造函数为什么需要初始化列表呢?从实现构造的功能上来说是为了应对以下几种情况:

      ①初始化一个引用成员变量的时候(引用定以后必须赋初值)。

      ②初始化一个常量成员变量的时候(常量成员也必须赋初值)。

      ③当调用一个基类的构造函数,而该构造函数有不少于一个参数的时候(需要为该构造函数传递参数)。

      ④当调用一个成员对象的构造函数,而该构造函数有不少于一个参数的时候(需要为该构造函数传递参数)。

在上述的几种情况下我们只能使用初始化列表来达到目的。因为只有在初始化列表中进行的才是初始化操作,而构造函数的函数体内其实是进行相应的计算操作。

对于普通的成员变量,其实是否放在初始化列表中并不会产生区别,但是对于类成员变量就会有所区别。

例如,定义如下的类:

class Person{public:int age;string name;};

若有如下的构造函数定义:

Person::Person()

     {

           age = 0;

           name =””;

     }

则在调用该构造函数时编译器可能会这样来处理:

Person::Person()

     {

           age = 0;

           name.String::String();//调用String的默认构造函数

           String  temp = String(“”);//产生临时对象

           name.String::operator=(temp);//调用String的等号操作符

           temp.String::~String();//析构临时对象

     }

若构造函数定义改为下面这样:

Person::Personname(“”)()

     {

           age = 0;

     }

则在调用该构造函数时编译器可能会这样来处理:

Person::Person()

     {

           name.String::String(“”);//调用String的构造函数直接构造name对象

           age = 0;

     }

那么编译器是如何处理初始化列表的呢?编译器会把初始化列表中的内容以适当的顺序安插在所有我们显示定义在构造函数体内的语句之前。那么所谓适当的顺序又是什么样的顺序?这里并不是在初始化列表中出现的顺序,而是成员变量在类中声明的顺序(如果有基类的操作,该操作将在处理本类成员之前)。所以说我们的代码不能依赖于初始化列表中的顺序,这很可能会引起一些“误会”。

例如,如下定义的类:

class  Example{int  val1;int  val2;};

如果定义有如下的构造函数:

Example::Example(int  val):val2(val),val1(val2){}

此时恐怕不能达到预期的目的,因为,其实val1(val2)语句会比val2(val)更早被执行,而那时val2还并未被初始化。

好了,我们已经对构造函数的相关知识点“七进七出”。嗯,那边趴桌子的同学可以醒醒了,下课了<( ̄︶ ̄)>







0 0
原创粉丝点击