您的位置:首页 > 其它

今天学习了一些初始化列表的相关知识

2012-03-22 10:43 239 查看
转自:/article/8944923.html

类成员变量的初始化不是按照初始化表的顺序被初始化的,而是按照在类中声明的顺序被初始化的。为什么会这样呢?我们知道,对一个对象的所有成员来说,它们的析构函数被调用的顺序总是和它们在构造函数里被创建的顺序相反。那么,如果允许上面的情况(即,成员按它们在初始化列表上出现的顺序被初始化)发生,编译器就要为每一个对象跟踪其成员初始化的顺序,以保证它们的析构函数以正确的顺序被调用。这会带来昂贵的开销。所以,为了避免这一开销,同一种类型的所有对象在创建(构造)和摧毁(析构)过程中对成员的处理顺序都是相同的,而不管成员在初始化列表中的顺序如何。

转自:http://hi.baidu.com/a708791/blog/item/d9f297af9fe3d8034b36d644.html

C++语言规定,在类的定义中,所有数据成员的说明语句都视为声明,而不是定义。所以,不能在类体中直接指定数据成员的初值,对象的初始化工作只能通过调用对象的构造函数来完成完成。在构造函数中,初始化列表扮演了十分重要的角色。对于普通的数据成员而言,使用初始化列表和在构造函数体内赋初值,效果是一样的。 但是,在另外一些情况下,只能使用初始化列表对成员进行初始化,否则会发生编译错误。

例如,数据成员是引用、常变量、类对象(该类没有提供不带参数的构造函数)等等

见下面的程序:

#include <iostream>

using namespace std;

class A

{

int num;

public:

A(int i){ num=i; }

void show(){ cout<<num<<endl; }

};

class B

{

int &r;

const double PI;

A a;

public:

B(int i);

void show()

{

cout<<r<<endl;

cout<<PI<<endl;

a.show();

}

};

int e=5;

B::B(int i):r(e),PI(3.1415926),a(i){}

int main()

{

B(1).show();

return 0;

}

此程序能通过编译并正常运行。

在类B的构造函数中,将初始化列表中的任何一个成员的初始化工作移到函数体内,都会导致编译错误。

B::B(int i){

r=e; //这是为引用赋值,并不是初始化

PI=3.1415926; //常量的值不允许改变

a=A(i); //调用类B的构造函数之前,会先调用类A的构造函数A(),而此函数无定义

}

有时,程序员试图利用对象之间的赋值来代替初始化,而不是显式使用初始化列表。这样虽然不会发生编译错误,但却造成了程序运行效率的降低。

如下面的程序:

#include <iostream>

using namespace std;

class A

{

int num;

public:

A()

{

cout<<"In default constructor"<<endl;

}

A(int i)

{

cout<<"In user-defined constructor"<<endl;

num=i;

}

A & operator=(const A& a)

{

cout<<"Using assignment"<<endl;

num=a.num;

return *this;

}

void show()

{

cout<<num<<endl;

}

};

class B

{

A a;

public:

B(int i);

void show()

{

a.show();

}

};

B::B(int i)

{

a=A(i);

}

int main(int argc, char* argv[])

{

B b(1);

b.show();

return 0;

}

程序的输出结果是:

In default constructor

In user-defined constructor

Using assignment

1

如果没有显式地在初始化列表中对成员对象a进行初始化,则在进入类B的构造函数体之前,会先调用类A的默认构造函数。在类B的构造函数体内进行的是赋值操作,并且要调用类A的构造函数A(int)先生成一个无名对象。这样的初始化方式不仅逻辑结构不清晰,而且效率低下。如果将类B的构造函数改写成:

B::B(int i):a(i){}

则程序的输出结果变成:

In user-defined constructor

1

这样不仅完成了初始化工作,而且效率高,避免了不必要的操作。

有些内容不管有没有显式写进初始化列表,都是会被“强行”加进去的。这些内容包括: ①如果该类是某个类的派生类,那么它的直接基类的构造函数一定要出现在初始化列表中。程序员如果没有显式地在初始化列表中调用直接基类的构造函数,编译器就会把直接基类的默认构造函数插入到初始化列表中。

②如果该类包含其他类的对象作为其数据成员,那么这些对象的初始化工作也一定要在初始化列表中完成。要么是程序员显式地在初始化列表中给出对象的构造形式,否则编译器会在初始化列表中调用这些对象的默认构造函数来完成初始化。

使用初始化列表还有一个问题要注意,初始化列表中各部分的执行顺序与它们的书写顺序无关,而是遵循这样的规则:基类的构造函数最先执行,其他的成员按照它们在类中声明的顺序依次被初始化。

考察下面的程序:

#include<iostream>

using namespace std;

class A

{

public:

int num;

A(int i)

{

num=i;

cout<<"In A constructor"<<endl;

}

};

class B

{

public:

int num;

B(int i)

{

num=i;

cout<<"In B constructor"<<endl;

}

};

class C

{

protected:

int num;

public:

C(int i)

{

num=i;

cout<<"In C constructor"<<endl;

}

};

class D:public C

{

A a;

int num;

B b;

public:

D(int i):num(i++),b(i++),a(i++),C(i++){}

void show()

{

cout<<"C::num="<<C::num<<endl;

cout<<"a.num="<<a.num<<endl;

cout<<"num="<<num<<endl;

cout<<"b.num="<<b.num<<endl;

}

};

void main()

{

D d=1;

d.show();

}

如果按照初始化列表的书写顺序执行,那么程序的输出结果应该是:

In B constructor

In A constructor

In C constructor

C::num=4

a.num=3

num=1

b.num=2

而实际上程序的运行结果是:

In C constructor

In A constructor

In B constructor

C::num=1

a.num=2

num=3

b.num=4

在初始化列表中,无法完成为对象的数组成员进行初始化的工作,原因是C++语言没有提供这样的机制,所以只能在构造函数体内分别为成员数组的每个元素分别赋初值。

如果数组元素本身也是对象,那么这种赋值操作会造成较大的运行时开销。问题还不止这些。如果在类体中定义一个常量数组,如何进行初始化呢?

见下面的程序:

#include<iostream>

int *CreateArr(int n)

{

int *p=new int
;

for(int i=0;i<n;i++)

p=i+1;

return p;

}

using namespace std;

class A

{

int arrsize;

const int * const arr;

public:

A(int n):arr(CreateArr(n)){ arrsize=n;}

void show()

{

for(int i=0;i<arrsize;i++)

cout<<arr<<' ';

cout<<endl;

}

~A(){delete[] const_cast<int *>(arr);}

};

int main()

{

A a(3);

a.show();

return 0;

}

程序的输出结果是:

1 2 3

由于arr是一个指向常量的指针常量,所以,arr的初始化工作一旦完成,就不能改变arr数组元素的值,也不能将arr指向别处。客观上,arr与一个常量数组的作用是相同的。在上面的程序中,将函数CreateArr()移到类A体内作为该类的一个成员函数,程序运行结果是一样的。由于实际上是在构造函数中为arr分配了空间,所以应该在类A的析构函数函数中释放这部分空间,即在类A中定义:

~A(){delete[] arr;}

这样就能保证不发生内存泄漏。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: