探究C++构造函数及其优化

news2024/11/27 6:40:11

目录

  • 一、 类的六个默认成员函数
    • 1.1 框架图
    • 1.2 具体介绍
      • (1)构造函数
      • (2)析构函数
      • (3)拷贝构造函数
      • (4)赋值运算符重载函数
    • 归纳我们不写,编译器默认生成了什么:
  • 二、深入学习初始化列表
    • 2.1 作用
    • 2.2 关于初始化顺序
  • 三、单参数的构造函数支持隐式类型转换
  • 四、C++11 的成员初始化新特性
  • 五、编译器的三个优化场景

一、 类的六个默认成员函数

1.1 框架图

在这里插入图片描述

1.2 具体介绍

重点关注前四个默认成员函数,对于最后两个即对普通对象取地址和对const普通对象取地址用编译器默认生成的即可。

(1)构造函数

作用: 构造函数可以帮助我们完成类的初始化的工作,如对成员变量进行赋值,开辟空间等。

特性: 当我们没有自己写构造函数时,编译器将默认生成一个构造函数,该构造函数无需参数。

该构造函数将具有以下功能:

  1. 对于类内的自定义类型,会调用他们的默认构造函数;对于内置类型不会进行初始化。
  • 自定义类型:如class, struct
  • 内置类型:语言原生定义的类型,如int, char, double,指针…,

补充:对默认构造函数的全面认识
默认构造函数并不仅仅指代我们不写,编译器自动生成的构造函数。事实上有三个默认构造函数,对默认构造函数更准确的理解为:不用参数就可以调用的构造函数。

三个默认构造函数分别为:

  1. 自己写的无参构造函数
  2. 自己写的全缺省构造函数
  3. 我们没写编译器默认生成的构造函数

以上三个都可以认为是默认构造函数。除了我们没写编译器默认生成的构造函数之外,无参的构造函数和全缺省的构造函数也是默认构造函数。注意:以上三个默认构造函数同时只能存在一个。
综上:写构造函数最好写一个全缺省的,这样既是默认构造函数,又可以传递参数进行构造。

(2)析构函数

作用: 完成资源清理的工作。备注:析构函数不是完成对象的销毁,对象的销毁是编译器完成的,当对象超出生命周期将由编译器进行销毁对象,而对象在销毁时会自动调用析构函数。

特性: 当我们没有自己写析构函数时,编译器将默认生成一个析构函数。编译器自动生成的析构函数,会调用自定义类型成员的析构函数,而对内置类型成员不进行操作。

补充:构造和析构顺序
因为对象是定义在函数中,函数调用会建立栈帧,栈帧中的构造和析构函数符合先进后出。即析构顺序和构造顺序是反着的。

(3)拷贝构造函数

作用: 用于将一个已存在的对象拷贝创建一个新对象。如果没有定义任何拷贝构造函数,编译器将生成一个默认的拷贝构造函数。默认的拷贝构造函数执行浅拷贝,即复制对象的所有成员变量。

特性: 当我们自己没有写拷贝构造函数时,编译器将默认生成一个拷贝构造函数。对于对象的自定义类型,会去调它的拷贝构造函数,对于对象的内置类型成员变量,则进行浅拷贝。

注意:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

(4)赋值运算符重载函数

作用: 用于将一个已存在的对象的内容赋值给另一个已经存在的对象。

特性: 当我们没有自己写赋值运算符时,编译器将生成一个默认的赋值运算符。它对内置类型进行浅拷贝,对于自定义类型会调用它们赋值运算符重载函数。

注意:

  1. 赋值运算符重载的返回值是该类对象的引用,目的是支持连续赋值。
  2. 赋值运算符重载的参数只有一个,推荐使用引用传参,使用传值方式可以避免调用拷贝构造。
  3. 若自己实现该函数,需要确保赋值操作不会出现对象的自我复制(即对象不能等于本身)。

初始化和赋值的区分
赋值运算符重载只能用于已经存在的对象,而初始化则是在创建对象时进行的。示例代码如下:

class MyClass {
public:
    MyClass(int n = 0) : num(n) {}
private:
    int num;
};

int main() {
    MyClass obj1(1);    // 构造函数
    MyClass obj2(2);    // 构造函数
    MyClass obj3(obj1); // 拷贝构造函数  
    obj2 = obj1;        // 赋值运算符重载
    return 0;
}

归纳我们不写,编译器默认生成了什么:

  1. 无参构造函数
  2. 拷贝构造函数
  3. 赋值运算符函数
  4. 默认析构函数

二、深入学习初始化列表

要先要明确一个结论:

初始化列表是每一个成员变量定义的地方

这个结论将贯穿这一章节的内容。

2.1 作用

