C++中的对象初始化操作代码

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

// new edit on 2020.7.23
#pragma once
#include <iostream>
using namespace std;

class ClassTest {
 public:

  //定义默认构造函数
  ClassTest()
  {
    c[0] = '\0';
    cout << "1) ClassTest()" << endl;
  }

  // 直接初始化
  ClassTest(const char* pc)
  {
    strcpy_s(c, pc);
    cout << "2) ClassTest (const char *pc)" << endl;
  }

  //复制/拷贝构造函数
  ClassTest(const ClassTest& ct)
  {
    strcpy_s(c, ct.c);
    cout << "3) ClassTest(const ClassTest& ct)" << endl;
  }

  //重载赋值操作符
  ClassTest& operator=(const ClassTest& ct)
  {
    strcpy_s(c, ct.c);
    cout << "4) ClassTest& operator=(const ClassTest &ct)" << endl;
    return *this;
  }

 private:
  char c[256];
};

ClassTest func(ClassTest temp) { return temp; }

int demo_test() {
  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 << "\nct6: "; // 依次調用 1)、2)、4),即默认、直接、重载
  ClassTest ct6;
  ct6 = "caoyan is a good boy!"; 

  cout << "\nct7: ";
  ClassTest ct7;  // 依次調用 1)、3)、3)、4)
  ct7 = func(ct6);

  return 0;
}

old code:

// (1)默认初始化
int i1;//默认初始化,在函数体之外(初始化为0)  

int f(void)
{
int i2;//不被初始化,如果使用此对象则报错
}  

string empty;//empty非显示的初始化为一个空串,调用的是默认构造函数  

// (2)拷贝初始化
string str1(10,'9');//直接初始化
string str2(str1);//直接初始化
string str3 = str1;//拷贝初始化  

 // (3)值初始化
vector<int> v1(10);//10个元素,每个元素的初始化为0
vector<string> v2(10);//10个元素,每个元素都为空   

int *pi = new int;//pi指向一个动态分配的,未初始化的无名对象
string *ps = new string;//初始化为空string
int *pi = new int;//pi指向一个未初始化的int  

int *pi = new int(1024);//pi指向的对象的值为1024
string *ps = new string(10,'9');//*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的初值:

  • 第一种情况:XXaa=a;第二种情况:XXaa(a);
  • 第三种情况:externfun(XXaa);fun(a)函数调用
  • 第四种情况:XXfun(){...};XXa=fun();函数返回值的时候

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

#include <iostream>
using namespace std;

class ClassTest {
public:
  ClassTest() //定义默认构造函数
  {
    c[0] = '\0';
    cout << "ClassTest()" << endl;
  }
  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;
  }
  ClassTest &operator=(const ClassTest &ct)  //重载赋值操作符
  {
    strcpy_s(c, ct.c);
    cout << "ClassTest& operator=(const ClassTest &ct)" << endl;
    return *this;
  }

private:
  char c[256];
};

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 0;
}

测试结果:

我们可以看到,比较复杂的是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 0;
}

执行结果:

这里需要注意的是,一般情况下我们总是以为在‘='运算符出现的地方都是调用copy assignment operator,上

面这种情况却是个例外。也就是,当一个新对象被定义的时候,即便这个时候是使用了'='运算符,它真实调用的是初始化函数copy constructor,而不是调用copy assignment operator去进行赋值操作。

Why初始化列表

一个对象在初始化时包括了两个步骤:

首先,分配内存以保存这个对象;

其次,执行构造函数。

在执行构造函数的时候,如果存在有初始化列表,则先执行初始化列表,之后再执行构造函数的函数体。那么,为什么会引入初始化列表呢?

C++与C相比,在程序组织上由“以函数为基本组成单位的面向过程”变迁到“基于以类为中心的面向对象”,与此同时类也作为一种复合数据类型,而初始化列表无非就是进行一些数据的初始化工作。考虑到这里,也可以较为自然的推测初始化列表与类这种数据类型的初始化有着关联。

在引入初始化列表之后,一个类对应数据成员的初始化就存在有两种方式。下面是类的数据成员类型分别为内置类型、自定义类型时的一个对比。 

