再谈C++类的构造函数

来源:互联网 发布:淘宝仿包 编辑:程序博客网 时间:2024/06/05 14:42


到CSDN的第一篇博文,老生常谈的一个问题,也是自己的一些总结,希望能解除大家的疑问。

 

1. 类的构造函数在什么时候被调用?它有什么用?

构造函数是一种特殊的类成员函数,只要创建新的类对象,就会调用构造函数(比如有个foo类,那么运行foo FOO;来产生一个foo类对象的时候,构造函数就会被调用)。它的作用是确保每个对象的数据成员有合适的初始值。

 

2.何时需要自己编写构造函数?如何编写?

用户应当为自己的类编写默认构造函数(C++ Primer 第4版 P392有过强调),利用它来给类成员初始化一个初值。如果需要传递参数,还可以对构造函数进行重载。因此,一个类可以存在多个构造函数。

注意:编译器并不会帮你的类成员初始化一个初值。用户定义的类中没有显式的定义任何构造函数,编译器就会自动为该类暗中声明一个trivial默认构造函数,并且在符合条件时(具体请参照inside the c++ P40,比较深的讨论了,这里不涉及),合成一个合成的默认构造函数(synthesized default constructor),该函数只会初始化全局域中的对象,而不负责局部作用域中的对象,而我们的类对象成员显然不应该是全局的。

以下例子可以证明:

#include <iostream>using namespace std;class foo{private:         int a;public:         void print(void)         {                   cout<<a<<endl;         }};int  main(){         foo FOO;         FOO.print();         return0;}

 

大家可以看一下a的输出结果,本机的结果是-858993460,验证了以上说法。

下面来说明如何编写默认构造函数:

对于上面那个例子,foo类可以写成

class foo{private:         int a;public:         void print(void) //声明中定义         {                   cout<<a<<endl;         }    foo() :a(10)// 声明中定义,初始化列表,a的值请随意    {};};

也可以写成

class foo{private:         int a;public:         void print(void) //声明中定义         {                   cout<<a<<endl;         }    foo()//声明中定义,运行时赋值{a = 10};};

另外还可以将声明与定义分离,在类的外部按照以上两种方式进行定义(外部定义时应注意加上域标识符)。注意:在类的内部直接声明中定义会被编译器默认为inline函数,如果语句较长,还是应该在类的外部进行定义。

那么使用初始化列表和在构造函数的函数体内对类成员进行赋值有什么区别,其实各位大师的书里都说的很明白了(1)如果只有内置类型(如int,char)和复合类型(指针,引用,数组)的类成员,那么两者没有太大区别。(2)对于没有默认构造函数(即用户自己只定义了非默认构造函数)的类,以及具有const或const引用类型成员的类来说,必须使用初始化列表(因为没有默认构造函数就无法创建接受赋值的对象,更谈不上给它赋值。而const成员变量,很显然不能当左值,只能用初始化列表的方法来初始化)

(3)如果有类成员,那么使用初始化列表的效率将更高。我用个例子说明一下为什么,

#include <iostream>using namespace std;class foo{private:int a;public:void print(void){cout<<a<<endl;}foo():a(10)//构造函数{cout<<"default constuctor called"<<endl;}foo(const foo& F):a(F.a)//拷贝构造函数{cout<<"copying constuctor called"<<endl;}foo& operator=(const foo& F)//赋值符{cout<<"= called"<<endl;//未处理自赋值a = F.a;return *this;}};class fun{private:foo fo;public:fun(const foo& FOO):fo(FOO);//fun类的构造函数{//fo=FOO;}};int  main(){foo FOO;fun FUN(FOO);return 0;}

这里首先定义了2个类foo和fun,将foo类的对象作为fun类的成员,然后在main函数中利用foo的一个对象FOO作为参数传递给fun类的构造函数来产生一个fun类的对象FUN。

我们可以发现如果使用初始化列表,那么结果是

default constructor called(这个是创建FOO时调用的)

copying constructor called

如果在构造函数体内赋值,那么结果是

default constructor called

default constructor called

= called

很显然,当采用初始化列表的fun类的构造函数被调用时,只需调用foo类的拷贝构造函数。而fun类的构造函数的函数体内赋值时,需要先调用foo类的默认构造函数构造出fo,再调用重载的“=”对其进行赋值,执行效率明显不如初始化列表。

So,一个好的原则是,能使用初始化列表的时候尽量使用初始化列表,至少它不会比函数体内赋值来初始化更差。当然也有很多无法完全使用初始化列表的情况,这时还是需要在构造函数进行处理。

另外要明确一点,默认构造函数并不一定是不带形参的构造函数,具有形参并且每个形参都有默认值的构造函数也被认为是默认构造函数。以下用一个例子说明:

#include <iostream>#include <string>using namespace std;struct Sample//相当于成员属性为public的class{string sPtr;int m_i;int m_j;Sample(const string&str="",int i=1, int j=2):sPtr(str),m_i(i),m_j(j){         cout<<"defaultconstructor called!"<<endl;}//三个形参都有默认值,为默认构造/*Sample():m_i(1),m_j(2),sPtr(s){   cout<<"default constructor called!"<<endl;}//没有给出形参,默认构造*/Sample(const string& str,int i, intj):sPtr(str),m_i(i),m_j(j)//没有默认值的形参需要放在前面{         cout<<"constructorcalled!"<<endl;}//str没有默认值,为重载};int main(){         string s("Hello World!");         string& s1=s;         Sample a(s1,3,4);         Sample b;         return 0;}

大家可以自己观察下结果,如果有两个默认构造函数存在,编译器会报错,请各位自己试一下。