类和对象(二)

news2025/1/10 21:57:51

目录

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

二、构造函数

2.1 概念

2.2 特性

三、析构函数

3.1 概念

3.2 特性

四、拷贝构造函数

4.1 概念

4.2 特性

五、赋值运算符重载

5.1 运算符重载

5.2 赋值运算符重载特性

六、const修饰成员函数

七、取地址及const取地址操作符重载


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

若一个类中什么成员都没有,其被称为空类。但是空类中并不是什么都没有,即使什么都不写,类中也会自动生成6个默认成员函数(不止6个,C++11新增两个)

class Date {}; //空类

注意:这里的"默认"和"缺省"意思相近。即不写这6个函数,编译器自动生成;若是写了,编译器则不生成

二、构造函数

2.1 概念

  • 名字与类名相同,创建对象时由编译器自动调用,保证每个数据成员都有合适的初始值,在对象的生命周期内只调用一次

如:以下日期类中的成员函数Date就是构造函数。用该日期类创建对象时,编译器会自动调用该构造函数对新创建的变量进行初始化

class Date
{
public:
	Date(int year = 0, 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;
};

2.2 特性

  1. 构造函数的函数名与类名相同
  2. 构造函数无返回值(构造函数无返回值是真正意义上的无返回值,并不是返回值为void)
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数支持重载
  5. 无参的构造函数、全缺省的构造函数以及编译器自动生成的构造函数都被称为默认构造函数,并且默认构造函数只能有一个(无需传参就可以调用的构造函数就是默认构造函数)
  6. 若类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,若用户显示定义了,则编译器就不再生成

既然在不写的情况下,编译器会自动生成一个构造函数,那就没有必要写构造函数了吗?这个想法是错误

#include <iostream>
using namespace std;
class Date
{
public:
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1; // 编译器将调用自动生成的默认构造函数对d1进行初始化
	d1.Print();
	return 0;
}

代码输出结果如下: 

为什么d1对象调用了编译器自动生成的构造函数后,d1对象的_year/_month/_day依旧是随机值呢?

编译器自动生成的构造函数机制

  • 编译器自动生成的构造函数对内置类型不做处理
  • 对于自定义类型,编译器会再去调用其默认构造函数

编译器自动生成的构造函数可能达不到预期的效果,所以大多数情况下都需程序员自行编写构造函数

三、析构函数

3.1 概念

  • 与构造函数功能相反,析构函数负责完成对象的销毁,对象在销毁时自动调用析构函数,完成类的一些资源清理工作

当一个类对象销毁时,其中的成员变量也会随着该对象的销毁而销毁,如:用日期类创建了一个对象d1,当d1被销毁时,对象d1中的成员变量_year/_month/_day也会被自动销毁。但是这并不意味着析构函数没有什么意义。某些情况下,对象会在堆区开辟空间使用,当该对象被销毁时,其动态开辟的堆空间并不会随之被销毁,需对其进行空间释放

3.2 特性

  • 一、析构函数的函数名是在类名前加上字符"~"
class Date
{
public:
	Date()// 构造函数
	{}
	~Date()// 析构函数
	{}
private:
	int _year;
	int _month;
	int _day;
};
  • 二、析构函数无参数,无返回值(真正意义上的无返回值,并不是返回值为void)
  • 三、对象生命周期结束时,C++编译器会自动调用析构函数
  • 四、一个类有且只有一个析构函数。若未显示定义系统会自动生成默认的析构函数

编译器自动生成的析构函数机制

  1. 编译器自动生成的析构函数对内置类型不做处理
  2. 对于自定义类型,编译器会再去调用它们自己的默认析构函数
  • 五、先构造的后析构,后构造的先析构

因为对象是定义在函数中的,函数调用会建立栈帧,栈帧中的对象构造和析构也要符合先进后出的原则

四、拷贝构造函数

4.1 概念

