系列文章目录
文章目录
前言
在初识C++中我们简要简介了一下引用作函数返回值的作用,在类和对象(中)上中我们介绍了3个类的默认成员函数(默认构造、析构函数、拷贝构造函数).本章我们详细理解引用作返回值的作用,已经后三个默认成员函数(赋值重载函数、取地址及const取地址重载函数),并且简要介绍一下友元和<<的重载注意事项,着重介绍各个运算符重载函数。
一、运算符重载
1.运算符重载(operator)
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
[ .* :: sizeof ?: . ]注意以上5个运算符不能重载。#
.*操作符是对类中的函数指针进行解引用操作:
class OB {
public:
void func() {};
};
//void(*ptr)()是一个函数指针,类型是void(*)() ;typedef时并不是 typedef void(*)() ptr
//而是 typedef void(*ptr )()
//类中的函数就要指明
typedef void(OB::* PtrFunc)();
int main()
{
PtrFunc fp = &OB::func;//定义一个函数指针,注意class内中的函数要被允许访问
OB tmp;//初始化一个类对象
(tmp.*fp)();//解引用这个函数指针访问func,*fp其实就是func,但是func是在类中的要有一个实例化的对象
}
2.operator使用方法
返回值 operator操作符(····)
{
//代码....
}
// 全局的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);
//编译器会转换成operator==(d1,d2);
cout << (d1 == d2) << endl;
//重载的函数可以显式调用
operator==(d1,d2);
}
如果类中的成员为共有:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
//private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
//编译器会转换成d1.operator==(d2);
cout << (d1==d2) << endl;
//重载的函数可以显式调用
d1.operator==(d2);
//operator==(d1,d2)
}
如果类中也有定义,(d1 == d2)调用时会调用类中的。
使用get和set函数:
#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;
}
int GetYear() const
{
return _year;
}
int GetMonth() const
{
return _month;
}
int GetDay() const
{
return _day;
}
private:
int _year;
int _month;
int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1.GetYear() == d2.GetYear() && d1.GetMonth() == d2.GetMonth() && d1.GetDay() == d2.GetDay();
}
void Test()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 26);
cout << (d1 == d2) << endl;
}
int main()
{
Test();
return 0;
}
对与友元函数我们在后面简介,接下俩我们重载成类成员函数:
Date.h中
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& d) const;
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d) const;
private:
int _year;
int _month;
int _day;
};
Date.cpp中
// ==运算符重载
bool Date::operator==(const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
代码中的const会在之后讲解,实际上就是修饰this的,const Date* const this。
3.赋值运算符重载
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;
}
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
二、前置++和后置++重载
1.前置++和后置++的实现
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
}
//前置++返回+1后的结果
//注意:this指向的对象函数结束之后不会被销毁,故用引用方式返回提高效率。
Date& operator++()
{
_day += 1;
return *this;
}
//后置++
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
//自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
//一份,然后给this + 1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date tmp(*this);
_day += 1;
return tmp;
}
private:
int _year;
int _month;
int _day;
};
2.引用作返回值
引用的语法概念就是指向同一块空间。
class Date
{
public:
//默认构造函数(全缺省)
Date(int year = 1900, int month = 1, int day = 1)
{
cout << "Date()" << endl;
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << '-' << _month << '-' << endl;
}
Date& operator++(int)
{
Date tmp(*this);
_day += 1;
return tmp;
}
//析构函数
~Date()
{
cout << "~Date()" << endl;
_year = 1900;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 4, 17);
Date d2(2024, 5, 1);
d2 = d1++;
return 0;
}
就拿后置++这个例子来说明为什么后置++一定要传值返回。在调用函数时,函数会开辟栈空间,通过将参数压栈,传给函数所在的代码空间,然后函数执行完之后自定义类型进行销毁,同时系统回收开辟的栈空间,有返回值(值传递)会通过寄存器返回。
tmp是在operator++()函数中的局部变量,用于返回未+1的结果,否则无法实现后置++的功能。函数结束后tmp调用析构函数,进行销毁,函数栈空间中,若系统未对该空间进行回收,则tmp的值就是(1900,1,1).那么通过调试观察,d2会是多少?
回到主函数发现tmp已经改变,将调用析构函数的tmp拷贝给d2;
所以要通过函数局部变量来返回的最好不要用引用来返回。
将tmp改成静态变量,那么tmp就在静态区,而不是栈。不会调用它的析构函数。
Date& operator++(int)
{
static Date tmp(*this);
_day += 1;
return tmp;
}
但是指向同一块非主函数空间还是会有隐患,所以这种情况最好使用传值返回。
再看下面的程序下面的程序:
Date& func()
{
Date d(2024,5,1);
return d;
}
int main()
{
Date& ref = func();
return 0;
}
使用引用接收返回值,那么说明ref指向的是d,是d的别名,那么d会不会销毁呢?通过调试观察到函数执行完之后同样调用了析构函数。
销毁func中的d调用析构。
ref和d是同一个地址,出了函数,这时ref就相当于野指针。
调用打印函数会怎么样?
int main()
{
Date& ref = func();
ref.Print();
return 0;
}
发现结果完全不同,这是为什么?因为ref指向的空间已经被回收了,当调用func()函数结束后,我们再调用了ref.Print()函数,这时重新开辟了栈空间,内容被覆盖。
const Date& ref 也是可以的,这样写就是权限的缩小,因为函数返回的不是临时变量,是d的别名,d是可以改变的。
能否不用引用接收?可以的,但是ref就需要拷贝构造d,同样d会被析构。这里要与值传递产生的临时变量区别。
总结一下:返回对象是一个局部对象或者临时对象,出了当前func函数作用域,就析构销毁了,那么不能用引用返回用引用返回是存在风险的,因为引用对象在func函数栈帧已经销毁了。
例如赋值重载,它就最好使用引用返回,若使用值返回效率就低了。
3.自定义类型作返回值
传值返回实际上都会创建一个临时变量来进行返回,自定义类型也是如此。
注意自定义类型具有常量的性质。
class Date
{
public:
//默认构造函数(全缺省)
Date(int year = 1900, int month = 1, int day = 1)
{
cout << "Date()" << endl;
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
Date operator++(int)
{
Date tmp(*this);
_day += 1;
return tmp;
}
//析构函数
~Date()
{
cout << "Date()" << endl;
_year = 1900;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
Date func()
{
Date d(2024,5,1);
return d;
}
int main()
{
Date d1 = func();
return 0;
}
观察输出结果:实际上有一次拷贝构造,只不过编译器把它优化了.(优化后面会介绍)
三、const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
const Date* 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;
}
如果将void Print()给注释掉,则d1表示权限的缩小。同时注意void Print()可以和 void Print() const;重载。
所以const修饰成员函数,本质上就是修饰*this,对于+、-、x、/ 、>、<、·····这些不改变原来的值的运算就应该使用const。如果不修饰,对于const对象左操作数*this就可改变,错误。
四、日期类的实现
1、主要接口
接下来实现日期类,我们采用声明和定义分离。(在内中定义加不加inline都会被识别为内联)给出声明:
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:
//默认构造函数(全缺省)
Date(int year = 1900, int month = 1, int day = 1);
//拷贝构造函数
Date(const Date& d);
//打印函数
void Print();
//析构函数
~Date();
//赋值重载
Date& operator=(const Date& day)
{
if (*this != d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
// == != > < >= <=
//复用
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+(int day);
Date& operator+=(int day);
//日期 - 天数
Date operator-(int d);
Date& operator-=(int day);
//日期-日期
Date operator-(const Date& d);
//前置++,后置++,前置--,后置--
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
首先主要的默认的函数+打印函数:
#include "Date.h"
//默认构造函数(全缺省)
//注意定义和声明中不能同时出现缺省,最好再声明给
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//打印函数
void Date::Print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
//析构函数
Date::~Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
2、逻辑函数(大于,小于等)
2.1 == !=
首先实现==,年月日都相同才为真。等于实现了,那么取反就是不等于。
Date.cpp中 bool Date::operator==(const Date& d)const { return _year == d._year && _month == d._month && _day == d._day; }
Date.cpp中 bool Date::operator!=(const Date& d)const { return !(*this == d); }
测试用例:
2.2 > < >= <=
比较年份,年份大的就大,如果年相同比较月份,月份大的就大,,如果月份相同,比较天数,天数大的就大。(小于就反过来)
2023 5 1 2023 6 1 2023 5 1
2024 5 1 2023 5 1 2023 5 4年大就大 年相同,月大就大 年月相同,天大就大
Date.cpp 中
bool Date::operator>(const Date& d)const
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
return _day > d._day;
}
}
//走到这说明年<d,或者月<d,或者天<=d
return false;
}
//更简单一点
bool Date::operator>(const Date& d)const
{
if (_year != d._year)
return _year > d._year;
if (_month != d._month)
return _month > d._month;
return _day > d._day;
}
测试用例:
大于和等于都写好了,实际上就可以将其他的用逻辑关系写出来了。
< 就是 ! >= , >= 就是 > && ==, <= 就是 !>.
Date.cpp中
//> == 都已经写好,接下来逻辑关系即可,复用> ==
bool Date::operator<=(const Date& d)const
{
return !(*this > d);
}
bool Date::operator>=(const Date& d)const
{
return (*this > d) || (*this == d);
}
bool Date::operator<(const Date& d)const
{
return !(*this >= d);
}
测试用例:
3、运算函数
3.1 + +=
实现加减乘除时要搞清楚 +-x/ 的意义,像日期+日期,是没有什么意义的,但是日期+天数就是有意义的。
比如算100天以后是哪一天。日期+天数返回值是日期。所以要注意是传值还是传引用。先拿传值来分析。
如果是+100天,那么之后的逻辑是一样的:
2024 4 15+ 100
------------------
2024 4 115 + 30 (四月的天数) -->月份进1
- 30(四月的天数)
------------------
2024 5 85 + 31(五月的天数) -->月份进1
- 31(五月的天数)
-------------------
以此内推·····
使用循环进行判断,进入循环说明天数大于本月天数,减去本月天数,月份++。
如果是12月怎么办,进1操作就会使得月份变为13.所以特殊情况特殊考虑。
超过12月即13月,将_month = 1;然后判断天数,如果大于本月天数就 -= 本月天数,
但是如何拿到每月的天数呢?年有闰年,闰年的二月天数不一样。如果直接去下手写,会很麻烦,所以我们在类中定义一个获取月份的函数GetMonthDay(int year ,int month);(内中相当于内联函数,此时效率提高)
class Date
{
public:
int GetMonthDay(int year, int month)
{
assert(month < 13 && month > 0);
//这个数组频繁创建,所以创建在静态区。
static int monthdays[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 29;
else
return monthdays[month];
}
//代码·····
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;
}
很明显这里通过this指针将原本的值都给修改了。实现的实际上是+=,+的功能不会改变原来的值,d1+100,d1是不会变的。所以应该通过函数内部的一个局部变量来返回。
Date Date::operator+(int day)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > tmp.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 += day;
while (tmp._day > tmp.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)
{
*this = *this + day;//这里又要调用+,+里拷贝了tmp操作过多
return *this;
}
复用+=实现+:
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;
return tmp;;
}
由于+不能改变原来的值,所以通过const来修饰。
Date.h
//+ += — —=
//复用
//日期 + 天数
Date operator+(int day)const;
Date& operator+=(int day);
//日期 - 天数
Date operator-(int day)const;
Date& operator-=(int day);
//日期-日期
Date operator-(const Date& d);
Date.cpp
Date Date::operator+(int day) const
{
Date tmp(*this);
tmp += day;
return tmp;;
}
可以通过网上的日期计算器验证。
3.2 - -=
和+ +=是一样的道理。
Date Date::operator-(int day) const
{
Date tmp(*this);
tmp -= day;
return tmp;
}
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_year++;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
测试用例:
如果是下面的代码会发生什么?
Date d1(2024, 1,15);
Date d2(2024, 12, 1);
d1 -= -100;
d1.Print();
d2 += -100;
d2.Print();
结果没有进入循环。
so:
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)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_year++;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
3.3 日期-日期
日期相减比较麻烦,主要原因就是日期不规则,闰年、大月小月.
我们采用一种暴力且简单的模式:
//日期-日期
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 count = 0;
while (min < max)
{
++min;//前置++使用传引用效率高
++count;
}
return flag * count;
}
测试用例:
4、日期前置++、前置--、后置++、后置--
复用+=;
//前置++,后置++,前置--,后置--
Date& Date::operator++()
{
return *this += 1;
}
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date& Date::operator--()
{
return *this -= 1;
}
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
前置和后置就是参数不同,int只是用于区分。前置就调用无参的,后置就调用有参的。里面的内容可以自己修改,但是要符合逻辑。在前面的函数重载中了解到,参数不同,编译器在查找的时候找到的就不同。
五、<<和>>重载
1.<<出处
对于内置类型我们使用printf和cout,scanf和cin就可以做到输出和输入,那么对于内置类型怎么办,能否使用printf和cout?是不能的。
如果要使用cout和cin输出和输入一个日期类,怎么实现?重载<<和>>是可以的,下面我们来介绍一下如何实现<<和>>对日期类的重载。
实际上<<是<ostream>中的函数:
实际上内置类型能使用<<是因为已经写好了,直接可以使用.
int i = 1;
cout << 1; ---> cout.operator<<(1);
double d 1.1;
cout << d;
自动识别类型,本质是因为这些流插入重载构成函数重载。
C++和C语言都有缓冲区,C++要兼容C语言就一定需要某种方法和C语言保存同步;如下面的代码顺序是1234,如果是分开的不是同步的,就会是1324.
cout << "1";
printf("2");
cout << "3";
printf("4");
2.实现重载<<
对于自定义类型就需要我们自己去实现了,重载<<和>>运算符 。在类中声明,内外定义:
Date.h中
void operator<<(ostream& out);
Date.cpp中
void Date::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
但是一旦你使用cout<<d1;就会发现错误.
而下面这样就可以调用类中的<<:
这是为什么呢?因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。那么建议重载成全局函数。
但是问题又来了,怎么访问内部私有成员?友元就可以实现访问内部成员,友元我们在后面再介绍。
这里也可以使用get和set方法,通过类内部成员函数get来得到私有成员数据,函数set来改变私有成员数据。这里我们演示将私有成员公开的重载。
我们将私有成员变为共有来演试:
Date.h中
··········
//void operator<<(ostream& out);
//private:
public:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out, const Date& d);
Date.cpp中
//void Date::operator<<(ostream& out)
//{
// out << _year << "年" << _month << "月" << _day << "日" << endl;
//}
void operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
但是不能连续输出,cout<<d1<<d2;
我们知道像赋值重载operator=()是从右往左结合 d1 = d2 = d3;
而<<应该从左往右结合,因为cout<<d1输出完后要cout<< d2;所以返回值应该是ostream& 的out。
Date,h
ostream& operator<<(ostream& out, const Date& d);
Date.cpp
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
换行是因为我们实现的时候加了endl输出。
3.实现重载>>
同样流提取>>也可以重载:
Date.h
ostream& operator<<(ostream& out, const Date& d);
istream& operator<<(istream& in, Date& d);
Date.cpp
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;
}
输入的日期要合法使用,所以我们写一个日期检测函数:
Date.h
bool CheckDate();
Date.cpp
bool Date::CheckDate()
{
if (_month < 1 || _month > 13
|| _day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
istream& operator>>(istream& in,Date& d)
{
in >> d._year >> d._month >> d._day;
if (!d.CheckDate())
{
cout << "日期非法!" << endl;
}
return in;
}
六、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
#include <iostream>
using namespace std;
class Date
{
public:
Date* operator&()
{
cout << "Date* operator&()" << endl;
return this;
}
const Date* operator&()const
{
cout << "const Date* operator&()const" << endl;
return this;
}
private:
int _year = 1900; // 年
int _month = 1; // 月
int _day = 1; // 日
};
int main()
{
Date a1;
const Date a2;
cout << &a1 << endl;
cout << &a2 << endl;
return 0;
}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!