// 数据成员类型为内置类型
class MyCppClass
{
public:
    // 赋值操作进行成员初始化
    MyCppClass
    {
        counter = 0;
    }

    // 初始化列表进行成员初始化
    MyCppClass : counter(0)
    {
    }

private:
    int    counter;
}

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

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

    // 初始化列表进行成员初始化
    MyCppClass : counter(0), 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 = 0;
        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, 1);

    return 0;
}

执行结果:

也就是,在涉及到自定义类型初始化的时候,使用初始化列表来完成初始化在效率上会有着更佳的表现。这也是初始化列表的一大闪光点。即便对于内置类型,在一些情况下也是需要使用初始化列表来完成初始化工作的,比如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(1);
    vecNumbericTestClass[0].PrintCounter(); 

    return 0;
}

对于没有进行初始化的内置类型,是一个未定义的值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++类对象初始化方式总结

Peter87C++中的对象初始化

到此这篇关于C++中的对象初始化的文章就介绍到这了,更多相关C++对象初始化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • c++ 数组定义及初始化详解

    C ++提供了一种数据结构,即数组,该数组存储一个固定大小的由相同类型元素构成的顺序集合. 数组中的元素存储在一个连续内存位置中,元素可通过数组索引访问, 最低地址对应于第一个元素,最高地址对应于最后一个元素. 声明数组 例如 声明固定长度的数组: const int Size = 5; int arr[Size] = {3, 6, 9, 12, 15}; arr[3] = 42; 注意:方括号[]中的内容(表示数组中元素的数量)必须是一个常量表达式,因为数组是静态内存块,必须在编译时确定大小,

  • 简述C++11就地初始化与列表初始化

    1.就地初始化 1.1简介 在C++11之前,只能对结构体或类的静态常量成员进行就地初始化,其他的不行. class C { private: static const int a=10; //yes int a=10; //no } 在C++11中,结构体或类的数据成员在申明时可以直接赋予一个默认值,初始化的方式有两种,一是使用等号"=",二是使用大括号列表初始化的方式.注意,使用参考如下代码: class C { private: int a=7; //C++11 only int

  • C++构造函数的初始化列表详解

    目录 1.问题 2.解决方法(初始化列表) 3.顺序问题 总结 1.问题 class A { private: int m_a; public: A(int a) { cout << "A(int a)......." << endl; m_a = a; } void print() { cout <<"m_a=" << m_a << endl; } }; class B { private: int m_

  • C++类成员初始化的三种方式

    目录 一.初始化方式 1.初始化方式一:初始化列表 2.初始化方式二:构造函数初始化 3.初始化方式三:声明时初始化(也称就地初始化,c++11后支持) 二.声明时初始化->初始化列表->构造函数初始化 1.声明时初始化的使用场景 2.列表初始化的使用场景 3.构造函数初始化的使用场景 前言: 在C++98中,支持了在类声明中使用等号"="加初始值的方式,来初始化类中静态成员常量.这种声明方式我们也称之为"就地"声明.就地声明在代码编写时非常便利,不过C

  • C++ 结构体初始化与赋值详解

    目录 1.结构体初始化 2.结构体赋值 参考文献 1.结构体初始化 结构体是常用的自定义构造类型,是一种很常见的数据打包方法.结构体对象的初始化有多种方式,分为顺序初始化.指定初始化.构造函数初始化.假如有如下结构体. struct A { int b; int c; }; (1)顺序初始化因为书写起来较为简约,是我们最常用的初始化方式,但相对于指定初始化,无法变更数据成员初始化顺序,灵活性较差,而且表现形式不直观,不能一眼看出 struct 各个数据成员的值. A a = {1, 2}; (2

  • C++中的对象初始化操作代码

    当对象在创建时获得了一个特定的值,我们说这个对象被初始化.初始化不是赋值,初始化的含义是创建变量赋予其一个初始值,而赋值的含义是把当前值擦除,而以一个新值来替代.对象初始化可以分为默认初始化.直接初始化.拷贝初始化以及值初始化. // new edit on 2020.7.23 #pragma once #include <iostream> using namespace std; class ClassTest { public: //定义默认构造函数 ClassTest() { c[0]

  • Java对象初始化过程代码块和构造器的调用顺序

    目录 前言 代码加载的优先级顺序 构造方法的执行顺序 各种代码块的定义 静态代码块 有关静态代码块再详细介绍下 局部代码块 验证各代码块的执行顺序 验证存在继承关系中各代码块的执行顺序 通过字节码深究实例代码块优先于构造器原因 前言 对Java对象初始化过程 代码块与构造器调用顺序进行整理说明.先说结论具体论证在下文. 代码加载的优先级顺序 静态代码块.静态成员变量->非静态代码块.非静态成员变量->new其他对象调用对应对象构造方法(在本地对象的方法外包括构造方法)->new本地对象调

  • JavaScript 中Date对象的格式化代码方法汇总

    JavaScript默认的时间格式我们一般情况下不会用,所以需要进行格式化,下面说说我总结的JavaScript时间格式化方法. 很多时候,我们可以利用JavaScript中Date对象的内置方法来格式化,如: var d = new Date(); console.log(d); // 输出:Mon Nov 04 2013 21:50:33 GMT+0800 (中国标准时间) console.log(d.toDateString()); // 日期字符串,输出:Mon Nov 04 2013

  • VC++角色游戏中的人物初始化模块代码实例

    本文以一个实例讲述VC++游戏中的人物角色动画初始化实现代码,本代码只是实现人物角色动画的初始化,不包括其它功能,并不是完整的一个游戏应用,现在将这个角色初始化代码与大家分享.希望能够对大家学习VC++有所帮助. #include "StdAfx.h" #include "Character.h" CCharacter::CCharacter(void) { } CCharacter::~CCharacter(void) { } //初始化人物 bool CChar

  • MYSQL中的json数据操作代码

    目录 MYSQL中的json数据操作 1.2 基础查询操作 1.2.1 一般json查询 1.2.2 多个条件查询 1.2.3 json中多个字段关系查询 1.2.4 关联表查询 1.3 JSON函数操作 1.3.1 官方json函数 1.3.2 ->.->>区别 1.3.2.2 在where条件中使用 1.3.3 json_extract():从json中返回想要的字段 1.3.4 JSON_CONTAINS():JSON格式数据是否在字段中包含特定对象 1.3.5 SON_OBJEC

  • Jquery中对数组的操作代码

    而在Jquery中则使用$.map().$.each()来操作数组: 首先是普通的数组(索引为整数的数组): 复制代码 代码如下: $.map(arr,fn); 对数组中的每个元素调用fn函数逐个进行处理,fn函数将处理返回最后得到的一个新的数组 var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1]; var newarr = $.map(arr, function(item) {return item*2 }); alert(newarr); $.each(array,f

  • Vue中的reactive函数操作代码

    reactive函数 之前给大家介绍过vue3.2 reactive函数问题小结,喜欢的朋友点击查看. 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数,ref函数可以用基本类型也可以对象类型) 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) reactive定义的响应式数据是“深层次的”. 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作. <tem

  • SQL Server Table中XML列的操作代码

    复制代码 代码如下: --创建测试表 DECLARE @Users TABLE ( ID INT IDENTITY(1,1), UserInfo XML ) ---插入测试数据 DECLARE @xml XML SET @xml=' <root> <user> <userid>1</userid> <userName>test1</userName> </user> </root>' INSERT INTO @

  • 深入介绍Java对象初始化

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的. 自动初始化(默认值) 一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值: class Default{ boolean t; char c; byte b; short s; int i; long l; float f; double d; public void show() { System.out.println("基本类型 初始化值\n"+ "bo

  • Java中对象初始化顺序的详细介绍

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.最近我发现了一个有趣的问题,这个问题的答案乍一看下骗过了我的眼睛.看一下这三个类: package com.ds.test; public class Upper { String upperString; public Upper() { Initializer.initialize(this); } } package com.ds.test; public class Lower extends

随机推荐