从C学C++(6)——构造函数和析构函数

若无特殊说明,本博客所执行的C++标准均为C++11.

构造函数与析构函数

构造函数定义

  • 构造函数是特殊的成员函数,当创建类类型的新对象,系统自动会调用构造函数构造函数是为了保证对象的每个数据成员都被正确初始化。

构造函数的特点

  • 函数名和类名完全相同

  • 不能定义构造函数的类型(返回类型)(因为构造函数需要返回对象本身,所以不能指定返回类型),也不能使用void。

  • 通常情况下构造函数应声明为公有函数,否则它不能像其他成员函数那样被显式地调用。

    (特殊情况,例如“单例模式”设计的时候,会将构造函数声明为私有函数,以禁止外界定义一个对象)

  • 构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(构造函数可以重载

析构函数定义和特点

  • 函数名和比类名前面多了一个字符“~”
  • 没有返回类型(和构造函数一样)
  • 没有参数,因此析构函数不能被重载

如果没有定义构造函数和析构函数,默认会生成两个空函数作为构造函数和析构函数。

class Test{
public:Test(){//默认构造函数就是这样,空的,且没有参数   }~Test(){//默认析构函数就是这样,空的,且没有参数 }
}

构造/析构函数和变量的生存期

当我们定义一个对象的时候,构造函数会被默认调用;当对象的生命周期结束的时候(一般是函数运行结束的时候,对全局对象来说是程序运行结束的时候),会默认调用析构函数进行对象的释放。

当我们new一个对象的时候,构造函数会被默认调用;当delete对象的时候,会默认调用析构函数进行对象的释放(而且通常需要程序员手动调用delete才能释放该对象)。

这里需要注意的是,全局对象的构造是先于main函数执行的(这个我其实存有疑惑,特别是在单片机这样的嵌入式设备的C++编程中,因为单片机的main函数入口前是汇编指令,负责声明对堆栈大小和中断向量表等操作,之后通过指令跳转的main函数入口的,C++的全局变量的构造函数是如何插入到指令跳转到main函数之前的呢?🤔)。

最后,析构函数是可以像普通成员函数一样显示调用的。

转换构造函数和explicit

转换构造函数

拥有单个参数的构造函数被编译器用于隐式类型转换的时候,称为转换构造函数。

因此,类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型。(可能在我们没有注意到的地方调用了构造函数)。如下:

class Test{Test(int a){//可以什么也不做}
}int main(void){Test t(0); //此时,带一个参数的构造函数,充当的是普通构造函数的功能t = 20; // 此时,编译器会默认帮我们找到一个能够用的构造函数(即一个参数的构造函数)进行隐性类型转换;遵循以下步骤:// 1. 调用转换构造函数将20这个整数转换成类类型(生成一个临时对象)// 2. 临时对象赋值给t对象(调用的是=运算符成员函数)
}

初始化中的=不是赋值运算符

在初始化语句中的等号不是运算符。编译器对这种表示方法有特殊的解释,编译器会将其等价于调用单个参数的构造函数构造对象,而不是赋值运算(调用=重载函数)

因为对编译器来说,Test t = 10; 这样的语句,是对象变量的初始化,直接调用单个参数的构造函数进行初始化就可以了(等价于 Test t(10););

而不需要像 Test t; t = 10; 中一样经过先调用构造函数构造对象,后面那个赋值语句(注意这个不是初始化语句),会分两步,先调用一个参数的构造函数做转换构造函数构造一个临时对象,再调用赋值构造函数(也就是=运算符的重载函数)将临时对象赋值给我们赋值的对象。

这里顺便说明等号运算符一般的重载形式,如下:

Test& Test::operator=(const Test& other){//需要的操作return *this; //返回对象本身
}

需要注意:

  • 返回对象的引用,一般我们会直接返回 *this , 因为这个重载函数是在 类似a=b 是调用的,其等价于 a = a.operator=(b) 这样的调用,因此,能够很明显函数,=运算符重载函数是需要返回对象自身的(建议返回引用 return *this)
  • 接收参数必须是对象的引用,这里注意到,如果接收参数不是对象的引用的话,而是一个对象的话(参数值传递),因为a=b 等价于 a = a.operator=(b) ; 而值传递的参数决定了,这里需要有一个实参给形参传值的操作,而实参给形参传值的操作也就是 形参=实参 这样的操作,那又会调用operator=(b) 函数,就会形成递归调用,死循环了。

explicit关键字

如果想要避免编译器对类做默认类型转换的话,我们可以给一个形参的构造函数前加上 explicit 这样如果我们使用类似 Test t; t = 10; 这样的操作时,编译器会报错,而不会使用一个参数的(转换)构造函数和等号运算符函数做隐式的类型转换。在这种情况下,对于Test 这个类对象,我们必须使用显式的类型转换。

构造函数和成员初始化

构造函数初始化列表

构造函数后可以使用: 跟一个构造函数初始化列表(成员变量(形参)),用于成员变量的初始化,注意,在构造函数体内执行的成员变量的操作是赋值操作,不是初始化操作。

Clock::Clock(int hour, int minute, int second) : m_hour(hour), m_minute(minute), m_second(second) {//初始化列表是初始化阶段// 函数体内是普通计算阶段// m_hour = hour;// m_minute = minute;  // m_second = second; //这部分是赋值操作而不是 初始化操作std::cout << "clock initialized: " << m_hour << ":" << m_minute << ":" << m_second << std::endl;
}

分清成员变量的初始化和赋值操作后,由之前的const变量引用变量 必须在初始化时赋初值(后期,const变量不可被赋值更改,而引用变量 更改则是对对象本身进行更改)可知。如果类内成员有const成员引用成员的话,只能在构造函数的初始化列表中完成初始化。

对象成员的初始化

如果一个类中含有其他类的对象作为成员变量(这里成为对象成员),如果我们没有在这个类的初始化列表中提供这个对象成员的初始化的话,那么当这个类的对象定义时,会默认调用这个对象成员没有任何参数的构造函数(即这个对象成员的默认构造函数);如果这个对象成员没有提供这个函数的话,就和我们平时定义一个类对象(这个类的构造函数只有一个有参数的构造函数)而我们定义时没有传入任何参数一样,会报错找不到合适的构造函数。

对象成员的初始化

所以,如果一个类拥有对象成员(且这个对象成员没有不带任何参数的构造函数),在外类对象的初始化的时候必须在初始化列表中给出这个对象成员的初始化,不让会报错找不到对应的构造函数

拷贝构造函数

拷贝构造函数

  • 功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
  • 声明:只有一个参数并且参数为该类对象的引用,形如Clock::Clock(const Clock& other)
  • 如果类中没有说明拷贝构造函数,则系统自动生成一个缺省拷贝构造函数(做逐成员赋值),作为该类的公有成员。

拷贝构造函数调用几种情况

凡是需要用到对象的赋值操作的,都会有拷贝构造函数调用(如函数参数的实参到形参的值传递,函数返回值的赋值等待)

  • 当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。
  • 当函数的返回值是类对象,函数执行完成返回调用者时使用。理由也是要建立一个临时对象中,再返回调用者。为什么不直接用要返回的局部对象呢?因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。

拷贝构造函数的几种情况

深/浅拷贝和禁止拷贝

深/浅拷贝

**对于像成员变量中有在堆上分配的类,一般都需要自己实现拷贝构造函数,因为默认的拷贝构造函数是浅拷贝,仅仅将对成员变量的值拷贝而已。**但成员变量中有在堆上分配的类,每个对象都需要重新分配一个空间,不然,当对象自动销毁时,由于不同对象内的指针指向同一个空间,当前面一个对象销毁后,后一个对象内的指针便成了野指针,无法delete。

#pragma once
#include  <iostream>
#include <cstring>
class Str
{
private:char* m_str;size_t m_length;
public:Str(const char* str = ""): m_length(strlen(str)), m_str(new char[m_length + 1]){strcpy(m_str, str);std::cout << "Str(const char* str) called: " << m_str << std::endl;}Str(const Str& other): m_length(other.m_length), m_str(new char[other.m_length + 1]){strcpy(m_str, other.m_str);std::cout << "Str(const Str& other) called: " << m_str << std::endl;}~Str(){std::cout << "Str Decon called:" << m_str << std::endl;delete[] m_str;}};
class Empty { //在空类中测试禁止拷贝
public:Empty() {std::cout << "Empty constructor called." << std::endl;}~Empty() {std::cout << "Empty destructor called." << std::endl;}// int a; // 如果有成员变量,Empty类的大小会大于1字节,计算方式与结构体一样
private: //需要将拷贝构造函数和赋值运算符声明为私有,以禁止拷贝Empty(const Empty&); // 禁止拷贝构造函数Empty& operator=(const Empty&); // 禁止赋值运算符
};

禁止拷贝

通过将拷贝构造函数与=运算符声明为私有,并且不提供它们的实现 ,可以使得,Str s2(S1); ,Str s2 = s1; , Str s2; s2 = s1 这样的语句直接在编译时报错,帮助我们实现只有单个对象禁止拷贝的功能。需要注意上面三个语句编译器所寻找的函数有一些不同。

  1. Str s2(S1); 找只有一个参数,且参数是Str 对象的构造函数,其实就是拷贝构造函数(这是我们人为对这个重载函数给的名字罢了)。
  2. Str s2 = s1; 找只有一个参数,且参数是Str 对象的构造函数,其实就是拷贝构造函数(这是我们人为对这个重载函数给的名字罢了),注意这里是初始化,所以是找拷贝构造函数,不是找=的重载函数。
  3. Str s2; s2 = s1 先找不带任何参数的构造函数,第二个语句找=运算符的重载函数 operator=(const Str& other)

空类默认产生的成员

一个类(类型是没有大小的,这里类的大小指的是这个类对象的大小,像sizeof(int) 得到的是int 变量的一个实例的大小一样) 的大小只取决于其中的非static成员大小(计算规则和struct一样),与以下都无关:

  • static成员(其实这些成员相当于全局变量,只是语法上编译器层面把他归类在类中,需要通过类域访问)
  • 成员函数(不论static还是一般成员函数,包括各种构造析构函数,这些函数都是放在程序的代码段,他们和类的大小没有关系,那这些成员函数怎么区分对象的?依靠我们之前说过的隐藏参数this指针)

如果一个类没有任何成员,是一个空的类,为了标识它的存在,编译器会给它分配一个字节的空间。

测试代码和结果:

深浅拷贝和空类

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

清理 Windows C 盘该注意什么

C 盘空间不足会严重影响系统性能。 清理 C 盘文件时&#xff0c;首要原则是安全。错误地删除系统文件会导致 Windows 无法启动。下面我将按照 从最安全、最推荐到需要谨慎操作的顺序&#xff0c;为你详细列出可以清理的文件和文件夹&#xff0c;并提供操作方法。 第一梯队&…

Python Selenium 滚动到特定元素

文章目录 Python Selenium 滚动到特定元素⚙️ **1. 使用 scrollIntoView() 方法&#xff08;最推荐&#xff09;**&#x1f5b1;️ **2. 结合 ActionChains 移动鼠标&#xff08;模拟用户行为&#xff09;**&#x1f9e9; **3. 使用坐标计算滚动&#xff08;精确控制像素&…

你写的 Express 接口 404,可能是被“动态路由”吃掉了

本文首发在我的个人博客&#xff1a;你写的 Express 接口 404&#xff0c;可能是被“动态路由”吃掉了 前情提要 最近参与公司的一个项目前端 React&#xff0c;后端用的 Express。目前我就做一些功能的新增或者修改。 对于 Express &#xff0c;本人没有公司项目实战经验&…

【Java面试】你是怎么控制缓存的更新?

&#x1f504; 一、数据实时同步失效&#xff08;强一致性&#xff09; 原理&#xff1a;数据库变更后立即失效或更新缓存&#xff0c;保证数据强一致。 实现方式&#xff1a; Cache Aside&#xff08;旁路缓存&#xff09;&#xff1a; 读流程&#xff1a;读缓存 → 未命中则…

react-嵌套路由 二级路由

什么是嵌套路由&#xff1f; 在一级路由中又内嵌了其他路由&#xff0c;这种关系就叫做嵌套路由&#xff0c;嵌套至一级路由内的路由又称作二级路由 嵌套路由配置 实现步骤 配置二级路由 children嵌套 import Login from "../page/Login/index"; import Home from …

【CMake基础入门教程】第八课:构建并导出可复用的 CMake 库(支持 find_package() 查找)

很好&#xff01;我们进入 第八课&#xff1a;构建并导出可复用的 CMake 库&#xff08;支持 find_package() 查找&#xff09;。 &#x1f3af; 本课目标 你将掌握&#xff1a; 如何构建一个库并通过 install() 导出其配置&#xff1b; 如何让别人在项目中使用 find_package…

Jenkins与Kubernetes深度整合实践

采用的非jenkins-slave方式 jenkins配置&#xff1a; Jenkins添加k8s master节点的服务器信息 在Jenkins容器内部与k8s master节点设置免费登录 # docker过滤查询出运行的Jenkins服务 $ docker ps | grep jenkins# 进入Jenkins容器内部 $ docker exec -it jenkins-server /bi…

GraphQL API-1

简介 判断GraphQL方式 判断一个网站是否使用了GraphQL API&#xff0c;可以通过以下几种方法&#xff1a; 1. 检查网络请求 查看请求端点 GraphQL 通常使用单一端点&#xff0c;常见路径如&#xff1a; /graphql/api/graphql/gql/query 观察请求特征 POST 请求为主&…

推荐C++题目练习网站

LeetCode LeetCode是一个全球知名的编程练习平台&#xff0c;提供大量C题目&#xff0c;涵盖数据结构、算法、系统设计等。题目难度从简单到困难&#xff0c;适合不同水平的学习者。平台支持在线编写代码并即时运行测试&#xff0c;提供详细的题目讨论区和官方解答。 Codeforc…

Spring Cloud 微服务(服务注册与发现原理深度解析)

&#x1f4cc; 摘要 在微服务架构中&#xff0c;服务注册与发现是整个系统运行的基础核心模块。它决定了服务如何被定位、调用和管理。 本文将深入讲解 Spring Cloud 中 Eureka 的服务注册与发现机制&#xff0c;从底层原理到源码分析&#xff0c;再到实际开发中的最佳实践&a…

【Linux 设备模型框架 kobject 和 kset】

Linux 设备模型框架 kobject 和 kset 一、Linux 设备模型概述二、kobject 与 kset 的核心概念1. kobject2. kset3. 关键数据结构 三、kobject 与 kset 的实现源码四、源码解析与使用说明1. kset 的创建与初始化2. kobject 的创建与属性3. sysfs 属性操作4. 用户空间访问示例 五…

一起学前端之HTML------(1)HTML 介绍

HTML 介绍 HTML 即超文本标记语言&#xff08;HyperText Markup Language&#xff09;&#xff0c;它是构成网页的基础技术之一。HTML 借助各种标签&#xff08;Tag&#xff09;对网页的结构与内容加以描述。下面为你介绍其核心要点&#xff1a; 关键特性 标签结构&#xff…

整体迁移法迁移 Docker 镜像

docker添加了新的镜像数据盘&#xff0c;数据盘迁移步骤 使用整体迁移法迁移 Docker 镜像后&#xff0c;可以在确认迁移成功且新数据盘正常使用后&#xff0c;删除旧数据目录来释放空间1。 # 停止 Docker 服务 sudo systemctl stop docker # 停止 socket 监听器 sudo systemct…

智能IDE+高效数据采集,让数据获取接近0门槛

亮数据也有了自己的官方账号&#xff0c;大家可以关注&#xff1a;https://brightdata.blog.csdn.net/ 现在正有福利&#xff0c;有兴趣的伙伴可以访问链接&#xff1a; https://www.bright.cn/products/web-scraper/?utm_sourcebrand&utm_campaignbrnd-mkt_cn_csdn_jhx…

GNSS位移监测站在大坝安全中的用处

一、实时监测大坝变形 整体位移监测 GNSS&#xff08;全球导航卫星系统&#xff09;位移监测站能够实时、连续地获取大坝在三维空间中的位置信息&#xff0c;包括水平位移和垂直位移。大坝在长期运行过程中&#xff0c;受到水压力、温度变化、地基沉降等多种因素的影响&#x…

数字图像处理(一):从LED冬奥会、奥运会及春晚等等大屏,到手机小屏,快来挖一挖里面都有什么

数字图像处理&#xff08;一&#xff09; 一、什么是图像&#xff1a;图像就是多维数组图像的存储每一个格子有自己的颜色、深浅如何访问图像&#xff1a;1.对于RGB图像&#xff0c;共有R/G/B三个通道&#xff0c;通过代码来看。图像有单通道和多通道之分&#xff0c;访问时只需…

关于汉语和英语哪个更先进、历史更久的争论

引言&#xff1a;热议背后的思考​ ​ 在全球化浪潮的推动下&#xff0c;英语作为国际通用语言&#xff0c;在世界范围内广泛传播&#xff0c;其在国际商务、科技交流、学术研究等领域占据着重要地位。而汉语&#xff0c;作为世界上使用人口最多的语言之一&#xff0c;承载着…

在不联网的情况下,从可以联网的计算机上拷贝过来的程序报错:nu1301 无法加载源,https://api.nuget.org/v3/index.json

解决方法&#xff1a; 在联网的计算机上&#xff0c;找到nuget文件&#xff0c;拷贝到&#xff0c;不能联网的计算机的相应位置 设置加载这个nuget包&#xff0c;把nuget.org取消。 注意如果出现好多包都不能加载&#xff0c;可能是框架版本的问题&#xff0c;修改框架版本&am…

TCP 状态流程及原理详解:从连接建立到性能优化

一、TCP 协议概述与核心价值 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网协议栈中的核心协议之一&#xff0c;为网络通信提供可靠的、面向连接的数据传输服务。在当今复杂多变的网络环境中&#xff0c;深入理解 TCP 协议的状态…

【STM32 学习笔记】PWR电源控制

在电子设备中&#xff0c;待机&#xff08;Standby&#xff09;和睡眠&#xff08;Sleep&#xff09;是两种不同的省电模式。 1. 待机模式&#xff08;Standby Mode&#xff09;&#xff1a;在待机模式下&#xff0c;设备仍然保持一定程度的活动&#xff0c;但大部分功能处于暂…