【C++干货篇】——类和对象的魅力(三)
赋值运算符重载
1.运算符重载
当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。 C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载, 若没有对应的运算符重载,则会编译报错。
运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符(像++,- -)有⼀个参数,⼆元运算符(像+,-)有两个参数,二元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第二个参数。
bool ret1 = operator<(d1,d2); //转换成调用对应的运算符重载函数 bool ret2 = d1 < d2;
如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。
bool ret1 = d1.operator<(d2); //转换成调用对应的运算符重载函数 bool ret2 = d1 < d2;
运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
.* :: sizeof ?: .
- 注意以上5个运算符不能重载。(选择题⾥⾯常考,⼤家要记⼀下)
class A
{
public:
void func()
{
cout << "A::func()" << endl;
}
};
typedef void(A::* PF)(); //成员函数指针类型
int main()
{
// C++规定成员函数要加&才能取到函数指针
PF pf = &A::func;
A obj;//定义ob类对象temp
// 对象调⽤成员函数指针时,使⽤.*运算符
(obj.*pf)();
return 0;
}
- 重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:
int operator+(int x,int y) { return x - y; }
一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator就没有意义。
重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。
++d1;// 编译器会转换成 d1.operator++(); d1++;// 编译器会转换成 d1.operator++(0); Date& operator++()//出了作用域,d1还在,所以要&引用 { cout << "前置++" << endl; *this += 1; return *this; } Date operator++(int) { Date tmp(*this); cout << "后置++" << endl; *this += 1; return tmp; }
重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。重载为全局函数ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。
cout<<d1; operator<<(cout,d1); void operator<<(ostream& out,const Date& d);
2.赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另⼀个要创建的对象。
赋值运算符重载的特点:
赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引用,否则会传值传参会有拷贝
有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值目的是为了支持连续赋值场景。
没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认拷⻉构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调⽤他的赋值重载函数。
像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显⽰实现赋值运算符重载。
像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷贝)。
像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显示实现了析构并释放资源,那么他就需要显时写赋值运算符重载,否则就不需要。
总结:
1.构造一般都需要自己写,自己传参定义初始化;
2.析构,构造时有资源申请(如malloc或者fopen)等,就需要显示写析构函数;
3.拷贝构造和赋值重载,显示写了析构,内部管理资源,就需要显示实现深拷贝。
3.日期类代码实现
- Date.h
#pragma once
#include<iostream>
#include<assert.h>
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();
bool CheckDate()
{
if (_month < 1 || _month > 12
|| _day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
//4年会多出一天,356天5小时+,所以是4年一润
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
return 29;
}
return monthDayArray[month];
}
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++
Date operator++(int);
//--d1;
Date& operator--();
//d1--
Date operator--(int);
int operator-(const Date& d);
void operator<<(ostream& out);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
- Date.cpp
#include"Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!CheckDate())
{
cout << "日期非法->" << endl;
cout << *this;
}
}
void const Date::Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool Date::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;
}
return false;
}
bool Date::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;
}
return false;
}
bool Date::operator>(const Date& d)
{
return !(*this <= d);
}
//d1<=d2
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 _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
//d1+=100
// d1+=-100
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 == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
//
//Date Date::operator+(int day)
//{
// Date tmp = *this;
// tmp += day;
// return tmp;
//}
//d1+100
//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 == 13)
// {
// tmp._year++;
// tmp._month = 1;
// }
// }
//
// return tmp;
//}
d1+=100
//Date Date::operator+=(int day)
//{
// *this = *this + day;
// return *this;
//}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
//++d1;
Date& Date::operator++()
{
*this += 1;
return *this;
}
//d1++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
//d1 - d2
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;
}
//void operator<<(ostream& out)
//{
// out << _year << "年" << _month << "月" << _day << "日" << endl;
//}
ostream& operator<<(ostream& out, const Date& d)
{
out <<d. _year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "请依次输入年月日:>";
in >> d._year >> d._month >> d._day;
if (d.CheckDate())
{
break;
}
else
{
cout << "日期非法,请重新输入" << endl;
}
}
return in;
}
- test.cpp
#include"Date.h"
//int main()
//{
// Date d1(2024, 8, 10);
// d1.Print();
//
// d1 += 100;
// d1.Print();
//
// d1 += -100;
// d1.Print();
// Date d2(d1 + 100);
// //Date d2 = d1 + 100;
// //d2.Print();
// //d1.Print();
//
//
// //d1 += 100;
// //d1.Print();
//
// //int i = 1;
// //i + 100;
//
// //d1 -= 100;
// //d1.Print();
//
// //cout << endl;
//
// //Date d3(2024, 8, 10);
// //d3 += 10000;
// //d3.Print();
//
// Date ret1 = ++d1;
// d1.Print();
//
// Date ret2 = d1++;
// d1.Print();
//
// ret1.Print();
// ret2.Print();
//
// return 0;
//}
int main()
{
Date d1(2024, 8, 10);
Date d2(2024, 2, 5);
cout << d1 - d2 << endl;
cout << d2 - d1 << endl;
//d1<<cout;
//d1.operator<<(cout);
cout << d1;
operator<<(cout, d1);
cin >> d1 >> d2;
cout << d1 << d2 << endl;
Date d(2023, 2, 29);
cout << d;
return 0;
}
最后,本篇文章到此结束,感觉不错的友友们可以一键三连支持一下笔者,有任何问题欢迎在评论区留言哦~