各位CSDN的uu们好呀,今天是小雅兰来到CSDN创作的第256天啦,是一个极具纪念意义的日子,在这个神圣的日子里,也不影响小雅兰继续创作博客!!!下面,让我们进入西嘎嘎类和对象的世界吧!!!
赋值运算符重载
日期类的实现
const成员
取地址及const取地址操作符重载
源代码
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- 不能改变操作符的操作数个数,一个操作符是几个操作数,那么重载的时候就有几个参数。
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
- * 可以重载。
#include<iostream> using namespace std; // 全局的operator== 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; }; // 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证? // 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。 bool operator==(const Date& d1, const Date& d2) { return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; } void Test() { Date d1(2018, 9, 26); Date d2(2018, 9, 27); cout << (d1 == d2) << endl; } int main() { Test(); return 0; }
也可以这样写:
class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } // d1 < d2 // d1.operator<(d2) // 这里需要注意的是,左操作数是this,指向调用函数的对象 // bool operator==(Date* this, const Date& d2) 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; } private: // 内置类型 int _year; int _month; int _day; };
赋值运算符重载
赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
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; };
赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } int _year; int _month; int _day; }; // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数 Date& operator=(Date& left, const Date& right) { if (&left != &right) { left._year = right._year; left._month = right._month; left._day = right._day; } return left; } // 编译失败: // error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值 运算符重载只能是类的成员函数。
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time { public: Time() { _hour = 1; _minute = 1; _second = 1; } Time& operator=(const Time& t) { if (this != &t) { _hour = t._hour; _minute = t._minute; _second = t._second; } return *this; } private: int _hour; int _minute; int _second; }; class Date { private: // 基本类型(内置类型) int _year = 1970; int _month = 1; int _day = 1; // 自定义类型 Time _t; }; int main() { Date d1; Date d2; d1 = d2; return 0; }
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。
如果是像栈这样的类,那就必须要自己实现赋值运算符重载了!!!
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。 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; }
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
前置++和后置++重载
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // 前置++:返回+1之后的结果 // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率 Date& operator++() { _day += 1; return *this; } // 后置++: // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载 // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1 //而temp是临时对象,因此只能以值的方式返回,不能返回引用 Date operator++(int) { Date temp(*this); _day += 1; return temp; } private: int _year; int _month; int _day; }; int main() { Date d; Date d1(2022, 1, 13); d = d1++; // d: 2022,1,13 d1:2022,1,14 d = ++d1; // d: 2022,1,15 d1:2022,1,15 return 0; }
后置++加一个int参数,进行占位,跟前置++构成函数重载进行区分
本质就是后置++调用,编译器进行特殊处理。
日期类的实现
class Date { public: //获取某年某月的天数 int GetMonthDay(int year, int month) { static int monthArray[13] = { 0,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 29; } return monthArray[month]; } // 全缺省的构造函数 Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; // 检查日期是否合法 if (month < 1 || month > 12 || day < 1 || day > GetMonthDay(year, month)) { cout << "非法日期" << endl; //exit(-1);//(暴力) } } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; } // 拷贝构造函数 // d2(d1) Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& operator=(const Date& d) { if (this != &d) { this->_year = d._year; this->_month = d._month; this->_day = d._day; } return *this; } // 析构函数 //~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 operator+(int day) { Date tmp(*this); tmp += day; return tmp; } //不是很好 //Date operator+(int day) //{ // // 等价的,都是拷贝构造 // //Date tmp(*this); // Date tmp = *this; // tmp._day += day; // while (tmp._day > GetMonthDay(tmp._year, tmp._month)) // { // // 月进位 // tmp._day -= GetMonthDay(tmp._year, tmp._month); // ++_month; // // 月满了 // if (tmp._month == 13) // { // ++tmp._year; // tmp._month = 1; // } // } // return tmp; //} //Date& operator+=(int day) //{ // //Date ret = *this + day; // //*this = ret; // *this = *this + day; // return *this; //} // 日期-=天数 Date& operator-=(int day) { if (day < 0) { return *this += (-day); } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { --_year; _month = 12; } _day += GetMonthDay(_year, _month); } return *this; } // 日期-天数 Date operator-(int day) { Date tmp(*this); tmp -= day; return tmp; } // 前置++ 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) { return !(*this <= d); } // ==运算符重载 bool operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; } // >=运算符重载 bool operator >= (const Date& d) { return !(*this < d); } // <运算符重载 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 *this < d || *this == d; } // !=运算符重载 bool operator != (const Date& d) { return !(*this == d); } // 日期-日期 返回天数 int 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; } private: int _year; int _month; int _day; };
const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
我们来看看下面的代码
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};void Test()
{
Date d1(2022, 1, 13);
d1.Print();
const Date d2(2022, 1, 13);
d2.Print();
}int main()
{
Test();
return 0;
}
1. const对象可以调用非const成员函数吗?
不可以。const修饰的对象实则修饰的是该对象的this指针,并且const修饰的成员不能进行修改,如果使用const调用非const成员函数,可能会将const对象进行修改。
2. 非const对象可以调用const成员函数吗?
可以。这里就是将一个可以修改的对象进行读取,是允许的。
3. const成员函数内可以调用其它的非const成员函数吗?
不可以。const成员函数调用非const成员函数,调用该const成员函数的对象已经被设置为const类型,只可以访问不能修改,在用该const成员函数访问其他非const成员函数可能会修改,因此const成员函数不能调用非const成员函数。
4. 非const成员函数内可以调用其它的const成员函数吗?
可以。与非const对象调用const对象类似。
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date { public: Date* operator&() { return this; } const Date* operator&()const { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 };
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
源代码
小雅兰把日期类函数的实现封装为三个部分,但是上面的代码并没有进行区分。
Date.h的内容:
#pragma once
#include<iostream>
using namespace std;class Date
{
public:
//获取某年某月的天数
int GetMonthDay(int year, int month);// 全缺省的构造函数
Date(int year = 1, int month = 1, int day = 1);void Print();
// 拷贝构造函数
// d2(d1)
Date(const Date& d);// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);// 日期+=天数
Date& operator+=(int day);// 日期+天数
Date operator+(int day);// 日期-=天数
Date& operator-=(int day);// 日期-天数
Date operator-(int day);// 前置++
Date& operator++();// 加一个int参数,进行占位,跟前置++构成函数重载进行区分
// 本质后置++调用,编译器进行特殊处理// 后置++
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);private:
int _year;
int _month;
int _day;
};Date.cpp的内容:
#include"Date.h"
int Date::GetMonthDay(int year, int month)
{
const static int monthArray[13] = { 0, 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 29;
}return monthArray[month];
}Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;// 检查日期是否合法
if (month < 1 || month > 12
|| day < 1 || day > GetMonthDay(year, month))
{
cout << "非法日期" << endl;
// exit(-1);
}
}void 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;
}
else
{
return false;
}
}bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}// 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 !(*this < d);
}bool Date::operator!=(const Date& d)
{
return !(*this == d);
}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;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}_day += GetMonthDay(_year, _month);
}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;
}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;
}Test.cpp的内容:
#include"Date.h"
void TestDate1()
{
Date d1(2023, 7, 23);
Date d2;d1.Print();
d2.Print();Date d3(2010, 2, 29);
d3.Print();Date d4(2023, 13, 29);
d4.Print();
}void TestDate2()
{
Date d1(2023, 8, 6);
// 拷贝构造,一个已经存在的对象去初始化另一个要创建对象
Date d2(d1);d1.Print();
d2.Print();Date d3(2023, 8, 13);
// 赋值,两个已经存在对象进行拷贝
//d1 = d3; // d1.operator=(d3)
d1 = d2 = d3;
d1.Print();
d2.Print();
d3.Print();d1 = d1;
int i, j;
i = j = 10;
cout << (j = 10) << endl;
}void TestDate3()
{
Date d1(2023, 7, 27);
d1 += 20000;
d1.Print();Date d2(2023, 7, 27);
//Date ret = d2;
//Date ret(d2 + 20000);
// 拷贝构造
//Date ret = d2 + 20000;Date ret;
// 赋值
ret = d2 + 20000;ret.Print();
}void TestDate4()
{
Date d1(2023, 7, 27);
d1 -= 20000;
d1.Print();Date d2(2023, 7, 27);
d2 += -200;
d2.Print();Date d3(2023, 7, 27);
d3 -= -200;
d3.Print();
}void TestDate5()
{
Date d1(2023, 7, 27);
Date ret1 = d1++;
//Date ret1 = d1.operator++(0);
ret1.Print();
d1.Print();Date ret2 = ++d1;
//Date ret2 = d1.operator++();
ret2.Print();
d1.Print();
}void TestDate6()
{
Date d1(2023, 7, 27);
Date d2(2003, 1, 1);cout << d1 - d2 << endl;
}void TestDate7()
{
const Date d1(2023, 7, 27);
// d1.Print(&d1);
d1.Print();Date d2(2023, 7, 27);
// d2.Print(&d2);
d2.Print();
}
好啦,小雅兰类和对象(中)的内容就到这里啦,同样,今天也是小雅兰创作的第256天,未来继续加油呀!!!