构造函数初始化有两种方式

  1. 函数体内初始化(有些情况下发生的是赋值)
  2. 初始化列表初始化

为什么要有初始化列表的方式,因为当类中包含以下成员,它们必须在定义的时候初始化。

  1. 引用成员变量
  2. const成员变量
  3. 没有默认构造函数的自定义类型成员

代码如下:

class MyClass2 {
public:
    MyClass2(const int i) : m_i(i) {}
private:
    int m_i;
};

class MyClass {
public:
    MyClass(int n, int& ref, const int c) : m_ref(ref), m_c(c), m_myClass2(10)//correct
    {
        m_num = n;//correct
        // 以下初始化方式都是错误的,不能在函数体内初始化
        /*m_ref = ref;
        m_c = c;
        m_myClass2(10);*/
    }
private:
    int m_num;
    int& m_ref;
    const int m_c;
    MyClass2 m_myClass2;
};

理解为什么有些变量的初始化必须在初始化列表中完成
首先要再次强调一个结论:初始化列表是每一个成员变量定义的地方

  • 对于const变量,引用都必须在定义的时候初始化,而初始化列表就是定义的地方,因此要在初始化列表那初始化
    • 初始化列表对成员变量做的事情叫做定义,而构造函数体内对成员变量做的事情就叫做赋值
  • 对于自定义类型
    • 假设它无需参数可完成初始化,那么初始化列表处自动调用默认构造函数即可完成对象的定义,无需在函数体内初始化
    • 假设它需要参数初始化且没写在初始化列表中,那么若该类没有默认构造函数(即不需要参数的构造函数),在初始化列表处自动调用默认构造函数时就会报错。即是提供了默认构造函数使得不报错,则此时调用了一次默认构造函数,在函数体内还需先定义一个新对象,再赋值给目标对象,使得函数体内初始化比初始化列表多调用一次构造函数和赋值函数。
class MyClass2 {
public:
    MyClass2(const int i) : m_i(i) {}
private:
    int m_i;
};

class MyClass {
public:
    MyClass(int n) : m_myClass2(10) // 使用初始化列表,调用一次构造函数
    {
        // 不使用初始化列表,等于 调用一次构造函数 + operator=
        /*MyClass2 tmp(10);
        m_myClass2 = tmp;*/
    }
private:
    MyClass2 m_myClass2;
};

2.2 关于初始化顺序

成员变量在在初始化列表中的初始化顺序就是在类中的声明次序,与其在初始化列表中的先后次序无关
建议:类中成员的声明次序应该与初始化列表中的初始先后顺序写的一致

三、单参数的构造函数支持隐式类型转换

单参数的构造函数可以隐式地将参数转换为对象;如果单参数的构造函数声明为 explicit(显式),则必须显式地调用该构造函数才能将参数转换为对象。
假设我们有以下类定义:

class Person {
public:
    Person(int age) : m_age(age) {}
private:
    int m_age;
};

在没有explict关键字修饰下,支持隐式类型转换

int main() {
    Person p = 18; // 单参数的构造函数支持隐式类型转换
    return 0;
}

实际编译器背后会用18构造一个无名对象Person(18),最后用无名对象给p对象进行赋值。
当上述代码中的构造函数被声明为 explicit,则无法进行隐式类型转换,即无法通过(对象=参数)来赋值。这意味着,以下代码将无法通过编译:

class Person {
public:
    explicit Person(int age) : m_age(age) {}
private:
    int m_age;
};

int main() {
    Person p = 18; // 隐式类型转换,编译错误
}

单参数的构造函数的赋值模式的应用,价值

  1. string类生成对象可直接等号
  2. vector类无需先创建对象,再push对象,可直接push参数进行单参数隐式类型转换构造对象,写起来方便。

代码示例:

int main() {
    vector<string> vec;
    string s1 = "Jack"; // string类生成对象可直接等号
    vec.push_back(s1); // 先创建对象再push麻烦
    vec.push_back("Mike"); // 直接push方便,本质是因为支持单参数的隐式类型转换
}

a. 对于“Jack”,先调用string的有参构造函数,再被引用
b. 对于“Mike”,参数被直接创建临时对象,该临时对象被const引用

四、C++11 的成员初始化新特性

C++11支持非静态成员变量在声明时进行初始化赋值。注意这里不是初始化,这里是给声明的成员变量缺省值

class MyClass2 {
private:
    int m_i;
};

class MyClass {
private:
    int num = 10; // 给缺省值
    MyClass2 m_myclass2 = 20;
};

五、编译器的三个优化场景

