C++——类和对象之运算符重载

这篇具有很好参考价值的文章主要介绍了C++——类和对象之运算符重载。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

运算符重载

本章思维导图:

C++——类和对象之运算符重载,C++教程,c++,开发语言

注:本章思维导图对应的xmind文件和.png文件都已同步导入至”资源“


1. 运算符重载的意义

我们都知道,对于内置类型我们是可以直接用运算符直接对其进行操作的,但是对于自定义类型,这种做法是不被允许的

例如对于Date类:

Date d1;
Date d2(2023, 11, 3);

d1 == d2;
//会报错:error C2676: 二进制“==”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换

因此,为了解决自定义类型不能使用操作符的问题,C++就有了运算符重载

2. 函数的声明

C++——类和对象之运算符重载,C++教程,c++,开发语言

一般来说,运算符重载的函数最好声明在类里面,这样就可以使用被private修饰的成员变量

声明方式:

operator 运算符 形参列表

  • 函数的返回值应该和运算符的意义相对应。例如对于比较运算符>、==就应该返回bool值,对于+、-就应该返回当前类类型
  • 函数的形参个数应该和运算符的操作数相对应

例如,我们要声明定义一个Date += 整数的函数,那我们就要重载运算符+=

//根据+=运算符的意义,其返回值应该是操作数本身,因此返回其引用
Date& operator +=(int day)
{
    //仅作为运算符重载如何声明定义的演示
    //实现细节先不做说明

    return *this;
}

2.1 声明运算符重载的注意事项

有小伙伴可能注意到:为什么Date& operator +=(int day)的形参只有一个day,不是说形参个数要和运算符的操作数对应吗?

我们不能忘记:在类的成员函数中,都有一个默认的形参this指针来指向当前的对象

同时还应该注意以下几点:

  • 不能通过连接其他符号来实现重载,例如operator @()是不被允许的
  • 不能修改内置类型运算符的意义
  • .* . :? sizeof ::这五个运算符是不可以重载的
  • 对于二元运算符(操作数为2的运算符),形参列表的第一个参数为左操作数,第二个参数为右操作数。因此对于声明在类里面的重载函数,this指针永远是左操作数

3. 函数的调用

C++——类和对象之运算符重载,C++教程,c++,开发语言

在声明定义好运算符重载后,有两种调用方法:

第一种——像内置类型一样直接使用运算符

d1 += 10;
//调用Date& operator +=(int day)函数,在对象d1的基础上加10

第二种——像调用函数一样,使用运算符重载

d1.operator+=(10);
//调用Date& operator +=(int day)函数,在对象d1的基础上加10

4. const成员函数

C++——类和对象之运算符重载,C++教程,c++,开发语言

我们来看下面的代码:

class Date
{
public:
	Date(int year = 2023, int month = 11, int day = 3)
    {
         //简单的值拷贝
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

	void Print()
	{
		cout << _year << ';' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	const Date d2;

	d1.Print();		//Yes
	d2.Print();		//Not

	return 0;
}
//d2.Print()会报错:不能将“this”指针从“const Date”转换为“Date &”

这是因为:

我们之前提到过,引用不能涉及到权限的放大const Date d2对应的是const Date* this,而函数Print里的默认的是Date* this,这就涉及到了权限的放大。

因此,为了解决被const修饰的对像不能调用成员函数的问题,我们可以用const来修饰成员函数,这样,本质上成员函数的this指针就被const修饰了,从而也就可以被const对象调用。

4.1 const成员函数的声明

将一个成员函数变为const成员函数,只要在该函数声明的最后加上const修饰符即可

例如对于上面提到的Pinrt()成员函数,将其变为const成员函数即:

void Print() const
{
    cout << _year << ';' << _month << ' ' << _day << endl;
}

4.2 注意点

应该知道,引用不能涉及权限的放大,但是可以进行权限的平移和缩小。

因此:

  • const对象可以调用const成员函数
  • 非const对象也可以调用const成员函数

并不能说可以将所有的成员函数都声明为const成员函数,因为对于有些成员函数,它需要在函数内部修改对象的成员变量。

但是对于那些不需要修改对象成员变量的成员函数,建议都将其声明为cosnt,这样const对象和非const对象都能调用

5. 两个默认成员函数

运算符重载中也有两个成员函数,分别是:赋值=运算符重载和取地址&\const &运算符重载

5.1 赋值运算符重载

赋值运算符重载是类的默认成员函数

C++——类和对象之运算符重载,C++教程,c++,开发语言

5.1.1 函数的声明

  • 因为赋值运算符重载是类的默认成员函数,因此必须声明在类的里面
  • =运算符的操作对象为2,因此该函数有两个形参。第一个形参为被隐藏的this指针,第二个形参就是要赋予值的对象,且该对象最好被const修饰
  • 为了支持连续赋值,函数的返回值应该是类类型的引用

例如,声明一个Date类的赋值运算符重载:

Date& operator=(const Date& d)
{
 	//
}

5.1.2 函数的定义

应该清楚,赋值实际上也是一种拷贝。而拷贝又分为深拷贝和浅拷贝:

浅拷贝:

  • 浅拷贝又称值拷贝
  • 浅拷贝只是对成员变量值的简单复制,而不是复制指向的动态分配的资源(如堆内存)
  • 原对象和拷贝对象将共享相同的资源

深拷贝:

  • 深拷贝又称址拷贝
  • 相较于浅拷贝只是对成员变量值的简单赋值,深拷贝会复制对象的成员变量以及指向的资源,包括指针指向的数据
  • 这确保了原对象和拷贝对象拥有彼此独立但内容相同的资源副本

关于深浅拷贝更为清楚的解释请移步👉C++——拷贝构造

既然拷贝分为深浅拷贝,那么我们的赋值运算符重载也应该分为值拷贝、址拷贝这两种情况:

值拷贝:

例如对于Date类,里面没有指针指向空间资源,因此只需要对其成员变量进行简单复制操作

Date& operator=(const Date& d)
{
 //简单的值拷贝
 _year = d._year;
 _month = d._month;
 _day = d._day;

 return *this;
}

址拷贝:

例如对于Stack类,里面有一个指针用于指向栈存储的空间,因此需要进行深拷贝来创建一个和源对象内容相同但地址不同的空间

Stack& operator=(const Stack& st)
{
 _capacity = st._capacity;
 _top = st._top;

 //深拷贝
 _a = (int*)malloc(sizeof(int) * _capacity);
 if (nullptr == _a)
 {
     perror("malloc");
     exit(-1);
 }
 memcpy(_a, st._a, sizeof(int) * _capacity);

 return *this;
}

5.1.3 赋值运算符重载和拷贝构造的区别

看完上面函数的定义,有小伙伴就肯定会有疑惑了:

这看起来二者之间没有什么区别嘛,为什么要做区分?

我们来看下面的例子:

class Date
{
public:
    //构造
	Date(int year = 2023, int month = 11, int day = 3)
	{
        _year = year;
        _month = month;
        _day = day;
	}
	//拷贝构造
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//赋值运算符重载
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2 = d1;	//拷贝构造还是赋值运算符重载???
    
	Date d3;
	d3 = d2;	//拷贝构造还是赋值运算符重载???

	return 0;
}

各位认为,上面问题的答案是什么?让我们来进行调试:

C++——类和对象之运算符重载,C++教程,c++,开发语言

可以得出结论:赋值运算符重载和拷贝构造的不同之处在于:

  • 拷贝构造注重的是构造,即用一个已经存在的对象来实例化构造一个新的对象
  • 赋值运算符重载注重的是赋值,即对两个已经存在的对象进行赋值操作

5.1.4 默认赋值运算符重载

和其他默认成员函数一样,如果自己没有声明和定义赋值运算符重载,编译器会自动生成一个

和拷贝构造一样,默认运算符重载只会对成员变量进行简单的值拷贝(浅拷贝),因此如果类的成员变量有指向内存空间的指针,仅依赖系统默认生成的赋值运算符重载是不够的,需要自己声明定义,来实现深拷贝

5.2 &和const &运算符重载

这两个函数都是类的默认成员函数

  • 他们的作用仅用于返回对象的地址
Date* operator&()
{
    return this;
}

const Date* operator&() const
{
    return this;
}

这两个默认成员函数一般用编译器自动生成的默认&运算符重载即可,不需要自己声明和定义

6. 流插入<<、流提取>>运算符重载

C++——类和对象之运算符重载,C++教程,c++,开发语言

通过查阅资料我们可以得知:coutostream类的对象,cinistream类的对象

extern ostream cout;
extern istream cin;

6.1 函数的声明

我们可以先看一个例子来推出这两个重载函数的返回值是什么:

cout << 10 << 20;

这串代码实现的是向屏幕连续输出两个数字10和20<<运算符的运算顺序应该是从左到右,而为了能够实现连续的cout,我们可以假象在执行完cout << 10后有生成了一个cout来完成cout << 20

C++——类和对象之运算符重载,C++教程,c++,开发语言

而为了能够实现执行完cout << 10后又生成一个coutcout运算符重载的返回值就应该是ostream类的引用,即ostream &

同理,cin运算符重载的返回值就应该是istream &

接下来的问题是,这两个运算符重载应该放在类里面还是类外面呢?

如果声明在类里面:

class Date
{
public:
 //构造
	Date(int year = 2023, int month = 11, int day = 3)
	{
     _year = year;
     _month = month;
     _day = day;
	}

	ostream& operator<<(ostream& _cout)
	{
		_cout << _year << ' ' << _month << ' ' << _day << endl;

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

那当我们使用时:

int main()
{
	Date d1;
	cout << d1;

	return 0;
}
//就会报错:二元“<<”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)

这是因为,我们在前面就说过,对于二元运算符,第一个形参就是左操作数,第二个形参就是右操作数。而对于类的成员函数,this指针就是第一个参数,即左操作数。因此对于流插入运算符<<,左操作数就是d1,右操作数就是_cout

正确的写法应该为:

d1 << cout;

显然,这种写法的可读性是极差的。我们**应该想办法使_cout成为左操作数,this指针成为右操作数,**但是对于类的成员函数,形参的第一个都必定是指向对象的this指针,所以,为了达到要求,我们应该将<<>>运算符的重载声明定义在类的外面

即:

class Date
{
public:
 //构造
	Date(int year = 2023, int month = 11, int day = 3)
	{
     _year = year;
     _month = month;
     _day = day;
	}

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

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << ' ' << d._month << ' ' << d._day << endl;

	return _cout;
}

我们将<<>>运算符的重载声明在类外面,可读性的问题是解决了,但是这样就访问不了被private修饰的成员变量了呀。

为了解决这个问题,我们可以使用友元函数friend

将非类成员函数的函数声明放在类里面,再在声明前面加上修饰符friend,这样这个函数就变成了友元函数。

  • 友元函数不是类的成员函数
  • 但是可以直接访问类的私有成员

例如,对于上面的代码:

class Date
{
public:
    //构造
	Date(int year = 2023, int month = 11, int day = 3)
	{
        _year = year;
        _month = month;
        _day = day;
	}
    
    friend ostream& operator<<(ostream& _cout, const Date& d);

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

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << ' ' << d._month << ' ' << d._day << endl;

	return _cout;
}

这样,我们就可以正确使用<<>>运算符重载了。

7. Date类的实现

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		int nums[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

		if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)))
			return nums[month] + 1;
		else
			return nums[month];
	}

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		if (year < 1 || month < 1 || month > 12 || day < 1 || day > GetMonthDay(year, month))
		{
			cout << "输入非法" << endl;
			exit(-1);
		}

