C++ 构造函数详解

2023-05-16

目录

0. 什么是构造函数

1. 默认构造函数

2. 一般构造函数

3. 拷贝构造函数

4. 转换构造函数

5. 移动构造函数


0. 什么是构造函数

在定义类的成员函数的时候,一般的成员函数与普通的全局函数没有太大的区别,在定义函数的时候都需要说明要返回的类型,比如:

int fun() {};  //返回int类型
void fun() {};  //什么都不返回

但是,类中有一种函数比较特殊,函数名与类名相同,且没有返回值类型,这中函数称为构造函数,它承担着类初始化的工作,非常重要。

常用的构造函数有默认构造函数、一般构造函数、拷贝构造函数、转换构造函数、移动构造函数。

1. 默认构造函数

首先要有一个概念,一个类时必有构造函数的,即便程序员没有手动定义构造函数,编译器也会创建一个默认构造函数,具体形式如下:

ClassName(){};

这是一个空的函数,即什么都不做。当然,实际上,默认构造函数也不是空的,这里只是简化一下,不作深究。

还值得注意的是,默认构造函数不止有一个,编译器还会创建其他类型的构造函数,比如,拷贝构造函数,这些后边再说。

所以,现在有一个认识就行,那就是如果程序员不手动定义构造函数,编译器会自动定义默认构造函数,即便这个构造函数可能什么都不做。

2. 一般构造函数

当程序员手动定义构造函数之后,编译器便不再生成默认构造函数。构造函数一般承担初始化工作,比如对成员变量初始化,看个栗子:

#include <iostream>
using namespace std;

class Person {
public:
    int age;
    char* name;

    Person(int age, char* name) {
        this->age = age;
        this->name = name;
    }
};

int main(){
    Person p(12,"kang");
    cout<<p.age<<endl;
    cout<<p.name<<endl;
}

运行结果:

构造函数支持重载,也就是说,可以有多个同名构造函数,不过他们的形参需要有区别。看栗子:

#include <iostream>
using namespace std;

class Person {
public:
    int age;
    char* name;

    Person();
    Person(int age, char* name);
    Person(char* name);

};

Person::Person() {
    this->age = -1;
    this->name = "-1";
}

Person::Person(int age, char* name) {
    this->age = age;
    this->name = name;
}

Person::Person(char* name) {
    this->age = -1;
    this->name = name;
}

int main(){
    Person p1(12,"kang");
    cout<<p1.age<<"  "<<p1.name<<endl;

    Person p2;
    cout<<p2.age<<"  "<<p2.name<<endl;

    Person p3("kang");
    cout<<p3.age<<"  "<<p3.name<<endl;
}

运行结果:

3. 拷贝构造函数

类在实例化的时候,有两步操作:

  1. 给对象分配内存,此时内存没有经过初始化,成员变量大多都是垃圾值;
  2. 对内存进行初始化,也就是调用构造函数对成员变量进行初始化。

对于一般构造函数,初始化的值来自于构造函数的输入参数,如果某个构造函数的输入参数是同类的对象,则称该构造函数为拷贝构造函数。

说白了,拷贝构造函数就是用别的对象来初始化新对象的内存。看一个栗子:

#include <iostream>
using namespace std;

class Person {
public:
    int age;
    char* name;

    Person(int age, char* name);  //一般构造函数
    Person(const Person &p);  //拷贝构造函数  

};

Person::Person(int age, char* name) {
    this->age = age;
    this->name = name;
}

Person::Person(const Person &p) {
    this->age = p.age;
    this->name = p.name;
}

int main(){
    Person p1(12,"kang");   //调用一般构造函数
    Person p3(14,"wang");
    Person p2 = p1;      //调用拷贝构造函数,等效于 Person p2(p1);
    cout<<p1.age<<"  "<<p1.name<<endl;
    cout<<p2.age<<"  "<<p2.name<<endl;

    p2 = p3;
}

运行结果:

注意!27行的 Person p2 = p1 语句不是赋值,是在创建对象,因此要调用拷贝构造函数,而31行是对象的赋值,调用的是运算符“=”的重载函数。所以要注意,这俩东西调用是不一样的,类似与变量创建和赋值的区别。

如果程序员没有手动创建拷贝构造函数,编译器会自动创建一个默认拷贝构造函数,如果手动创建了,编译器就不会再创建了。默认拷贝构造函数很简单,就是用老对象”的成员变量对“新对象”的成员变量进行一一赋值,和上面 Person 类的拷贝构造函数非常类似。所以,上边那个例子,不定义拷贝构造函数,程序也是没问题的,因为编译器帮我们创建了一个默认构造函数。

