C++四大函数

news/2024/3/28 18:04:35/文章来源:https://blog.csdn.net/weixin_44548228/article/details/129254711

  在C++中,每个类都有三种成员函数——构造函数、析构函数和赋值函数(ps:构造函数有构造函数和拷
贝构造函数两种)。
  对于任意一个类A,如果不显式地声明定义以上函数,编译器会自动为A生成4个默认函数,如下:

A(); //默认构造函数
A(const A&); //默认拷贝函数
~A(); //析构函数
A& operator = (const A& a); //默认赋值函数

构造函数和析构函数

  一个类中只有一个析构函数,不能被重载,无参数,无返回值。
  一个类中可以有多个构造函数,能被重载,有参数,无返回值。
  对象的初始化工作放在构造函数中,清除工作放在析构函数中。创建对象时,构造函数被自动执行;当销毁对象时,析构函数被自动执行。不要在构造函数内做与初始化对象无关的工作,不要在析构函数内做与销毁一个对象无关的工作,这样会降低效率甚至降低代码可读性。
  构造函数和析构函数的名字规定:要与类同名,不可随意更改。因两者功能相反,析构函数加了前缀“~”来区分两者(有“取反”之意)。
  C++对象可以用构造函数初始化,构造函数是任何对象创建时自动调用的第一个成员函数,它也只能被每个对象调用一次。
  最好为每个类显式地定义构造函数和析构函数,即使它们暂时空着,尤其是当含有指针成员或引用成员的时候。

构造函数的成员初始化列表

  开始时我们一般都在构造函数体内来初始化数据成员,但这并不是真正意义上的初始化,而是赋值。但因为构造函数是创建一个对象时自动调用的第一个成员函数,所以我们就把它体内的赋值语句当成初始化来看待。
真正的初始化是使用“初始化列表”来进行的。初始化列表位于构造函数参数列表后,在函数体{}之前。这说明该列表里的初始化工作发生在函数体内的任何代码被执行之前,大部分时候构造函数既可以使用初始化列表又能在函数体内赋值初始化,但当类成员变量属于以下三者之一时,必须使用初始化列表。
1、非静态 const 数据成员。
2、引用成员。
3、没有默认构造函数的类对象。(当一个类定义了一个非默认构造函数,且未定义显式默认构造函数,则编
译器不会生成默认构造函数,即此类无默认构造函数)

ps:在继承关系中,子类必须在初始化列表中调用父类的构造函数。
初始化列表以一个冒号开始,接着一个逗号分隔数据列表,每个数据成员都放在一个括号中进行初始化。尽量使用初始化列表进行初始化,因为它更高效。

class A
{
public:A()//构造函数{_a1 = 0;_a2 = 0;}A(const A& a) //拷贝构造函数{_a1 = a._a1;_a2 = a._a2;}
private:int _a1;int _a2;
};
class B
{
public:
// (1)函数体内赋值方式初始化B(int b1, int b2, const A& a){_b1 = b1;_b2 = b2;_a = a;}
// (2)初始化列表的方式初始化B(int b1, int b2, const A& a): _b1(b1), _b2(b2), _a(a){}
private:int _b1;int _b2;A _a;
};
void Test()
{A a;B b(1, 1, a);
}

方式(1),类B的构造函数在函数体内用赋值的方式将成员对象_a初始化。这里看起来只有一条赋值语句,但实际上B的构造函数做了两件事:先暗地里创建 _a 对象(调用了A的构造函数),再调用类A的赋值函数,才将参数 a 赋值给 _a。(上述只针对自定义类型)
方式(2),类B的构造函数在其初始化列表里调用了类A的拷贝构造函数,从而将成员对象 _a 初始化。成员变量按声明顺序依次初始化,而非初始化列表出现的顺序。

class A
{
public:A(int n): _a2(n), _a1(_a2){}
private:int _a1;int _a2;
}
int main()
{A a(1);return 0;
}

  上面代码并不能如愿给 a 初始化。因为成员变量按声明顺序依次初始化,而非初始化列表出现的顺序。这里是先初始化 _a1 ,再初始化 _a2 ,而我们的初始化列表里, _a1 是使用 _a2 的值来初始化的,但此时_a2还没有被初始化。