		_year = year;
		_month = month;
		_day = day;
	}

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


    // 赋值运算符重载
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}

	// 日期+=天数
	Date& operator+=(int day)
	{
		if (day < 0)
		{
			return *this -= (-day);
		}
		
		_day += day;

		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}

		return *this;
	}

	// 日期+天数
	Date operator+(int day)
	{
		Date tmp = *this;
        
		tmp += day;

		return tmp;
	}

	// 日期-天数
	Date operator-(int day)
	{	
		Date tmp = *this;

		tmp -= day;

		return tmp;
	}

	// 日期-=天数
	Date& operator-=(int day)
	{
		if (day < 0)
			return *this += (-day);
		
		_day -= day;

		while (_day < 1)
		{
			_month--;
			if (_month <= 0)
			{
				_month = 12;
				_year--;
			}

			_day += GetMonthDay(_year, _month);
		}

		return *this;
	}

	// 前置++
	Date& operator++()
	{
		*this += 1;
		return *this;
	}

	// 后置++
	Date operator++(int)
	{
		Date tmp = *this;
		*this += 1;

		return tmp;
	}

	// 后置--
	Date operator--(int)
	{
		Date tmp = *this;
		*this -= 1;

		return tmp;
	}

	// 前置--
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}

	// >运算符重载
	bool operator>(const Date& d)
	{
		if (_year > d._year)
			return true;
		else if (_year == d._year && _month > d._month)
			return true;
		else if (_year == d._year && _month == d._month && _day > d._day)
			return true;
		else
			return false;
	}

	// ==运算符重载
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}

	// >=运算符重载
	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) || (*this < d);
	}


	// !=运算符重载
	bool operator != (const Date& d)
	{
		return !(*this == d);
	}


	// 日期-日期 返回天数
	int operator-(const Date& d)
	{
		int flag = 1;
		if (!(*this >= d))
			flag = -1;

	    Date cmp_big = *this >= d ? *this : d;
		Date cmp_small = *this < d ? *this : d;
		int count = 0;

		while (cmp_big != cmp_small)
		{
			count++;
			++cmp_small;
		}

		return flag * count;
	}

	// 析构函数
	~Date()
	{
		//cout << "~Date()" << endl;
	}


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

