(注:本篇文章介绍部分内容时,需要用到上盘文章中日期类的代码,文章链接如下:C++类与对象基础(5)——日期类的实现-CSDN博客)
目录
1. 运算符重载的相关补充:
1.1流运算符重载出现的问题:
1.2 针对上述问题的解决方法:
1.2.1 通过创建友元函数来实现对私有变量的访问:
2. const成员函数:
2.1 const成员函数基本介绍:
2.2 什么类型的函数需要加:
3.取地址及const取地址操作符重载:
4
1. 运算符重载的相关补充:
1.1流运算符重载出现的问题:
在对对象进行打印时,一般会在类中编写一个用于打印的成员函数用于打印,即:
void Date::Print()
{cout << _year << " " << _month << " " << _day << endl;
}
int main()
{Date d(2024, 1, 8);d.Print();return 0;
}
运行结果如下:
在针对C++系列的第一篇文章中,就提到了在C++中,输出变量的方法不光只有函数,也可以使用流插入。上面所展示的代码虽然用到了,但是并不是直接调用,而是将封装在一个类的成员函数中,进行调用的。如果针对上面的对象,直接利用进行打印,即:
int main()
{cout << d;return 0;
}
运行代码,此时编译器会显示错误,即:
错误的原因在之前介绍运算符重载的时候提到过:对于自定义类型不能直接调用操作符,而是需要利用运算符重载。因此,为了实现自定义类型变量,即:,在类中加入一个运算符重载,即:
(注:本篇文章所有的运算符重载都采用声明和定义分离的方式)
void Date::operator<<(ostream& out)
{out << _year << "年" << _month << "月" << _day << "日" << endl;
}
运行下方代码:
int main()
{Date d1(2024, 1, 8);cout << d1;return 0;
}
此时仍然显示运行错误,但是将上方代码更改为下面的形式:
int main()
{Date d1(2024, 1, 8);/*cout << d1;*/d1 << cout;return 0;
}
代码成功运行,结果如下:
这是因为,对于双操作数的运算符,第一个参数是左操作数,第二个参数是右操作数,之前的文章中多次提到,对于成员函数来说,通常会有一个隐藏的参数,即指针。所以,针对上面的运算符重载,其完整的参数应该为:
void Date::operator<<(Date* this, ostream& out)
{out << _year << "年" << _month << "月" << _day << "日" << endl;
}
所以,这就解释了为社么上面打印自定义类型时,这种形式会造成编译错误,而可以正常运行的原因。虽然将代码改为上述形式后,可以正常使用运算符,但是,与平时利用的使用习惯不符,改进的方法将在下一小节中进行介绍
1.2 针对上述问题的解决方法:
上面提到,造成问题的原因时因为成员函数会有一个隐藏的参数,为了避免此问题,可以将运算符重载的声明放在类之外,即作为一个全局函数,而非一个成员函数。对于全局函数,没有隐藏的参数,因此可以人为定义参数的顺序,即:
void operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
虽然解决了操作数的顺序问题,但是由于在运算符中,三个成员变量 _, _, _会受到访问限定符的影响。因此,还需要解决成员变量的访问问题。对于此问题的解决,文章给出一种方法:
1.2.1 通过创建友元函数来实现对私有变量的访问:
实现方法只需要在类中加上友元函数即可,即:
friend void operator<<(ostream& out, const Date& d);
(注:对于友元的相关知识将在下一篇文章中进行介绍,本文中只给出使用方法)
在加入了友元函数后,上述的运算符重载即可正常进行使用,即:
int main()
{Date d1(2024, 1, 8);cout << d1;return 0;
}
运行结果如下:
对于流提取,采用和流插入一样的实现方法,即:
创建运算符重载:
void operator>>(istream& in,Date& dd)
{in >> dd._year >> dd._month >> dd._day;
}
在类中加入友元函数:
friend void operator>>(istream& in, Date& dd);
测试流提取的功能:
int main()
{Date d1(2024, 1, 8);cin >> d1;cout << d1;return 0;
}
运行结果如下:
2. const成员函数:
2.1 const成员函数基本介绍:
文章在介绍刘运算符重载的时候举了一个例子,即创建一个对象,并且将其打印,即:
Date d1(2024, 1, 8);
在之前对于引用进行介绍的文章中,提到了常饮用这个概念,即在引用的前面加上,如果在上面给出的自定义类型前加上,再对这个自定义类型进行打印,即:
int main()
{const Date d3(2024, 1, 9);d3.Print();return 0;
}
运行代码,此时编译器会显示错误。
具体错误原因与成员函数的隐藏变量以及权限变化有关。在常饮用那一节就提到过:权限平移或者缩小而不能放大,对于前面加了的自定义类型,当向函数传递参数时,其参数类型为,而函数的的类型为,因此,在传递参数的过程中,涉及了权限放大。对于此问题的解决方法需要在函数后面加上一个
(注:如果函数的声明和定义分离,则在声明和定义后都需要加上)
//函数声明
void Print() const;
//函数定义
void Date::Print() const
{cout << "void Date::Print() const" << endl;cout << _year << " " << _month << " " << _day << endl;
}
运行结果如下:
上面提到,权限不能放大,但是可以缩小或者平移,因此,非类型的变量可以调用类型的函数,例如:
int main()
{Date d4(2222, 2, 22);d4.Print();return 0;
}
运行结果如下:
2.2 什么类型的函数需要加:
在日期类中,给了很多的关于运算符重载的例子,例如:
bool operator>(Date& d);
利用上述运算符对下面两个自定义类型变量进行比较,即:
int main()
{const Date d3(2024, 1, 9);d3.Print();Date d4(2222, 2, 22);d4.Print();bool ret1 = (d3 > d4);cout << ret1 << endl;return 0;
}
运行代码,此时编译器会报错。
错误原因依旧涉及权限放大的问题,通过两个自定义类型的传递参数的顺序可以发现,在传递参数时,也涉及了权限的放大,因此,也需要将上述函数进行更改,即:
bool operator>(Date& d)const;
bool Date::operator>(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;}
}
通过上面的两个例子可以看到,类型的对象可以调用类型函数,非型的对象也可以调用类型函数。因此,对于能够定义成类型的成员函数,一般都需要加上
前面的例子中,不难发现,在函数声明后面加上的作用,主要是修饰,因此,如果一个成员函数涉及到对象的更改,则不能用修饰。因此,对于日期类中的所有函数类型,可以加的如下:
bool operator==(Date& d)const;bool operator!=(Date& d)const;bool operator>(Date& d)const;bool operator>=(Date& d)const;bool operator<=(Date& d)const;bool operator<(Date& d)const;Date operator+(int day)const;Date operator-(int day)const;
需要注意,文章上面给出的流插入、流提取这两个函数由于不是成员函数,故不能用修饰。
3.取地址及const取地址操作符重载:
在前面的部分,介绍了默认成员函数中的4个,本部分将给出剩余的两个,由于这两个默认成员函数的实用意义远小于构造、析构、拷贝构造、赋值重载,因此文章只给出这两个函数的格式以及简单的应用:
格式如下:
//取地址操作符重载Date* operator&(){cout << "Date* operator&()" << endl;return this;}//const取地址操作符重载const Date* operator&() const{cout << "const Date* operator&() const" << endl;return this;}
需要注意的时,这两个函数不光类型不同,其参数类型也不同,对于取地址操作符重载,第一个用于修饰函数的返回值,第二个修饰的是。所以二者的参数类型不同。
下面给出测试代码来对取地址操作符重载以及取地址操作符重载的调用进行演示:
const Date d3(2024, 1, 9);//d3.Print();Date d4(2222, 2, 22);//d4.Print();cout << &d3 << endl;cout << &d4 << endl;
运行结果如下:
不难发现,自定义类型被修饰,因此优先调用取地址操作符重载,自定义类型没有被修饰,优先调用取地址操作符重载。 加入,在这两个函数中去掉一个,例如去掉取地址操作符重载,即:
//取地址操作符重载/*Date* operator&(){cout << "Date* operator&()" << endl;return this;}*///const取地址操作符重载const Date* operator&() const{cout << "const Date* operator&() const" << endl;return this;}
再运行下面的代码:
const Date d3(2024, 1, 9);//d3.Print();Date d4(2222, 2, 22);//d4.Print();cout << &d3 << endl;cout << &d4 << endl;
运行结果为:
当两个函数都去掉后,即:
//取地址操作符重载/*Date* operator&(){cout << "Date* operator&()" << endl;return this;}*///const取地址操作符重载/*const Date* operator&() const{cout << "const Date* operator&() const" << endl;return this;}*/
再次运行下方代码:
cout << &d4 << endl;cout << &d3 << endl;
运行结果如下:
此时代码依旧正常运行。这是因为取地址操作符重载和取地址操作符重载是默认构造函数,他的性质与构造函数类似,当不人为编写上述两个重载时,编译器会自动生成,当认为编写两个重载时,编译器会去调用已经编写好的。
4. 勘误:
由于个人能力有限,书中难免出现汉字拼写错误、代码意义解释错误、内容逻辑以及理解错误等不同类型的错误。首先感谢各位大佬能花掉自己宝贵的时间阅读此文章,愿大佬们斧正,发现错误可以通过私信联系,本人不胜感激。