前提:
设已经有一个Date类,代码如下
Date类

  1. 第一个优化场景传参场景:当在函数调用传一个匿名对象(临时对象)过去,再用这个对象拷贝给形参对象,编译器可能会优化,将这两个对象合二为一,构造出一个对象。说明如下:
    传参优化
    可以看到,main函数调用func时传递匿名对象,理论上应该此处调用构造函数,然后该匿名对象拷贝给func函数形参d,触发一次拷贝构造函数。但实际上这个过程只有一次构造函数,所以实参和形参的对象用的是同一个,编译器优化了。即出现了传参场景下的优化。

  2. 第二个优化场景单参数的构造函数支持隐式类型转换

    先看现象:

第二个优化场景
在上面的代码中,Date d=3理论上应该先用3定义个临时对象Date tmp(3),再用tmp拷贝构造dDate d(tmp),但是运行结果并没有调用拷贝构造,原因是现在的的编译器会优化,直接用3调用构造函数,相当于Date d(3)。更准确地说,Date d=3先用3定义个匿名对象 Date(3),再用该匿名对象拷贝构造d。此时优化变为第一个场景,故没有出现拷贝构造函数被调用。

  1. 第三个优化场景返回值场景

以下是优化场景演示:

第三个优化场景
构造后马上去拷贝构造或者拷贝构造再马上拷贝构造,编译器可能会优化。
在一个表达式中,连续多个构造函数(包括无参,有参,拷贝构造),可能会被编译器优化为一次构造。如上图自己的代码,当func函数返回时,理论上是先用d拷贝一个临时对象,再把临时对象拷贝给dd,共两次拷贝构造函数,即蓝色箭头逻辑。但实际上只发生了一次拷贝,即红色箭头逻辑,dd直接用d进行拷贝构造。

补充:拷贝构造出的对象马上赋值给已存在的对象不能被优化,如下图,d还是先拷贝构造生成tmptmp赋值给dd

分析
总结:如果编译器要优化,只有构造和拷贝构造才会被优化合并,且需在表达式中(函数参数传递和返回值返回也视作连续的表达式),优化掉的是临时对象或匿名对象。更多细节见《深度探索C++对象模型》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023宁波市赛 天一永安杯赛前模拟题部分wp

