IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】类和对象3 -> 正文阅读

[C++知识库]【C++】类和对象3


一、析构函数

1.概念

上一篇文章中讲到构造函数来初始化一个对象,那么析构函数的作用是什么呢?

已创建的对象在销毁(也即生命周期结束)时会自动调用析构函数,完成类的一些资源清理工作

注意:构造函数不是创建对象,析构函数不是销毁对象。构造函数初始化对象,析构函数完成资源清理工作。


2.特性

(1)函数名

析构函数的函数名是在类名前加上字符~

(2)无参数无返回值

注意这里与构造函数一样是无返回值,而不是返回值为空。同时析构函数不需要参数。


(3)一个类有且只有一个析构函数,编译器自动调用

若显式定义,则在对象销毁时自动调用已经写好的析构函数。否则系统会自动生成并调用默认的析构函数。

之前实现的Date类的析构函数其实不需要实现,因为它的成员变量是三个内置类型,且没有动态开辟内存等等的操作,编译器自动生成的已经足够了。

但是下面这个栈的类由于有动态开辟内存,所以就需要自己实现析构函数(注意需要自己实现析构函数不仅限于动态开辟了内存的类,这里以此为例)。

代码如下(示例):

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		_size = 0;
		_capacity = capacity;
	}

	//析构函数,清理资源
	~Stack()//无参数,无返回值!!!
	{
		free(_a);//释放动态开辟的内存
		_a = nullptr;
		_size = 0;
		_capacity = 0;
	}

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

(4)编译器生成的默认的析构函数

同编译器生成的默认构造函数一样,编译器生成的默认的析构函数对内置类型不处理,对自定义类型则去调用它自己的析构函数。


二、拷贝构造函数

1.概念

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


2.特性

(1) 拷贝构造函数是构造函数的一个重载

拷贝构造函数和构造函数函数名相同,形参列表不同,所以构成重载。

(2) 拷贝构造函数的参数

拷贝构造函数的参数只有一个,必须使用引用传参,建议在前面加const,使用传值方式会引发无穷递归调用。

(其实传参时传地址可以正常实现功能,但这样就不太像是拷贝构造,而且在C++中建议尽量使用引用)

代码如下(示例):

