【C++】类和对象(二)——构造/析构/拷贝构造函数

💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

  • 导读
  • 1. 默认成员函数
  • 2. 构造函数
    • 2.1 引入
    • 2.2 特性
    • 2.3 默认构造函数
  • 3. 析构函数
    • 3.1 概念
    • 3.2 特性
    • 3.3 默认析构函数
  • 4. 拷贝构造函数
    • 4.1 概念
    • 4.2 特性
    • 4.3 默认拷贝构造函数

导读

我们上次讲了类和对象的一些定义相关的知识,今天我们进一步的来讲构造函数、析构函数和拷贝构造函数。

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(2024, 1, 30);
	d1.Print();
	Date d2;
	d2.Init(2024, 1, 31);
	d2.Print();
	return 0;
}

对于Date类,我们可以使用成员函数Init()给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦。

那有没有什么办法能在创建对象时,自动将我们要传递的内容放置进去呢?
那就需要我们的构造函数了。

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

构造函数的特点包括:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
class Date
{
public:
	Date() //无参构造函数
	{
		_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(2024, 1, 31);//调用带参构造函数
	d2.Print();
	return 0;
}

在这里插入图片描述
不给参数时就会调用 无参构造函数,给参数则会调用 带参构造函数。
注意事项:

  • 如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。
class Date
{
public:
	Date() //无参构造函数
	{
		_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();  //这样不能调用无参初始化
	return 0;
}
  • 这里如果调用带参构造函数,我们需要传递三个参数

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

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 d1;
	d1.Print();
	return 0;
}

在这里插入图片描述
为什么是随机值呢?我们接着引入下一特性。

2.3 默认构造函数

我们上述使用默认构造函数时,生成的默认值似乎并没有什么用处。

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

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
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	d.Print();
	return 0;
}

在这里插入图片描述
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

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

3. 析构函数

3.1 概念

析构函数是一种特殊的成员函数,用于在对象销毁时执行清理工作。它的名称与类名相同,前面加上一个波浪线(~)。在C++中,每个类都可以有一个析构函数,它会在对象的生命周期结束时被自动调用。

