目录
- Date& Date::operator--()
- {
- *this -= 1;
- return *this;
- }
后置--:
- Date Date::operator--(int)
- {
- Date ret = *this;
- *this -= 1;
- return ret;
- }
- #include"Date.h"
- void TestDate1()
- {
- //Date d1(2022, 10, 19);
- ///*
- //(d1 + (-10)).Print();*/
- //(++d1).Print();
- //(d1++).Print();
- Date d1(2022, 10, 19);
- (--d1).Print();
- (d1--).Print();
- }
- int main()
- {
- TestDate1();
- return 0;
- }

对于自定义对象,最好用前置:
1:前置是返回--之后的值
2:前置的传引用返回。
- 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)
- {
- ++n;
- ++min;
- }
- return n*flag;
- }
- #include"Date.h"
- void TestDate1()
- {
- //Date d1(2022, 10, 19);
- ///*
- //(d1 + (-10)).Print();*/
- //(++d1).Print();
- //(d1++).Print();
- Date d1(2022, 10, 19);
- /*(--d1).Print();
- (d1--).Print();*/
- Date d2(2023, 11, 23);
- cout << d1 - d2 << endl;
- }
- int main()
- {
- TestDate1();
- return 0;
- }


cin是istream类型的对象 ,cout是ostream类型的对象。

istream是一个类,ostream也是一个类。
为什么cin和cout可以自动识别参数的类型:


cout是流插入,表示把d1的数据插入到cout对象里去。
cin是流提取,表示把输入的数据提取到d1里去。

因为库中有这些内置类型的函数重载。
cout是ostream类型的对象,相当于cout符号重载<<。
假如我们要在类里面定义流插入函数:

这种写法是错误的:首先在类内定义的函数,this指针会默认抢占第一个参数的位置,并且对于<<,有两个操作数,也就是有两个函数参数,一个是Date类型的对象,一个是ostream类型的对象。
我们需要这样写:
- void operator<<(ostream&out)
- {
- out << _year << " " << _month << " " << _day;
- }
- void TestDate1()
- {
- Date d1, d2;
- /*cin >> d1;
- cout << d2;*/
- d1 << cout;
- }
-
- int main()
- {
- TestDate1();
- return 0;
- }
但是我们发现只能这样写:
![]()
这样就没有可读性了,原因在哪里?
对于双操作数的符号,第一个参数参数就是左操作数,而对于成员函数,第一个位置被this指针抢占,所以我们在类内无法实现cout。

我们只好写成全局函数:
- void operator<<(ostream&out, const Date&d)
- {
- out << d._year << " " << d._month << " " << d._day;
- }
但是在全局的函数无法访问类内私有的成员变量。
我们可以先把成员变量设置为共有的。
![]()
报错的原因是:我们在头文件中定义了全局函数,头文件在两个源文件变为目标文件时进行展开
两个目标文件分别有两个同名函数,两个符号表也有两个同名函数,所以重定义问题。
不仅仅是我们运算符重载出现的问题,我们在头文件中定义全局函数就容易产生问题:
- void Print(const Date&d)
- {
- cout << d._year << "/" <
"/" << d._day << endl; - }

注意:在头文件中尽量不要定义全局变量和全局函数,否则会导致重定义的问题。
我们如何解决这个问题:
静态函数:
static修饰全局变量和全局函数时会改变其链接属性,使其只在当前文件中可见,其他文件不可见,也无法使用。
声明和定义分离:
我们在cpp文件中进行定义,在.h文件中进行声明。
头文件Date.h会在两个cpp文件中展开,但是Date.h的文件中只有声明没有定义,所以无法进入符号表。
- void TestDate1()
- {
- Date d1, d2;
- /*cin >> d1;
- cout << d2;*/
- cout << d1;
- }
-
- int main()
- {
- TestDate1();
- return 0;
- }
![]()
需要优化的问题:

无法像cout调用内置类型一样连续调用。
从左往右进行调用,cout< 我们还可以用内联函数解决: 注意:内联函数直接定义在头文件中即可,不能声明和定义分离: 内联的原理:内联会在调用时直接展开,不会进入符号表。 但是以上的方法都是在我们的成员变量为公有的前提下,假如成员变量为私有呢? 第一种方法:创建函数取成员变量。 我们在类中进行声明,表示我们这个运算符重载函数是一个友元函数,友元函数可以直接访问成员变量。 对于流提取函数,我们也要进行友元声明。 我们发现,用const修饰的对象无法调用Print函数: 原因如下: 在我们的Print函数中,有一个隐藏的this指针,该指针的类型是Date*,const用来修饰this,防止对this本身进行修改。 而const修饰的是d2,我们传参的时候传递的也是d2,d2用const修饰是只读的,而对于this,我们却可以访问this指向的数据。 更简单的理解: 把d2的地址传递给this,发生了权限的扩大。 我们如何避免这种问题呢? 我们可以后置const 后置const表示把this的类型由Date*this修改为const Date*this的形式。 并且对于普通的对象也可以调用print函数,因为权限的缩小是不受限制的。 我们再举一个例子: 同样属于权限的放大,我们后置const。 凡是函数内部没有改变成员变量的,这些成员函数的声明和定义都要加上const修饰。 这里的取地址相当于调用取地址重载函数: 对于const类型的对象,我们调用该函数。 这两个函数,我们即使不写,编译器也会自动生成: 我们注释掉取地址重载函数进行运行: 我们如何使用取地址重载呢? 假如我们规定,在日期类中不能使用取地址,我们可以这样写: 我们可以在构造函数中使用初始化列表,构造函数可以什么都不用写(针对的是日期类) 写一个栈类的初始化列表: 也可以这样写: 所以初始化列表和函数体内初始化可以混着一起使用。 _n是cons类型的对象,我们知道const类型的对象只能在定义的时候初始化,我们的构造函数是无法对_n进行修改的。 const修饰的对象定义的时候必须初始化。 在这里是_n的声明,创建对象时才会定义。 这里是定义,是整体定义,我们无法对类内的一些const修饰的对象定义。 对象的每一个成员变量是在初始化列表时定义的。 我们可以用初始化列表来定义const修饰的成员变量: 每一个成员变量都会走初始化列表,就算没有显示,也会走。 这里的_m也会走初始化列表: 如果我们没有在初始化列表中显示的写,对于内置类型,默认是随机值,对于自定义类型调用其默认构造。 对于这个随机值,c++打了一个补丁: 我们可以在成员变量的声明位置给缺省值。 这个缺省值是在初始化列表中使用的,假如我们的初始化列表没有显示写成员变量,就用缺省值。 总结: _a走初始化列表时,_a是自定义类型,对于自定义类型调用默认构造,因为A类没有默认构造,所以报错。 有两种方法解决: 1:注释掉A的构造函数,使用系统给的默认构造: 这时候的_aa是随机值。 方法2: 我们可以在初始化列表给_a一个值,相当于调用_a的构造函数。 再举一个例子: 这样写不会报错:对于自定义类型Stack,调用其默认构造,对于内置类型我们有缺省值,就给缺省值0. 当我们把栈的函数写成是非默认构造就会报错。 初始化顺序是按照成员变量的声明顺序的: 先对_a2进行定义,_a1还不存在,所以报错原因是编译不通过。
![]()
友元声明:
friend ostream& operator<<(ostream&out, const Date&d);
流提取:


const成员




取地址重载:

const修饰的取地址重载:


初始化列表

为什么要使用初始化列表:








引用也必须在初始化列表初始化
初始化顺序