可能会有疑问,既然编译器自己会创建拷贝构造函数,那为什么还需要用户手动创建呢?答案是,默认构造函数里边只是简单的浅拷贝,碰到指针就G了。对于指针指向的数据,如果用默认构造函数,那么“新对象”的指针跟“老对象”的指针指的是同一块内存,”老对象“的修改会影响到“新对象”,所以这时候就需要用户手动创建拷贝构造函数,使用深拷贝创建对象。

4. 转换构造函数

对于类型转换,大家可能并不陌生,C++自带很多类型转换规则,比如int转double,(int*)转(float*),以及向上型转等等,除了这些自带的转换规则之外,用户还可以自定义转换规则,转换构造函数就是将其他类型转化为当前类。

借助前边的例子,如果我的对象创建语句是这么写的:

Person p = "Kang";

这个代码是编译不过的,因为Person类没有对应的构造函数,但是如果定义一个转换构造函数,自定义一下转换规则,那么这条语句就是可行的,看一个详细的例子:

#include <iostream>
#include <string.h>
using namespace std;


class Person {
public:
    int age;
    char* name;

    Person(int age, char* name);  //一般构造函数
    Person(char* name);  //转换构造函数

};

Person::Person(int age, char* name) {
    this->age = age;
    this->name = name;
}

Person::Person(char* name) {  //转换构造函数
    this->name = name;
    this->age = 0;
}


int main(){
    Person p1(12,"kang");   //调用一般构造函数
    Person p2 = "Tom";      //调用转换构造函数,等效于 Person p2(“Tom”);
    cout<<p1.age<<"  "<<p1.name<<endl;
    cout<<p2.age<<"  "<<p2.name<<endl;

    p2 = "Jack";  //调用转换构造函数,因为=右边不是Person类型,所以不会调用运算符=的重载函数
    cout<<p2.age<<"  "<<p2.name<<endl;
}

运行结果:

 可以看到,Person p2 = "Tom" 是没有问题的,正常来说,"Tom"是一个字符串,无法用他对 p2 进行初始化的,但是编译器会判断类型转换规则能不能用,再查看类中是否有转换构造函数,然后将“Tom”通过转换构造函数转化为Person类的对象,然后再调用拷贝构造函数对p2进行初始化。相当于中间用转换构造函数创建了一次匿名类,然后再使用拷贝构造函数。不得不说,编译器真的很努力了~

对于 p2 = "Jack" ,则先调用转换构造函数,再调用了赋值运算符重载函数。

需要强调的是,转换构造函数用于标准类型向自定义类转换,因此转换构造函数的形参只能有一个,或者有多个形参但最多只有一个不是默认参数。

Person::Person(char* name) {  //转换构造函数
    this->name = name;
    this->age = 0;
}

Person::Person(char* name, int age = 0) {  //转换构造函数
    this->name = name;
    this->age = age;
}

Person::Person(int age, char* name) {  //不是转换构造函数
    this->age = age;
    this->name = name;
}

5. 移动构造函数

拷贝构造函数可以用其他对象初始化新对象,但是,如果成员指针变量指向的数据量很大,再进行深拷贝,那么拷贝函数的时间开销是巨大的。而移动构造函数的诞生,就是为了解决拷贝构造函数时间开销大的问题。

借助一个例子来说明移动构造函数是如何工作的:

Person p = Person();

这条语句会先调用一般构造函数或者默认构造函数(取决于有没有手动定义一般构造函数),创建一个匿名对象;再调用拷贝构造函数。如果Person成员指针变量指向的数据量很大,那么时间开销是很大的。

移动构造函数的优化思路是:既然一般构造函数创建出来的是一个匿名对象,我们不妨把新对象的指针直接指向匿名对象指向的空间,说简单一些就是,指针简单拷贝,然后将匿名对象的指针置空,因为匿名对象是无法被用户调用的,如此一来,匿名对象成员指针指向的数据都归新对象的指针变量了。有点鸠占鹊巢的意思==>

移动构造函数的声明形式如下:

ClassName(CalssNmae &&obj);

可以看到,为了能够引用匿名对象,移动构造函数的输入是右值引用。

光说不直观,看一个例子:

#include <iostream>
#include <string.h>
#include <chrono>
using namespace std;

