C++ - 成员初始化探讨

来源:互联网 发布:ssh工具 知乎 编辑:程序博客网 时间:2024/06/05 10:30

/* discuss how to initialize static and const/& member in C++ class*/


// const member must be initialized in member-initilization list
// static member must be initialized in source file globally

类对象的构造顺序是这样的:
1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员
2.进入构造函数后在构造函数中执行一般计算



About "member-initialization list" and const member

C++初始化类的成员,不但可以用构造函数(constructor)完成,而且可以用初始化类成员列表来完成。

使用初始化列表有两个原因:

1. 必须这样做:
        如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。

class ABC
{
public:
         ABC(
int x,int y,int z);
private:
         
int a;
         
int b;
         
int c;
}
;
class MyClass
{
public:
        MyClass():abc(
1,2,3){}
private:
        ABC abc;
}
;

        因为ABC有了显式的带参数的构造函数,那么它是无法依靠编译器生成无参构造函数的,所以没有三个int型数据,就无法创建ABC的对象。
        ABC类对象是MyClass的成员,想要初始化这个对象abc,那就只能用成员初始化列表,没有其他办法将参数传递给ABC类构造函数。


        另一种情况是这样的:当类成员中含有一个const对象或者一个引用时,它们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中做的是对他们的赋值,这样是不被允许的


2. 效率要求这样做:
       类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对他们的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化列表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次复制操作符的调用,如果是类对象,这样做效率就得不到保障

注意:构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在声明时的顺序是一致的,与列表的先后顺序无关,所以要特别注意,保证两者顺序一致才能真正保证其效率。初始化列表会在相应构造函数正式调用前被调用

为了说明清楚,假设有这样一个类:
class foo{
   private :
    int a, b;
};
<1>  foo(){}和foo(int i = 0){}都被认为是默认构造函数,因为后者是默认参数。两者不能同时出现。
<2>  初始化列表的初始化顺序不是按照列表的顺序,而是按照变量声明的顺序。比如foo里面,a在b之前,             那么会先构造a再构造b。所以无论 foo():a(b + 1), b(2){}还是foo():b(2),a(b+1){}都不会让a得到期望的值。如果先声明b再声明a则会更好。
<3>  初始化列表能够对const成员初始化。比如foo里面有一个int const c;则foo(int x) : c(x){}可以让c值赋成x。不过需要注意的是,c必须在每个构造函数(如果有多个)都有值。
<4>  在继承里面,只有初始化列表可以构造父类的private成员。比如说
class child : public foo{
}
foo里面的构造函数是这样写的:foo (int x) { a = x; }.
而在child里面写child(int x) { foo(x); }是通过不了编译的。只有把父类初始化改为foo(int x) : a(x){},而子类构造写作child (int x) : foo(x){}才可以。

 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

 

class A
{
  public:
    int member_var; //成员变量
    A();            //构造函数
}

A::A():member_var(0)
{
}

A::A()
{
   member_var=1;
}

 

第一种方法是真正的初始化(initialization), 而在构造函数内实现的“=”操作其实是赋值(assign)。
区别大概如下:
 
  1. 普通变量编译器都会默认的初始化。它们既能初始化,也能被赋值的;而常量(const)只能被初始化,不能赋值,否则与变量就无区别了。所以常量成员(const member)只能用成员初始化列表来完成他们的“初始化”,而不能在构造函数内为他们“赋值”。
  2. 我们知道类的对象的初始化其实就是调用他的构造函数完成,如果没有写构造函数,编译器会为你默认生成一个。如果你自定义了带参数的构造函数,那么编译器将不生成默认构造函数。这样这个类的对象的初始化必须有参数。如果这样的类的对象来做另外某个类的成员,那么为了初始化这个成员,你必须为这个类的对象的构造函数传递一个参数。同样,如果你在包含它的这个类的构造函数里用“=”,其实是为这个对象“赋值”而非“初始化”它。所以一个类里的所有构造函数都是有参数的,那么这样的类如果做为别的类的成员变量,你必须显式的初始化它,只能通过成员初始化列表来完成初始化。

const 成员和引用类型的成员都是必须在定义时初始化的,一般情况下,作为局部常量/变量使用的时候,定义和初始化可以一起进行。但是在类定义中不可行。因为定义类,实际是在申明一个对象的类型,是不可以直接在定义语句后面使用 = 来进行初始化的。因此,类构造提供了一个初始化列表来对需要初始化的成员进行初始化,



About static member

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域操作符来指出静态成员所属的类。但如果静态成员是整型或是枚举型const,则可以在类声明中初始化!!!
比如:
/* (#) Test.h */
class Test {
public:
    // 申明int 型的MASK 常量
    static const int MASK;
};

/* (#) Test.cpp */
#include "Test.h"

// 定义Test::MASK 常量,注意这里不需要static 关键字
const int Test::MASK = 0xFFFF;


static const 定义的常量是全局的,对该类的每一个实例(对象)甚至在全局范围都有效,而且一致。
与全局对象一样对于静态数据成员在程序中也只能提供一个定义,这意味着静态数据成员的初始化不应该被放在头文件中而应该放在含有类的非inline函数定义的文件中。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


A useful blog about this topic:
http://blog.csdn.net/houdy/archive/2007/07/29/1714906.aspx