【类和对象(中)】六大默认成员函数

news2024/12/23 7:16:44

文章目录

  • 前言
  • 一、🌺构造函数(重点🌺)
      • 1.构造函数的特性
  • 二、🌺析构函数(重点🌺)
      • 1.析构函数的特性
  • 三、🌺拷贝构造函数 (重点🌺)
      • 1.拷贝构造函数特性
        • 构造函数和拷贝构造函数的区别和联系
  • 四、🌺赋值运算符重载(重点🌺)
    • 🌺1.什么是运算符重载
      • 1.1运算符重载存在的意义
      • 1.2运算符重载的一些注意事项
    • 🌺2.赋值运算符重载的特性
  • 五、🌺取地址运算符重载(了解)
  • 六、🌺const取地址运算符重载(了解)
  • 总结


前言

本文继类和对象上,开始讲述默认成员函数。
默认成员函数是:我们不具体写,编译器会自动生成的函数叫默认成员函数。


一、🌺构造函数(重点🌺)

构造函数是类的一个默认成员函数,它虽然叫构造函数,但它的作用并不是构造一个对象,而是初始化一个对象。
它与Init函数不同, 每次实例化一个新的对象都要调用 Init函数来完成对象的初始化,比较麻烦,而这个构造函数,可以解决这个问题。

1.构造函数的特性

1.函数名与类名相同

2.没有返回值

3.编译器实例化时会自动调用该构造函数

4.构造函数可以重载

stack::stack(int default_capapacity)
{
	cout << "stack(int default_capapacity = 4)" << endl;

	_a = (int*)malloc(sizeof(int) * default_capapacity);
	if (nullptr == _a)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	_capacity = default_capapacity;
	_top = 0;
	cout << default_capapacity << endl;
}

stack::stack()
{
	cout << "stack(int default_capapacity = 4)" << endl;
	int default_capapacity = 4;
	_a = (int*)malloc(sizeof(int) * default_capapacity);
	if (nullptr == _a)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	_capacity = default_capapacity;
	_top = 0;
	cout << default_capapacity << endl;
}

这两个构造函数可以构成函数重载,并且一个是全缺省的构造函数,一个是无参的构造函数,这两个构造函数都是默认构造函数,对于编译器来讲,默认构造函数只能存在一个。

所以如果两个都写,编译器会报一个错误:调用不明确。在这里插入图片描述

一般写构造函数最好写全缺省的默认构造函数。

注意:默认构造函数的声明和定义不能同时给缺省值。
不能使用下面的方法调用构造函数:

	stack s1();
	//这样看还不想函数声明
	stack func();//这样看就像是函数声明

这样调用构造函数会给编译器造成困扰,编译器不知道这样到底是调用默认构造函数还是一个函数的声明。
构造函数比普通的函数更特殊,它也有自己的调用方法。

正确的调用方法:

	stack s1;
	//上面这中是不给参数,编译器就会默认使用缺省值
	//两种都可以,下面这种是自己给参数
	stack s1(4);

5.如果类中没有显式定义一个默认构造函数,那么编译器会自动生成一个无参数的默认构造函数。一旦我们自己定义构造函数,编译器就不会再自动生成。


6. 编译器自己生成的默认构造函数不会对内置类型做处理,对自定义类型会调用自定义类型自己的默认构造函数。
所以:有内置类型就必须要自己写构造函数,不能用编译器自己生成的默认构造函数。
如果全部都是自定义类型成员,则可以考虑让编译器自己生成。


但是在C++11的标准发布之后,打了一个补丁:在成员变量声明的时候可以自己给缺省值。注意:不是初始化,只是声明。
缺省值的意义在于:如果我们没有自己给指定的值,编译器就会用缺省值,如果我们自己给了,编译器就不会用缺省值了。


7.无参的构造函数和全缺省的构造函数都可以叫默认构造函数,还有编译器自己生成的构造函数也可以叫做默认构造函数。这几个默认构造函数只能存在一个。

