operator
这里我们使用date 日期类为大家讲解类的比较与运算
class Date
{
private:
int _year;
int _month;
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& out,Date& d);
int _day;
public:
Date(int year = 1, int month = 1, int day = 1);
void Print()const;
int GetMonthDay(int year, int month)const;
//bDate& operator=(const Date& d);
//------------------------------------------------------------------
Date operator=(int day);
Date operator+(int day);
Date operator-(int day);
Date& operator+=(int day);
Date& operator-=(int day);
//------------------------------------------------------------------
//++d1
Date& operator++();
Date& operator--();
//d1++;为了区分,增加一个参数占位
Date operator++(int d);
Date operator--(int);
//------------------------------------------------------------------
bool operator>(const Date& x)const;
bool operator<(const Date& x)const;
bool operator==(const Date& x)const;
bool operator!=(const Date& x)const;
bool operator>=(const Date& x)const;
bool operator<=(const Date& x)const;
//------------------------------------------------------------------
//void operator<<(ostream& out)const;
//void operator>>(ostream&out)const;
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
int operator-( Date& x);
};
这里重点介绍类的operator各个符号重载以及一些特殊需要处理的情况;
比较
我们先知道运算符的一些规则
a<=b == !(a>b)
a!=b == !(a==b)
a>=b == a>b||a==b
a<b == !(a>=b)
a<=b == !(a>b)
看代码,会发现我们只需要将a>b 和a==,这两个运算符重载函数,就可以利用他们,其他比较运算符重载函数调用就可以实现。
所以我们只需要将这两个实现就好
bool Date::operator>(const Date& x)const
{
if (_year != x._year)
{
return _year > x._year ? true : false;
}
if (_month != x._month)
{
return _month > x._month ? true : false;
}
return _day > x._day ? true : false;
}
bool Date::operator==(const Date& x)const
{
return _year == x._year && _month == x._month && _day == x._day;
}
其他比较运算符重载函数可以利用题目他们实现。
调用时运算符重载有两种方式。
int main()
{
Date d1(2022, 3, 25);
Date d2(2019, 7, 19);
//1.利用成员函数调用
cout << d1.operator<(d2)<<endl;
//2.直接比较
cout << (d1<d2)<<endl;
return 0;
}
这里我们要注意,流插入运算符优先级高于比较运算符。所以我们直接比较两个类时,需要使用括号包括起来否者会发生错误。
赋值与一元
a+=10
a-=20
a+10
a-10
a++
++a
a--
--a
这里我们先看二元
+=与-=会改变a自身的值,a+10与a-10并不改变a的值
看代码实现
int Date::GetMonthDay(int year, int month)const
{
static int monthDayArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && Isyear(year))
{
return 29;
}
else
{
return monthDayArr[month];
}
}
//a+=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;
}
//a+10;
Date Date::operator+(int day)
{
Date tmp(*this);
if(day<0)
{
return tmp-=(-day)
}
tmp.operator+=(day) ;
return tmp;
}
a+=10由于是对像调用函数改变对象自身的值的,使用可以传*this返回,使用&引用返回,不调用拷贝函数,栈销毁不影响返回值。
而a-10是改变a的值减去10后的结果,不影响a本身,所以我们使用拷贝构造,拷贝一份对象a,将该拷贝对象调用+=运算符重载,然后传值传回拷贝对象,这里返回类型不可使用引用返回。
而减与减等和加与加等类似不做过多阐述
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
if (--_month == 0)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)
{
Date ret(*this);
if (day < 0)
{
return ret += -day;
}
ret -= day;
return ret;
}
会发现如果day为负数,那么将day改为-day然后+ +=与- -=调换调用
赋值运算符重载函数=
这个也是一个默认函数:如果没有实现,编译器会自动生成一个。
这个运算符挺有意思的,他和构造函数很像,都是使用一个对象改变类一个对象
这对双胞胎不同的是,构造函数是对一个,将要声明定义的类实例化对象赋一个已经实例化的对象值,而赋值重载是两个都已经实例化的对象,赋值改变。
int main()
{
Date d1(2022, 3, 25);
Date d2(2019, 7, 19);
//构造函数 d3还未实例化
Date d3(d2);
//赋值重载 d1 d2已经实例化
d1 = d2;
return 0;
}
这里有个小知识点:定义了拷贝构造一定要定义构造函数,因为拷贝构造与构造形成函数重载,如果有了拷贝构造,那么编译器就不再生成默认构造函数。
class Ans
{
public:
//Ans();
Ans(Ans& c);
~Ans();
private:
};
Ans::Ans(Ans& c)
{
}
Ans::~Ans()
{
}
int main()
{
Ans a;//会报错!!!!!!
return 0;
}
一元运算符++ --
在对单个变量的一元运算符前后置不会有多少性能影响,但是在类实例化对象使用时,我们建议使用前置运算。
首先先介绍,函数声明;
//++d1
Date& operator++();
//d1++;
Date operator++(int d);
由于前置后置函数声明是一样的,为了区别,编译器在调用后置时会自动传入一个整型数据,这个整型数据不做其他任何事情,只是为了让前后置构成函数重载。
使用我们在声明与定义的时候要多定义一个整型参数,必须是整型,因为编译器只会自动传入整型数据到函数。
看代码
int main()
{
Date d1(2022, 3, 25);
Date d2(2019, 7, 19);
d1++;
++d1;
}
看似没有什么区别,让我们回忆一下,这两个在变量赋值时有什么区别
Date ret1=d1++;
Date ret2=++d2;
第一行d1先赋值给ret1在自增,而第二行d2先自增后赋值给ret2;这有什么影响吗?这难道就是为什么建议用前置而不用后置吗?不是的,让我们看一元运算符函数代码
//++d1
Date& Date::operator++()
{
this->operator+=(1);
return *this;
}
//d1++
Date Date::operator++(int d)
{
Date ret(*this);
this->operator+=(1);
return ret;
}
先看前置++代码,会发现前置++是对自身加后返回
直接将this的值赋给ret1
而后置++代码,是返回++前的值
所以要创建临时空间保存改变前的值,自增结束后再返回原先的值,由于该对象为临时对象,在函数结束的时候,会销毁,所以要返回值,而不是引用,这里创建和返回就要调用拷贝构造3次(创建一次,拷贝到临时空间一次,临时空间拷贝到ret)而前置只需要一次拷贝构造(返回的时候直接拷贝到ret)。
所以在效率上前置++优于后置++
前置--后置--同理。
有元函数
这里使用流运算符引出‘
int main()
{
Date d1(2022, 3, 25);
Date d2(2019, 7, 19);
std::cout<<d1;
}
我们想要直接使用cout直接打印d1的函数
这是错误的,我们要自定义该类运算符重载函数;
普及一个知识:cout是ostream类的一个实例化对象。
我们可以在Date类中设置<<运算符重载函数
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << ' ' << d._month << ' ' << d._day << endl;
return out;
}
但是这样的话有个缺点
int main()
{
Date d1(2022, 3, 25);
Date d2(2019, 7, 19);
d1<<cout;
}
明明是d1插入cout中,这里看起来像cout插入d1中
能不能改成cout<<d1;
因为只要是类成员函数,第一个参数一定是this ,而操作符的左边操作数为传入的第一个函数,而右边操作数才是传入的第二个操作数,this是作为第一个参数是编译器规范的。
为了cout为第一个传入参数,函数就不能定义在类中,而要定义在类外,但是类外怎么才能访问私有成员变量呢?
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << ' ' << d._month << ' ' << d._day << endl;
return out;
}
这个时候就要在类中声明允许外界的该函数访问私有成员变量
“你是我的朋友函数”
在类中定义
friend ostream& operator<<(ostream& out, const Date& d);
//使用全局函数,但是函数要调用类中私有成员,要在类中说明该函数为有元函数,然后再在类外面定义
//为什么呢?因为在类中的成员函数,第一个参数是左操作数,为了让d1成为右操作数,必须要定义在类外,而且还要使用类私有成员变量,就要在类中定义有元
//---------------------------------------------------------------------
//Date.h
class Date
{
private:
int _year;
int _month;
int _day;
public:
//.......
friend ostream& operator<<(ostream& out, const Date& d);//有元函数
//不是一定要定义在public中
//.......
};
ostream& operator<<(ostream& out, const Date& d);
//-------------------------------------------------------------------------
//Date.cpp
#includ"Date.h"
//....
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << ' ' << d._month << ' ' << d._day << endl;
return out;
}
//....
//-----------------------------------------------------------------------------
//test.cpp
#includ"Date.h"
int main()
{
Date d1(2022, 3, 25);
d1 << cout;//调用成员函数
cout << d1;//调用有元函数
return 0;
}
END
ps:个人笔记
编译器优化
定义了拷贝构造,必须也定义构造
编译器优化,一次调用,如果多次拷贝构造,会被编译器优化,合二为一
匿名对象,过当前行程序就会立刻销毁
声明和传值的构造函数 本来2次拷贝构造和二为一
本来3次拷贝构造优化为1次
A f(A x)
{
return x;
}
int main()
{
A y;
A x= f(f(y));
return 0;
}
第一次返回作为第二次的参数,会省略第一次拷贝构造和返回时的创建匿名对象拷贝和传入第二次的拷贝构造,会合三为一
省略临时拷贝和将z直接直接传入x
应该是十次,第一次返回值拷贝到 临时空间,然后临时空间拷贝到匿名对象,然后匿名对象拷贝第二次函数调用参数。
Date Date::operator+(int day)
int main()
{
Date d1(2022, 3, 25);
d1 + 10; ----d1.operator+(10)
10 + d1;//错误的 10.operator+(d1),这是错误的,在两个操作数的时候,第一个参数是左操作数,
//第二个参数是右操作数
return 0;
}
如果想要cout<<d1;就要使用有元函数