当对象在创建时获得了一个特定的值,我们说这个对象被初始化。初始化不是赋值,初始化的含义是创建变量赋予其一个初始值,而赋值的含义是把当前值擦除,而以一个新值来替代。对象初始化可以分为默认初始化、直接初始化、拷贝初始化以及值初始化。

// (1)默认初始化
int i1;//默认初始化,在函数体之外(初始化为0) int f(void)
{
int i2;//不被初始化,如果使用此对象则报错
} string empty;//empty非显示的初始化为一个空串,调用的是默认构造函数 // (2)拷贝初始化
string str1(,'');//直接初始化
string str2(str1);//直接初始化
string str3 = str1;//拷贝初始化 // (3)值初始化
vector<int> v1();//10个元素,每个元素的初始化为0
vector<string> v2();//10个元素,每个元素都为空 int *pi = new int;//pi指向一个动态分配的,未初始化的无名对象
string *ps = new string;//初始化为空string
int *pi = new int;//pi指向一个未初始化的int int *pi = new int();//pi指向的对象的值为1024
string *ps = new string(,'');//*ps为"9999999999" string *ps1 = new string;//默认初始化为空string
string *ps2 = new string();//值初始化为空string
int *pi1 = new int;//默认初始化
int *pi2 = new int();//值初始化为0

1、C++ Copy初始化

在《inside the c++ object model》一书中谈到copy constructor的构造操作,有三种情况下,会以一个object的内容作为另一个object的初值:

  1. 第一种情况: XX aa = a;
  2. 第二种情况: XX aa(a);
  3. 第三种情况: extern fun(XX aa); fun(a)函数调用
  4. 第四种情况: XX fun(){...}; XX a = fun();函数返回值的时候

下面我们就上述的四种情况来一一验证:

    class ClassTest{
public:
ClassTest()//定义默认构造函数
{
c[] = '\0';
cout << "ClassTest()" << endl;
}
ClassTest& operator=(const ClassTest &ct) //重载赋值操作符
{
strcpy_s(c, ct.c);
cout << "ClassTest& operator=(const ClassTest &ct)" << endl;
return *this;
}
ClassTest(const char *pc)
{
strcpy_s(c, pc);
cout << "ClassTest (const char *pc)" << endl;
}
ClassTest(const ClassTest& ct)//复制构造函数
{
strcpy_s(c, ct.c);
cout << "ClassTest(const ClassTest& ct)" << endl;
}
private:
char c[];
}; ClassTest func(ClassTest temp){
return temp;
} int main(){
cout << "ct1: ";
ClassTest ct1("ab");//直接初始化
cout << "ct2: ";
ClassTest ct2 = "ab";//复制初始化
/*输出说明:
ClassTest ct2 = "ab";
它本来是要这样来构造对象的:首先调用构造函数ClassTest(const char *pc)函数创建一个临时对象,
然后调用复制构造函数,把这个临时对象作为参数,构造对象ct2。然而编译也发现,复制构造函数是
公有的,即你明确地告诉了编译器,你允许对象之间的复制,而且此时它发现可以通过直接调用重载的
构造函数ClassTest(const char *pc)来直接初始化对象,而达到相同的效果,所以就把这条语句优化为
ClassTest ct2("ab")。
*/
cout << "ct3: ";
ClassTest ct3 = ct1;//复制初始化
cout << "ct4: ";
ClassTest ct4(ct1);//直接初始化
cout << "ct5: ";
ClassTest ct5 = ClassTest();//复制初始化
cout << "ct6: ";
ClassTest ct6;//复制初始化
ct6 = "caoyan is a good boy!";
cout << "ct7: ";
ClassTest ct7;
ct7 = func(ct6);
return ;
}

测试结果:

我们可以看到,比较复杂的是ct6和ct7,其中ct6还是比较好理解的,ct7这种情况比较难懂,为什么会有两个拷贝构造函数的调用????

第一次拷贝构造函数的调用:第一次很简单,是因为函数参数的传递,将ct6作为参数传递给temp,用ct6的值初始化temp会调用拷贝构造函数;

第二次拷贝构造函数的调用:因为要返回一个ClassTest对象,我们的编译器怎么做????首先它将temp对象拷贝到func函数的上一级栈帧中,它的上一级栈帧是main函数的栈帧,那么当函数返回时,参数出栈,temp对象的内存空间就会被收回,但是它的值已经被拷贝到main栈帧的一个预留空间中,所以从temp到预留空间的拷贝也是调用拷贝构造函数,最后一步就是给ct7赋值,毫无疑问调用赋值构造函数;对栈帧不同的同学可以看看《程序员的自我修养》一书,里面讲得很详细!