class demo{
public:
    demo(){               //一般构造函数
        num = new int[100000000];
        cout<<"constructor"<<endl;
    }
    demo(const demo& de) {   //拷贝构造函数
        num = new int[100000000];
        memcpy(this->num, de.num, 100000000);
        cout<<"copy constructor"<<endl;
    }
//    demo(demo &&de) {     //移动构造函数
//        num = de.num;
//        de.num = NULL;
//        cout<<"move constructor"<<endl;
//    }
private:
   int *num;
};

demo get_demo(){
    return demo();
}

int main(){

    auto start = std::chrono::system_clock::now();
    demo a = demo();
    auto finish = std::chrono::system_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
    std::cout<<double(duration.count())<< "ms" <<std::endl;

    return 0;
}

这个代码不能用IDE去编译运行,因为IDE会进行优化,我们就无法观察程序原本的运行轨迹了。

在Linux系统下,找到cpp所在文件夹,打开终端,输入:

g++ main.cpp  -fno-elide-constructors -o main

其中,g++是选择的编译器,main.cpp是源文件,-fno-elide-constructors是禁用构造函数优化。然后,当前目录下会生成一个名为main的可执行程序,运行便可得到程序输出。

运行结果:

 可以看到,拷贝构造函数为了复制长度100000000的数组,运行了35ms。

现在我们将17-21行的注释去掉,也就是定义了移动构造函数,再次编译运行,结果为:

 可以看到,没有了大块的内存拷贝,运行时间已经不在ms量级了(大约0.1ms),这样就大大降低了拷贝构造函数的时间开销。

从这个代码也能看出来:

当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

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

