类和对象之运算符重载

Posted 可乐不解渴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类和对象之运算符重载相关的知识,希望对你有一定的参考价值。

在这里插入图片描述

一.运算符重载

1.1 概念

在 C++语言中,可以用关键字 operator 加上运算符来表示函数,叫做运算符重载。
C++编译器在对运算符进行编译处理时,将一个运算符编译成以下形式:
在这里插入图片描述

例如两个复数相加函数:

Complex Add(const Complex &a, const Complex &b); 

可以用运算符重载来表示:

Complex operator +(const Complex &a, const Complex &b); 

1.2 运算符重载规则

1.2.1 运算符的重载规则

  • ~重载后运算符的优先级与结合性不会改变。
  • ~不能改变原运算符操作数的个数。
  • ~不能重载C++中没有的运算符。
  • ~不能改变运算符的原有语义。

1.2.2 可重载的运算符

在C++中的运算符除了以下5个运算符之外,其余全部可以被重载。

  • . :成员选择运算符
  • .*:成员指针运算符
  • :::作用域分辨符
  • ?:三目选择运算符
  • sizeof:计算数据大小运算符
    从语法上讲,运算符既可以定义为全局函数,也可以定义为成员函数
运算符规则
所有的一元运算符建议重载为成员函数
“= “、”() “、”[]”、" -> "只能重载为成员函数
“+=”、"-="、"/=" 、"*=" 、"&=" 、“l=” 、"~="、 “%=” 、">>=" 、"<<="建议重载为成员函数
所有其它运算符建议重载为全局函数

1.3 重载为类的友元函数

运算符之所以要重载为类的友元函数,是因为这样可以自由地访问该类中的任何数据成员。在类中声明类外定义友元函数的格式如下:

class name
{
	friend  函数类型  operator 运算符(形参表);
};

函数类型  operator 运算符(形参表)
{
	//函数体
}

示例:

class Person
{
	friend ostream& operator<<(ostream& out, Person& p);  //全局函数做友元
private:
	//利用成员函数重载 左移运算符 p.operator<<(cout)  简化版本p<<cout
	//不会利用成员函数重载<<运算符,因为无法实现 cout在左侧
	int m_A;
	int m_B;
};
//只能利用全局函数重载<<运算符
//ostream对象只能有一个
ostream& operator<<(ostream &out,Person &p)    //本质 operator<<(cout,p) 简化cout<<p
{

	cout << "m_A = " << p.m_A << "   " << "m_B=" << p.m_B << endl;
	return cout;
}

★总结

当运算符重载为类的友元函数时,函数参数个数与运算符的原操作数个数相同。函数中用到的数据(包括对象)均通过参数表传递。

1.4 重载为类的成员函数

将运算符函数重载为类的成员函数,这样运算函数就可以自由的访问本类中的所有的数据成员。
重载运算符函数为类的成员函数语法形式如下:

返回类型 类名::operator 运算符(形参表)
{
	函数体;
}

其中:

  • 类名是要重载该运算符的类,如果在类中定义运算符函数,类名与作用域分别符可以省略。
  • operator与运算符构成运算符函数名。
  • 当运算符重载为类的成员函数时,函数的参数个数将比原来的操作数个数少一个,原因是通过对象调用该运算符函数时,对象本身充当了运算符函数最左边的操作数,少的操作数就是该对象本身。

示例

class Complex
{
public:
    Complex(double real = 0.0, double image = 0.0)
    {
        this->real = real;
        this->image = image;
    }
    Complex operator +(Complex B);		//运算符 + 重载成员函数	
private:
    double real;	//复数实部
    double image;	//复数虚部
};

//重载运算符 + 的函数实现
Complex Complex::operator+(Complex B)
{
	//创建一个临时对象作为返回值,这里会调用拷贝构造函数
    return Complex(this->real+B.real,this->image+B.image);
}

1.5 Date类的实现

光听光看,而不写是不可能完全领悟的,下面我们就以Date类设计与实现为例:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iomanip>
#include<iostream>

//由于我们经常调用这个函数,且函数体较小
// 获取某年某月的天数
inline int GetMonthDay(int year, int month);

class Date
{
public:
    // 全缺省的构造函数
    Date(int year = 2000, int month = 1, int day = 1);

    // 拷贝构造函数
    // d2(d1)
    Date(const Date& d);

    // 赋值运算符重载
    // d2 = d3 -> d2.operator=(&d2, d3)
    Date& operator=(const Date& d);

    // 析构函数
    ~Date();

    // 日期+=天数
    Date& operator+=(int day);

    // 日期+天数
    Date operator+(int day);

    // 日期-天数
    Date operator-(int day);

    // 日期-=天数
    Date& operator-=(int day);

    //前置++、--需要返回引用,后置++、--返回的不是引用,而是一个临时值,所以不能进行连续使用。
    //后置--、++  例如 a=b--;  我们是先把b原本的值给a,在让b自减1,而不是让它返回b的引用,返回b的引用会导致a与b的值相同
    // 前置++
    Date& operator++();

    // 后置++
    Date operator++(int);

    // 后置--
    Date operator--(int);

    // 前置--
    Date& operator--();

    // >运算符重载
    bool operator>(const Date& d);

    // ==运算符重载
    bool operator==(const Date& d);

    // >=运算符重载
    bool operator >= (const Date& d);

    // <运算符重载
    bool operator < (const Date& d);

    // <=运算符重载
    bool operator <= (const Date& d);

    // !=运算符重载
    bool operator != (const Date& d);

    // 日期-日期 返回天数
    int operator-(const Date& d);