7.1 对于部分代码的说明

  1. 注意代码的复用,这样可以少些许多代码,提高效率。

    例如:定义好+=、-=的运算符重载后,-、+运算符的重载就可以复用+=、-=的代码;定义好==、>的运算符重载后,其他比较运算符就可以复用==、>的运算符重载来实现,从而大幅减少了代码量。

  2. 注意前置++和后置++的运算符重载(前置--和后置--同理)

    由于C++规定,要构成运算符重载,运算符必须跟在operator后,因此当碰到前置++和后置++这种运算符相同(函数名相同)但函数实现的功能不同的情况时,就需要利用函数重载。我们可以在后置++的运算符重载函数的形参列表里加入一个形参,和前置++构成函数重载,这样就可以避免问题了。文章来源地址https://www.toymoban.com/news/detail-743966.html

到了这里,关于C++——类和对象之运算符重载的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [C++ ]:5.类和对象中(运算符重载补充)+ 类和对象下(初始化列表)

    我们知道进行运算符重载这个函数的参数的左右类型是非常重要的,我们尝试在类中去定义这个流插入重载! 1. 考虑到隐含的参数指针: 2.进行优化! 我们观察上面的代码发现可以实现在类中进行流插入运算符的一个重载但是我们需要考虑隐含参数的位置所以我们进行传参

    2024年02月06日
    浏览(48)
  • 【C++初阶】五、类和对象(日期类的完善、流运算符重载函数、const成员、“&”取地址运算符重载)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】四、类和对象 (构造函数、析构函数、拷贝构造函数、赋值运算符重载函数)-CSD

    2024年02月05日
    浏览(50)
  • 【C++】:类和对象(中)之拷贝构造函数+赋值运算符重载

    在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎 那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢? 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调

    2024年02月06日
    浏览(47)
  • C++ -3- 类和对象 (中) | 拷贝构造函数 & 赋值运算符重载

    示例: 拷贝初始化构造函数的作用是将一个已知对象的数据成员值拷贝给正在创建的另一个同类的对象 无穷递归 ? Date(Date d){……} 首先,分析 传值传参的过程 传引用传参 : 没有拷贝 的过程,直接传 传值传参: 内置类型 编译器可以直接拷贝(浅拷贝/值拷贝——一个字节

    2023年04月19日
    浏览(80)
  • 【C++】类和对象(中)之拷贝构造与运算符、操作符重载

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》 🌝 每一个不曾起舞的日子,都是对生命的辜负 我们继续学习默认成员函数,本篇文章博主带来的是拷贝构造函数与运算符、操作符重载的讲解,并且还有

    2024年02月05日
    浏览(54)
  • 【C++深入浅出】类和对象中篇(六种默认成员函数、运算符重载)

    目录 一. 前言  二. 默认成员函数 三. 构造函数 3.1 概念 3.2 特性 四. 析构函数 4.1 概念 4.2 特性 五. 拷贝构造函数 5.1 概念 5.2 特性 六. 运算符重载 6.1 引入 6.2 概念 6.3 注意事项 6.4 重载示例 6.5 赋值运算符重载 6.6 前置++和后置++运算符重载 七. const成员函数 7.1 问题引入 7.2 定义

    2024年02月09日
    浏览(64)
  • 【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 本篇主要内容:类的6个默认成员函数中的 拷贝构造函数 和 赋值运算符重载 在上篇文章中我们讲到了类的默认成员函数的

    2024年04月17日
    浏览(48)
  • C++ 类和对象篇(八) const成员函数和取地址运算符重载

    目录 一、const成员函数 1. const成员函数是什么? 2. 为什么有const成员函数? 3. 什么时候需要使用const修饰成员函数?  二、取地址运算符重载 1. 为什么需要重载取地址运算符? 2. 默认取地址运算符重载函数 3. 默认const取地址运算符重载函数 4. 什么时候要显示重载取地址运算

    2024年02月07日
    浏览(58)
  • c++类和对象(拷贝构造、运算符重载、初始化列表、静态成员、友元等)

    拷贝构造函数的特征: 1、拷贝构造函数是构造函数的一个重载形式; 2、拷贝构造函数的参数只有一个且必须是同类类型对象的引用, 使用传值方式编译器直接报错 ,因为会引发无穷递归调用。 在c++中自定义类型 传值传参 的时候要调用拷贝构造函数。 3、若未显式定义,

    2024年02月15日
    浏览(38)
  • 【C++初阶】四、类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载函数)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】三、类和对象 (面向过程、class类、类的访问限定符和封装、类的实例化、类对象模

    2024年02月05日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包