  • 只有单个形参,该形参是对本类类型对象的引用(常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)// 构造函数
	{
		_year = year;
		_month = month;
		_day = 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(2021, 5, 31);
	Date d2(d1); // 用已存在的对象d1创建对象d2

	return 0;
}

4.2 特性

  • 一、拷贝构造函数是构造函数的一个重载形式(拷贝构造函数的函数名也与类名相同)
  • 二、拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用

要调用拷贝构造函数就需先传参,若传参使用传值传参,那么在传参过程中又需进行对象的拷贝构造,如此循环往复,最终引发无穷递归调用

  • 三、若未显示定义拷贝构造函数,系统将生成默认的拷贝构造函数
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 0, 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(2021, 5, 30);
	Date d2(d1); // 用已存在的对象d1创建对象d2
	d1.Print();
	d2.Print();
	return 0;
}

编译器自动生成的拷贝构造函数机制:

  1. 编译器自动生成的拷贝构造函数对内置类型会完成浅拷贝(值拷贝)
  2. 对于自定义类型,编译器会再去调用它们的拷贝构造函数

代码中,并没有显示定义拷贝构造函数,但编译器自动生成的拷贝构造函数最终还是完成了对象的拷贝构造

  • 四、编译器自动生成的拷贝构造函数不能实现深拷贝

深浅拷贝问题

编译器自动生成的拷贝构造函数会对内置类型完成浅拷贝。对于以下这句代码,浅拷贝实际上就是将d1的内容完完全全的复制了一份拷贝给d2,所以说浅拷贝也被称为值拷贝。

Date d2(d1);// 用已存在的对象d1创建对象d2

但某些场景下浅拷贝并不能达到预期的效果。如,如下栈(Stack)这样的类,编译器自动生成的拷贝构造函数就不能满足需求了

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_ps = (int*)malloc(sizeof(int)* capacity);
		_size = 0;
		_capacity = capacity;
	}
	void Print()
	{
		cout << _ps << endl;// 打印栈空间地址
	}
private:
	int* _ps;
	int _size;
	int _capacity;
};

类中没有显示定义拷贝构造函数,那么用已存在的对象来创建另一个对象时,将调用编译器自动生成的拷贝构造函数

int main()
{
	Stack s1;
	s1.Print();// 打印s1栈空间的地址
	Stack s2(s1);// 用已存在的对象s1创建对象s2
	s2.Print();// 打印s2栈空间的地址
	return 0;
}

结果打印s1栈和s2栈空间的地址相同,意味着,创建完s2栈后,对s1栈做的任何操作都会直接影响到s2栈

这种情况下,还会出现对同一块空间释放多次的问题。所以像Date这样的类,需要的就是浅拷贝,那么编译器自动生成的拷贝构造函数就够用了,不需显示定义。像Stack这样的类,浅拷贝会导致析构两次、程序崩溃等问题,需显示定义对应的拷贝构造函数

五、赋值运算符重载

5.1 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其目的是让自定义类型可以像内置类型一样可以直接使用运算符进行操作

d1 == d2;// 可读性高(书写简单)
IsSame(d1, d2);// 可读性差(书写麻烦)
返回值 operator运算符 (参数列表)//函数原型

注意:

  1. 不能通过连接其他符号来创建新的操作符,如:operator@
  2. 重载操作符必须有一个类类型或枚举类型的操作数
  3. 用于内置类型的操作符,重载后其含义不能改变
  4. 作为类成员的重载函数时,函数有一个默认的形参this,限定为第一个形参
  5. sizeof 、:: 、.* 、?: 、. 这5个运算符不能重载

示例:前置++重载、后置++重载

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载。C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

//前置++
Date& Date::operator++() {
    //...
	return *this;
}
//后置++
Date Date::operator++(int) {
    //...
	return tmp;
}

示例:重载运算符==

