运算符重载
C 为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。 函数名字为:关键字operator后面接需要重载的运算符符号。
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型 ,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。
先定义一个日期类 (先用日期类作为用例)
定义一个日期类
代码语言:javascript复制class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
我们先将日期类的成员改成共有的 写一个运算符重载函数operator==
代码语言:javascript复制bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
可以看见如果写成全局的函数,我们必须将成员变量改成共有的,所以我们可以将运算符重载函数写成成员函数
代码展示
代码语言:javascript复制bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
上面说完运算符重载接下来来讨论一下赋值拷贝函数
由于赋值操作我们改变的是调用这个函数的对象,所以我们在参数中可以加上cosnt
修饰,传值和传引用,我们选择传引用,最后返回也返回引用,这样可以避免调用拷贝构造函数
注意:返回值是*this
代码展示
代码语言:javascript复制class Date
{
public :
//构造函数
Date(int year = 1900, 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;
}
//赋值拷贝函数
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year ;
int _month ;
int _day ;
};
注意:赋值拷贝函数和拷贝构造函数类似,但是调用的场景是不相同的
注意:拷贝构造函数和赋值拷贝函数的调用方式十分相同,但是拷贝构造函数调用是在对象不存在时,在创建的时候,调用拷贝构造函数,赋值拷贝函数是,对象已经存在了,调用的赋值拷贝函数 . 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注 意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。 既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实 现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
代码语言:javascript复制// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size ;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 = s1;
return 0;
}
注意:赋值拷贝函数和拷贝构造函数一样,当没有malloc和new还有其他动态申请的空间时,是不需要写的,一旦有动态申请的资源存在时,就必须写一个赋值拷贝函数
下图是对上面代码的解释
运算赋值重载 =和
由于日期类设计到平年和闰年每一个月的日期不同,所以我们可以将1到12月的每个月的天数存在一个数组中,然后获取每个月的天数,这里我们可以只存放平年的,然后闰年的天数只有二月是不相同的,所以二月我们单独拿出来讨论
注意:这个函数我们可以写在类中,类中完成的函数默认都是内联函数,因为我们后面会经常调用这个函数
代码语言:javascript复制 inline int GetMonthDay(int year, int month)
{
assert(month > 0 && month <= 12);
static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };//获取每一个月的月份
if (month == 2 && year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return monthDayArray[2] 1;
}
return monthDayArray[month];
}
注意:先判断是否是二月可以增加效率,因为二月只有一个月,如果每次都判断年的话,每次都要进行多余的判断,如果先判断是否是二月的话,如果是二月才进行后面的判断,如果不是二月直接就跳出了
代码语言:javascript复制下面来完成运算符重载中的 和 =,注意:我们可以只写一个 =然后用 去复用 =
Date& 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;
}
//d1 10
Date Date::operator (int day)
{
Date tmp = *this;
tmp = day;
return tmp;//出作用域会销毁,所以不能返回引用
}
运算赋重载前置 和后置
由于运算符重载中,运算符只能写在operator的后面,所以我们只能利用函数重载来区别后置 和前置 ,前置 可以直接不给参数,后置 可以在参数中给一个int和前置 作区分。
接下来来写一下日期类的后置 和前置 ,注意 就是相当于 =1,所以我们可以服用前面的 =运算符重载
后面写的函数全都是成员函数
代码语言:javascript复制//前置
Date& Date::operator ()
{
*this = 1;
return *this;
}
//为了区分,构成重载,给后置 ,强行增加了一个int形参
//这里不需要写形参名,因为接收值是多少不重要,也不需要用
//这个参数仅仅只是一个标志
//后置
Date& Date::operator (int)
{
Date tmp(*this);
*this = 1;
return tmp;
}
上面我们写了 操作,–操作也同理 –操作
注意:–操作我们应该先写-=操作和-操作,然后去复用这两个函数
代码语言:javascript复制Date& Date::operator-=(int day)
{
if (*this < 0)
{
return *this = (-day);
}
_day -= day;
while (_day <= 0)
{
//借上个月的时间
_day = GetMonthDay(_year, _month - 1);
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
//前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
//前置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
<,<=,>,>=,==,!=运算符重载
注意:对于日期类的比较,我们可以直接比较年,如果年大则返回true
,如果年相当则比较月,如果月大则返回true
,如果月相当则比较日,如果日大,则返回true
,否则剩下的情况则返回false
。
代码展示
代码语言:javascript复制bool Date::operator<(const Date& d)//内联函数
{
if (_year < d._day)
{
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 Date::operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator<=(const Date& d)
{
return *this < d || *this == d;
}
bool Date::operator>(const Date& d)
{
return !(*this <= d);
}
bool Date::operator>=(const Date& d)
{
return *this > d || *this == d;
}
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
**我们只用写一个,剩下的全都可以复用
日期类的实现
Date.h
代码语言:javascript复制#pragma once
#include<iostream>
#include<cassert>
using namespace std;
class Date
{
//友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1);
void Print() const;
//内联不能声明和定义声明,直接写在类中的默认就是内联函数
inline int GetMonthDay(int year, int month)
{
assert(month > 0 && month <= 12);
static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };//获取每一个月的月份
if (month == 2 && year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return monthDayArray[2] 1;
}
return monthDayArray[month];
}
bool CheckDate();
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);
Date& operator =(int day);
Date operator (int day);
Date& operator-=(int day);
Date operator-(int day);
// d1;
Date& operator ();
//d1
//为了区分,构成重载,给后置 ,强行增加了一个int形参
Date& operator (int);//
//--d1
Date& operator--();
//d1--
Date operator--(int);
int operator-(const Date& d);
int Getyear()
{
return _year;
}
int Getmonth()
{
return _month;
}
int Getday()
{
return _day;
}
//流插入
// 不建议,因为Date*this占据了第一个参数位置,使用d<<cout不符合使用习惯
//void operator<<(ostream& out);//第一个参数是隐含的this
private:
int _year;
int _month;
int _day;
};
Date.cpp
代码语言:javascript复制#include"Date.h"
bool Date::CheckDate()
{
if (_month < 1 || _month>12 || _day<1 || _day>GetMonthDay(_year, _month))
{
return false;
}
return true;
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!CheckDate())
{
cout << "日期错误";
}
}
void Date::Print() const //加上这个const之后,this指针本身不能改,this指针指向的内容也不能改
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//d1<d2
bool Date::operator<(const Date& d)//内联函数
{
if (_year < d._day)
{
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 Date::operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator<=(const Date& d)
{
return *this < d || *this == d;
}
bool Date::operator>(const Date& d)
{
return !(*this <= d);
}
bool Date::operator>=(const Date& d)
{
return *this > d || *this == d;
}
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
// 复用 =
//思路:先加上天上 天满了先加到月上 月满了加到年上
//d1 =10;
Date& 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;
}
//d1 10
Date Date::operator (int day)
{
Date tmp = *this;
tmp = day;
return tmp;//出作用域会销毁,所以不能返回引用
}
// =复用
//Date Date::operator (int day)
//{
// Date tmp = *this;
// tmp._day = day;
// while (tmp._day > GetMonthDay(tmp._year, tmp._month))
// {
// tmp._day -= GetMonthDay(tmp._year, tmp._month);
// tmp._month ;
// if (tmp._month > 12)
// {
// tmp._month = 1;
// tmp._year ;
// }
// }
// return tmp;
//}
//
//Date& Date::operator =(int day)
//{
// *this = *this day;
// return *this;
//}
Date& Date::operator-=(int day)
{
if (*this < 0)
{
return *this = (-day);
}
_day -= day;
while (_day <= 0)
{
//借上个月的时间
_day = GetMonthDay(_year, _month - 1);
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
Date& Date::operator ()
{
*this = 1;
return *this;
}
//为了区分,构成重载,给后置 ,强行增加了一个int形参
//这里不需要写形参名,因为接收值是多少不重要,也不需要用
//这个参数仅仅
Date& Date::operator (int)
{
Date tmp(*this);
*this = 1;
return tmp;
}
//前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
//前置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
//赋值拷贝
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
min;
n;
}
return n * flag;//flag控制的是符号,如果前面大就是正的,如果后面大就是负数
}
<<流插入和>>流提取的运算符重载
代码语言:javascript复制ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请依次输入年月日:";
in >> d._year >> d._month >> d._day;
return in;
}
这里加入了返回值之后,就可以进行连续的流插入和流提取了
总结
在本文中,我们深入探讨了运算符重载和赋值拷贝函数在C 中的应用。通过运算符重载,我们可以为自定义类型定义各种操作,使得代码更加清晰和易读。而赋值拷贝函数则在对象拷贝和赋值过程中起到了至关重要的作用,确保对象之间的正确复制和管理。通过深入理解和熟练应用这些概念,我们可以写出更加健壮和高效的代码。 在实践中,我们需要注意运算符重载和赋值拷贝函数的使用场景和规范,以避免潜在的错误和性能问题。同时,对于特定的项目和需求,我们也可以进一步扩展和定制这些功能,以满足更复杂的应用场景。 最后,我希望本文能够帮助读者更好地理解和应用运算符重载和赋值拷贝函数,并在实际开发中发挥出它们的作用。让我们继续探索C 语言的奥秘,写出更加优雅和强大的代码!