[C++] 类与对象(中)类中六个默认成员函数(1)

news2024/10/6 10:40:55

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

如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

2、构造函数

2.1 构造函数的概念

我们这里来看看日期类的初始化:

class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
    	cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();
    
    return 0;
}

运行结果:

我们刚接触C++,一定会这样初始化。

如果我们实例化的对象太多了,忘记初始化对象了,程序运行出来的结果可能就是随机值了,也可能出问题。

这里C++祖师爷想到了,为我们设计了构造函数。

我们先来看一下忘记初始化直接打印的结果:

这里是随机值,那这是为什么呢?我们接着往下看。

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值(不是void,是不用写)。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。

我们先写一个日期类的构造函数来看看:

class Date
{
public:
	Date()//构造函数,无参构造
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day;
	}


private:
	int _year;
	int _month;
	int _day;
};

我们测试看一下:

我们main函数里没有调用构造函数,但是这里打印了我们做的标记,这里我们实验出来了实例化对象时构造函数是自动调用的。
我们再来看将我们写的构造函数注释掉会发生什么:

我们能看到,注释掉后,仍然能打印出来,只不过是随机值。因为当我们不写,编译器会自动生成默认的构造函数,并自动调用。

C++将类型分为内置类型(基本类型):如int,char,double,int*……(自定义类型*也是);

自定义类型:如class,struct,union……。

并且这里我们能看出来,对于内置类型的成员不会处理,在C++11,支持成员变量给缺省值,算是补漏洞了。

2.2.1 构造函数的重载:

class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}
	
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}


private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    Date d1;
	d1.Print();
	Date d2(2023, 8, 1);//这里初始化必须是这样写,这是语法
	d2.Print();

    return 0;
}

运行结果:

注意我们在实例化对象的时候,当调用的构造函数没有参数,不能在对象后加括号,语法规定。

如果这样写,编译器分不清这到底是函数声明还是在调用。d2不会混淆是因为有传值,函数声明不会出现那样的写法。

2.2.2 全缺省的构造函数:

我们其实可以将上面的两个构造函数合并为一个

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}


private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    Date d1;
	d1.Print();

	Date d2(2023, 8, 1);
	d2.Print();

	Date d3(2023, 9);
	d3.Print();

    return 0;
}

运行结果:

全缺省构造函数才是最适用的。无参构造与全缺省可以同时存在,但是不建议这样写,虽然不报错,但是在调用全缺省时我们不想传参,编译器不知道我们到底想调用哪个构造,会产生二义性。

我们再看用两个栈实现队列的问题:

class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_size = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_size = -1;
			_capacity = n;
		}
	}

	void Push(int n)
	{
		if (_size + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_size++] = n;
	}

	int Top()
	{
		return _a[_size];
	}

	void Pop()
	{
		assert(_size > -1);
		_size--;
	}

	void Destort()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

	bool Empty()
	{
		return _size == -1;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;

};

一般情况下都需要我们自己写构造函数,决定初始化方式,成员变量全是自定义类型,可以考虑不写构造函数。会调用自定义类型的默认构造函数。

总结:无参构造函数、全缺省构造函数、我们不写编译器默认生成的构造函数,都可以认为是默认构造函数,并且默认构造函数只能存在一个(多个并存会产生二义性)。

3、析构函数

3.1 析构函数的概念

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

3.2 特性

析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(对内置类型不做处理,自定义类型会调用它自己的析构函数)。注意:析构函数不能重载。
4. 对象生命周期结束时,C++编译系统会自动调用析构函数。

我们先来看日期类的析构:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
        cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()" << endl;
		_year = 0;
		_month = 0;
		_day = 0;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;

    return 0;
}

运行结果:

我们这里可以看出析构函数也是自动调用的。

我们不写,编译器自动生成默认的析构函数。

析构函数的调用顺序跟栈类似,后实例化的先析构。

如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

我们画图来看一下:


栈中的析构函数就代替了栈的销毁:

class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_top = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_top = -1;
			_capacity = n;
		}
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
	//void Destort()
	//{
	//	free(_a);
	//	_a = nullptr;
	//	_top = _capacity = 0;
	//}

	void Push(int n)
	{
		if (_top + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_top++] = n;
	}

	int Top()
	{
		return _a[_top];
	}

	void Pop()
	{
		assert(_top > -1);
		_top--;
	}

	bool Empty()
	{
		return _top == -1;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;

};

对于栈这样的,我们析构函数代替了销毁函数,析构函数会自动调用,以前我们需要手动调用销毁函数的接口,现在不用调用了。

因此,构造函数和析构函数最大的优势是自动调用。

4、拷贝构造函数

4.1 拷贝构造函数的概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器会引发无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

拷贝构造就像是复制粘贴一样。

拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器会引发无穷递归调用。

传值拷贝会发生无穷递归,我们来写一个拷贝构造函数。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造
	Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	~Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}

private:
	int _year;
	int _month;
	int _day;
};
void func(Date d)
{
	d.Print();
}

int main()
{
	Date d1(2023, 8, 2);
	func(d1);

    return 0;
}

内置类型的拷贝是直接拷贝,自定义类型的拷贝要调用拷贝构造完成。

在vs2019中,传值传参编译器会报错:

因此,我们要是写拷贝构造函数,形参必须是同类型的引用:

引用是给变量起别名,析构自动调用的顺序是后定义先析构,拷贝的时候d1还没有析构,因此是可以使用引用的,这样就不会导致递归拷贝了。

我们再来看栈的拷贝构造:

