C++直接初始化和复制初始化

来源:互联网 发布:sql in 两个字段 编辑:程序博客网 时间:2024/05/01 13:06
现在正式对C++中对象建立和初始化做一个总结。

(1)复制初始化的基本原理
我们知道,对象在内存中的直接表象是在内存中占有一个一定大小的空间。分配空间是建立对象的第一步。但是刚刚分配的空间就像一个没有开垦的荒田,或者是前面对象占有之后留下来的残余,从理论上讲,由于不管是什么大家都是0101,所以就算没有初始化也是有值的,只是这个值你是不能正确使用的。所以建立对象的关键在于如何对该对象所占有的空间进行正确的初始化。

初始化虽然与赋值的结果很像,但是其所面临的状态和发生的时间是不同的。从他们的功能来讲,初始化与给某个变量赋值的唯一特点就是他是第一次初始化。当然当你认识到这点的时候,他们的区别也就是无所谓了。关键在于,初始化,是一个必须的赋值过程,因为如果你不做这方面的动作,你的对像是一个没有用的(或者不能正常使用的)

直接初始化,就是使用构造函数,定义在申请了对象空间之后如何对各个子空间进行初始赋值,称他们为直接初始化是很贴切的,应为他就是最为普通的初始化,是构建一个完整对象的一个过程——先将空间申请好,然后给各个子空间(数据属性)进行相应的赋值。

复制初始化,特点特别之处在于“复制”二字,核心意义就是,我通过对一个已有对象的完全复制,来构建对象。它的过程可以理解成这样——先申请空间,然后将被复制的对象(空间一样大)的所有内容全部复制过去,就形成了这个对象。所以,要明确,两种初始化的方式,都是一构造函数的形式存在的。区别在于他们的参数方面,复制初始化,表达的就是对一个已有同类型的对象进行复制,那么这种构造函数就应该使用某个对象来进行复制,同时因为,C++参数的传递默认都是值传递,要声明使用引用的方式(要不然就又复制了一次了);并且是const类型的。


也就是说任何复制初始化的表面特征就是使用“=”号来表达,左边是对该对象的空间的申明,右边是另外一个同类型的对象,注意,一定是同类型的对象(即使不是,也会使用类型转换构造函数来进行构造(前面说过)!形如:

classname objectname=objectname2;    复制初始化。
classname objectname(,,,,,);    直接初始化,有参数。
classname objectname;     直接初始化,没有参数。
classname *objectpointorname=new classname(,,);   这里的操作有两个过程,先使用对象构造函数通过直接初始化构造出一个对象,然后将指针放回,不管有没有参数。

classname objectname=other_objectname3;  这里表达的是,这个othe_objectname3,不是classname这个类型。这个个就要知道,右边的会通过调用直接初始化构造函数,构造出对应的对象,然后调用复制初始化构造函数。



所以: 构造函数应该分为1)直接初始化构造函数,和2)复制初始化构造函数。还是要说,复制初始化构造函数看起来像赋值,但是,其实只是像而已,它其实是借用了这种表象,来触发调用了那个使用同类型对象引用作为参数的复制构造函数。

string null_book="9-999-9999-9";    先调用了隐式转换的直接初始化构造函数,然后调用了复制初始化构造函数。

string dots(10, '.');直接调用直接初始化构造函数。

string empty_copy=string(); 先调用显示直接初始化构造函数,然后调用复制初始化构造函数。

string empty_direct;  直接调用没有参数的(默认)构造函数

ifstream file1("filename");   直接初始化

ifstream file2="filename";   虽然语法没有错,但是,由于文件对象不能复制,所以不能使用复制初始化,这种做法在文件领域中通常是错误的。

Sales_item item=string("9-999-99999-9");  这个语法对与错,关键是看Saltes_item有没有隐式(没有使用explicity标志)转换以string类型为参数的构造函数。


(2)构造函数(用于直接或复制初始化 )的使用模式。

事实上,我们说这些东西特殊,关键是他们时候的时候特殊,两种构造函数(对应两种初始化方式)都有显示和隐式的构造方法。

对于直接初始化,我们知道显示的方法,就是使用类名加上参数(其实就是在调用构造函数),隐式的方法,就是在一个需要该类对象的地方出现了其他的数据类型,于是系统会自动调用对应函数(这个应该在编译的时候应该调用了)因为这个时候可以检测出错误来。

对于复制初始化,最显示的调用手段就是使用“=”符号(这个时候应该成为复制初始化符号)。那么还有许多地方是隐式的调用。如参数传递时,函数返回时,初始化容器时!

1)对于参数传递:我们知道除非是引用参数,否则就是一个使用上层对象复制初始化函数参数的过程。

2)对于函数返回值:我们知道除非是引用返回,否则在return的那个语句就是使用函数内的对象,复制初始化一个上层对象(通常是临时的,然后马上有被用于)

