C++中的对象初始化

2023-10-27

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

 

复制代码

// (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的初值:

  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] = '\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[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成分”。

 

 

 

转载于:https://my.oschina.net/u/920274/blog/3089325

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Word不同页面设置不同页眉

    Word不同页面设置不同页眉 每页加上分页符号 取消链接到前一页 实现任意页面页眉不一样 每页加上分页符号 取消链接到前一页

随机推荐

  • python:批量对文件匹配excel改名

    需求 需要将excel内文件名所对应的修改应用到实际文件中 思路 1 读取文件的名字 2 打开excel 3 将文件名与excel内文件名做循环匹配 4 匹配上后 读取相邻单元格所需修改的数据 5 然后改名 保存 先上结果 然后上代码 im
  • 论文笔记:Tip-Adapter: Training-free CLIP-Adapter for Better Vision-Language Modeling详解

    论文 https arxiv org abs 2111 03930 代码 GitHub gaopengcuhk Tip Adapter 摘要 对比性视觉语言预训练模型 即CLIP 为使用大规模图像 文本对学习视觉表征提供了一个新的范式 它通
  • --------三种简单的程序设计模式实例

    1 题目名称 1 简单工厂模式 使用简单工厂模式模拟女娲 Nvwa 造人 Person 如果传入参数M 则返回一个Man对象 如果传入参数W 则返回一个Woman对象 请实现该场景 现需要增加一个新的Robot类 如果传入参数R 则返回一个
  • Winform无法加载基类的错误解决

    相信在vs2010进行开发的人 如果用到了继承窗体的功能 基本上都会遇到 无法加载基类 的错误 这类错误说大也不大 可说小也不小 基本上都是vs2010分析数据时遇到错误造成的 基本上解决方法有三 1 重新生成解决方案 2 删除第三方引用控
  • harbor离线安装

    1 介绍 Docker容器应用的开发和运行离不开可靠的镜像管理 虽然Docker官方也提供了公共的镜像仓库 但是从安全和效率等方面考虑 部署我们私有环境内的Registry也是非常必要的 Harbor是由VMware公司开源的企业级的Doc
  • 花里胡哨的CentOS

    花里胡哨的CentOS 文章基于如下环境 WSL2下的CentOS7 下载地址为 CentOS7 在WSL2下安装CentOS7 首先启用win10或者win11的wsl2功能 这里不过多介绍 不会的自行问度娘 把下载好的CentOS7 z
  • 电脑重置网络 解决网络异常问题

    电脑重置网络可以解决网络异常问题 修复网络链接不畅问题 步骤 1 首先在屏幕左下角的开始 单点击右键 在弹出的菜单中xz选择 Windows Power Shell 管理员 A 打开命令提示符 2 然后在弹出的命令符窗口输入 netsh w
  • 主动扫描系列文章(1):nmap的基础使用

    20201024 目录 主动扫描系列文章 1 nmap的基础使用 主动扫描系列文章 2 masscan zmap扫描主机与端口 主动扫描系列文章 3 nmap与masscan的配合使用 0 引言 关于nmap的使用 这种文章网上满天飞 本篇
  • 哈夫曼树实现文件的压缩与解压

    一 哈夫曼树的构造方法 1 首先要拿到一组数据及其权重 其中每个数据都作为一颗哈夫曼树 也可以被视为一个结点 2 在这些树中找到权重最小的两棵树 将其合并为一棵树 合并方法 将这两棵树的权重相加 权重和为一个新的结点 并且为原来两棵树的父节
  • 计算均值的95%置信区间的下限值和上限值(计算置信区间)

    计算均值的95 置信区间的下限值和上限值 计算置信区间 在统计学中 置信区间是用来估计总体参数的范围 当我们想要估计总体均值的置信区间时 可以使用R语言进行计算 下面将介绍如何使用R语言计算均值的95 置信区间的下限值和上限值 首先 我们需
  • MySQL数据库之表中的数据增删改查

    前置操作 在数据库中新建表kk2 1 增 insert into 表名 字段1 字段2 values 数据1 数据2 向表中新增数据 可以选择部分字段插入数据 注意 编写插入语句时 在前面括号内写的字段和后面括号内的数据的位置 数量必须保持
  • 第二章 软件测试策略

    第二章 软件测试策略 根据mooc课程所做的笔记 课程链接 文章目录 第二章 软件测试策略 2 1 软件开发过程及模型 2 2 软件测试过程 单元测试 2 3 软件测试过程 集成测试 2 4 静态白盒测试 2 5 静态黑盒测试 2 1 软件
  • 手把手教你做计算机网络基础大题—地址分配

    一 题目 某单位分配到一个起始地址为14 24 74 0 24的地址块 该单位需要用到三个子网 它们的三个子网地址块的具体要求是 子网N1需要120个地址 子网N2需要60个地址 子网N3需要10个地址 请给出地址的分配方案 二 解答 起始
  • 通信上的RX、TX分别表示什么?

    RX 表示接收数据 Receive rx Data 的简写形式 TX 表示发送数据 Transmit tx Data 的简写形式
  • 使用 Docker-Compose部署JavaWeb项目并运行JavaWeb项目

    一共需要两个容器 Tomcat mysql 然后需要jpress的war包 创建一个新的composeJava目录 将war包放入该目录下 并且以下操作都在 该目录下进行 创建server xml 内容如下
  • 名人名言(持续更新中)

    杰克威尔逊 他说 领导者的工作 就是每天把全世界各地最优秀的人才延揽过来 他们必须热爱自己的员工 拥抱自己的员工 激励自己的员工 作为一个过来人 韦尔奇给公司领导者传授的用人秘诀是他自创的 活力曲线 一个组织中 必有20 的人是最好的 70
  • Android 删除文件功能代码

    Hello everyone 我是鲁班 一个热衷于科研和软开的胖子 亲测有效 废话不说 直接上代码 有问题评论或私信 public static boolean deleteFoder File file if file exists 判断
  • 实用插件_《FF14》将封禁一批第三方插件 其中不乏实用插件

    SE旗下人气网游 最终幻想14 Final Fantasy 14 的最新5 2版本即将上线 正在所有光之战士准备摩拳擦掌大干一场时 官方似乎给这些玩家浇了一盆冷水 先是制作人吉田直树希望玩家们在游戏中停止使用一些色情mod 然后官方表示在此
  • 尚医通——数据字典开发

    这里写目录标题 一 数据字典介绍 二 数据字典开发 1 搭建service cmn模块 1 1 搭建service cmn模块 1 2 修改配置 1 3 启动类 2 数据字典列表 2 1 数据字典列表接口 2 1 1 model模块添加数据
  • C++中的对象初始化

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