typedef int DataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_size = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_size = -1;
			_capacity = n;
		}
	}

	//拷贝构造
	Stack(Stack& s)
	{
		_a = s._a;
		_size = s._size;
		_capacity = s._capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

	void Push(int n)
	{
		if (_size + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_size++] = n;
	}

	int Top()
	{
		return _a[_size];
	}

	void Pop()
	{
		assert(_size > -1);
		_size--;
	}

	bool Empty()
	{
		return _size == -1;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
	Stack s1;
	Stack s2(s1);
	
    return 0;
}

我们这里为栈写的拷贝构造,我们来试一下拷贝构造:

这里为什么引发了异常呢?

我们调试看看:

这里我们可以看到,s1的_a与s2的_a地址是一样的,当s2拷贝完后就会析构,s2的_a被释放掉后,s1还会再调用一次析构函数,这时再去释放_a,_a的空间已经被释放过了,就会引发空指针异常的问题。

因此,对于有空间申请的对象,在写拷贝构造的时候必须要深拷贝。

我们来改正代码:

typedef int DataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_size = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_size = -1;
			_capacity = n;
		}
	}

	//拷贝构造
	Stack(Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
		//深拷贝
		_a = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (nullptr == _a)
		{
			perror("malloc fail:");
			exit(-1);
		}

		memcpy(_a, s._a, sizeof(DataType) * (s._size+1));
		_size = s._size;
		_capacity = s._capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

	void Push(int n)
	{
		if (_size + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_size++] = n;
	}

	int Top()
	{
		return _a[_size];
	}

	void Pop()
	{
		assert(_size > -1);
		_size--;
	}

	bool Empty()
	{
		return _size == -1;
	}

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

运行结果:

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

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

相关文章

ESP32 LVGL:无法显示过大的GIF图片(修改VLGL RAM缓存大小)

文章目录 问题描述&#xff1a;问题解决更改LVGL RAM缓存大小看ESP32的RAM使用情况 参考链接 问题描述&#xff1a; 使用LVGL可显示64 * 64的GIF&#xff0c;但是却无法显示120*120的GIF。 问题解决 更改LVGL RAM缓存大小 分析原因&#xff1a;在用LVGL显示GIF图片时&#…

【JavaEE】Spring Boot - 项目的创建和使用

【JavaEE】Spring Boot 开发要点总结&#xff08;1&#xff09; 文章目录 【JavaEE】Spring Boot 开发要点总结&#xff08;1&#xff09;1. Spring Boot 的优点2. Spring Boot 项目创建2.1 下载安装插件2.2 创建项目过程2.3 加载项目2.4 启动项目2.5 删除一些没用的文件 3. Sp…

安全基础 --- html标签 + 编码(01)

html标签 &#xff08;1&#xff09;detail标签 <details>标签用来折叠内容&#xff0c;浏览器会折叠显示该标签的内容。 <1> 含义&#xff1a; <details> 这是一段解释文本。 </details> 用户点击这段文本&#xff0c;折叠的文本就会展开&#x…

MFC第二十六天 CRgn类简介与开发、封装CMemoryDC类并应用开发

文章目录 CRgn类简介与开发CRgn类简介CRgn类区域管理开发CRgn类区域管理与不规则形状的选取 封装CMemoryDC类并应用开发CMemoryDC.h封装CMemoryDC开发游戏透明动画CFlashDlg.hCFlashDlg.cpp 封装CMemoryDC开发游戏动画 附录四大窗口CDC派生类 CRgn类简介与开发 CRgn类简介 CR…

Meta开源Llama 2免费大语言模型,媲美ChatGPT,可在线试玩

Llama 2是Llama 1模型的升级版本,引入了一系列预训练和微调 LLM,参数量范围从7B到70B (7B、13B、70B)。其预训练模型比 Llama 1模型有了显著改进,包括训练数据的总词元数增加了 40%、上下文长度更长 (4k 词元),以及利用了分组查询注意力机制来加速 70B模型的推理! 但最激动…

PSO粒子群优化算法

PSO粒子群优化算法 算法思想matlab代码python代码 算法思想 粒子群算法&#xff08;Particle Swarm Optimization&#xff09; 优点: 1&#xff09;原理比较简单&#xff0c;实现容易&#xff0c;参数少。 缺点: 1&#xff09;易早熟收敛至局部最优、迭代后期收敛速度慢的…

【逗老师的PMP学习笔记】项目的运行环境

一、影响项目运行的因素 主要分两种因素 事业环境因素&#xff08;更多的是制约和限制因素&#xff09;组织过程资产&#xff08;可以借鉴的经验和知识&#xff09; 1、细说事业环境因素&#xff08;更多的是制约和限制因素&#xff09; 资源可用性 例如包括合同和采购制约…

代码随想录算法训练营第三十三天 | Leetcode随机抽题检测

Leetcode随机抽题检测 70 爬楼梯未看解答自己编写的青春版重点题解的代码日后复习重新编写 118 杨辉三角未看解答自己编写的青春版重点题解的代码日后复习重新编写 198 打家劫舍未看解答自己编写的青春版重点题解的代码日后复习重新编写 279 完全平方数未看解答自己编写的青春版…

WPF上位机7——MySql

MySql DML语句 db操作、表操作 字段的数据类型 修改表 表的数据操作 DQL语句 数据查询和去重查询 条件查询 模糊查询 聚合查询 分组查询 排序查询 分页查询 DCL语句 函数 字符串处理函数 数值函数 日期函数 流程函数 约束 外键约束 多表查询 内连接 外连接 自连接 子查询 列…

JavaScript-DOM

目录 DOM 访问节点 节点信息 操作节点 DOM DOM&#xff1a;Document Object Model&#xff08; 文档对象模型&#xff09; 访问节点 使用 getElement系列方法访问指定节点 getElementById()、getElementsByName()、getElementsByTagName()根据层次关系访问节点 节点属性 属…

面向对象程序三大特性一:继承(超详细)

目录 1.继承基本介绍 2.继承关系中的成员访问 2.1就近原则 2.2继承关系中的构造方法 3.super和this 4.代码块和构造方法的执行顺序 4.1在没有继承关 系时的执行顺序。 4.2继承关系上的执行顺序 5.protected的 范围 6.继承的本质分析(重要) 7.final 关键字 1.继承基本…

《动手学深度学习》-64注意力机制

沐神版《动手学深度学习》学习笔记&#xff0c;记录学习过程&#xff0c;详细的内容请大家购买书籍查阅。 b站视频链接 开源教程链接 注意力机制 生物学中的注意力提示 灵长类动物的视觉系统接受了大量的感官输入&#xff0c;这些感官输入远远超出了大脑所能够完全处理的能力…

vue 标题文字字数过长超出部分用...代替 动态显示

效果: 浏览器最大化: 浏览器缩小: 代码: html: <div class"title overflow">{{item.name}}</div> <div class"content overflow">{{item.content}}</div> css: .overflow {/* 一定要加宽度 */width: 90%;/* 文字的大小 */he…

台风来袭,这份避险防御指南一定收好

台风天气的到来&#xff0c;我们必须高度警惕&#xff01;大到暴雨、雷电、雷雨大风&#xff0c;甚至短时强降水等强对流天气&#xff0c;可能给我们的生活带来严重威胁。为了确保家人安全&#xff0c;让我们共同学习一些智慧防护措施&#xff0c;做好个人安全防范。定期关注天…

C++初阶之一篇文章让你掌握vector(理解和使用)

vector&#xff08;理解和使用&#xff09; 1.什么是vector&#xff1f;2.vector的使用2.1 vector构造函数2.2 vector迭代器&#xff08;Iterators&#xff09;函数2.2.1 begin()2.2.2 end()2.2.3 rbegin()2.2.4 rend()2.2.5 cbegin()、cend()、crbegin()和crend() C11 2.3 vec…

Java类集框架(二)

目录 1.Map&#xff08;常用子类 HashMap&#xff0c;LinkedHashMap&#xff0c;HashTable&#xff0c;TreeMap&#xff09; 2.Map的输出&#xff08;Map.Entry,iterator,foreach&#xff09; 3.数据结构 - 栈&#xff08;Stack&#xff09; 4.数据结构 - 队列&#xff08;Q…

485modbus转profinet网关连三菱变频器modbus通讯触摸屏监控

本案例介绍了如何通过485modbus转profinet网关连接威纶通与三菱变频器进行modbus通讯。485modbus转profinet网关提供了可靠的连接方式&#xff0c;使用户能够轻松地将不同类型的设备连接到同一网络中。通过使用这种网关&#xff0c;用户可以有效地管理和监控设备&#xff0c;从…

人工智能与物理学(软体机器人能量角度)的结合思考

前言 好久没有更新我的CSDN博客了&#xff0c;细细数下来已经有了16个月。在本科时期我主要研究嵌入式&#xff0c;研究生阶段对人工智能感兴趣&#xff0c;看了一些这方面的论文和视频&#xff0c;因此用博客记录了一下&#xff0c;后来因为要搞自己的研究方向&#xff0c;就…

使用Golang实现一套流程可配置,适用于广告、推荐系统的业务性框架——组合应用

在《使用Golang实现一套流程可配置&#xff0c;适用于广告、推荐系统的业务性框架——简单应用》中&#xff0c;我们看到了各种组合Handler的组件&#xff0c;如HandlerGroup和Layer。这些组件下面的子模块又是不同组件&#xff0c;比如LayerCenter的子组件是Layer。如果此时我…

Windows用户如何将cpolar内网穿透配置成后台服务,并开机自启动?

Windows用户如何将cpolar内网穿透配置成后台服务&#xff0c;并开机自启动&#xff1f; 文章目录 Windows用户如何将cpolar内网穿透配置成后台服务&#xff0c;并开机自启动&#xff1f;前置准备&#xff1a;VS Code下载后&#xff0c;默认安装即可VS CODE切换成中文语言 1. 将…