Web pop 进hint.php 伪协议读index.php <?php class Tiger{public $string;protected $var;// 恶意参数public function __construct($var){$this->var $var;}public function __toString(){return $this->string;}public function boss($value){// 0eval($valu…

自动化测试作为软件测试的一种技术手段,时常被大家讨论

自动化测试作为软件测试的一种技术手段&#xff0c;时常被大家讨论。本人在自动化技术方面有过略有小成&#xff0c;今天聊一聊关于自动化的一些误区&#xff0c;以帮助新手能正确的了解和认识自动化一些概念。 测试的行为本质是什么&#xff1f; 为什么先从这个概念开始谈起&…

OpenGL之创建窗口

目录 什么是OpenGL&#xff1f; 核心模式与立即渲染模式 立即渲染模式 (Immediate mode) 核心模式(Core-profile) 状态机 对象 创建窗口 配置环境 什么是OpenGL&#xff1f; 一般它被认为是一个API(Application Programming Interface, 应用程序编程接口)&#xff0c;…

黑客最常用的10款黑客工具

以下所有这些工具都是捆绑在一起的Linux发行版&#xff0c;如Kali Linux或BackBox&#xff0c;所以我们一定会建议您安装一个合适的Linux黑客系统&#xff0c;使您的生活更轻松 - 尤其是因为这些黑客工具可以&#xff08;自动&#xff09;更新。 1、Nikto&#xff08;网站漏洞…

中断与freeRTOS任务进行同步

S32K144在做CAN通信时&#xff0c;通过FlexCAN中断接收CAN数据&#xff0c;并希望让freeRTOS 的CAN处理任务拿到CAN数据并进行数据处理。因此就需要找到能够满足中断与freeRTOS任务进行同步的方式方法。 遇到这个问题&#xff0c;第一时间想到的就是查找freeRTOS手册《FreeRTO…

Spring 拦截器

目录 今日良言&#xff1a;心若有所向往&#xff0c;何惧道阻且长 一、Spring 拦截器 1.拦截器简介 2.实现自定义拦截器 今日良言&#xff1a;心若有所向往&#xff0c;何惧道阻且长 一、Spring 拦截器 1.拦截器简介 Spring Boot 拦截器是面向切面编程-----AOP 的具体实现…

人工智能基础部分17-隐马尔科夫模型在序列问题的应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分16-隐马尔科夫模型在序列问题的应用&#xff0c;隐马尔可夫模型(HMM)是一种统计模型&#xff0c;广泛应用于各种领域&#xff0c;如语音识别、自然语言处理、生物信息学等。本文将介绍隐马尔可夫模…

杂记——25.eclipse如何连接并实现对数据库的操作

这篇文章我们来讲一下eclipse&#xff08;即编辑器&#xff09;如何连接并实现对数据库的操作&#xff0c;这里以eclipse为主连讲解演示一下。 第一步&#xff1a; 查看我们本机安装的数据库的版本&#xff0c;即查看我们本机是否安装了数据库 在命令提示窗口输入&#xff1…

games103——作业3

实验三主要使用FEM和hyperelastic模型完成弹性体的模拟 完整项目已上传至github。 文章目录 Linear finite element method(FEM)二维空间有限元方法变形梯度(Deformation Gradient)格林应变(Green Strain)应变能量密度函数(Strain Energy Density Function)力(Force) Finite Vo…

威胁猎人 | 2018年上半年国内公有云云上资产合规现状报告

声明&#xff1a;本报告版权属于威胁猎人情报中心&#xff0c;并受法律保护。转载、摘编或利用其它方式使用本报告文字或者观点的&#xff0c;应注明“来源&#xff1a;威胁猎人”。违反上述声明者&#xff0c;将追究其相关法律责任。 一、报告背景 自2005年亚马逊发布AWS伊始…

基于nodejs+vue3 的高仿网易云音乐

大家好&#xff0c;我是小寻&#xff0c;欢迎大家关注我的公众号&#xff1a;工具优选&#xff0c;加入前端、java群聊哦&#xff01; 今天给大家分享一个超高水准的项目&#xff1a;基于nodejsvue3研发的高仿网易云音乐&#xff0c;项目内容出自寻码网&#xff01; 技术栈&a…

行业唯一丨冠珠瓷砖荣获人民日报社“ESG年度案例”

践行社会责任&#xff0c;推动品牌高质量发展。5月11日&#xff0c;由人民日报社指导、人民日报社经济社会部主办的“中国企业社会责任高峰论坛”在上海盛大举行。 本次论坛围绕乡村振兴、共同富裕、绿色低碳等重点议题进行深入研讨&#xff0c;邀请国家发展和改革委员会、商务…

Thread线程学习(2) Linux线程的创建、终止和回收

目录 1.首先要了解什么是线程ID&#xff0c;以及它的作用是什么 2.创建线程 3.终止线程 4.回收线程 5.总结 在Linux系统中&#xff0c;线程是轻量级的执行单元&#xff0c;能够在同一个进程中并发执行。本文将介绍如何在Linux环境下创建、终止和回收线程&#xff0c;并提供…

〖Web全栈开发③〗—HTTP协议和静态web服务器

HTTP协议和静态web服务器 &#xff08;一&#xff09;三次握手和四次挥手&#xff08;二&#xff09;HTTP协议2.1 HTTP协议的定义2.2 HTTP协议的组成 &#xff08;三&#xff09;搭建python自带静态web服务器3.1 静态web服务器是什么3.2 如何搭建python自带的静态web服务器3.3 …

【栈和队列】的特性以及基本接口的实现

目录 一、栈 1.1 栈的概念 1.2 栈的接口实现 二、队列 2.1 队列的概念 2.2 队列的接口实现 2.3 栈和队列的区别 三、栈和队列LeetCode练习 3.1 力扣_232.用栈实现队列 3.2 力扣_225.用队列实现栈 3.3 力扣_622.设计循环队列 3.4 力扣_20.有效的括号 一、栈 第一次学…

电容在电路中的作用

电容、也称为电容器&#xff0c;字面意思理解就是一种“装电的容器”&#xff0c;是一种容纳电荷的器件。它拥有两个电极板&#xff0c;由两个电极板及其中间所夹的介质封装而成。 常用电容极性判断&#xff1a;   铝电解电容&#xff1a;长脚为正极&#xff0c;短脚为负极&…

【MySQL学习】MySQL索引特性

文章目录 一、初识MySQL索引1.1 MySQL索引的概念1.2 MySQL索引的作用 二、MySQL的数据存储2.1 MySQL存储与磁盘之间的关系2.2 MySQL与磁盘交互的基本单位2.3 认识数据页Page 三、索引的理解3.1 测试案例3.2 探究单个和多个Page存储数据时的情况3.3 页目录3.4 为什么InooDB存储引…

《面试1v1》CAS

我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 面试官&#xff1a; 上个面试官对你的基础有了一定了解&#xff0c;听说你小子很不错&#xff01;下面我们聊点有深度的。 面试官&#xff1a; 简单介绍下 CAS 你了…

10款Photoshop免费在线工具推荐

AdobePhotoshop下载繁琐&#xff0c;付费昂贵&#xff0c;让很多设计师望而却步&#xff01; 经过几个小时的筛选和测试&#xff0c;筛选出10款Photoshop免费在线工具&#xff0c;与Photoshop一样强大。让我们看看&#xff01; 1.即时设计 智能抠图 当我们想要去重图片背景&…