class Date
{
public:
	//默认构造函数
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造,传引用
	Date(const Date& d)
	{
		//将d的三个成员变量的值依次赋给*this
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	//析构函数什么都不需要做,也可以不写
	~Date()
	{}

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

若使用传值的方式:

代码如下(示例):

class Date
{
public:
	//...
	Date(const Date d)
	{
		//将d的三个成员变量的值一次赋给*this
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	//...
}

int main()
{
	Date d1(2021, 7, 9);
	Date d2(d1);
	
	return 0;
}

传值本质上就是一个拷贝,以上面的代码为例就是拿main函数中的d1初始化拷贝构造函数中的形参d,但是这本身仍是一个拷贝构造,所以又需要用d1拷贝构造一个形参d,如此下去无穷递归调用。

这里可能较难理解,但只要记住,实现拷贝构造时一定要传引用,最好再加上const防止被修改即可。编译器在这个地方的检查本身也比较严格。上面的代码在VS2013编译器下无法通过编译。

编译结果如下:

在这里插入图片描述


(3)系统生成的默认的拷贝构造函数

若未显示定义,系统会自动生成默认的拷贝构造函数。 默认的拷贝构造函数按内存存储的字节序一个字节一个字节依次完成拷贝,这种拷贝一般叫做浅拷贝,或者值拷贝。

(深拷贝会在之后的文章中写到)

对上面的Date类来说系统生成的拷贝构造函数足够了,但对Stack类来说不行。

代码如下(示例):

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		_size = 0;
		_capacity = capacity;
	}

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

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

int main()
{
	Stack st1;
	Stack st2(st1);
	
 	return 0;
}

这里没有实现Stack的拷贝构造,系统自动生成的默认的拷贝构造函数按照字节把st1的内容(三个内置类型)全部拷贝到st2内,也就是说这两个对象内的三个成员变量是完全一致的,其中各自的_a指向同一片空间在析构函数调用时,对同一块内存空间free了两次,报错。同时若其中一个对象对这块空间的处理会影响另一个对象。

而对自定义类型的拷贝构造则会调用这个自定义类型的拷贝构造。


三、赋值运算符重载

1.运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。这样就可以让自定义类型像内置类型一样使用运算符。

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

运算符重载和函数重载虽然都是重载,但没有关联。函数重载是支持同名函数,运算符重载是为了让自定义类型像内置类型一样使用运算符。

下面看一个例子(省去了不必要的成员函数):比较两个日期类是否相等

代码如下(示例):

class Date
{
public:
	bool Equal(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

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

int main()
{
	Date d1(2021, 7, 9);
	Date d2(d1);
	d1.Equal(d2);
 	return 0;
}

但是内置类型的比较相等是通过==来实现的,如果Date类也能实现这个运算符,那么可读性会更强。

代码修改如下(示例):

class Date
{
public:
//返回值类型 operator 操作符(参数列表)
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

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

int main()
{
	Date d1(2021, 7, 9);
	Date d2(d1);
	if(d1 == d2)//编译器会自动转化成d1.operator==(&d1,d2)
	{
		//...
 	}
	return 0;
}

这样,就可以把各种两个日期的比较都写出来:

代码如下(示例):

class Date
{
public:

	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	bool operator>(const Date& d)
	{
		if (_year > d._year)
			return true;
		else if (_year == d._year)
		{
			if (_month > d._month)
				return true;
			else if (_month == d._month)
			{
				if (_day > d._day)
					return true;
			}
		}
		return false;
	}
	
	//注意下面的代码复用
	bool operator>=(const Date& d)
	{
		return (*this > d) || (*this == d);
	}
	bool operator<(const Date& d)
	{
		return !(*this >= d);
	}
	bool operator<=(const Date& d)
	{
		return !(*this > d);
	}
	bool operator!=(const Date& d)
	{
		return !(*this == d);
	}
private:
	int _year;
	int _month;
	int _day;
};

注意:
(1)不能通过连接C语言没有的操作符来创建操作符:比如operator@
(2)重载操作符必须至少有一个类类型或者枚举类型(非内置类型)的操作数。如果全部都是内置类型就没必要重载,所以至少有一个非内置类型的操作数
(3)用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义,仍应该为加法操作
(4)作为类成员的重载函数时,其形参看起来比操作数数目少1,因为成员函数的操作符有一个默认的形参this,不嗯呢该写出且被限定为第一个形参
(5)不能重载的5个运算符
.* (注意不是 * 而是.* )、::(域作用限定符) 、sizeof 、?:(三目运算符) 、.(访问类的成员的操作符)


2.赋值运算符重载

赋值也是拷贝,但是拷贝构造是创建对象时,用同类对象初始化这个新的对象;而赋值是两个对象都已经存在、已经被初始化过,把一个对象的值赋给另一个对象。

代码如下(省去了其它成员函数):

class Date
{
public:
	void operator=(const Date& d)//被转化为void operator=(Date* this, const Date& d)
	{
		if(this != &d)//防止自己给自己赋值
		{
			//把d的成员变量赋值给*this
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
	}

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

int main()
{
	Date d2(2021, 7, 9);
	Date d1;
	d1 = d2;//会被编译器处理成d1.operator=(&d1, d2);
	return 0;
}

下面的赋值运算符重载就可以支持类似i = j = k(i、j、k是三个内置类型)的连续赋值。

代码如下:

Date& operator=(const Date& d)
{
	if(this != &d)
	{
		//把d的成员变量赋值给*this
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

一个类如果没有显式定义赋值运算符重载,编译器也会自动生成一个,这个自动生成的赋值运算符重载与拷贝构造的特性一致,完成对象按字节序的值拷贝


四、const成员函数

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

代码如下:

class Date
{
public:
	void print() const//相当于void print(const Date* this)
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

上面的print函数被const修饰(注意const的位置),这样写相当于void print(const Date* this),即该函数内部无法对this修改。


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

即下面两种操作符重载:

代码如下:

Date* operator&();
Date* operator&() const;

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载。


感谢阅读,如有错误请批评指正

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-10 11:31:37  更:2021-07-10 11:31:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 13:29:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码