    // 打印日期
    void PrintDate();

private:
    int m_year;      //年
    int m_month;     //月
    int m_day;       //日
};

#include"Date_class.h"

inline int GetMonthDay(int year, int month)
{
	//这里创建一个元素个数为13个的数组,这样我们就可以把下标与月份一一对应上
	static int DayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31};
	int day = DayArray[month];
	if ((month == 2) && ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
	{
		day += 1;
	}
	return day;
}

//全缺省的构造函数
Date::Date(int year, int month, int day)
{
	//检查初始化日期的值是否合法
	if ((year >= 0) && ((month > 0) && (month < 13)) && (day <= GetMonthDay(year, month)))
	{
		this->m_day = day;
		this->m_month = month;
		this->m_year = year;
	}
	else
	{
		std::cout << "非法日期" << std::endl;
		exit(-1);
	}
}

//拷贝构造函数
Date::Date(const Date& d)
{
	this->m_year = d.m_year;
	this->m_month = d.m_month;
	this->m_day = d.m_day;
}

//重载 = 号
Date& Date::operator=(const Date& d)
{
	//有人可能想把自己对象的值赋给自己 例如  a=a;
	if (this == &d)
	{
		return *this;
	}
	else
	{
		this->m_year = d.m_year;
		this->m_month = d.m_month;
		this->m_day = d.m_day;

		return *this;
	}

}

//析构函数
Date::~Date()
{
	//由于我们并没有在堆区开辟空间,故不需要在析构函数中写任何代码
}

//重载 += 号
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		//复用operator -=重载
		return *this -= -day;
	}
	else
	{
		this->m_day += day;
		while (this->m_day > GetMonthDay(this->m_year, this->m_month))
		{
			this->m_day -= GetMonthDay(this->m_year, this->m_month);
			this->m_month++;
			if (this->m_month > 12)
			{
				this->m_year++;
				this->m_month = 1;
			}
		}
		return *this;
	}
}

//重载 + 号
Date Date::operator+(int day)
{
	//由于+是两个数相加,对原来的对象不能有任何的改变,所以我们需要用值传递,返回一个临时变量
	Date tempD(*this);
	//复用operator +=重载
	tempD += day;
	return tempD;
}

//重载 - 号
Date Date::operator-(int day)
{
	//由于-是两个数相减,对原来的对象不能有任何的改变,所以我们需要用值传递,返回一个临时变量
	Date tempD(*this);
	//复用operator -=重载
	tempD -= day;
	return tempD;
}

//重载 -= 号
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		//复用operator +=重载
		return this->operator+=(-day);
	}
	else
	{
		this->m_day -= day;
		while (this->m_day <= 0)
		{
			this->m_month--;
			if (this->m_month < 1)
			{
				this->m_year--;
				this->m_month = 12;
			}
			this->m_day += GetMonthDay(this->m_year, this->m_month);
		}
		return *this;
	}

}

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

//后置 ++
Date Date::operator++(int)
{
	Date tempD(*this);
	*this += 1;
	return tempD;
}

//后置 --
Date Date::operator--(int)
{
	Date tempD(*this);
	*this -= 1;
	return tempD;
}

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



//重载 != 号
bool Date::operator!=(const Date& d)
{
	//复用==号重载
	return !(*this==(d));
}



//重载 > 号
bool Date::operator>(const Date& d)
{
	if (this->m_year > d.m_year)
	{
		return true;
	}
	else if (this->m_year == d.m_year)
	{
		if (this->m_month > d.m_month)
		{
			return true;
		}
		else if (this->m_month == d.m_month)
		{
			if (this->m_day > d.m_day)
			{
				return true;
			}
		}
	}

	return false;
}

//重载 == 号
bool Date::operator==(const Date& d)
{
	if ((this->m_year == d.m_year) && (this->m_month == d.m_month) && (this->m_day == d.m_day))
	{
		return true;
	}
	else
	{
		return false;
	}
}

//重载 >= 号
bool Date::operator>=(const Date& d)
{
	//复用重载 == 号 和 >
	return this->operator==(d) || this->operator>(d);
}

//重载 < 号
bool Date::operator<(const Date& d)
{
	//复用重载的 >= 号
	return !this->operator>=(d);
}

//重载 <= 号
bool Date::operator<=(const Date& d)
{
	//复用重载的 >号
	return !this->operator>(d);
}

//重载 - 号
int Date::operator-(const Date& d)
{
	int flag = 1;

	Date tempMax = *this;
	Date tempMin = d;
	if (tempMin < tempMax)
	{
		//利用模板中的swap函数来交换两个自定义的数据类型
		std::swap(tempMax, tempMin);
		flag = -1;
	}

	int ABS_day = 0;
	while (tempMin < tempMax)
	{
		++tempMin;
		++ABS_day;
	}

	return flag * ABS_day;

}
//打印日期
void Date::PrintDate()
{
	std::cout << std::left << std::setw(4) << this->m_year << "年"
		<< std::left << std::setw(2) << this->m_month << "月"
		<< std::left << std::setw(2) << this->m_day << "日" << std::endl;
}

★总结:

  1. 双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数。
  2. 前置单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。
  3. 后置单目运算符重载为类的成员丽数时,为了与前置单目运算符相区别,函数要带有一个整型形参。
    在这里插入图片描述

END...

以上是关于类和对象之运算符重载的主要内容,如果未能解决你的问题,请参考以下文章

类和对象—4

类和对象——补充(运算符重载,static和explicit关键字和友元,内部类)

C++类和对象--运算符重载

学习:类和对象——运算符重载

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读