2、初始化列表、构造函数与=赋值之间的区别

总所周知,C++对象在创建之时,会由构造函数进行一系列的初始化工作。以没有继承关系的单个类来看,除了构造函数本身的产生与指定,还涉及到初始化步骤,以及成员初始化方式等一些细节,本篇笔记主要对这些细节进行介绍,弄清C++对象在初始化过程中一些基本运行规则。

构造函数指定
通常,我们在设计一个类的时候,会为这个类编写对应的default constructor、copy constructor、copy assignment operator,还有一个deconstructor。即便我们仅仅编写一个空类,编译器在编译时仍旧会为其默认声明一个default constructor、copy constructor、copy assignment operator与deconstructor,如果在代码里面存在着它们的使用场景,那么这个时候编译器才会创建它们。
class MyCppClass {}
一旦我们为一个类编写了default constructor,那么编译器也就不会为其默认生成default constructor,对于其他几个函数也一样。对于编译器默认生成的constructor来说,它会以一定规则对每一个数据成员进行初始化。考虑到成员初始化的重要性,在编写自己的constructor时就需要严谨认真了,特别是在类的派生与继承情况下这点显得尤为重要。对于copy constructor和assignment operator的运用场景,这里不得不多说一点,见如下代码:
#include <iostream>

using std::cout;
using std::endl; class MyCppClass
{
public:
MyCppClass()
{
std::cout <<"In Default Constructor!" <<std::endl;
} MyCppClass(const MyCppClass& rhs)
{
std::cout <<"In Copy Constructor!" <<std::endl;
} MyCppClass& operator= (const MyCppClass& rhs)
{
std::cout <<"In Copy Assignment Operator!" <<std::endl; return *this;
}
}; int main()
{
MyCppClass testClass1; // default constructor
MyCppClass testClass2(testClass1); // copy constructor
testClass1 = testClass2; // copy assignment operator MyCppClass testClass3 = testClass1; // copy constructor return ;
}

执行结果:

 
这里需要注意的是,一般情况下我们总是以为在‘=’运算符出现的地方都是调用copy assignment operator,上面这种情况却是个例外。也就是,当一个新对象被定义的时候,即便这个时候是使用了'='运算符,它真实调用的是初始化函数copy constructor,而不是调用copy assignment operator去进行赋值操作。
 
Why初始化列表
一个对象在初始化时包括了两个步骤:
首先,分配内存以保存这个对象;
其次,执行构造函数。
在执行构造函数的时候,如果存在有初始化列表,则先执行初始化列表,之后再执行构造函数的函数体。那么,为什么会引入初始化列表呢?
 
C++与C相比,在程序组织上由“以函数为基本组成单位的面向过程”变迁到“基于以类为中心的面向对象”,与此同时类也作为一种复合数据类型,而初始化列表无非就是进行一些数据的初始化工作。考虑到这里,也可以较为自然的推测初始化列表与类这种数据类型的初始化有着关联。
在引入初始化列表之后,一个类对应数据成员的初始化就存在有两种方式。下面是类的数据成员类型分别为内置类型、自定义类型时的一个对比。 
// 数据成员类型为内置类型
class MyCppClass
{
public:
// 赋值操作进行成员初始化
MyCppClass
{
counter = ;
} // 初始化列表进行成员初始化
MyCppClass : counter()
{
} private:
int counter;
}

当类的数据成员类型为内置类型时,上面两种初始化方式的效果一样。当数据成员的类型同样也为一个类时,初始化的过程就会有不一样的地方了,比如:

// 数据成员类型为自定义类型:一个类
class MyCppClass
{
public:
// 赋值操作进行成员初始化
MyCppClass(string name)
{
counter = ;
theName = name;
} // 初始化列表进行成员初始化
MyCppClass : counter(), theName(name)
{
} private:
int counter;
string theName;
}

在构造函数体内的theName = name这条语句,theName先会调用string的default constructor进行初始化,之后再调用copy assignment opertor进行拷贝赋值。而对于初始化列表来说,直接通过copy constructor进行初始化

明显起见,可以通过如下的代码进行测试。

