今天学习了一些初始化列表的相关知识
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;}
这样就能保证不发生内存泄漏。
类成员变量的初始化不是按照初始化表的顺序被初始化的,而是按照在类中声明的顺序被初始化的。为什么会这样呢?我们知道,对一个对象的所有成员来说,它们的析构函数被调用的顺序总是和它们在构造函数里被创建的顺序相反。那么,如果允许上面的情况(即,成员按它们在初始化列表上出现的顺序被初始化)发生,编译器就要为每一个对象跟踪其成员初始化的顺序,以保证它们的析构函数以正确的顺序被调用。这会带来昂贵的开销。所以,为了避免这一开销,同一种类型的所有对象在创建(构造)和摧毁(析构)过程中对成员的处理顺序都是相同的,而不管成员在初始化列表中的顺序如何。
转自: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;}
这样就能保证不发生内存泄漏。
相关文章推荐
- 学习Javaweb的一些相关知识
- 今天开始要详细的记录学习sharepoint 的进度和相关的一些资料
- JS中一些对象的学习和一些函数的相关知识
- 今天关于一些学习了国际化的知识
- 今天继续学习安卓,好多不懂,看到网站那么高深的知识,我都不好意思发帖问一些小问题了,哈哈,不过学习不分大小,只看态度
- Android 学习第16课,java 包、类等相关的一些基础知识
- 学习中收集的一些有关网页UI设计、前端开发的相关知识网址分享,赶紧收藏走起。。。
- 今天是第一天所以写两个,这个是总结以前学到的一些知识看看自己以后的学习方向
- C++构造函数的初始化列表的学习
- 数据结构和算法学习第3天:队列的相关知识
- linux下多进程的文件拷贝与进程相关的一些基础知识
- 学习Java基础的一些知识(11)构造方法
- 加密相关的一些理论知识
- java学习笔记,关于java的一些基础知识,适用于初学者,第一节
- winform学习之-----关于按键操作的一些小知识(如何获取焦点所在的当前控件)20160623
- 音视频编解码相关知识学习
- 死锁相关知识的学习
- php及wml的一些相关知识
- 一些图论相关算法的学习研究和C#版演示程序
- 嵌入式arm学习总结(二)--arm相关知识