3)在某些容器初始化的过程中如:
   vect<string> svec(5);
   这里的过程就是,先使用string默认构造出一个实例对象,然后使用这个对象,复制初始化其它的元素。这个过程是容器的实现细节,其实从外面看,可以理解为直接初始化。
4)数组初始化,有时候使用这样的语法:

  Sales_item primer_eds[]={  string("1231231"),
                                              string("3123123")
                                           }
 可知这个过程,就是一个先调用直接初始化生成string,然后继续隐式调用直接初始化生成Sales_item。最后使用复制初始化,给那个数组的各个元素初始化。

从上面两个关于容器(包括数组)的初始化过程可以看出,他们与普通的类(也是包含许多的元素对象)的不同了 !

(3)组装复制构造函数。

对,我们现在学习了,默认构造函数(没有参数的,可能是系统定义,可能是用户定义,系统只有在没有任何构造函数的情况下定义默认构造函数),而系统定义的默认构造函数就叫做组装默认构造函数,还有一种特殊的构造函数——类型转换构造函数(其实并不特殊);现在学习了组装复制构造函数(只要用户没有主动构造一个复制构造函数(使用类型引用做参数),系统就会自行组装);就算只定义了复制构造函数,系统也不会自动组装默认构造函数,所以,如果你定义了复制构造函数,那么一定要定义普通构造函数(最好有默认构造函数)。要不然,就没有构造函数了。

想到这里,我们发现不管什么什么类型的构造函数,功能都是实例并初始化对象,可能复制构造函数与普通构造函数的过程有些不同(其实就是使用的方法和领域不同),但是他们仍然是平等的。所以只有一个默认构造函数,并且只有当没有任何自定一构造函数的时候,系统才会有组装构造函数;而复制构造函数,是一定有的(不管在什么情况)。

其实复制构造函数的本质依然是构造函数,它的功能就是使用那个已知的对象中的元素,“逐个”的赋值给那个需要初始化的对象。所以,你可以把组装复制构造函数,想象成一个带有所有对象元素(顺序也一至)的初始化列表的构造函数。

与普通构造函数一样,涉及到对某个元素的初始化时,对于内建类型,直接使用copy的方法,对于类类型,使用定义复制构建函数。如果没有定义复制构建函数,就使用组装的,毕竟复制构建函数的参数是一定的,所以基本不存在无法复制的元素的可能。 但是直接构造函数就有可能出现,子元素没有默认构造函数的情况,而不能进行构造,并且直接构造的时内建类型也有可能不初始化(依编译器而定)。

另外一个要注意的是,虽然我们没有办法进行复制初始化数组(这就是为什么数组没有办法作为参数传递,或则作为返回值返回,或者定义的时候用一个数组来复制初始化另外一个数组),但是当数组在某个其他类型的里面的是后,这个时候如果发生了复制初始化,作为元素的数组会也被复制初始化,但是,是通过逐个复制元素的方法。
(4)自定义复制构造函数

class Foo{
public:
   Foo();   //默认构造函数
   Foo(const Foo&);   //复制构造函数 
}

其中,const可以不写(但通常建议这么做),由于复制的作用要用到传递参数、返回值,这些都是隐式的调用,所以一定不能将其声明为explicit。所以可以知道explicit的作用就是承认在可能的情况下,系统默认在需要使用该函数的情况下,能不能自动使用。

从前面我们说可以将组装复制构造函数用相应的带有完全初始化列表的构造函数来代替,我们可以发现,复制构造函数要实现的功能基本稳定,所以通常组装复制构造函数基本可以满足要求。

所以我们经常不怎么自己定义复制构造函数,但是有些时候程序实现要求我们必须自定义构造函数,这个时候我们就表明了,构建复制构造函数的困难之处,不在于语法 ,它与普通的构造函数是一样的;关键是在,构建这个东西的用途,当用途明确后,其他就简单了。

有些时候,例如
1)对象成员是一个指向某个资源的指针(用户本意不想只是复制指针,那表明没有复制指针指向的对象),
2)或者该类型规定每新建一个对象需要做一些动作,那么这个时候就需要自定义复制构造函数
(注意哦!与普通构造函数一样,构造函数理论上的功能包括空间分配、元素初始化,以及相关处理,那个复制初始化符号“=”左右应该看成一个整体。)
3)或者该类型的每个对象都富有一个唯一ID成员的机制

(5)如何阻止类的复制初始化功能

1)我们知道,我们使用explicity可以声明,该复制构造函数不能被隐式使用,于是在参数、返回值的那些地方都不能用,但是如何让普通的复制初始化也不用呢? 那就是声明为private。我们说过。构造函数的特殊还在于,它是直接被外层使用的,不需要套一个什么类的帽子(因为它就是类名),所以如果申明为private那么就不能使用了,这就是为什么大部分的构造函数都被申明为public了。