3.2 特性

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

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Date {
public:
    Date(int year = 1, int month = 0, int day = 0) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() 
    {
        printf("%d-%d-%d\n", _year, _month, _day);
    }

    ~Date() {
        // Date 类没有资源需要清理,所以Date不实现析构函都是可以的
        cout << "~Date()  " << endl;  // 测试一下,让他吱一声
    }

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

int main(void)
{
    Date d1;
    d1.Print();
    Date d2(2024, 1, 31);
    d2.Print();

    return 0;
}

在这里插入图片描述
我们在之前学习数据结构时,多用malloc()等函数来开辟空间,最后也要写个函数释放空间,但有时候我们会忘记释放,这时就需要我们的析构函数了,我们不用特意的去调用,系统会自己帮我们调用。

typedef int StackDataType;
class Stack {
public:
    /* 构造函数 - StackInit */
    Stack(int capacity = 4) {  // 这里只需要一个capacity就够了,默认给4(利用缺省参数)
        _array = (StackDataType*)malloc(sizeof(StackDataType) * capacity);
        if (_array == NULL) {
            cout << "Malloc Failed!" << endl;
            exit(-1);
        }
        _top = 0;
        _capacity = capacity;
    }

    /* 析构函数 - StackDestroy */
    ~Stack() {  
        free(_array);
        _array = nullptr;
        _top = _capacity = 0;
    }

private:
    int* _array;
    size_t _top;
    size_t _capacity;
};

int main(void)
{
    Stack s1;
    Stack s2(2); 

    return 0;
}

3.3 默认析构函数

  1. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
    和默认构造函数一样:
  • 对于 “内置类型” 的成员变量:不作处理
  • 对于 “自定义类型” 的成员变量:会调用它对应的析构函数。
class Time
{
public:
	~Time()
	{
		cout << "调用了time的析构函数" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _time;
};

int main()
{
	Date d1;
	return 0;
}

在这里插入图片描述

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

4. 拷贝构造函数

4.1 概念

拷贝构造函数是一种特殊的构造函数,用于创建对象的拷贝。它接受与对象类型相同的另一个对象作为参数,并使用该参数的值来初始化新对象。拷贝构造函数通常用于实现深拷贝,即创建一个对象的独立副本,而不是仅复制指针或引用。
拷贝构造函数的语法如下:

类名(const 类名& 另一个对象)
{
    // 初始化新对象的成员变量
}

4.2 特性

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:
	Date(int year = 2024, int month = 1, int day = 31)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() {
		printf("%d-%d-%d\n", _year, _month, _day);
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	d1.Print();
	d2.Print();

	return 0;
}

在这里插入图片描述
为什么要用引用呢?
如果拷贝构造函数使用了对象作为参数而不是引用,那么在调用拷贝构造函数时又需要创建一个新的对象,这会引起一次不必要的拷贝构造操作。而如果使用引用作为参数,就可以直接引用原对象,避免了对象的拷贝。

此外,如果拷贝构造函数使用了对象作为参数,在进行传递时会调用拷贝构造函数,而拷贝构造函数又需要调用拷贝构造函数,这会导致无限递归的问题。而使用引用作为参数,可以避免这种无限递归的情况。

4.3 默认拷贝构造函数

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

class Date {
public:
    Date(int year = 0, 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() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }

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

int main(void)
{
    Date d1(2024, 1, 31);
    Date d2(d1);
    d1.Print();
    d2.Print();

    return 0;
}

在这里插入图片描述
默认拷贝构造函数似乎和前面的默认构造函数以及默认析构函数不太一样,它能够解决我们的需求。
但是!
这并不意味着我们不用写拷贝构造函数,
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

拷贝构造函数典型调用场景:

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date
{
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()
    {
        cout << "~Date():" << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
Date Test(Date d)
{
    Date temp(d);
    return temp;
}
int main()
{
    Date d1(2024, 1, 31);
    Test(d1);
    return 0;
}

在这里插入图片描述

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

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

相关文章

如何发布一款移动 App?

如何发布一款移动 App&#xff1f; 本文转自 公众号 ByteByteGo&#xff0c;如有侵权&#xff0c;请联系&#xff0c;立即删除 今天来聊聊如何发布一款移动 App。 移动 App 的发布流程不同于传统方法。下图简化了这一过程&#xff0c;以帮助您理解。 移动应用程序发布流程的典…

计算机设计大赛 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 垃圾邮件(短信)分类算…

力扣349两个数的交集

题目连接&#xff1a;349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09; 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1&#xff1a; 输入&#xff1a; nums1 [1,2,2…

elementUI之el-form-item的嵌套的场景

像这种&#xff0c;计费规则这几个字而且带红点&#xff0c;外观上是el-form-item&#xff0c;但是其并没有直接和控件进行相关联&#xff0c;这是和其他的el-form-item不同之处。所以这里就得用上嵌套了。也就是说elementUI中el-form-item是可以嵌套使用的。

幻兽帕鲁(Palworld)v0.1.3免安装中文版(下载及配置中文及服务器搭建)

配置中文 进入到游戏文件夹中 Palworld.v0.1.3.0\game\Engine\Binaries\ThirdParty\Steamworks\Steamv153\Win64\steam_settings设置中文 simplified chinesewindows搭建服务器 要求 中央处理器4核&#xff08;推荐&#xff09;内存16千兆字节&#xff08;GB&#xff09; …

深入玩转Playwright:高级操作解析与实践

playwright高级操作 iframe切换 ​ 很多时候&#xff0c;网页可能是网页嵌套网页&#xff0c;就是存在不止一个html标签&#xff0c;这时候我们的selenium或者playwright一般来说定位不到&#xff0c;为什么呢&#xff1f; ​ 因为默认是定位到第一个标准的html标签内部。 …

Python算法题集_滑动窗口最大值

本文为Python算法题集之一的代码示例 题目239&#xff1a;滑动窗口最大值 说明&#xff1a;给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗…

蓝桥杯 第 2 场 小白入门赛

目录 1.蓝桥小课堂-平方和 2.房顶漏水啦 3.质数王国 4.取余 5.数学尖子生 6.魔术师 比赛链接 1.蓝桥小课堂-平方和 简单签到直接按照题目处理即可注意开long long void solve(){LL x; cin>>x;LL ans x*(x1)*(2*x1)/6;cout<<ans<<endl; } 2.房顶漏水…

opencv-python计算视频光流

光流基本概念 光流表示的是相邻两帧图像中每个像素的运动速度和运动方向。具体&#xff1a;光流是空间运动物体在观察成像平面上的像素运动的瞬时速度&#xff0c;是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系&#xf…

仰暮计划|“那时候在生产队下面,集体干活,吃大锅饭,由队里分粮食,吃不饱饭是常事,队里分的粮食就那么点,想要吃饱真的太难了”

希望未来的中国越来越好&#xff0c;大家的生活也越来越好 老人是1955年在河南省洛阳市洛宁县的一个小山村里出生的&#xff0c;前半辈子为了生活&#xff0c;为了孩子而打拼&#xff0c;虽然经历了不少的苦难&#xff0c;但后半辈子也算是苦尽甘来&#xff0c;生活美满。现在就…

【MATLAB源码-第130期】基于matlab的BPSK-ZF迫零均衡,对比均衡前后的误码率曲线以及理论曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 信道均衡是通信系统中的一项关键技术&#xff0c;其主要目的是减少或消除由于信道特性导致的信号失真。在数字通信中&#xff0c;尤其是在无线通信系统中&#xff0c;由于多径传播等原因&#xff0c;接收到的信号会受到严重的…

浅析云性能监控的重要性及核心功能

随着企业日益依赖云计算服务&#xff0c;云性能监控变得至关重要。云性能监控是一种实时监测、分析和报告云基础设施及应用程序性能的方法。本文将深入探讨云性能监控的目的、重要性以及其核心功能&#xff0c;以帮助企业更好地理解和实施这一关键的运维实践。 一、云性能监控的…

[设计模式Java实现附plantuml源码~结构型]不兼容结构的协调——适配器模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

PCB设计10条重要布线原则(学习笔记)

文章目录 一、连线精简二、避免走直角线三、差分走线四、蛇形走线五、圆滑走线六、数字与模拟分开七、3W原则八、20H原则九、铜箔承载电流十、过孔承载电流 一、连线精简 尽量用最短的路径去布线 1、可以省资源 2、信号差损少 3、线能不拐弯就不拐弯 4、能不换层就不换层 二…

MongoDB安装以及卸载,通过Navicat 15 for MongoDB连接MongoDB

查询id&#xff1a; docker ps [rootlocalhost ~]# docker stop c7a8c4ac9346 c7a8c4ac9346 [rootlocalhost ~]# docker rm c7a8c4ac9346 c7a8c4ac9346 [rootlocalhost ~]# docker rmi mongo sudo docker pull mongo:4.4 sudo docker images 卸载旧的 sudo docker stop mong…

Django4.2(DRF)+Vue3 读写分离项目部署上线

文章目录 1 前端2 后端2.1 修改 settings.py 文件关于静态文件2.2 关于用户上传的文件图片 3 Nginx4 镜像制作4.1 nginx4.3 Django镜像4.3.1 构建 5 docker-compose 文件内容 1 前端 进入前端项目的根目录&#xff0c;运行如下命令进行构建 npm run build构建完成后&#xff…

Apple Vision Pro 评测:这款顶尖头显仅是对未来的初步探索

原文&#xff1a;Apple Vision Pro Review: The Best Headset Yet Is Just a Glimpse of the Future 作者&#xff1a;Joanna Stern 戴上 Apple Vision Pro 混合现实头显整整近 24 小时后&#xff0c;有几件事让我颇感意外&#xff1a; 我居然没感到恶心。我竟然高效完成了大…

云纱网签约百望云,联手打造数字化产业闭环

近日&#xff0c;百望云签约广东云纱数字科技有限公司&#xff0c;共建数字化发票管理系统&#xff0c;赋能产业链上下游供应商的协同交易与运营&#xff0c;助力企业实现数字化四流合一交易&#xff0c;打造数字化产业闭环。 云纱网是广东云纱数字科技有限公司依托于深厚的产业…

利用牛顿方法求解非线性方程(MatLab)

一、算法原理 1. 牛顿方法的算法原理 牛顿方法&#xff08;Newton’s Method&#xff09;&#xff0c;也称为牛顿-拉弗森方法&#xff0c;是一种用于数值求解非线性方程的迭代方法。其基本思想是通过不断迭代来逼近方程的根&#xff0c;具体原理如下&#xff1a; 输入&#…

STM32——DMA

STM32——DMA 1.DMA介绍 什么是DMA&#xff1f; DMA(Direct Memory Access&#xff0c;直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通&#xff0c;而不需要依赖于CPU&#xff0c;在这个时间中&…
最新文章