拷贝构造函数

  拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。
  拷贝构造函数是这样一个函数:第一个参数为本类对象的引用、const引用、volatile引用或const volatile引用,并且无其他参数,或者其他参数都有默认值。
  不可同时定义一个无参构造函数和一个参数全部都有默认值的构造函数,会造成二义性。
  拷贝构造函数的参数必须是同类对象的引用,而不能是对象值。
示例:

class A
{
public:A(A copy){...} //(1)A(const A& other){...} //(2)
};

C++中不可能允许定义(1)这种值传递的拷贝构造函数,原因如下:
这个拷贝构造函数在传参过程中要调用拷贝构造函数本身,因为是“值传递”,而调用拷贝构造函数时要先进行参数传递,参数传递又要调用拷贝构造函数……这样就陷入了不停分配堆栈的无限递归中。

何时调用拷贝函数:
一个对象以值传递的方式传入函数体。
一个对象以值传递的方式从函数返回。
一个对象需要通过另一个对象进行初始化。
何时编译器会生成默认的拷贝构造函数:
  如果用户没有自定义拷贝构造函数,并且在代码中使用到了拷贝构造函数,编译器就会生成默认的拷贝构造函数。但如果用户定义了拷贝构造函数,编译器就不再生成。
如果用户定义了一个构造函数,但不是拷贝构造函数,而此时代码中又用到了拷贝构造函数,那编译器也会生成默认的拷贝构造函数。

深浅拷贝问题
  先前说到如果程序员不显式地声明和定义这四个成员函数,C++编译器会默认生成。但是编译器生成的默认拷贝构造函数的工作方式是内存拷贝,也就是浅拷贝。如果此时类中含有指针成员或引用成员,那么就会出现问题。
  浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的两个指针指向两个不同的地址空间。

为了避免此类错误,我们最好还是别偷懒,自己手动用深拷贝方式实现拷贝构造函数:

String::String(const String& other)
{size_t len = strlen(other.m_data);m_data = new char[len + 1];strcpy(m_data, other.m_data);m_size = len;
}

赋值函数

当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数。
拷贝构造函数和赋值函数的区别:
  拷贝构造函数是在对象被创建并用另一个已存在的对象来初始化它时调用的,而赋值函数只是把一个对象赋值给另一个已存在的对象,使得那个已存在的对象具有和源对象相同的状态。
  一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象。
  实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。

String a("Change");
String b("word");
String c(a); // 调用拷贝构造函数(此前c还不存在)
c = b; // 调用赋值函数(c已经存在了)

赋值函数实现分为四步:
(1)检查自赋值。(要检查地址而不是值)
(2)分配新的内存资源,并复制内容。(strlen不会把‘\0’计算在内,但是strcpy会拷贝‘\0’)
(3)释放原本的内存资源。(防止造成内存泄漏)
(4)返回本对象的引用。(返回 *this 而不是 this更不是 other)