#include <iostream>
#include <string> class SubClass
{
public:
SubClass()
{
std::cout <<" In SubClass Default Constructor!" <<std::endl;
} SubClass(const SubClass& rhs)
{
std::cout <<" In SubClass Copy Constructor!" <<std::endl;
} SubClass& operator= (const SubClass& rhs)
{
std::cout <<" In SubClass Copy Assignment Operator!" <<std::endl; return *this;
}
}; class BaseClass
{
public:
BaseClass(const SubClass &rhs)
{
counter = ;
theBrother = rhs;
std::cout <<" In BaseClass Default Constructor!" <<std::endl;
} BaseClass(const SubClass &rhs, int cnt):theBrother(rhs),counter(cnt)
{
std::cout <<" In BaseClass Default Constructor!" <<std::endl;
} BaseClass(const BaseClass& rhs)
{
std::cout <<" In BaseClass Copy Constructor!" <<std::endl;
} BaseClass& operator= (const BaseClass& rhs)
{
std::cout <<" In BaseClass Copy Assignment Operator!" <<std::endl; return *this;
}
private:
int counter;
SubClass theBrother;
}; int main()
{
SubClass subClass; std::cout <<"\nNo Member Initialization List: " <<std::endl;
BaseClass BaseClass1(SubClass); std::cout <<"\nMember Initialization List: " <<std::endl;
BaseClass BaseClass2(SubClass, ); return ;
}

执行结果:

 
也就是,在涉及到自定义类型初始化的时候,使用初始化列表来完成初始化在效率上会有着更佳的表现。这也是初始化列表的一大闪光点。即便对于内置类型,在一些情况下也是需要使用初始化列表来完成初始化工作的,比如const、references成员变量。这里有篇笔记,对初始化列表有着非常详尽的描述。
 
几个初始化名词
在阅读《Accelerated C++》中文版时,总是碰到“缺省初始化”、“隐式初始化”以及“数值初始化”,最初在理解这几个名词的时候几费周折,总觉得为什么一个初始化操作造出了如此多的名词,为此没少花时间来弄清楚它们之间的关系。

为了更好的理解它们,先对C++当中的数据类型进行简单划分。在C++里面,数据类型大致可以分为两种:第一种是内置类型,比如float,
int, double等;第二种是自定义类型,也就是我们常用的class,
struct定义的类。在对这些类型的数据进行初始化时,差别就体现出来了:对于内置类型,在使用之前必须进行显示的初始化,而对于自定义类型,初始化责任则落在了构造函数身上。 

int x = 0;          // 显示初始化x
SubClass subClass; // 依赖SubClass的default constructor进行初始化

上面的名词“缺省初始化”描述的就是当内置类型或者自定义类型的数据没有进行显示初始化时的一种初始化状态。而“隐式初始化”描述的是在该状态下面进行的具体操作方式,比如对于内置类型来说,缺省初始化状态下进行的隐式初始化实际上是未定义的,而自定义类型的隐式初始化则依赖于其constructor。

 
前面提到过C++不保证内置类型的初始化,但是当内置类型在作为一个类的成员时,在某些特定的条件下该内置类型的成员会被编译器主动进行初始化,对于这个过程也就是所谓的数值初始化。在《Accelerated C++》当中列出了如下的几种情况:
  1. 对象被用来初始化一个容器元素
  2. 为映射表添加一个新元素,对象是这个添加动作的副作用
  3. 定义一个特定长度的容器,对象为容器的元素
测试如下:
#include <iostream>
#include <vector>
#include <map>
#include <string> using std::cout;
using std::endl;
using std::vector;
using std::map;
using std::string; class NumbericInitTestClass
{
public:
void PrintCounter()
{
cout <<"counter = " <<counter <<endl;
}
private:
int counter;
}; int main()
{
NumbericInitTestClass tnc;
tnc.PrintCounter(); map<string, int> mapTest;
cout <<mapTest["me"] <<endl; vector<NumbericInitTestClass> vecNumbericTestClass();
vecNumbericTestClass[].PrintCounter(); return ;
}
对于没有进行初始化的内置类型,是一个未定义的值2009095316,而对于2, 3种情况来说,均被初始化为0,对于第1种情况我还没有想到合适的场景。
 
回过头想想,为了书中的一些相似的名词,去想办法把它们凑在一起总是显得有些牵强附会:) 
 
一些规则
这里附上几条有关初始化的基本规则,它们多来源于《Effective C++》
 
1. 为内置型对象进行手工初始化,因为C++不保证初始化它们。
2. 构造函数最好使用成员初值列(member initialization list),而不要在构造函数体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中声明的次序相同。
3. C++不喜欢析构函数吐出异常。
4. 在构造函数与析构函数期间不要调用virtual函数,因为这类调用从不下降至derived class。
5. copying函数应该确保复制“对象内所有成员变量”及“所有base class成分”。
 
 
 

参考文章

C++中对象初始化方式

 
 
 