对于编译器自己生成的默认构造函数中,有一个特点:
内置类型不做处理,自定义类型会调用它自己的默认构造函数。

所以我们自己实现默认构造函数的时候,如果有内置类型,最好写成全缺省的构造函数,这样编译器就会调用该构造函数。


不需要我们自己实现默认构造函数的两种情况:
1.内置类型成员都有缺省值,且缺省值符合我们的要求。

2.全是自定义类型的构造,且这些自定义类型都定义默认构造函数。
比如:两个栈实现队列,两个栈都是内置类型,不需要自己写构造函数,会直接调用自定义类型的默认构造函数

二、🌺析构函数(重点🌺)

析构函数同样是类的一个默认成员函数,析构函数的功能是不是销毁对象本身的,销毁对象本身是编译器完成的,析构函数是销毁对象中申请的资源空间。

析构函数会在对象销毁时自动调用。

1.析构函数的特性

  1. 析构函数名是在类名前加上字符 ~。

  2. 无参数无返回值类型。

~stack();

这是一个典型的析构函数的声明。

  1. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

注意:(1)析构函数不能重载.
(2)默认生成的析构函数:对内置类型不做处理,对自定义类型则会调用它的默认析构函数

两种不需要写析构函数的情况:
(1).当成员都是内置类型时(没有动态申请资源的),不需要写析构函数。

(2).需要释放资源的都是自定义类型的,也不需要写析构函数。(对于自定义类型,编译器会自动调用它的析构函数)


  1. 类对象生命周期结束时,C++编译系统系统自动调用析构函数。

三、🌺拷贝构造函数 (重点🌺)

拷贝构造函数是特殊的构造函数,是构造函数的重载形式。
它的作用是用已存在的类对象来初始化一个新的类对象。

与下面所讲的赋值运算符重载区别,注意区分。


1.拷贝构造函数特性

  1. 拷贝构造函数是构造函数的一个重载形式。

  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。(另一个参数是this指针) 因为C++规定,自定义类型在传参时会调用它的拷贝构造函数。

在这里插入图片描述
因为把d1传给d时,需要调用d的拷贝构造函数,才能传给d,调用d的拷贝构造函数后,又需要传参,传d1给d,而传参又会调用d的拷贝构造函数,这样会引发无穷递归。

所以拷贝构造函数需要传引用,这样传引用就不会调用他的拷贝构造函数了,因为d 就是d1的引用。

注意:拷贝构造函数的参数最好加一个const修饰,因为可能会错写成下面这样:
在这里插入图片描述
这样就写反了。


  1. 若未显式定义,编译器会生成默认的拷贝构造函数。

默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

与构造和析构函数不同,拷贝构造函数会对内置类型和在自定义类型都处理。

内置类型完成的拷贝构造会完成值拷贝(浅拷贝),
自定义类型会调用它的拷贝构造函数。

C++规定:调用函数传参时:内置类型直接拷贝
自定义类型必须调用它的拷贝构造函数。

但是不能因为编译器会处理,就不写拷贝构造函数,因为编译器仅仅是完成浅拷贝。
比如下面的案例:

class stack
{
public:
stack(int default_capapacity =4 )
{
	cout << "stack(int default_capapacity = 4)" << endl;

	_a = (int*)malloc(sizeof(int) * default_capapacity);
	if (nullptr == _a)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	_capacity = default_capapacity;
	_top = 0;
	cout << default_capapacity << endl;
}


~stack()
{
	cout << "stack::~stack()" << endl;
	if (_a)
	{
		free(_a);
		_a = NULL;
		_capacity = _top = 0;
	}
}


private:
	int _capacity;
	int _top;
	int *_a;
};

int main()
{
	stack s1;
	stack s2(s1);
	return 0;
}

当我们实例化一个类对象时,会自动调用它的构造函数,然后实例化一个类对象s2(这个s2是已经存在的!)时,调用的是拷贝构造函数。
在这里插入图片描述

