直接初始化与复制初始化
2014-09-20 00:18
176 查看
这是C++中所支持的两种初始化方式。
复制初始化使用=符号,而直接初始化将初始化式放在圆括号中。
(1)对于一般的内建类型,这两种初始化基本上没有区别。
int a(5);//直接初始化
int a=5;//复制初始化
int a=int (5);//直接初始化
(2)当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后使用复制构造函数将那个临时对象复制到正在创建的对象。
string null_book = "9-999-99999-9 ";//copy-initialization
string dots(10, '. ');//direct-initialization
string empty_copy = string();//先direct,然后copy-initialization
string empty_direct;//direct-initialization
对于类类型对象,只有指定单个实参或显式创建一个临时对象用于复制时,才使用复制初始化。
创建dots时,调用参数为一个数量和一个字符的string构造函数并直接初始化dots的成员。创建null_book时,编译器首先调用接受一个C风格字符串形参的string构造函数,创建一个临时对象,然后,编译器使用string复制构造函数将null_book初始化为那个临时对象的副本。
empty_copy和empty_direct的初始化都调用默认构造函数。对前者初始化时,默认构造函数创建一个临时对象,然后复制构造函数用该对象初始化empty_copy。对后者初始化时,直接运行empty_direct的默认构造函数。
支持初始化的复制形式主要是为了与C的用法兼容。当情况许可时,可以允许编译器跳过复制构造函数直接创建对象,但编译器没有义务这样做。
通常直接初始化和复制初始化仅在低级别优化上存在差异。然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:
ifstream file1("filename");//ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private
//This initialization is okay only if the sales_item(const string&) constructor is not explicit
Sales_item item = string("9-999-99999-9");
...
item的初始化是否正确,取决于正在使用哪个版本的Sales_item类。某些版本将参数为一个string的构造函数定义为explicit。如果构造函数是显示的,则初始化失败;如果构造函数不是显示的,则初始化成功。
对于复制初始化,最显示的调用手段就是使用“=”符号(这个时候应该成为复制初始化符号)。那么还有许多地方是隐式的调用。如参数传递时,函数返回时,初始化容器时!
1)对于参数传递:我们知道除非是引用参数,否则就是一个使用上层对象复制初始化函数参数的过程。
2)对于函数返回值:我们知道除非是引用返回,否则在return的那个语句就是使用函数内的对象,复制初始化一个上层对象(通常是临时的,然后马上有被用于)
3)在某些容器初始化的过程中如:
vect<string> svec(5);
这里的过程就是,先使用string默认构造出一个实例对象,然后使用这个对象,复制初始化其它的元素。这个过程是容器的实现细节,其实从外面看,可以理解为直接初始化。
4)数组初始化,有时候使用这样的语法:
Sales_item primer_eds[]={ string("1231231"),
string("3123123")
}
可知这个过程,就是一个先调用直接初始化生成string,然后继续隐式调用直接初始化生成Sales_item。最后使用复制初始化,给那个数组的各个元素初始化。
这儿是对复制构造函数和复制初始化的介绍:http://www.cnblogs.com/xkfz007/archive/2012/03/01/2376376.html
下面是一个测试构造函数的类:
[cpp] view
plaincopyprint?
#include <iostream>
using namespace std;
class A{
friend ostream& operator<<(ostream& os,const A& rhs);
public:
A(int a=10):m_a(a){
id=count++;
cout<<"A(int a):id="<<id<<endl;
}
A(const A& rhs):m_a(rhs.m_a){
id=count++;
cout<<"A(const A&rhs):id="<<id<<endl;
}
A& operator=(const A& rhs){
if(this !=&rhs){
id=count++;
m_a=rhs.m_a;
}
return *this;
}
~A(){
cout<<"~A:id="<<id<<endl;
}
private:
int m_a;
int id;
static int count;
};
int A::count=0;
ostream& operator<<(ostream& os,const A& rhs){
os<<"<A:m_a="<<rhs.m_a<<",id="<<rhs.id<<">";
return os;
}
int main1(){
A a1;
cout<<a1<<endl;
A a2=a1;
cout<<a2<<endl;
A a3;
cout<<a3<<endl;
a3=a2;
cout<<a3<<endl;
A a4(a3);
cout<<a4<<endl;
return 0;
}
int main(){
A a1,a2(5),a3(200);
A b=A(8000);
cout<<b<<endl;
A arr[]={a1,a2,a3};
int i(5),j=5;
int k=int();
cout<<i<<j<<k<<endl;
}
赋值运算符(operator=)和复制构造函数:A(A& a){}都是用已存在A的对象来创建另一个对象B。不同之处在于:赋值运算符处理两个已有对象,即赋值前B应该是存在的;复制构造函数是生成一个全新的对象,即调用复制构造函数之前B不存在。
exp:
CTemp B(A); //复制构造函数,C++风格的初始化
CTemp B=A; //仍然是复制构造函数,不过这种风格只是为了与C兼容,与上面的效果一样
在这之前B不存在,或者说还未构造好。
CTemp B;
B = A; //赋值运算符
在这之前B已经通过默认构造函数构造完成。
-----------------------------------------------------------------------------------------------------------------------
1、为什么要有拷贝构造函数,它跟构造函数有什么区别?
答:拷贝构造函数其实也是构造函数,只不过它的参数是const的类自身的对象的引用。如果类里面没有指针成员(该指针成员指向动态申请的空间),是没有必要编写拷贝构造函数的。我们知道,如果有一个类CObj,它已经产生了一个对象ObjA,现在又用CObj去创建ObjB,如果程序中使用语句ObjB
= ObjA;也就是说直接使用ObjA的数据给ObjB赋值。这对于一般的类,没有任何问题,但是如果CObj里面有个char* pStr的成员,用来存放动态申请的字符串的地址,在ObjA中使用new方法动态申请了内存并让ObjA.pStr指向该申请的空间,在OjbB
= OjbA之后,ObjA.pStr和ObjB.pStr将同时指向那片空间,这样到导致了谁也不知道到底该由谁来负责释放那块空间,很有可能导致同一块内存被释放两次。使用拷贝构造函数,先申请ObjA.pStr所指向的空间大小的空间,然后将空间内容拷贝过来,这样就不会同时指向同一块内存,各自有各自申请的内存,各自负责释放各自申请的内存,从而解决了刚才的问题。所以这里的“拷贝”拷贝的是动态申请的空间的内容,而不是类本身的数据。另外注意到,拷贝构造函数的参数是对象的引用,而不是对象的指针。至于为什么要用引用,不能够用指针暂时还没有搞明白,等搞明白了再说。
2、为什么要对=赋值操作符进行重载?
答:接上面的例子,用户在使用语句ObjB = ObjA的时候,或许ObjB的pStr已经指向了动态申请的空间,如果直接简单将其指向的地址覆盖,就会导致内存泄露,所以需要对=赋值操作符进行重载,在重载函数中判断pStr如果已经指向了动态申请的空间,就先将其释放。
3、拷贝构造函数和=赋值操作符重载的关系。
答:从原文的例子中可以看出,=赋值操作符重载比拷贝构造函数做得要多,它除了完成拷贝构造函数所完成的拷贝动态申请的内存的数据之外,还释放了原本自己申请的内存空间。所以原文最后给出的拷贝构造函数的实现可以使用=赋值操作符的重载来完成。
4、拷贝构造函数何时被调用?
a.对象的直接赋值也会调用拷贝构造函数(既然有了=赋值操作符重载,为什么还要调用拷贝构造函数?);
b.函数参数传递只要是按值传递也调用拷贝构造函数;
c.函数返回只要是按值返回也调用拷贝构造函数。
5、如问题4的回答中所提问的,既然有了=赋值操作符重载,为什么还要调用拷贝构造函数?
答:写个小程序测试一下。
复制初始化使用=符号,而直接初始化将初始化式放在圆括号中。
(1)对于一般的内建类型,这两种初始化基本上没有区别。
int a(5);//直接初始化
int a=5;//复制初始化
int a=int (5);//直接初始化
(2)当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后使用复制构造函数将那个临时对象复制到正在创建的对象。
string null_book = "9-999-99999-9 ";//copy-initialization
string dots(10, '. ');//direct-initialization
string empty_copy = string();//先direct,然后copy-initialization
string empty_direct;//direct-initialization
对于类类型对象,只有指定单个实参或显式创建一个临时对象用于复制时,才使用复制初始化。
创建dots时,调用参数为一个数量和一个字符的string构造函数并直接初始化dots的成员。创建null_book时,编译器首先调用接受一个C风格字符串形参的string构造函数,创建一个临时对象,然后,编译器使用string复制构造函数将null_book初始化为那个临时对象的副本。
empty_copy和empty_direct的初始化都调用默认构造函数。对前者初始化时,默认构造函数创建一个临时对象,然后复制构造函数用该对象初始化empty_copy。对后者初始化时,直接运行empty_direct的默认构造函数。
支持初始化的复制形式主要是为了与C的用法兼容。当情况许可时,可以允许编译器跳过复制构造函数直接创建对象,但编译器没有义务这样做。
通常直接初始化和复制初始化仅在低级别优化上存在差异。然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:
ifstream file1("filename");//ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private
//This initialization is okay only if the sales_item(const string&) constructor is not explicit
Sales_item item = string("9-999-99999-9");
...
item的初始化是否正确,取决于正在使用哪个版本的Sales_item类。某些版本将参数为一个string的构造函数定义为explicit。如果构造函数是显示的,则初始化失败;如果构造函数不是显示的,则初始化成功。
对于复制初始化,最显示的调用手段就是使用“=”符号(这个时候应该成为复制初始化符号)。那么还有许多地方是隐式的调用。如参数传递时,函数返回时,初始化容器时!
1)对于参数传递:我们知道除非是引用参数,否则就是一个使用上层对象复制初始化函数参数的过程。
2)对于函数返回值:我们知道除非是引用返回,否则在return的那个语句就是使用函数内的对象,复制初始化一个上层对象(通常是临时的,然后马上有被用于)
3)在某些容器初始化的过程中如:
vect<string> svec(5);
这里的过程就是,先使用string默认构造出一个实例对象,然后使用这个对象,复制初始化其它的元素。这个过程是容器的实现细节,其实从外面看,可以理解为直接初始化。
4)数组初始化,有时候使用这样的语法:
Sales_item primer_eds[]={ string("1231231"),
string("3123123")
}
可知这个过程,就是一个先调用直接初始化生成string,然后继续隐式调用直接初始化生成Sales_item。最后使用复制初始化,给那个数组的各个元素初始化。
这儿是对复制构造函数和复制初始化的介绍:http://www.cnblogs.com/xkfz007/archive/2012/03/01/2376376.html
下面是一个测试构造函数的类:
[cpp] view
plaincopyprint?
#include <iostream>
using namespace std;
class A{
friend ostream& operator<<(ostream& os,const A& rhs);
public:
A(int a=10):m_a(a){
id=count++;
cout<<"A(int a):id="<<id<<endl;
}
A(const A& rhs):m_a(rhs.m_a){
id=count++;
cout<<"A(const A&rhs):id="<<id<<endl;
}
A& operator=(const A& rhs){
if(this !=&rhs){
id=count++;
m_a=rhs.m_a;
}
return *this;
}
~A(){
cout<<"~A:id="<<id<<endl;
}
private:
int m_a;
int id;
static int count;
};
int A::count=0;
ostream& operator<<(ostream& os,const A& rhs){
os<<"<A:m_a="<<rhs.m_a<<",id="<<rhs.id<<">";
return os;
}
int main1(){
A a1;
cout<<a1<<endl;
A a2=a1;
cout<<a2<<endl;
A a3;
cout<<a3<<endl;
a3=a2;
cout<<a3<<endl;
A a4(a3);
cout<<a4<<endl;
return 0;
}
int main(){
A a1,a2(5),a3(200);
A b=A(8000);
cout<<b<<endl;
A arr[]={a1,a2,a3};
int i(5),j=5;
int k=int();
cout<<i<<j<<k<<endl;
}
赋值运算符(operator=)和复制构造函数:A(A& a){}都是用已存在A的对象来创建另一个对象B。不同之处在于:赋值运算符处理两个已有对象,即赋值前B应该是存在的;复制构造函数是生成一个全新的对象,即调用复制构造函数之前B不存在。
exp:
CTemp B(A); //复制构造函数,C++风格的初始化
CTemp B=A; //仍然是复制构造函数,不过这种风格只是为了与C兼容,与上面的效果一样
在这之前B不存在,或者说还未构造好。
CTemp B;
B = A; //赋值运算符
在这之前B已经通过默认构造函数构造完成。
-----------------------------------------------------------------------------------------------------------------------
1、为什么要有拷贝构造函数,它跟构造函数有什么区别?
答:拷贝构造函数其实也是构造函数,只不过它的参数是const的类自身的对象的引用。如果类里面没有指针成员(该指针成员指向动态申请的空间),是没有必要编写拷贝构造函数的。我们知道,如果有一个类CObj,它已经产生了一个对象ObjA,现在又用CObj去创建ObjB,如果程序中使用语句ObjB
= ObjA;也就是说直接使用ObjA的数据给ObjB赋值。这对于一般的类,没有任何问题,但是如果CObj里面有个char* pStr的成员,用来存放动态申请的字符串的地址,在ObjA中使用new方法动态申请了内存并让ObjA.pStr指向该申请的空间,在OjbB
= OjbA之后,ObjA.pStr和ObjB.pStr将同时指向那片空间,这样到导致了谁也不知道到底该由谁来负责释放那块空间,很有可能导致同一块内存被释放两次。使用拷贝构造函数,先申请ObjA.pStr所指向的空间大小的空间,然后将空间内容拷贝过来,这样就不会同时指向同一块内存,各自有各自申请的内存,各自负责释放各自申请的内存,从而解决了刚才的问题。所以这里的“拷贝”拷贝的是动态申请的空间的内容,而不是类本身的数据。另外注意到,拷贝构造函数的参数是对象的引用,而不是对象的指针。至于为什么要用引用,不能够用指针暂时还没有搞明白,等搞明白了再说。
2、为什么要对=赋值操作符进行重载?
答:接上面的例子,用户在使用语句ObjB = ObjA的时候,或许ObjB的pStr已经指向了动态申请的空间,如果直接简单将其指向的地址覆盖,就会导致内存泄露,所以需要对=赋值操作符进行重载,在重载函数中判断pStr如果已经指向了动态申请的空间,就先将其释放。
3、拷贝构造函数和=赋值操作符重载的关系。
答:从原文的例子中可以看出,=赋值操作符重载比拷贝构造函数做得要多,它除了完成拷贝构造函数所完成的拷贝动态申请的内存的数据之外,还释放了原本自己申请的内存空间。所以原文最后给出的拷贝构造函数的实现可以使用=赋值操作符的重载来完成。
4、拷贝构造函数何时被调用?
a.对象的直接赋值也会调用拷贝构造函数(既然有了=赋值操作符重载,为什么还要调用拷贝构造函数?);
b.函数参数传递只要是按值传递也调用拷贝构造函数;
c.函数返回只要是按值返回也调用拷贝构造函数。
5、如问题4的回答中所提问的,既然有了=赋值操作符重载,为什么还要调用拷贝构造函数?
答:写个小程序测试一下。
相关文章推荐
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- C++直接初始化和复制初始化1
- C++的一大误区-深入理解直接初始化与复制初始化的区别
- 深入解释直接初始化与复制初始化的区别
- c++中的直接初始化与复制初始化
- C++直接初始化和复制初始化2
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- 直接初始化和复制初始化
- 直接初始化和复制初始化有什么不同
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- 直接初始化与拷贝(复制)初始化
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- C++对象的直接初始化和复制初始化
- C++中直接初始化与复制初始化
- 直接初始化与复制初始化
- C++的一大误区——深入解释直接初始化与复制初始化的区别
- 直接初始化和复制初始化
- C++的一大误区——深入解释直接初始化与复制初始化的区别