C++中的对象初始化的更多相关文章

  1. Hive_UDF函数中集合对象初始化的注意事项

    UDF函数中定义的集合对象何时初始化 udf函数放在sql中对某个字段进行处理,那么在底层会创建一个该类的对象,这个对象不断的去调用这个evaluate(...)方法,截图如下:   1.1 如果说对 ...

  2. Java中子类对象初始化的过程

    Java中的继承机制看似简单,实际上包含了很多细节.最近在刷题过程中屡屡跳坑,于是自己仔细再学习了一下Java中子类初始化的细节,与大家分享. class Father { Father(){}; } ...

  3. AJPFX关于面向对象中的对象初始化整理,综合子父类、代码块等等

    今天总结了一下子父类当中含有静态代码块.代码块.构造函数.成员变量.子类复写父类方法时子类的初始化过程,把思路理清一下 class Fu { //父类成员变量 private int num = 3; ...

  4. javascript 中的对象初始化

    参考 developer.mozilla.org 网站.这个是一个前端必须经常光顾的网站. 记录一下对象的创建方法,虽然很简单,但是确需要非常注意. Objects can be initialize ...

  5. C++ 匿名对象初始化新对象

    //c++中匿名对象初始化新对象 #include<iostream> using namespace std; class Point{ public: Point(){ cout &l ...

  6. Java对象初始化

    自动初始化(默认值) 一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值: class Default{ boolean t; char c; byte b; short s; ...

  7. C++中对象初始化

    在C++中对象要在使用前初始化,永远在使用对象之前先将它初始化. 1.对于无任何成员的内置类型,必须手工完成此事. 例如: int x=0; double d; std::cin>>d; ...

  8. SKSpriteNode对象初始化在iPhone 6 plus中显示不正确的分析及解决

    一个SpriteKit项目在其他设备上运行都无问题(无论是真机或是模拟器),但是在iPhone6 Plus上会出现精灵对象纹理被过度放大的现象: 从上图中大家可以看到无论是主角或是道具球都过大了. 看 ...

  9. C#中的自动属性、隐式类型var、对象初始化器与集合初始化器、扩展方法

    1.自动属性(Auto-Implemented Properties) //以前的写法 .net2.0 private string _userName; public string UserName ...

随机推荐

  1. 每天学点Linux-切割命令split

    一种常见的需求是,有一个比较大的文件,需要把它切割成比较小的几个文件,在Linux系统中你就可以使用Split命令了.Split命令可以将一个大的文件按照文件大小或者行数切割成小文件.Split命令的 ...

  2. 链路追踪工具之Zipkin学习小记

    (接触了Zipkin,权将所了解或理解的记于此,以备忘) 分布式追踪系统 随着业务发展,系统拆分多个微服务.此时对于一个前端请求可能需要调用多个后端端服务才能完成,当整个请求变慢或不可用时,我们是无法 ...

  3. IE 浏览器不支持 ES6 Array.from(new Set( )) SCRIPT438: 对象不支持“from”属性

    [转]解决老浏览器不支持ES6的方法 现象: Array.from(new Set( )) SCRIPT438: 对象不支持“from”属性或方法   解决方法: 安装babel 引入browser. ...

  4. DBA-mysql-授权

    权限系统介绍 权限系统的作用是授予来自某个主机的某个用户可以查询.插入.修改.删除等数据库操作的权限. 不能明确的指定拒绝某个用户的连接. 权限控制(授权与回收)的执行语句包括create user, ...

  5. CentOS上安装配置Ruby on Rails

    0.install sublime editor(optional) ref:http://www.tecmint.com/install-sublime-text-editor-in-linux/ ...

  6. PHP读取HTML生成doc

    PHP代码如下: <?php //获取1.html文档的内容(包括html代码) $result = file_get_contents('./word.html'); echo "$ ...

  7. go语言学习笔记1 Go开发环境

    什么是Go?Go是一门并发支持.垃圾回收的编译型系统编程语言,旨在创造一门具有在静态编译语言的高性能和动态语言的高效开发之间拥有良好平衡的一门编程语言. Go的主要特点有哪些?* 类型安全 和 内存安 ...

  8. Cocos2dx项目在各种IDE中新建类之后的可行编译方式

    注:这里说可行,但是并不是最好的,只是可以完成编译. 1.linux+code::blocks下的cocos2dx项目新建一个类TestScene.h 新建的TestScene.h和TestScene ...

  9. 生成Oracle的AWR报告

    1.打开CMD命令对话框,登录SQLPLUS(主要如果是管理员账户,用户名需要加 as sysdba,密码格式为  password@IP/实例名),输入AWR执行SQL语句(注意,路径根据实际安装路 ...

  10. 学习Vue 入门到实战——学习笔记(二)

    闲聊: 哈哈哈!过了好几天才更新博客啦,嘻嘻,马上过年了,大家最近是不是都开心的快飞起来了,小颖好几个朋友公司都已经放假了,可是我们公司要等到腊月29上完班才给放假,哎!心情不美气的很,用我之前大学舍 ...