可以看到,s2完完全全拷贝了s1的内容,此时的情况是这样的:
在这里插入图片描述
所以此时s1的_a和s2的_a指向的是同一块空间。
那么在s2生命周期结束的时候会调用它的析构函数,释放了_a指向的空间,在s1生命周期结束的时候也会调用它的析构函数,此时就会重复释放_a指向的空间,会出问题。

注意:是s2先调用析构函数,即后进先出原则!!!
注意:是s2先调用析构函数,即后进先出原则!!!
注意:是s2先调用析构函数,即后进先出原则!!!

所以:像这样有动态申请空间的不能直接让编译器生成默认拷贝构造函数,需要自己实现深拷贝构造函数。

上面的例子中正确的实现拷贝构造函数的方法如下:

	stack(const stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc fail\n");
			exit(-1);
		}
		// destination, source,num
		memcpy(_a, st._a, sizeof(int) * st._top);
		_capacity = st._capacity;
		_top = st._top;
	}

总结:浅拷贝会出现两个问题:
1、如果析构两次,会出现重复释放空间的问题。
2、一个地方修改会影响到另一个地方。


构造函数和拷贝构造函数的区别和联系

联系:构造函数和拷贝构造函数都属于构造函数。

区别:构造函数是初始化一个类对象,
而拷贝构造函数是两个已经存在的对象之间的拷贝。

四、🌺赋值运算符重载(重点🌺)

🌺1.什么是运算符重载

运算符重载是具有特殊函数名的函数,(运算符重载的关键字为operator)也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

1.1运算符重载存在的意义

运算符重载的存在是为了让自定义类型能够像内置类型一样可以使用赋值,比较,等操作符。

1.2运算符重载的一些注意事项

函数名字为:关键字operator后面接需要重载的运算符符号。
函数的原型:返回值类型 + operator+ 操作符(参数列表)

比如:重载一个赋值运算符,则该赋值运算符的函数原型为:

Date& operator=(const Date& d);

重点注意(🌺)

  1. 不能通过连接其他符号来创建新的操作符:比如operator@

  2. 重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

  3. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

  4. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

🌺2.赋值运算符重载的特性

特性1. 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义

Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;
}

以日期类为例:赋值运算符重载的正确写法如上:
(1)返回引用:可以返回引用的原因是:this是一个形参指针,生命周期就在这个栈区,但是*this是外面的对象, 生命周期远超过这个作用域,返回 *this没有影响,还是同一个对象。

(2)加const,既然是赋值运算符,右值不可以修改,为了防止像拷贝构造一样写反,最好加上一个const。

(3)防止自己给自己赋值,可以加一个判断。
(注意&d的&是取地址的意思)

特性2.
赋值运算符只能重载成类的成员函数不能重载成全局函数。

原因:如果类中没有赋值运算符,编译器会自己实现,此时如果再在类外面自己定义一个全局的赋值运算符重载,就和编译器自动生成的产生冲突,所以赋值运算符只能实现在类里面。

特性3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。(浅拷贝)

注意:对于内置类型来说,编译器自己生成的赋值运算符会进行浅拷贝(拷贝值)
对于自定义类型来说,编译器会调用它的赋值运算符重载。

五、🌺取地址运算符重载(了解)

以下两个重载均以日期类为例:

const Date* operator&()
{
	return this;
}

六、🌺const取地址运算符重载(了解)

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针。

表明在该成员函数中不能对类的任何成员进行修改。

const Date* Date::operator&() const
{
	return this;
}

const Date* Date::operator&() const
等价于:
const Date* Date::operator&(const Date * this)

因为前面我们说过,this指针不能在形参和实参部分显式地使用,但是可以在成员函数内部直接使用。

取地址运算符和const修饰的取地址运算符构成重载,因为
一个是Date* this ,
一个是const Date* this。

总结

本文讲述了六大默认成员函数的具体特性,以及他们的使用场景还有各种细节。

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

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

