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

转载 2012年03月22日 10:43:22

转自:http://blog.csdn.net/tianwailaibin/article/details/6322828

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

 

转自: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[n];
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;}
这样就能保证不发生内存泄漏。


Fiddler之Sessions列表相关知识

Fiddler界面的左边就是Session列表,这里显示了Fiddler捕获的每个session 列表栏 # --session idResult --响应状态码Protocol --Se...

CSS列表的相关知识

1:有序列表、无序列表与定义列表   测试 无序列表 奔驰 通用 丰田 ...

今天是第一天所以写两个,这个是总结以前学到的一些知识看看自己以后的学习方向

从最开始接触编程开始回忆: 最开始我学习的是vb,后来是c再后来是html还有c++、c#、css、asp、汇编、asp.net最后是java。我学习的系统有win还有小红帽,ubuntu,学习的网...

今天零碎学习的小知识点

1.枚举变量的值如何计算? 在枚举中,某个枚举变量的值默认为前一个变量的值加1,如果第一个枚举变量没有赋值,则默认为0。 如:enum{a,b=5,c,d=4,e} 则实际上是a=0,b=5,c...
  • nawuyao
  • nawuyao
  • 2015年11月30日 22:51
  • 385

据说程序员是最爱学习的群体,IT男都知道,这个行业日新月异,必须不断地学习新知识,不断地为自己注入新鲜的血液,才能使自己跟上技术的步伐。 今天我们来讲一下Android中BroadcastReceiv

Android 中的BroadCastReceiver                                                 作者:阿福       BroadCa...

今天在csdn看到一博主今年大三,初中就喜欢上编程,学习了各种知识,初中…挺震惊。不禁细数我的编程史,挺有感。

今天在csdn看到一博主今年大三,初中就喜欢上编程,学习了各种知识,初中…挺震惊。不禁细数我的编程史,挺有感。 要说我第一次接触编程,也是在初中,但这次接触我觉得并没有对我有所影响。 初中时期,记不...
  • bboxhe
  • bboxhe
  • 2015年06月18日 22:27
  • 542

2015年1月9日从今天起我定记录一下我自己的学习成果,知识的积累从一点一滴开始!window下搭建solr

solr-4.7.2.的搭建其实并不难,网上已经有很多教程我,我今天主要说一下我自己的搭建思路 solr是什么: Solr它是一种开放源码的、基于 Lucene Java 的搜索服...

jQuery学习笔记--JqGrid相关操作 方法列表 备忘 重点讲解(超重要)

JqGrid相关操作备忘 方法列表 1.获得当前列表行数:$("#gridid").getGridParam("reccount"); 2.获取选中行数据(json):$("#...

jQuery学习笔记--JqGrid相关操作 方法列表 备忘 重点讲解(超重要)

JqGrid相关操作备忘 方法列表 特别推荐:怎样获取某一方某一列的值: [html] view plain copy var row...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:今天学习了一些初始化列表的相关知识
举报原因:
原因补充:

(最多只允许输入30个字)