现在的位置: 首页 > 综合 > 正文

C++构造函数:初始化的那些事

2018年02月19日 ⁄ 综合 ⁄ 共 8070字 ⁄ 字号 评论关闭

C++构造函数:初始化的那些事


       
 说起C++的构造函数,大家绝不会陌生,似乎老生常谈,我一度也这么认为,但是有时概念的不清晰也许会使我们陷入莫名的困惑,下面我们就来初步的探索一下C++构造函数中那些有关初始化的事。(vs2010)

 

1、默认的初始化

    想必大家都知道C++会在我们没有提供任何构造函数的情况下为我们提供默认的缺省的构造函数,而构造函数的作用无非两样,第一步分配空间,初始化成员变量。换句话说,当我们调用构造函数时,发生了下面两件事情:

(1) 为对象非配足够的内存块,若是new关键字产生对象,则会调用operator new .

(2) 调用构造函数生成对象,并返回指针。

    然而实际上构造过程中是否对成员变量初始化这一概念似乎不太明确,下面我们自己动手看看:

class base{
public:
        int getData(){return data;}
private:
        int data;
};
 
class derived:publicbase{
public:
        int getParentData(){returnbase::getData();}
        int getData(){return data;}
private:
        int data;
};
#include"construct.h"
#include<stdio.h>
 
int main()
{
        base b;
        printf("b.getData() =%d\n",b.getData());
        derived d;
        d.getParentData();
        printf("d.getParentData() =%d\n",d.getParentData());
        d.getData();
        printf("d.getData() =%d\n",d.getData());
        getchar();
        return 0;
}

程序的输出结果表明:默认构造函数并不会初始化成员变量。但事实真的如此吗,我们进一步探讨一下,请继续看示例:

class some{
public:
        some():a(100){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        int getData(){return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};
 
class derived:publicbase{
public:
        int getParentData(){returnbase::getData();}
        int getData(){return data;}
        int getParentSomeData(){returnbase::getSomeData();}
private:
        int data;
};
#include"construct.h"
#include<stdio.h>
 
int main()
{
        base b;
        printf("b.getData() =%d\n",b.getData());
        printf("b.getSomeData() =%d\n",b.getSomeData());
        derived d;
        printf("d.getParentSomeData() =%d\n",d.getParentSomeData());
        printf("d.getParentData() =%d\n",d.getParentData());
        printf("d.getData() =%d\n",d.getData());
        getchar();
        return 0;
}

输出结果却表明:base 中的s会被初始化,这个原因是默认构造函数会调用成员变量的默认构造函数初始化对其进行初始化工作。而C++内置类型没有默认构造,一般是否会被初始化可能与具体的声明方式和编译器有关,他就像你平时声明int a 一样,是否初始化时机不同结果不同的。这个我们可以初步的验证一下:

static base sb;
printf("sb.getData()= %d\n",sb.getData());

结果表明,在该种情景下数据会被初始化:所以我们可以得出一个初步的结论:

(1)  C++默认构造函数会调用成员变量的默认构造函数来对其进行初始化;

(2)  对于类类型成员变量,它一般必须要存在一个无参构造函数,否则很多时候会造成包含它的类的默认构造函数无法正常工作;

(3)  对于内置类型的成员变量其实同样可以理解默认构造函数会调用其无参构造函数来初始化它,但由于它是内置类型,其默认的缺省构造函数的行为并非保持固定的行为,这与他们的属性有关。就像我们单独声明他一样。也即(a)当该变量所在的对象是局部变量时他通常不会被初始化;(b)当该变量所在的对象时全局的或者静态类型时,它通常被初始化的;(c)即使他是常量同样满足(a)(b).

2、基类默认,派生类自定义

    前面有一篇文章我们讲到复制函数时知道,当派生类定义了复制函数时,就不会默认调用基类的对应的默认复制函数,那么构造函数是否也是如此呢?看段代码吧:

class some{
public:
        some():a(100){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};
 
class derived:publicbase{
public:
        derived(){}
        int getParentData(){returnbase::getData();}
        int getData(){return data;}
        int getParentSomeData(){returnbase::getSomeData();}
private:
        int data;
};
#include"construct.h"
#include<stdio.h>
 
int main()
{
        base b;
        printf("b.getData() =%d\n",b.getData());
        printf("b.getSomeData() =%d\n",b.getSomeData());
        derived d;
        printf("d.getParentSomeData() =%d\n",d.getParentSomeData());
        printf("d.getParentData() =%d\n",d.getParentData());
        printf("d.getData() =%d\n",d.getData());
 
        static base sb;
        printf("sb.getData() =%d\n",sb.getData());
 
        const base cb;
        printf("sb.getData() =%d\n",cb.getData());
        getchar();
        return 0;
}

事实证明,当派生类自定义无参构造函数时,会隐式调用基类的缺省或者无参构造函数,下面是无参构造函数的例子:

class some{
public:
        some():a(100){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        base():data(8888){}
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};
 
class derived:publicbase{
public:
        derived(){}
        int getParentData(){returnbase::getData();}
        int getData(){return data;}
        int getParentSomeData(){returnbase::getSomeData();}
private:
        int data;
};
#include"construct.h"
#include<stdio.h>
 
int main()
{
        base b;
        printf("b.getData() =%d\n",b.getData());
        printf("b.getSomeData() =%d\n",b.getSomeData());
        derived d;
        printf("d.getParentSomeData() =%d\n",d.getParentSomeData());
        printf("d.getParentData() =%d\n",d.getParentData());
        printf("d.getData() = %d\n",d.getData());
 
        static base sb;
        printf("sb.getData() =%d\n",sb.getData());
 
        const base cb;
        printf("sb.getData() =%d\n",cb.getData());
        getchar();
        return 0;
}

3、基类仅有有参构造函数

    当基类仅有有参构造函数时,其实派生类是必须显示调用的,否则无法通过编译:

class some{
public:
        some():a(100){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        base(int d):data(d){}
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};
 
class derived:publicbase{
public:
        derived(){}
        int getParentData(){returnbase::getData();}
        int getData(){return data;}
        int getParentSomeData(){returnbase::getSomeData();}
private:
        int data;
};
#include"construct.h"
#include<stdio.h>
 
int main()
{
        base b;
        printf("b.getData() = %d\n",b.getData());
        printf("b.getSomeData() =%d\n",b.getSomeData());
        derived d;
        printf("d.getParentSomeData() =%d\n",d.getParentSomeData());
        printf("d.getParentData() =%d\n",d.getParentData());
        printf("d.getData() =%d\n",d.getData());
 
        static base sb;
        printf("sb.getData() =%d\n",sb.getData());
 
        const base cb;
        printf("sb.getData() =%d\n",cb.getData());
        getchar();
        return 0;
}

编译失败,提示无合适的默认构造函数,此时我们必须显示调用,如下:

class some{
public:
        some():a(100){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        base(int d):data(d){}
        //base(int d):data(d){}
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};
 
class derived:publicbase{
public:
        derived():base(100){}
        int getParentData(){returnbase::getData();}
        int getData(){return data;}
        int getParentSomeData(){returnbase::getSomeData();}
private:
        int data;
};
#include"construct.h"
#include<stdio.h>
 
int main()
{
        /*base b;
        printf("b.getData() =%d\n",b.getData());
        printf("b.getSomeData() =%d\n",b.getSomeData());*/
        derived d;
        printf("d.getParentSomeData() =%d\n",d.getParentSomeData());
        printf("d.getParentData() =%d\n",d.getParentData());
        printf("d.getData() =%d\n",d.getData());
 
        /*static base sb;
        printf("sb.getData() =%d\n",sb.getData());
 
        const base cb;
        printf("sb.getData() =%d\n",cb.getData());*/
        getchar();
        return 0;
}

注释部分很好理解,没有默认构造函数的情况下,没有无参构造函数,base b等都无法编译通过。事实上只要我们定义了任何一个构造函数(包括无参,有参,拷贝构造),编译器就不会再为我们生成一个缺省的无参构造函数。而当类没有无参构造函数时,会造成许多麻烦,入1中讲到的它会使得包含该类型对象作为成员变量的默认构造函数甚至其他函数都无法正常工作,除非你非常确定该类对象在任何时候声明时都必须明确指定相关信息,否则,最好给它一个无参构造函数,例如:

class some{
public:
        some(int n):a(n){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        base(){}
        base(int d):data(d){}
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};
int main()
{
      base b;
      base b2(10);
      return 0;
 
}

结果是base的两个构造函数都无法正常工作,因为编译器无法隐式的初始化some类型的变量s,除非你显示初始化,向下面这样:

class some{
public:
        some(int n):a(n){}
        int getData(){return a;}
private:
        int a;
};
 
class base{
public:
        base():s(10){}
        base(int d):s(11),data(d){}
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        int data;
        some s;
};

当然这样也许也有他的好处,毕竟再也不会出现忘记初始化造成的苦恼,你懂的,有时候忘记初始化也许会带来无法弥补的灾难。

 

4、成员变量部分初始化

    前面我们知道,当我们使用默认构造函数或者无参构造函数时,成员变量会被通过调其自身所属类的默认构造函数来隐式初始化,那么如果我们使用有参构造函数,且只是显示初始化部分成员变量时,那么其他成员是否会同样被隐式初始化呢?

class some{
public:
        some():a(100){}
        int getData(){return a;}
private:
        int a;
};
 
class thing{
public:
        thing(){}
        thing(int t):a(t){}
private:
        int a;
};
 
class base{
public:
        base(){}
        base(int d):data(d),t(10){}
        int getData() const{return data;}
        int getSomeData(){return s.getData();}
private:
        thing t;
        int data;
        some s;
};
int main()
{
    base xb(10);
    printf("xb.getSomeData() =%d\n",xb.getSomeData());
}

结果表明,尽管我们只在base的有参构造函数中显示初始化了d,和t,但是s同样还是会隐式初始化。

 

5、基类的部分初始化

    上面提到在构造函数中如果没有对成员变量进行显示初始化,几乎总是被隐式初始化,这一点编译器非常尽职尽责,但是前提是你必须确保其存在缺省构造函数或者无参构造函数。那么在继承关系中又是怎样的呢?

#include<stdio.h>
class base0{
public:
        base0(){data ="NULL";}
        base0(char*):data("base0"){}
        voidmyName(){printf("%s\n",data);}
private:
        char *data;
};
 
class base1{
public:
        base1(){data ="NULL";}
        base1(char*):data("base1"){}
        voidmyName(){printf("%s\n",data);}
private:
        char *data;
};
 
class derived0:publicbase0,public base1{
public:
        derived0():base0(0){}
        void showParent()
        {
               base0::myName();
               base1::myName();
        }
};
int main()
{
        derived0 de;
        de.showParent();
}

结果表明:即使显示调用部分基类的构造函数,其他基类的默认或无参构造函数也会被隐式调用。

 

 

结论:

(1)在构造对象的同时,构造函数会企图初始化所有成员变量,当指定了显示的初始化方式则按指定方式初始化,否则调用其所属类的默认构造函数或者无参构造函数进行隐式初始化,如果是内置类型,则跟对象是否为局部变量相同,其初始化行为就好像是在当前声明对象的作用域单独声明该变量一样。

(2)按照(1)的结论,为了使得我们的类支持隐式初始化,必须提供隐式初始化所需要的机制,那就是要么类有缺省构造函数,要么为其提供一个无参构造函数。

(3)在继承关系的初始化规则中,无论何种情况,基类总是需要被构造的,当你显示指定其构造方式时,就会按照指定的方式构造该基类对象,若未指定,那么就会按照隐式构造方式(调用基类的缺省构造函数,或者无参构造函数)来构造基类对象,所以几乎无参构造函数总是需要的,如果你打算定制构造函数的话。

(4)当你定制了你的构造函数时(无论是无参,有参,拷贝构造),编译器便不再为你提供缺省的构造函数。这是给类添加一个无参函数几乎总是合适的(除非你希望对类的使用不允许发生太多隐式行为)。

(5)这部分内容可以和复制函数的一些特性对比,在构造函数中,只要你不显示指定构造方式,那么隐式构造几乎总是被执行,但是复制函数则不同,在复制函数中,只要你试图定制复制行为,那么所有的隐式行为将不会发生。

 

基于上述结论,当我们想避免我们的类所产生的对象因未初始化而带来各种令人困扰这一问题时,我们只需要在所有自定义类型中将内置类型显示初始化即可,同时记得为每一个常规类提供无参构造函数。

 

 

无参构造函数和缺省构造函数(默认构造函数)(个人理解)

缺省构造函数:编译器默认生成的,不进行任何显示构造或者初始化

无参构造函数:用户自定的不带任何参数的构造函数,但是可以在初始化列表中指定显示构造和初始化行为,个人认为当无参构造函数的初始化列表为空且函数体为空时行为与缺省构造函数应该是相同的。

 

抱歉!评论已关闭.