相关文章

Powerlink协议在嵌入式linux上的移植和测试(电脑和linux板通信实验)

使用最新的openPOWERLINK 2.7.2源码&#xff0c;业余时间搞定了Powerlink协议在嵌入式linux上的移植和测试&#xff0c;并进行了下电脑和linux开发板之间的通信实验。添加了一个节点配置&#xff0c;跑通了源码中提供的主站和从站的两个demo。这里总结下移植过程分享给有需要的…

4。计算机组成原理(4)CPU

嵌入式软件开发&#xff0c;非科班专业必须掌握的基本计算机知识 核心知识点&#xff1a;数据表示和运算、存储系统、指令系统、总线系统、中央处理器、输入输出系统 CPU&#xff08;中央处理器&#xff09;是计算机的核心部件&#xff0c;它主要负责执行计算机指令&#xff0c…

万字超详细的Java图书管理系统

&#x1f495;”生命中的每个人都是一个故事&#xff0c;而每个故事都值得被讲述“&#x1f495; &#x1f386;作者&#xff1a;不能再留遗憾了&#x1f386; &#x1f43c;专栏&#xff1a;Java学习&#x1f43c; &#x1f697;该文章主要内容&#xff1a;用Java实现简单的图…

三、PEMFC基础之组件间热传导

三、PEMFC基础之组件间热传导 一、理论基础二、编程实践 一、理论基础 热传导主要基于傅里叶热传导定律。在燃料电池中&#xff0c;除了各组件内部的热传导外&#xff0c;还有冷却流体与双极板的对流换热。公式略。 燃料电池内部稳态导热&#xff1a; d 2 T d x 2 q i n t k…

iMazing2软件最新版本功能技术参数详细介绍

这里有iMazing 的产品概述、功能特性、技术参数等详细介绍&#xff0c; 可以帮助您快速入门&#xff0c;了解iMazing的功能。不管是 iPhone、iPad 或 iPod Touch 设备&#xff0c;只要使用 USB 电缆将设备连接到计算机&#xff0c;就可以处理不同类型的数据。 自动备份 iMazi…

【Linux】基础IO——文件描述符

目录 什么是文件描述符标准输入、输出、错误的返回值类型FILE*的理解进程中文件描述符的分配规则重定向的原理重定向的实际使用方法dup2 如何理解缓冲区 什么是文件描述符 在基础IO的上一篇博客里有提到过&#xff0c;系统调用open与close的返回值问题&#xff1a; 成功返回文…

PyQGIS中一次性加载多个shp文件

目录 遍历添加多个图层 打印图层列表清单 打开QGIS Desktop 3.22.16&#xff0c;点击菜单栏 【设置】——>【Python控制台】 在Python控制台中点击【显示编辑器】按钮&#xff0c;打开Python编辑器 点击Python编辑器的第一个按钮 【打开脚本文件】&#xff0c;选择加载遍历…

2023年继续使用WordPress的6个最重要原因

为什么要使用 WordPress&#xff1f;我的网站不够好吗&#xff1f;为什么我需要从另一个平台切换到 WordPress&#xff1f; 在本文中&#xff0c;我们将分享您应该使用 WordPress 的最重要原因。我们还将涵盖您可以使用 WordPress 创建的所有不同类型的网站&#xff0c;并展示…

c高级(常用命令及软件安装与下载)

初始工作路径不在家目录下&#xff0c;在不切换路径的情况下&#xff0c;在家目录下创建一个subdir目录&#xff0c;在subdir这个目录下&#xff0c;创建subdir1和subdir2&#xff0c;并且把/etc/passwd拷贝到subdir1中&#xff0c;把/etc/group文件拷贝到subdir2中&#xff0c…

开源趣事~ 记给 OpenHarmony 提 PR 的那些事