String& String::operator=(const String& other)
{if (this != &other) { //(1)检查自赋值char* temp = new char[strlen(other.m_data) + 1]; //(2)分配新的内存资源strcpy(temp, other.m_data);delete[]m_data; //(3)释放原本的内存资源m_data = temp;m_size = strlen(other.m_data);}return *this;//(4)返回本对象的引用
}

如果我们实在不想编写拷贝构造函数和拷贝赋值函数,又不想编译器自动生成默认函数,这时候我们可以把拷贝构造函数和拷贝赋值函数声明为 private,不去实现他们。

class A
{
//...const char *GetType();
private:A(const A& a); //私有的拷贝构造函数A& operator=(const A& a); //私有的赋值函数
};

如果有人写了以下程序:
A b(a); // 调用了私有的私有的拷贝构造函数
b = a; // 调用了私有的赋值函数
此时编译器就会报错,因为外界不能操作A的私有函数。

构造、拷贝和赋值到底用哪个

对象不存在,要创建它,无其它对象给它初始化 —— 调构造函数
对象不存在,要创建它,有其它对象给它初始化 —— 调构造拷贝函数
对象已存在,要改变它的值,用其他对象给他来赋值 —— 调赋值函数

String类完整代码实现

class String
{
public:String(const char *str = ""); // 默认构造函数String(const String& copy); // 拷贝构造函数~String(); // 析构函数String& operator= (const String& assign); // 赋值函数
private:size_t m_size;// 当前长度char *m_data;// 指向字符串的指针
};// 构造函数
String::String(const char *str)
{if (NULL == str){m_data = new char[1];*m_data = '\0';m_size = 0;}else{int length = strlen(str);m_data = new char[length + 1];strcpy(m_data, str);m_size = length;}
}
// 拷贝构造函数
String::String(const String& other)
{size_t len = strlen(other.m_data);m_data = new char[len + 1];strcpy(m_data, other.m_data);m_size = len;
}
// 析构函数
String::~String()
{delete[]m_data;
}// 赋值函数
String& String::operator=(const String& other)
{if (this != &other) //(1)检查自赋值{char* temp = new char[strlen(other.m_data) + 1]; //(2)分配新的内存资源strcpy(temp, other.m_data);delete[]m_data; //(3)释放原本的内存资m_data = temp;m_size = strlen(other.m_data);}return *this; //(4)返回本对象的引用
}

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

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

相关文章

AIGC被ChatGPT带火!底层基础算力有望爆发式增长

ChatGPT火爆全球的背后,可以窥见伴随人工智能技术的发展,数字内容的生产方式向着更加高效迈进。ChatGPT属于AIGC的具体应用,而AIGC是技术驱动的数字内容新生产方式。AIGC类产品未来有望成为5G时代新的流量入口,率先受益的有望是AI…

MySQL实战之深入浅出索引(下)

1.前言 在上一篇文章中,我们介绍了InnoDB索引的数据结构模型,今天我们再继续聊一下跟MySQL索引有关的概念。 在介绍之前,我们先看一个问题: 表初始化语句 mysql> create table T ( ID int primary key, k int NOT NULL DEFA…

03、SVN 建立版本库

SVN 建立版本库1 版本库2 版本库的建立步骤2.1 创建版本库的根目录2.2 创建子目录2.3 通过命令创建版本库2.4 生成目的介绍1 版本库 Subversion 是将文件数据信息保存到版本库中进行管理的Subversion 允许用户对版本库目录进行定制 2 版本库的建立步骤 2.1 创建版本库的根目…

RK3568平台开发系列讲解(驱动基础篇)Makefile 详解

🚀返回专栏总目录 文章目录 一、Makefile是什么二、Makefile 详解三、Makefile 语法沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将详细介绍Makefile。 一、Makefile是什么 如果只编译一个hello.c文件,非常简单,所以直接执行下面的指令非常方便: gcc hel…

Java List去重 Lis集合去重 List去重效率对比 List去重复元素效率对比 List去重效率

Java List去重 Lis集合去重 List去重效率对比 List去重复元素效率对比 List去重效率 --- List 去重复元素的几种办法 一、概述 面试的时候,有个常见的问题:“List集合如何去除重复元素”。 常见的回答是:“set集合,for循环对比&a…

KingbaseES V8R3 表加密

前言 透明加密是指将数据库page加密后写入磁盘,当需要读取对应page时进行加密读取。此过程对于用户是透明, 用户无需干预。 该文档进行数据库V8R3版本测试透明加密功能,需要说明,该版本发布时间早于V8R6,所以只能进行表…

SQLite安装及常用语句

SQLite简介SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL 数据库引擎。SQLite 源代码不受版权限制。SQLite安装官网下载 SQLite Download Page新建一个sqlite文件夹,将下载…

【Servlet篇2】创建一个web项目

在上一篇文章当中,已经提到了什么是Maven,以及如何使用maven从中央仓库下载jar包。【Tomcat与Servlet篇1】认识Tomcat与Maven_革凡成圣211的博客-CSDN博客Tomcat,mavenhttps://blog.csdn.net/weixin_56738054/article/details/129228140?spm…

2023年java春招面试题及答案

2023年java春招面试题1、下面有关jdbc statement的说法错误的是?2、下面有关JVM内存,说法错误的是?3、下面有关servlet service描述错误的是?4、下面有关servlet和cgi的描述,说法错误的是?5、下面有关SPRIN…

LeetCode 1237. Find Positive Integer Solution for a Given Equation【双指针,二分,交互】

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

开发场景中前端交付的对于后端数据的获取功能书写+页面简繁体转换+页面链接跳转新页面

1,开发场景中前端交付对于后端数据的获取功能书写 首先,我们明确基本逻辑概念,前端获取数据本质是利用ajax中的api接口来获取变量,再将其导入我们的data; 明确基本概念开发就可以进行ajax的定义 下文中e变量是获取前端…

全志T3+FPGA国产核心板——Pango Design Suite的FPGA程序加载固化

本文主要基于紫光同创Pango Design Suite(PDS)开发软件,演示FPGA程序的加载、固化,以及程序编译等方法。适用的开发环境为Windows 7/10 64bit。 测试板卡为全志T3+Logos FPGA核心板,它是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计…

【观察】连续八年霸榜云数据库“领导者”,揭秘亚马逊云科技背后的“统治力”...

日前,全球市场分析机构 Gartner发布《2022 云数据库管理系统魔力象限》报告。其中,在Gartner本次魔力象限报告评估的20家供应商中,亚马逊云科技在纵轴“执行能力”和横轴“愿景完整性”两个维度分别处于最高、最右位置,这也是亚马…

ANTLR的IDE——ANTLRWorks2的安装及基本使用

1. ANTLRWorks2的简单介绍 ① ANTLR官网对ANTLRWorks2的介绍 ANTLRWorks 2.此IDE是ANTLR v3 / v4语法以及StringTemplate模板的复杂编辑器。 它可以运行ANTLR工具来生成识别器,并可以运行TestRig(在命令行上运行)来测试语法。 要将ANTLR生成…

Java内置队列和高性能队列Disruptor

一、队列简介 队列是一种特殊的线性表,遵循先入先出、后入后出(FIFO)的基本原则,一般来说,它只允许在表的前端进行删除操作,而在表的后端进行插入操作,但是java的某些队列运行在任何地方插入删…

EEGLAB处理运动想象脑电数据

最近在看论文时,经常看到作者处理数据的过程,之前都是一代而过,知道怎么处理就可以了,一直没有实践,最近需要一些特殊的数据,需要自己处理出来,这里尝试着自己用MATLAB处理数据,记录…

Kubernetes12:k8s集群安全机制 ***与证书生成***

Kubernetes12:k8s集群安全机制 1、概述 1)访问一个k8s集群的时候,需要经过以下三个步骤才能完成具体操作 第一步:认证操作第二部:鉴权操作(授权)第三部:准入控制操作 2&#xff…

Java枚举详解

一.枚举 1.为什么有枚举? 如果我们的程序需要表示固定的几个值: 比如季节:spring (春),summer(夏),autumn(秋),winter(冬) 用常量表示: public static final int SEASON_SPRING 1;public st…

记一次MySQL数据迁移到SQLServer全过程

为什么要做迁移? 由于系统版本、数据库的升级,导致测试流程阻塞,为了保证数据及系统版本的一致性,我又迫切需要想用这套环境做性能测试,所以和领导、开发请示,得到批准后,便有了这次学习的机会…

idea 安装JUnit单元测试框架

JUnit是一套专门用于java的单元测试框架,主要是测试方法 junit4官方网站: JUnit – About junit5官方网站:JUnit 5 框架依赖:junit-4.12.jar;hamcrest-core-1.3.jar 安装步骤: (1&#xff…