类和对象(中)(构造函数、析构函数和拷贝构造函数)

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

任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

//空类
class Date{};

默认成员函数:用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数

2.构造函数

构造函数 是一个 特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
构造函数主要任务不是开空间创建对象,而是初始化对象。
其特征如下:
        ① 函数名与类名相同。
        ② 无返回值。//不需要写void
        ③ 对象实例化时编译器 自动调用 对应的构造函数。
        ④ 构造函数可以重载。
多个构造函数,有多种初始化方式,一般情况,建议每个类,都可以写一个全缺省的构造(好用)
class Date{public://他们俩构成函数重载,但是无参调用时会存在歧义// 1.无参构造函数Date(){_year = 1;_month = 1;_day = 1;}// 2.带参构造函数// 一般情况,建议每个类,都可以写一个全缺省的构造(好用)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后面不能带括号否则定义对象无法跟函数声明区分开
//Date func();//这就是d1为什么不能带括号Date d1;d1.Print();
//类型 对象(2024,4,2)Date d2(2024, 4, 2);//这里调用构造函数是对象名加参数列表  
//这里可以和函数声明进行区分,如下一行的函数声明所示//Date func(int x, int y, int z);d2.Print();Date d3(2024);d3.Print();Date d4(2024, 4);d4.Print();return 0;
}

        ⑤如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦            用户显式定义编译器将不再生成。