大家好哇&#xff0c;许久不见&#xff0c;也感谢大家这么久一直以来的关注&#xff0c;也感谢在短视频盛行的今天&#xff0c;你们还能静下心来坚守文字的阵地。 说到这次的主题&#xff0c;参加鸿蒙项目的开源&#xff0c;也是小编第一次拥抱开源&#xff0c;就像是别人有困…

vue脚手架+elementUI,实现登录用户时的Loading...窗口

文章目录 App.vuevuex全局变量登陆成功Login组件使用AboutMe组件中关闭 登录失败情况login组件中关闭 改为aop思想的请求拦截器 App.vue 为了全局通用控制此标签&#xff0c;所以我建议把他放到App.vue文件中 <!--全局加载ing&#xff0c;保证不会在转换组件时被销毁-->…

Day965.从持续集成到持续部署 -遗留系统现代化实战

从持续集成到持续部署 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于从持续集成到持续部署的内容。 只有做好任务分解和小步提交&#xff0c;才能放心大胆地 PUSH 代码&#xff0c;触发持续构建&#xff1b; 只有通过质量门禁&#xff0c;才能得到一个有信心的制…

【Spring MVC】Spring MVC的执行流程以及运行原理

文章目录 一、 什么是MVC&#xff1f;二、什么是SpringMVC&#xff1f;三、SpringMVC中的核心组件四、SpringMVC的执行流程五、关于DispatcherServlet的配置说明六、关于SpringMVC的配置文件以及常用部分注解解释七、参考资料 一、 什么是MVC&#xff1f; MVC 是 Model、View …

软件工程的基础

软件危机软件工程软件工程是将系统化的&#xff0c;严格约束的&#xff0c;可量化的方法应用于软件的开发&#xff0c;运行和维护&#xff0c;将工程应用于软件。 软件工程的三个要素&#xff1a;方法&#xff0c;工具&#xff0c;过程软件的生命周期&#xff0c;是指从从软…

Vue列表展示【第二篇】

&#x1f331; 1、vue列表展示案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>vueDemo02列表展示</title> </head> <body> <div id"xy"><!--原始展示方式…

深入篇【C++】类与对象:运算符重载详解 -(下)+日期类的实现

[TOC](深入篇【C】类与对象&#xff1a;运算符重载详解-(下&#xff09;日期类的实现&#x1f4af;干货满满&#xff01;) ⏰一.运算符重载 内置类型(int /double…… )是可以之间进行运算符之间的比较的&#xff0c;因为编译器知道它们之间的比较规则&#xff0c;可以之间转…

Linux下C/C++(端口扫描技术)

随着互联网使用的不断增加&#xff0c;网络攻击也在增加。互联网本质上已经成为现代。因此&#xff0c;了解互联网和使用互联网是非常重要的。网络技术的安全性在当今时代是非常重要和必要的。 在本文中&#xff0c;我们将讨论一个非常重要的主题&#xff0c;即可能破坏计算机…

es 7.x 通过DSL的常用查询语句

一 模拟造数 1.1 模拟数据 添加数据修改数据的请求方式为post http://localhost:9200/student/_doc/101 { "name":"zhangsan", "nickname":"zhangsan","sex":"男","age":30,"createTime":…

InnoDB线程模型

新版本结构演变 MySQL 5.7 版本 将 Undo日志表空间从共享表空间 ibdata 文件中分离出来&#xff0c;可以在安装 MySQL 时由用户自行指定文件大小和数量增加了 temporary 临时表空间&#xff0c;里面存储着临时表或临时查询结果集的数据Buffer Pool 大小可以动态修改&#xff0…

2.3 利用NumPy进行统计分析

2.3 利用NumPy进行统计分析 2.3.1 读/写文件1、二进制的文件读写2、读取文本格式的数据 2.3.2 使用数组进行简单统计分析1、排序2、去重与重复数据3、常用的统计函数 2.3.1 读/写文件 NumPy文件读写主要有二进制的文件读写和文件列表形式的数据读写两种形式 1、二进制的文件读…