C++ 构造函数详解 的相关文章

  • 纯手工解密几大在线js加密网站(1)

    0x0 开头 最近闲来无事 xff0c 来看一下目前网络上哪家加密工具的强度最高的 本人技术有限 xff0c 最终结果不能代表什么 xff0c 大家有遇到什么其他的js加密技术破解难题的 xff0c 也可以一起互相讨论 xff0c 也可以问
  • 纯手工解密几大在线js加密网站(3)

    0x0 开头 续接上章 xff0c 心血来潮想挨个破解一下各大js加密的网站 xff0c 了解一下现有的js加密的逻辑 0x1 介绍 Sojson支持js的不可逆混淆加密 xff0c 和很多高级的加密配置 xff0c 还增加了小白专用的一键
  • 如何在局域网内设置多个网段

    连接状态中的 属性 按钮 xff0c 选择 TCP IPv4 协议 xff0c 点击下面的 属性 按钮 xff0c 点击 高级 按钮 在高级TCP IP设置里面点击 添加 按钮 输入新网段的ip地址 xff08 以192网段为例 xff09
  • B-猫猫向前冲(拓扑排序

    题意 xff1a input 输入有若干组 xff0c 每组中的第一行为二个数N xff08 1 lt 61 N lt 61 500 xff09 xff0c M xff1b 其中N表示猫猫的个数 xff0c M表示接着有M行的输入数据 接下
  • 配置wsl2的图形界面

    1 更新源 span class token function sudo span span class token function apt get span update span class token function sudo s
  • 关于yyyyMMdd的正则表达式的使用

    String pattern 61 34 0 9 3 1 9 0 9 2 1 9 0 9 1 0 9 1 1 9 0 9 2 1 9 0 9 3 34 43 34 0 13578 1 02 0 1 9 12 0 9 3 01 0 469 1
  • qt下使用opencascade源代码

    c 43 43 基础太弱 xff0c 纠正一下 xff0c 在PRO中使用包含目录就可以使用 lt gt xff0c 将下载的opencascade文件通过make编译和安装 xff0c 添加引用就可以了 如果你依然对以下没用的操作感兴趣
  • Linux系统下CPU频率的调整

    省电 or 流畅 root 64 android sys devices system cpu cpu0 cpufreq cat scaling available governors hotplug conservative ondema
  • 禁止显示Apache目录列表-Indexes FollowSymLinks

    第一种方法 禁止显示Apache目录列表 Indexes FollowSymLinks 如何修改目录的配置以禁止显示 Apache 目录列表 缺省情况下如果你在浏览器输入地址 xff1a http localhost 8080 如果你的文件
  • 第十一章、远程联机服务器SSH / XDMCP / VNC / RDP

    维护网络服务器最简单的方式不是跑去实体服务器前面登入 xff0c 而是透过远程联机服务器联机功能来登入主机 xff0c 然后再来进行其他有的没的维护就是了 Linux 主机几乎都会提供 sshd 这个联机服务 xff0c 而且这个服务还是主
  • 如何查看电脑jdk/jre版本以及安装路径

    一 按快捷键win 43 r打开运行窗口 二 输入cmd 回车 xff0c 打开命令框 三 输入 java version 查看jdk版本 注意 xff1a java后面需要有空格 xff0c 不然会报错 java span class t
  • AndroidID、IMEI、OAID获取

    前言 因为项目中经常会遇到要上传一系列设备信息的功能 xff0c 为了方便使用 xff0c 所以就拆分成以下系列文章来单独介绍如何获取各类设备信息 手机运营商获取 AndroidID IMEI OAID获取 地理位置信息经纬度获取 公网IP
  • (二)裸机汇编--点亮LED

    目标 xff1a 点亮LED 1 查数据手册 硬件图中 xff0c 找到LED灯对应的GPIO 从二极管方向看出 xff0c 端口输出低电平时 xff0c 电流经过 xff0c LED点亮 再到数据手册查找对应的寄存器 GPBCON xff
  • Excel中将十六进制字符串转换为汉字的方法

    比如十六进制字符串 D0C2BDAE 转换方法是 在公式里面输入 61 CHAR HEX2DEC LEFT C6 4 amp CHAR HEX2DEC MID C6 5 4 其中C6 是十六进制所在的单元格 xff0c 原理很简单 xff0
  • C - 班长竞选(Kosaraju算法

    题意 xff1a input 本题有多组数据 第一行 T 表示数据组数 每组数据开始有两个整数 N 和 M 2 lt 61 n lt 61 5000 0 lt m lt 61 30000 xff0c 接下来有 M 行包含两个整数 A 和 B
  • debian 11安装pip

    安装curl sudo apt get install curl下载文件get pip py curl https bootstrap pypa io get pip py o get pip py安装依赖python3 distutils
  • Linux运行jar包报错:Error:Invalid or corrupt jarfile xxx.jar

    各种查找 xff0c 没有对症的 xff0c 最后仔细查看信息提示发现是自己大意造成的 xff0c 原因是服务器空间不足导致保存传递过来的jar包不完整导致的 巨坑 xff01 怪自己不够仔细
  • [HPM] Error occurred while trying to proxy request /login/account from localhost:8000 to localhost:8

    HPM Error occurred while trying to proxy request login account from localhost 8000 to localhost 8888 api ENOTFOUND https

随机推荐

  • Unable to perform this action because the process is running.

    总感觉会有小伙伴和我一样纠结吧 我在使用vs code的时候想用调试控制台输入输出 xff0c 结果在输入的时候遇到了这个问题 xff0c 为什么呢 xff0c 我也不知道 xff0c 但是有一个是明确的 xff0c 就是调试控制台不可以作
  • VS Code 翻译插件

    translate var 使用翻译api将其他语言到英文 转换成常见的变量命名形式 Automatically translate words into English variable name Keybindings win 34 A
  • 从ASP.NET Core 3.1中的当前URL获取主机信息

    目录 介绍 问题陈述 解决方案 介绍 在处理 Web 应用程序时 xff0c 很自然 xff0c 我们需要在产品生命周期的各个阶段在各种环境 xff08 即开发 xff0c 测试 xff0c 生产等 xff09 之间切换 换句话说 xff0
  • ONNX系列六 --- 在Java中使用可移植的ONNX AI模型

    目录 安装和导入ONNX运行时 载入ONNX模型 使用ONNX运行时进行预测 摘要和后续步骤 参考文献 下载源547 1 KB 系列文章列表如下 xff1a ONNX系列一 带有ONNX的便携式神经网络 ONNX系列二 使用ONNX使Ker
  • 绒毛动物探测器:通过TensorFlow.js中的迁移学习识别浏览器中的自定义对象

    目录 起点 MobileNet v1体系结构上的迁移学习 修改模型 训练新模式 运行物体识别 终点线 下一步是什么 xff1f 我们可以检测到脸部吗 xff1f 下载TensorFlowJS Examples master zip 6 1
  • YOLOv7 在 ML.NET 中使用 ONNX 检测对象

    目录 什么是 YOLO ONNX 模型 执行预测 示例和参考 References 什么是 YOLO YOLO xff08 You Only Look Once xff09 是一种先进的实时目标检测系统 它是一个在COCO数据集上预训练的物
  • 如何转换PyTorch模型并使用OpenVINO™工具包运行它

    注意 xff1a 本文是使用 OpenVINO 2022 1 创建的 如果您想知道如何使用 OpenVINO 2021 4 的旧 API xff0c 请查看 此notebook 尽管 PyTorch 是 AI 训练的绝佳框架 xff0c 可
  • 程序设计思维与实践 Week14 作业

    A Q老师与石头剪刀布 题意 xff1a 每一个大人曾经都是一个小孩 xff0c Q老师 也一样 为了回忆童年 xff0c Q老师 和 Monika 玩起了石头剪刀布的游戏 xff0c 游戏一共 n 轮 无所不知的 Q老师 知道每一轮 Mo
  • 高可用:MongoDB 容器部署

    MongoDB 是一款 NoSQL 数据 xff0c 通常用来存储非结构化数据 xff0c 我们的产品中也有用到 xff0c 例如 xff1a 一些文件存储在 MongoDB 的 GridFS 中 MongoDB 有三种方式来实现高可用 x
  • 在C#语言中监视SQL Server中的实时数据库更改

    目录 介绍 介绍 在本文中 xff0c 我想向您解释如何在不需要 SQL 依赖和服务代理的情况下监视数据库 有许多方法可以监视数据库更改 xff0c 其中一种方法是 SQL 依赖和服务代理 xff0c 但是在本教程中 xff0c 我们将不使
  • 用Python 3编写的Python 3代码生成器

    目录 介绍 使用代码 关于create python prog py 关于variable name 关于parameter type 关于parameter count token 关于parameter switches 有关布尔参数的
  • 未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0"[已解决]

    前两天升级系统架构 xff0c 升级后打开网页报错了 xff01 xff01 xff01 详细信息如下 xff1a 未能加载文件或程序集 Newtonsoft Json Version 61 4 5 0 0 Culture 61 neutr
  • 联想拯救者Y7000系统安装之路(Win10系统)

    最近新购得联想拯救者Y7000 xff0c 到手的第一件事情就是重装系统 xff0c 这个大家都懂的 使用F2进入BIOS界面 xff0c 使用F12可进入快速启动选择U盘启动 接下来问题来了 xff0c 我用老毛桃制作的U盘启动盘 xff
  • win10 服务主机:DCOM服务器进程启动器 进程导致电脑卡死解决思路

    新买的笔记本 xff1a 联想拯救者Y7000 系统 xff1a win10专业版 xff08 已经禁用了网上可搜的服务 xff0c 没有win10开始菜单的磁条 xff09 原因 xff1a 总是在开机一段时间后系统卡死 xff0c 只能
  • Windows 全新终端 Windows Terminal

    本项目包含 xff1a Windows TerminalWindows 控制台主机 conhost exe 上述两项目的共享组件ColorTool示例项目 将展示如何使用 Windows Console APIs Windows Termi
  • python项目打包发布详解

    PyInstaller打包Python项目详解 lt h1 gt lt div class 61 34 clear 34 gt lt div gt lt div class 61 34 postBody 34 gt PyInstaller打
  • python批处理打开多个文件

    背景 xff1a 有时候我们需要在服务器上同时运行多个程式 xff0c 但是却需要一个一个的打开 xff0c 比较费时间 xff0c 而且一旦服务器重启后 xff0c 不懂程式运行的人受限于环境及代码原理 xff0c 很难逐个将程式逐个打开
  • 挂载别的系统挂掉的磁盘解决步骤,mount: unknown filesystem type ‘LVM2_member‘ 报错

    挂载别的系统挂掉的磁盘解决步骤 1 在新的虚机添加磁盘 按照下边操作步骤即可使linux系统重新读取并识别到新硬盘 xff1a 1 1 确定主机总线号 xff1a root 64 iNeedle ls sys class scsi host
  • 序设计思维与实践 CSP-M4

    A 题意 xff1a 题目描述 这一天 xff0c TT因为疫情在家憋得难受 xff0c 在云吸猫一小时后 xff0c TT决定去附近自家的山头游玩 TT来到一个小湖边 xff0c 看到了许多在湖边嬉戏的鸭子 xff0c TT顿生羡慕 此时
  • C++ 构造函数详解

    目录 0 什么是构造函数 1 默认构造函数 2 一般构造函数 3 拷贝构造函数 4 转换构造函数 5 移动构造函数 0 什么是构造函数 在定义类的成员函数的时候 xff0c 一般的成员函数与普通的全局函数没有太大的区别 xff0c 在定义函