类和对象(中)2
- 一、赋值运算符重载
- 运算符重载
- 赋值运算符重载
- 前置++和后置++重载
- 二、日期类的实现
- 三、const成员
- 四、取地址及const取地址操作符重载
一、赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5. .* :: sizeof ?: .注意以上5个运算符不能重载。这个经常在笔试选择题中出现
// 全局的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;
}
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year;&& _month == d2._month&& _day == d2._day;
}
private:int _year;int _month;int _day;
};
赋值运算符重载
1. 赋值运算符重载格式
参数类型: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 ;
};
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
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 =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
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,14d = ++d1; // d: 2022,1,15 d1:2022,1,15return 0;
}
二、日期类的实现
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 = 1, int month = 1, int day = 1);int GetMonthDay(int year, int month)const;bool operator==(const Date& d)const;bool operator<(const Date& d)const;bool operator<=(const Date& d)const;bool operator>(const Date& d)const;bool operator>=(const Date& d)const;bool operator!=(const Date& d)const;Date& operator=(const Date& d);void Print()const;Date& operator+=(int day);Date operator+(int day)const;Date& operator-=(int day);//d1-100Date operator-(int day)const;//d1-d2int operator-(const Date& d)const;//++d1 //d1.operator++();Date& operator++();//d1++ //d1.operator++(0);//int 参数 仅仅是为占位,跟前置重载区分Date operator++(int);//--d1 ->d1.operator--()Date& operator--();//d1-- ->d1.operator--(0)Date operator--(int);void operator<<(ostream& out);private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);inline ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}inline istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"int Date::GetMonthDay(int year, int month)const
{assert(month > 0 && month < 13);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;}else{return monthArray[month];}
}Date::Date(int year, int month, int day)
{if (month > 0 && month < 13&& (day > 0 && day <= GetMonthDay(year, month))){_year = year;_month = month;_day = day;}else{cout << "日期非法" << endl;}
}/*Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}*///d1==d2
bool Date::operator==(const Date& d)const
{return _year == d._year&& _month == d._month&& _day == d._day;
}//d1<d2
bool Date::operator<(const Date& d)const
{/*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;}*/return (_year < d._year)|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day);
}//d1<=d2
bool Date::operator<=(const Date& d)const
{return *this < d || *this == d;
}//d1>d2
bool Date::operator>(const Date& d)const
{return !(*this <= d);
}//d1>=d2
bool Date::operator>=(const Date& d)const
{return !(*this < d);
}//d1!=d2
bool Date::operator!=(const Date& d)const
{return !(*this == d);
}//返回值为了支持连续赋值,保持运算符的特性
//d3=d1=d2
//d1=d1
Date& Date::operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}void Date::Print()const
{cout << _year << "-" << _month << "-" << _day << endl;
}private:
//int _year;
//int _month;
//int _day;//+=
//Date& Date::operator+=(int 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 += 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;
//}//+复用+=
//Date Date::operator+(int day)
//{
// Date tmp(*this);
// tmp += day;
//
// return tmp;
//}//int main()
//{
// Date d1(2023, 1, 4);
// Date d2(2023, 3, 4);
//
// cout << d1.operator==(d2) << endl;
// cout << (d1 == d2) << endl; //转换成d1.operator==(d2);
//
// cout << (d1 < d2) << endl;
// //d1 < d2;
//
// d1 = d2;//d1.operator=(d2);
// d1.Print();
//
// Date d5 = d1; //拷贝构造
// Date d6(d1); //拷贝构造
//
// return 0;
//}//+
Date Date::operator+(int day)const
{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;
}//+=复用+
Date& Date::operator+=(int day)
{if (day < 0){*this -= -day;return *this;}*this = *this + day;return *this;
}//++d1
Date& Date::operator++()
{*this += 1;return *this;
}//d1++
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}Date& Date::operator-=(int day)
{if (day < 0){*this += -day;return *this;}_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day)const
{Date tmp(*this);tmp -= day;return tmp;
}//--d1
Date& Date::operator--()
{*this -= 1;return *this;
}//d1-- ->d1.operator--(1)
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}//d1-d2
int Date::operator-(const Date& d)const
{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 Date::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)
//{
// in >> d._year >> d._month >> d._day;
// return in;
//}
test.cpp
void TestDate1()
{Date d1(2023, 2, 4);d1.Print();/*Date d2(2023, 2, 29);d2.Print();*/Date d2 = d1 + 1000;d2.Print();d1.Print();Date d3 = d1;//d1=d3+=100;d3 += 1000;d3.Print();d1.Print();}void TestDate2()
{Date d1(2023, 2, 4);d1.Print();Date ret1 =++d1; //d1.operator++();d1.Print();ret1.Print();Date ret2 = d1++; //d1.operator++(0);d1.Print();ret2.Print();
}void TestDate3()
{Date d1(2023, 2, 4);d1.Print();d1 -= 100;d1.Print();Date d2(2023, 2, 7);d2 += -100;d2.Print();Date d3(2023, 2, 7);d3 -= -100;d3.Print();--d1;d1--;}void TestDate4()
{Date d1(2023, 2, 4);d1.Print();Date d2(2023, 4, 5);//d2 += -100;d2.Print();cout << (d1 - d2) << endl;cout << (d2 - d1) << endl;}void TestDate5()
{//流插入Date d1(2023, 2, 4);Date d2(2022, 1, 1);//cout << d1;//d1.operator<<(cout);//d1<<cout;operator<<(cout, d1);cout << d1;cout << d1 << d2 << endl;d1 -= 100;//cout << d1;int i = 1;double d = 1.11;//运算符重载+函数重载//cout << i;//cout.operator<<(i)//int//cout << d;//cout.operator<<(d)//double
}void TestDate6()
{Date d1;cin >> d1;cout << d1;}//int main()
//{
// //TestDate1();
// //TestDate2();
// //TestDate3();
// //TestDate4();
// //TestDate5();
// TestDate6();
//
// return 0;
//}
三、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();
}
请思考下面的几个问题:
- const对象可以调用非const成员函数吗?
- 非const对象可以调用const成员函数吗?
- const成员函数内可以调用其它的非const成员函数吗?
- 非const成员函数内可以调用其它的const成员函数吗?
const 修饰this,this的类型变成const A,内部不改变成员变量的成员函数,最好加上const,const对象和普通对象都可以调用。
四、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public :Date* operator&(){return this ;}const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!