#include<iostream>
using namespace std;
class Date
{
public:/*// 如果用户显式定义了构造函数,编译器将不再生成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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;//对象实例化的时候自动调用对应的构造函数return 0;
}

如果用户显示定义了构造函数,编译器将不会生成无参的默认构造函数,这时候如果定义一个无参的类如Date d1,会编译失败。

⑥C++ 把类型分成内置类型 ( 基本类型 ) 和自定义类型。内置类型就是语言提供的数据类型,如:int/char.../任意类型指针 ,自定义类型就是我们使用 class/struct/union 等自己定义的类型。
如果我们没写构造函数,编译器自动生成构造函数,对于编译器自动生成的构造函数
对于内置类型的成员变量,编译器没有规定要不要做处理!(有些编译器会处理成0,但是C++标准并没有规定)
对于自定义类型的成员变量,才会调用他的默认成员函数即无参构造,如果没有无参构造会报错。(不传参就可以调用的那个构造,全缺省构造)
#include<iostream>
using namespace std;
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

这里我们发现编译器自动生成的构造函数对于自定义类型调用了它的默认构造函数。

注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在
类中声明时可以给默认值
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

总结:不传参数就可以调用的函数就是默认构造

Ⅰ一般情况构造函数都需要我们自己显式去实现

Ⅱ只有少数情况下可以让编译器自动生成构造函数类似MyQueue,成员全是自定义类型

3.析构函数

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

析构函数是特殊的成员函数,其特征如下:

①析构函数名是在类名前加上字符 ~
②无参数无返回值类型。
③一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
④对象生命周期结束时,C++ 编译系统系统自动调用析构函数

⑤对于编译器自动生成的默认析构函数,对于自定义类型成员会调用它的析构函数。

跟构造函数类似:

a、内置类型不做处理       

b、自定义类型去调用他的析构

class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}//对象在这里被销毁后,这里会自动调用析构函数

程序运行结束后输出: ~Time() ,在main 方法中根本没有直接创建 Time 类的对象,为什么最后会调用 Time 类的析构函数?
因为: main 方法中创建了 Date 对象 d ,而 d 中包含 4 个成员变量,其中 _year, _month,
_day 三个是 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t Time 类对 象,所以在d销毁时,要将其内部包含的 Time 类的 _t 对象销毁,所以要调用 Time 类的析构函数。但是:main函数 中不能直接调用Time 类的析构函数,实际要释放的是 Date 类对象,所以编译器会调用 Date 类的析构函数,而Date 没有显式提供,则编译器会给 Date 类生成一个默认的析构函数,目的是在其内部调用Time 类的析构函数,即当Date 对象销毁时,要保证其内部每个自定义对象都可以正确销毁
 main函数中并没有直接调用 Time 类析构函数,而是显式调用编译器为 Date 类生成的默认析 构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date 类;有资源申请时,一定要写,否则会造成资源泄漏,比如 Stack
自动生成的构造函数和析构意义何在?两个栈实现一个队列
Stack.h如下:
#pragma once
#include<stdlib.h>
#include<iostream>
using namespace std;class Stack
{
public:Stack(int n = 4);~Stack();//void Init();//void Destroy();void Push(int x);bool Empty();void Pop();int Top();
private:// 成员变量int* _a;int _top;int _capacity;
};class Queue
{
public:void Init();void Push(int x);};

Stack.cpp如下:

#include"Stack.h"Stack::Stack(int n)//缺省参数声明和定义不能同时给,规定了只在声明时候给,定义的时候不给
{_a = (int*)malloc(sizeof(int)*n);_top = 0;_capacity = n;
}Stack::~Stack()
{cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;
}//void Stack::Init()//指明类的作用域就指明了类的出处
//{
//	_a = nullptr;
//	_top = 0;
//	_capacity = 0;
//}//任何一个变量都得先定义再使用,不符合语法报语法错误//void Destroy()
//{
//	//...
//}void Stack::Push(int x)
{// ..._a[_top++] = x;
}bool Stack::Empty()
{return _top == 0;
}void Stack::Pop()
{--_top;
}int Stack::Top()
{return _a[_top - 1];
}//void Queue::Push(int x)
//{
//
//}

test.cpp
#icnlude<Stack.h>
class MyQueue
{
private:Stack _pushst;Stack _popst;
};int main()
{MyQueue q;return 0;
}

这里创建MyQuque类q时,会自动调用调用Stack类的默认构造函数,q销毁时,也会自动调用Stack类的默认析构函数。

下面是一个括号匹配问题,利用c和c++实现的比较,可以发现,利用c++的构造和析构特性后,会方便很多

实践中总结:
1、有资源需要显示清理,就需要写析构。如:Stack List
2、有两种场景不需要显示写析构,默认生成就可以了
a、没有资源需要清理,如:Date
b、内置类型成员没有资源需要清理。剩下的都是自定义类型成员
4.拷贝构造函数
拷贝构造函数: 只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存 在的类类型对象创建新对象时由编译器自动调用
特征:
①拷贝构造函数 是构造函数的一个重载形式
拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 因为会引发无穷递归调用。
class Date
{
public:Date(int year = 1970, int month =1, int day=1){_year = year;_month = month;_day = day;}//Date(Date d)错误写法//Date d2(d1);//d1传给了d,d2就是thisDate(const Date& d)//加了const之后d的内容就无法修改{cout << "const Date& d" << endl;//this->_year = d._year;_year = d._year;_month = d._month;_day = d._day;}
//	Date(Date* d)//规定这里不是拷贝构造,就是一个普通构造,编译器会自动生成一个默认的拷贝构造
//	{
//		_year = d->_year;
//		_month = d->_month;
//		_day = d->_day;
//	}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:// 基本类型(内置类型)int _year ;int _month ;int _day ;};
void func(Date d)
{d.Print();
}
int main()
{Date d1(2024,4,18);Date d2(d1);func(d2);return 0;
}

//Date(Date d)是错误写法,

原因:对于自定义类型传值传参要调用拷贝构造完成,这是一种规定,自定义类型的拷贝都要调用拷贝构造才能完成。这里用d1去构建d2,规定需要调用拷贝构造,调用拷贝构造得先传参,先传参形成了一个新的拷贝构造,新的拷贝构造假设去调用,去调用这个拷贝构造,又要先传参,从逻辑上来说就是一个无穷递归

对于
func(d2);
void func(Date d)
{d.Print();
}
Date(const Date& d)
{cout << "const Date& d" << endl;_year = d._year;_month = d._month;_day = d._day;
}

与构造和析构函数一样,内置类型就直接传,自定义类型传值传参就要调用拷贝构造完成,这里调用func之前先传参,传参就会形成一个拷贝构造,d是d2的别名,函数结束回来,传参完成,参数的传递就是完成拷贝构造调用,最后调用调用func函数,结束

也可以从建立函数栈帧的方式去看函数func(d2)的调用

当然也可以采用指针或者引用的方式,这种方式不会调用拷贝构造。

	func(d2);
void func(Date& d)
{d.Print();
}
//这种方式也不会调用拷贝构造,d是d2的别名func(&d2);
void func(Date* d)
{d->Print();
}
//这种情况下没有拷贝构造,因为传的是内置类型,把d2地址进行传递,用指针进行接收

③若未显示定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
④深拷贝
编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,这是浅拷贝或者值拷贝,对于日期类不需要自己显示实现,值拷贝就是将一块空间里面的值按照字节一个一个的拷贝过来,有点像memcpy拷贝—样,memcpy就是按字节拷贝
但是如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝,如:Stack Queue List等
//这里如何去掉Stack的拷贝构造会发现下面的程序会崩溃掉,这里就需要深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}Stack(const Stack& st){_array = (DataType*)malloc(st._capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}memcpy(_array, st._array, st._size*sizeof(DataType));_size = st._size;_capacity = st._capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}bool Empty(){return _size == 0;}DataType Top(){return _array[_size - 1];}void Pop(){--_size;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
class MyQueue
{
private:Stack _st1;Stack _st2;int _size = 0;
};
int main()
{Stack st1(10);st1.Push(1);st1.Push(1);st1.Push(1);Stack st2 = st1;st2.Push(2);st2.Push(2);while (!st2.Empty()){cout << st2.Top() << " ";st2.Pop();}cout << endl;//输出1 1 1while (!st1.Empty()){cout << st1.Top() << " ";st1.Pop();}cout << endl;//输出2 2 1 1 1 MyQueue q1;MyQueue q2(q1);//这里自定义类型会去调用栈的拷贝构造(栈的拷贝构造是深拷贝),内置类型完成值拷贝return 0;
}

注意:这里_array按照字节拷贝,相当于然s2的_array指向的空间和s1的_array指向的空间一样,但是这样会导致两个问题:(只要指针指向资源的都会有问题)
1.s1 push后s2上也能看见,因为s1改变的是和s2同一块空间的值,同时s1的size会改变,但是s2的size不会改变
⒉析构的时候,s1会free一次,s2也会free—次,相当于对一块空间free两次,也即析构两次
这里可以用深拷贝来解决,深拷贝就是你的形状和你的空间是什么样子就去开和你—样的空间,放一样的值,复制和你一样的出来。
在类里面加上如下的拷贝构造即可
	Stack(const Stack& st){_array = (DataType*)malloc(st._capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}memcpy(_array, st._array, st._size*sizeof(DataType));_size = st._size;_capacity = st._capacity;}

实践中总结:
1、如果没有管理资源,一般情况不需要写拷贝构造,默认生成的拷贝构造就可以。如:Date
2、如果都是自定义类型成员,内置类型成员没有指向资源,也类似默认生成的拷贝构造就可以。如:MyQueue3、一般情况下,不需要显示写析构函数,就不需要写拷贝构造
4、如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝。如:Stack Queue List等

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

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

相关文章

docker容器技术篇:centos7搭建docker swarm集群

centos7搭建docker swarm集群 一 docker swarm 概述 1.1 swarm简介 Docker Swarm是 Docker 的集群管理工具&#xff0c;Swarm 在 Docker 1.12 版本之前属于一个独立的项目&#xff1b;其主要作用是把Docker集群抽象为一个整体&#xff0c;并且通过一个统一管理这些 Docker 主…

密码学 | 数字证书:应用

&#x1f951;原文&#xff1a;数字签名和数字证书的原理解读 - 知乎 &#x1f951;前文&#xff1a;密码学 | 数字签名 数字证书 - CSDN &#x1f951;提示&#xff1a;把客户端想成 Alice&#xff0c;服务器端想成 Bob 即可。客户端实际上指的是客户端浏览器。 下面&#…

前端入门:HTML(CSS边框综合案例)

案例&#xff1a; 源代码&#xff1a; css-borders.html: <body> <div id"square"> </div> <br> <div id"triangle"> </div> <br> <div id"trapezium"> </div> <br> <div id…

【C语言】指针篇-深入探索数组名和指针数组(2/5)- 必读指南

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 认识指针与数组之间的关系(涉及二级指针)**数组名****指针访问…

面试后,公司如何决定你的去留

在现代职场中&#xff0c;求职者在经历了一系列严格的面试流程后&#xff0c;往往会进入一段等待期。在这段时间里&#xff0c;他们满怀希望地等待企业的最终反馈。但有一个现象普遍存在&#xff1a;无论面试过程如何&#xff0c;最终决定权总是掌握在公司手中&#xff0c;由公…

企业常用Linux三剑客awk及案例/awk底层剖析/淘宝网cdn缓存对象分级存储策略案例/磁盘知识/awk统计与计算-7055字

高薪思维&#xff1a; 不愿意做的事情:加班&#xff0c;先例自己在利他 生活中先利他人在利自己 感恩&#xff0c;假设别人帮助过你&#xff0c;先帮助别人&#xff0c;感恩境界 awk三剑客老大 find其实也算是一种新的第四剑客 find 查找文件 查找文件&#xff0c;与其他命令…

推荐实用网站——算法可视化

网站链接 https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

variant

class RTTR_API variant 对github项目rttr&#xff08;C反射库&#xff09;解析&#xff0c;链接&#xff1a;https://github.com/rttrorg/rttr namespace rttr { class variant_associative_view; class variant_sequential_view; class type; class variant; class argumen…

真实世界的密码学(四)

原文&#xff1a;annas-archive.org/md5/655c944001312f47533514408a1a919a 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十六章&#xff1a;加密何时何地失败 本章涵盖 使用加密时可能遇到的一般问题 遵循烘烤良好的加密的要点 加密从业者的危险和责任 问候…

论文笔记:Time-LLM: Time Series Forecasting by Reprogramming Large Language Models

iclr 2024 reviewer 评分 3888 1 方法 提出了 Time-LLM&#xff0c; 是一个通用的大模型重编程&#xff08;LLM Reprogramming&#xff09;框架将 LLM 轻松用于一般时间序列预测&#xff0c;而无需对大语言模型本身做任何训练 为什么需要时序数据和文本数据对齐&#xff1a;时…

Oracle 窗口函数 02 (排名问题)

目录 一、什么是窗口函数 1.语法里每部分表示什么 2.窗口函数可以解决这几类经典问题 二、排名问题 1.学生成绩排名 2.去除最大值、最小值后求平均值 知识点&#xff1a; 一、什么是窗口函数 窗口函数也叫作OLAP&#xff08;Online Analytical Processing&#xff0c;联…

正确的原因是错误的:可解释的 ML 技术能否检测出虚假相关性?

Right for the Wrong Reason: Can Interpretable ML Techniques Detect Spurious Correlations? 摘要 虽然深度神经网络模型提供了无与伦比的分类性能&#xff0c;但它们容易在数据中学习虚假相关性。如果测试数据与训练数据来自相同的分布&#xff0c;则使用性能指标很难检…

mysql基础20——数据备份

数据备份 数据备份有2种 一种是物理备份 一种是逻辑备份 物理备份 物理备份 通过把数据文件复制出来 达到备份的目的 用得比较少 逻辑备份 逻辑备份 把描述数据库结构和内容的信息保存起来 达到备份的目的 是免费的 数据备份工具 mysqldump &#xff08;3种模式&#x…

【蓝桥杯2025备赛】集合求和

集合求和 题目描述 给定一个集合 s s s&#xff08;集合元素数量 ≤ 30 \le 30 ≤30&#xff09;&#xff0c;求出此集合所有子集元素之和。 输入格式 集合中的元素&#xff08;元素 ≤ 1000 \le 1000 ≤1000&#xff09; 输出格式 s s s 所有子集元素之和。 样例 #1 …

Java面试八股之marshalling和demarshalling

marshalling和demarshalling Marshalling&#xff08;序列化&#xff09;是将内存中的对象状态转化为适合传输或存储的格式&#xff08;如字节流、JSON、XML&#xff09;&#xff0c;以便进行网络通信、持久化存储或跨平台/语言交互操作。Demarshalling&#xff08;反序列化&a…

AI大模型探索之路-实战篇3:基于私有模型GLM-企业级知识库开发实战

文章目录 前言概述一、本地知识库核心架构回顾&#xff08;RAG&#xff09;1. 知识数据向量化2. 知识数据检索返回 二、大模型选择1. 模型选择标准2. ChatGLM3-6B 三、Embedding模型选择四、改造后的技术选型五、资源准备1. 安装git-lfs2. 下载GLM模型3. 下载Embeding模型 六、…

开源啦!一键部署免费使用!Kubernetes上直接运行大数据平台!

市场上首个K8s上的大数据平台&#xff0c;开源啦&#xff01; 智领云自主研发的首个 完全基于Kubernetes的容器化大数据平台 Kubernetes Data Platform (简称KDP) 开源啦&#x1f680;&#x1f680; 开发者只要准备好命令行工具&#xff0c;一键部署 Hadoop&#xff0c;Hi…

如何在Matplotlib中绘制平滑曲线

很多时候&#xff0c;我们有从非常分散的数据列表中生成的线图&#xff0c;这使得图形看起来像连接点的直线&#xff0c;或者非常密集&#xff0c;这导致数据点彼此非常接近&#xff0c;因此图看起来很混乱。 默认情况下&#xff0c;matplotlib.pyplot.plot()函数通过用直线连…

在protobuf里定义描述rpc方法的类型

service UserServiceRpc //在test.proto中定义 { rpc Login(LoginRequest)returns(LoginResponse); rpc GetFriendLists(GetFriendListRequest)returns(GetFriendListResponse); } test.proto文件生成test.pb.cc protoc test.proto --cpp_out./ 将生成的…

IDM的实用功能介绍+下载地址

下载地址 &#xff1a; 下载到idm 互联网下载管理器&#xff08;IDM&#xff09;实用功能概述 1. 多线程下载 IDM使用多线程技术&#xff0c;将文件分割成多个部分同时下载&#xff0c;显著提高下载速度。 2. 计划任务 用户可以设定下载任务的开始时间&#xff0c;甚至在特…