可以将该运算符重载函数作为类的一个成员函数,此时该函数的第一个形参默认为this指针

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() {
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	bool operator==(const Date& d)// 运算符重载函数
	{
		return _year == d._year && _month == d._month
		&&_day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

也可将该运算符重载函数放在类外面,但此时外部无法访问类中的成员变量,这时可以将类中的成员变量设置为共有(public),这样外部就可以访问该类的成员变量了(也可以用友元函数或提供接口解决该问题)。并且在类外没有this指针,所以此时函数的形参必须显示的设置两个

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	int _year;
	int _month;
	int _day;
};
bool operator==(const Date& d1, const Date& d2)// 运算符重载函数
{
	return d1._year == d2._year && d1._month == d2._month
	&&d1._day == d2._day;
}

5.2 赋值运算符重载特性

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)// 构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)// 赋值运算符重载函数
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void Print()// 打印函数
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
  • 一、参数类型设置为引用,并用const进行修饰

赋值运算符重载函数的第一个形参默认是this指针,第二个形参是赋值运算符的右操作数。由于是自定义类型传参,若是使用传值传参,会额外调用一次拷贝构造函数,所以函数的第二个参数最好使用引用传参。其次,第二个参数,即赋值运算符的右操作数,在函数体内不会对其进行修改,所以最好加上const进行修饰

  • 二、函数的返回值使用引用返回

若是只以d2 = d1这种方式使用赋值运算符,赋值运算符重载函数就没必要有返回值,因为在函数体内已经通过this指针对d2进行了修改。但是为了支持连续赋值,即d3 = d2 = d1,就需要为函数设置一个返回值了。而且返回值应该是赋值运算符的左操作数,即this指针指向的对象。和使用引用传参的道理一样,为了避免不必要的拷贝,最好还是使用引用返回。此时出了函数作用域this指针指向的对象并没有被销毁,所以可以使用引用返回

  • 三、赋值前检查是否是给自己赋值

若是出现d1 = d1,就不必进行赋值操作

  • 四、引用返回的是*this

赋值操作进行完毕时,应该返回赋值运算符的左操作数,而在函数体内只能通过this指针访问到左操作数,所以要返回左操作数就只能返回*this

  • 五、一个类如果没有显示定义赋值运算符重载,编译器也会自动生成一个,完成对象按字节序的值拷贝

赋值运算符重载编译器也可以自动生成,并且是支持连续赋值的。但是编译器自动生成的赋值运算符重载完成的是对象按字节序的值拷贝,如d2 = d1,编译器会将d1所占内存空间的值完完全全地拷贝到d2的内存空间中去,类似于memcpy。对于日期类,编译器自动生成的赋值运算符重载函数就可以满足需求。但当遇到一些特殊的类,还是得显示定义赋值运算符函数

注意 

Date d2(d1);//拷贝构造函数
Date d3 = d1;//拷贝构造函数
d1 = d3;//赋值运算符重载函数
  • 拷贝构造函数:用一个已经存在的对象去构造初始化另一个即将创建的对象
  • 赋值运算符重载函数:在两个对象都已经存在的情况下,将一个对象赋值给另一个对象

六、const修饰成员函数

const修饰类成员函数,实际修饰的是类成员函数隐含的this指针,表明在该成员函数中不能对this指针指向的对象进行修改

void Print()const {//cosnt修饰的打印函数
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其他的非const成员函数吗?
  4. 非cosnt成员函数内可以调用其他的cosnt成员函数吗?

答案是:不可以、可以、不可以、可以

  1. 非const成员函数,即成员函数的this指针没有被const所修饰,传入一个被const修饰的对象,用没有被const修饰的this指针进行接收,属于权限的放大,函数调用失败
  2. const成员函数,即成员函数的this指针被const所修饰,传入一个没有被const修饰的对象,用被const修饰的this指针进行接收,属于权限的缩小,函数调用成功
  3. 在一个被const所修饰的成员函数中调用其他没有被const所修饰的成员函数,即:将一个被const修饰的this指针的值赋值给一个没有被const修饰的this指针,属于权限的放大,函数调用失败
  4. 在一个没有被const所修饰的成员函数中调用其他被const所修饰的成员函数,即:将一个没有被const修饰的this指针的值赋值给一个被const修饰的this指针,属于权限的缩小,函数调用成功

七、取地址及const取地址操作符重载

取地址操作符重载和const取地址操作符重载,这两个默认成员函数一般不用显示定义,使用编译器自动生成的即可

class Date
{
public:
	Date* operator&() { return this; }//取地址操作符重载
	const Date* operator&()const { return this; }//const取地址操作符重载
private:
	int _year;
	int _month;
	int _day;
};

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

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

相关文章

Html基础知识学习——css精灵(十五)

这里写自定义目录标题 定义示例一示例二 定义 将网页用到的图片放在一张图片上&#xff0c;进行定位展示 优点:防止网页http请求次数过多&#xff0c;从而提高页面性能 缺点&#xff1a;降低开发效率。维护难度加大 示例一 使用图 网页制作图 <!DOCTYPE html> <…

如何能够极速浏览大体积tif影像文件?

在无人机航拍的高清正射影像中&#xff0c;大体积文件是普遍现象&#xff0c;几十GB的TIFF文件很常见。常规的浏览方式特别容易导致卡顿&#xff0c;用户需要花费半天时间等待影像图的呈现。 建议您尝试将tiff格式快速转换为mbtiles或lrp格式&#xff0c;mbtiles和lrp格式专门…

JSP+MySql实战

0目录 JSPMySql实战 创建maven工程和包 创建2张表 User表 Employee表 实体类封装 Util 包BaseDao类 Dao包 EmployeeDao类 UserDao类 实现接口方法 Dao包新建impl包 UserDaoImp EmployeeDaoImpl 新建Service包 接口方法 UserService EmployeeSe…

【分布式任务调度】XXL-JOB的任务调度实现原理(四)

文章目录 1.前言2.调度中心流程2.1.任务配置扫描流程2.2.计算任务触发时机2.2.1.已超时5秒以上2.2.2.超时未超过5秒2.2.3.还未到触发时间 2.3.任务触发流程2.3.1.任务触发线程池2.3.2.参数处理2.3.3.任务触发2.3.4.分片广播策略&#xff08;补充&#xff09; 3.执行器流程3.1.任…

笔记本安装Centos Stream9系统的详细安装教程

U盘刻录工具&#xff1a; Index of /downloadshttp://rufus.ie/downloads/

库迪身陷“价格”囹圄,融资苦难户还有突围的希望吗?

作者 | 心怡 来源 | 洞见新研社 三伏天已至&#xff0c;正是咖啡品牌借冰咖笑傲市场的好时机。没想到的是&#xff0c;靠低价狂奔的库迪却率先传出涨价的消息。 消息称&#xff0c;7月起&#xff0c;库迪划线价格上调1-2元&#xff0c;8.8元的团购价涨到9.9元&#xff0c;热门…

如何使用vb上位机串口通信

今天我来分享一下如何使用vb上位机串口通信。首先&#xff0c;我们需要准备一台具有串口的上位机设备和一台计算机。刚好&#xff0c;我这里有上位机入门&#xff0c;学习线路图&#xff0c;各种项目&#xff0c;需要留个6。接下来&#xff0c;我们需要安装相应的控件和程序包。…

【PHP面试题44】PHP5的版本和PHP7之间有哪些区别

文章目录 一、前言二、底层调整2.1性能提升2.2 新的引擎2.3 数据类型改进2.4 错误处理改进2.5 语言特性增加 三、应用层差异3.1 兼容性3.2 类和方法改进3.3 错误处理机制3.4 性能优化3.5 新的扩展支持 四、一些语法糖示例4.1 标量类型声明示例4.2 新增了Spaceship操作符&#x…

新字符设备驱动实验

目录&#xff1a; 1. 新字符设备驱动原理1.1. 分配和释放设备号1.2. 添加字符设备 2.自动创建设备节点2.1. mdev机制2.2. 创建和删除类2.3. 创建设备2.4. 新字符设备驱动框架总结 3. 文件结构体和文件私有数据4. 实验程序编写4.1. 驱动程序4.2. 应用程序 5. 运行测试 1. 新字符…

RabbitMQ 同样的操作一次成功一次失败

RabbitMQ 是一个功能强大的消息队列系统&#xff0c;广泛应用于分布式系统中。然而&#xff0c;我遇到这样的情况&#xff1a;执行同样的操作&#xff0c;一次成功&#xff0c;一次失败。在本篇博文中&#xff0c;我将探讨这个问题的原因&#xff0c;并提供解决方法。 我是在表…

DatenLord前沿技术分享 No.30

达坦科技专注于打造新一代开源跨云存储平台DatenLord&#xff0c;通过软硬件深度融合的方式打通云云壁垒&#xff0c;致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题&#xff0c;以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。在本周…

【雕爷学编程】Arduino动手做(161)---16路PWM舵机驱动板2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

windows搭建git服务器 无法识别 ‘git‘ 命令:exec: “git“: executable file not found in %PATH%

无法识别 git 命令&#xff1a;exec: "git": executable file not found in %PATH% 确保已经安装git&#xff0c;如下图配置环境变量即可。

ylb-接口7注册发送短信

总览&#xff1a; 在common模块下引入短信验证码的依赖项&#xff08;生成4位随机数&#xff09;&#xff1a; 在web模块下的resources/application.yml&#xff0c;添加配置信息&#xff08;京东万象&#xff09;&#xff1a; #短信配置 jdwx:sms:url: https://way.jd.com…

在 3ds Max 中使用Mental Ray渲染 wip 图像

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 本教程面向初学者&#xff0c;每个步骤都详细概述和显示。如果您是 3D Studio MAX 的新手&#xff0c;您可能想先尝试我们的其他一些教程。 使用默认的 3D Studio MAX 渲染器创建粘土渲染 步骤 1 在 3D S…

Unity DOTS如何优雅地实现ECS框架下的定时器Timer系统(无对象池,零GC)

实现定时器并不复杂&#xff0c;就是写个类存放回调&#xff0c;再写个类来统一管理这些回调类。但是ECS写代码的方式变了&#xff0c;所以还是有些区别的。 实现过程中需要注意的几点&#xff1a; 1、由于IComponentData不能存放managed类型的数据&#xff0c;所以无法用常规…

微服务架构+创建微服务工程(商品/订单)

目录 1.微服务架构 1.1.单体应用架构 1.2.微服务应用 1.3 微服务架构的优势 1.4.微服务架构的缺点(挑战) 1.5. SpringCloud与微服务关系 1.6.SpringBoot和SpringCloud关系 2. 创建微服务工程 2.1.数据库 2.2.搭建父工程 2.2 创建公共模块 2.3.商品系统 2.4.订单微…

【通讯协议备忘录】stm32的CAN外设

文章目录 帧结构测试模式&#xff08;静默/换回/环回静默&#xff09;&#xff1a;环回测试配置 过滤器的使用测试参考用例过滤器的初始化发送和接收 中断 帧结构 CAN的报文结构&#xff1a; 测试模式&#xff08;静默/换回/环回静默&#xff09;&#xff1a; 静默模式&…

Apache(httpd) 搭建笔记

Apache 搭建笔记 安装Apache HTTP服务器&#xff1a;启动Apache服务并设置开机自启 配置SSL证书配置Apache的SSL虚拟主机&#xff1a;重启Apache服务以使更改生效&#xff1a; 多站点配置第一个虚拟主机配置第二个虚拟主机创建每个站点的根目录&#xff1a; 强制跳转http>&g…

mysql及事务隔离级别

目录 一 事务之间相互影响分为几种 二 mysql常见的储存引擎 三 死锁 四 查看使用的储存引擎 五 修改储存引擎 六 总结 一 事务之间相互影响分为几种 脏读:就是读取了没有提交的数据, 不可重复读 :前后多次读取内容不一致 幻读:两次读的结果不一样 丢失更新:后一个会覆…