当前位置:   article > 正文

C++流插入提取,const成员函数和初始化列表学习

C++流插入提取,const成员函数和初始化列表学习

目录

流插入和流提取

流插入<<

问题提出:

解决方案1

友元函数(friend)

解决方案2

 流提取

运算符重载知识小补充

const成员函数

 问题提出1

分析 

解决办法

问题提出2

分析

解决办法

取地址重载(一般都不会写)

 再学构造函数

初始化列表

 为什么要用初始化列表

什么是初始化列表

初始化列表的顺序

 


流插入和流提取

一般来说自定义类型是不支持运算符的,想支持的话就必须运算符重载

其中重载流插入的其中一个对象便是cout

cin和cout分别是istream和ostream类型的一个对象,它是在iostream那个头文件里定义的

这些都是C++已经写好在库里面的众多重载函数,所以说cout能支持内置类型

 

流插入<<

问题提出:

我们通常一开始会这样运算符重载<<

  1. void operator<<(ostream& out){
  2. out<<_year<<"年"<<_month<<"月"<<_day<<"日"<<endl;
  3. }

因为双操作运算符,第一个参数是左操作数,第二个参数是右操作数

结果便是报错

究其原因就是在Date类中,this指针已经悄悄的将第一个参数,即左参数给占用了

所以应该变为下述所讲

 

但是此种方式又不适合我们平常所用的,不符合可读性    

解决方案1

将此重载函数放到全局的位置,让ostream对象放到第一个位置,第二个位置才是自定义类型

    

但这又会出现另一个问题,就是类外面不能访问私有成员,所以又会报错

改正的方法是:将private暂时屏蔽

但是这样私有成员变得将不再安全

所以此种解决方案是不合理的 

 

介绍另一种方法之前,先介绍一个新的东西:友元函数

友元函数(friend)

加了友元之后,便可在不是某类里面访问某类的私有成员

就相当于,声明了这个友元函数是这个类的好朋友,这个类相信它,允许它访问

解决方案2

第二种解决方案就是在类里面加上一个友元声明

	friend void operator<<(ostream& out, const Date& d);

为了能够连续的输出自定义类型,即能做到cout<<d1<<d2<<endl; 

所以应该将其的返回值变为 ostream!

  1. //类里
  2. friend ostream& operator<<(ostream& out, const Date& d);
  3. //类外
  4. ostream& operator<<(ostream& out,const Date& d) {
  5. out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
  6. return out;
  7. }

 流提取

流提取的第二个参数便不能加const了,因为他要走到自定义类型里面去

  1. //类里
  2. friend istream& operator>>(istream& in, Date& d);
  3. //类外
  4. istream& operator>>(istream& in, Date& d) {
  5. in >> d._year >> d._month >> d._day;
  6. return in;
  7. }

流本质是为了解决,自定义类型输入和输出的问题,因为普通的printf,scanf是无法解决复杂的自定义类型输入和输出的问题的

运算符重载知识小补充

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型函数
  3. 用于内置类型的运算符,其含义不会改变
  4. 作为类成员函数重载的时候,其形参看起来比操作数少1,是因为隐藏了this指针这个形参
  5. 有5个运算符是不能够重载的
    1. 去成员变量的点 .   
    2. 域作用限定符 ::
    3. sizeof
    4. 三目运算符?:
    5. .* (点星)

*是可以重载的

const成员函数

在正常情况下,下述代码是没有任何问题的

  1. Date d1(2024,7,17);
  2. d1.print();

 问题提出1

但当我们变成这样呢

  1. const Date d1(2024,7,17);
  2. d1.print();

出现了错误

分析 

我们把print()函数拿过来综合对比

  1. void print() {
  2. cout << _year << "-" << _month << "-" << _day << endl;
  3. }
  4. const Date d1(2024, 7, 16);
  5. d1.print();

 

        但this指针是隐藏的,无法将它找出来加上const

解决办法

在成员函数的后面加const,来弥补无法直接在Date* const this 的前头加const的缺点,且这是规定

  1. void print() const{
  2. cout << _year << "-" << _month << "-" << _day << endl;
  3. }
  4. const Date d1(2024, 7, 16);
  5. d1.print();

 此时的this就相当于变为 const Date* const this

非const对象也能调用,属于权限缩小

问题提出2

在问题一修正的基础上

第一个编不过,第二个又能编过

分析

 

解决办法

与问题一样,不给权限放大就可以了,加const,一般不会修改成员就加一下const

所以有两个原则

  1. 能定义成const的成员函数都应该定义成const,这样const对象和非const对象都可以调用
  2. 要修改成员变量的成员函数,不能定义成const

 

例如,+=就不能加const

  1. Date& operator+=(int day) {
  2. if (day < 0) {
  3. *this -= (-day);
  4. return *this;
  5. }
  6. _day += day;
  7. while (_day > getMonthday(_year, _month)) {
  8. _day -= getMonthday(_year, _month);
  9. _month++;
  10. if (_month > 12) {
  11. _year++;
  12. _month = 1;
  13. }
  14. }
  15. return *this;
  16. }

取地址重载(一般都不会写)

取地址重载有两个,但也是默认成员函数,不写也没有什么过多问题

  1. Date* operator&() {
  2. return this;
  3. }
  4. const Date* operator&() const {
  5. return this;
  6. }

 

 再学构造函数

先来看看原本的构造函数

  1. Date(int year = 1900, int month = 1, int day = 1) {
  2. _year = year;
  3. _month = month;
  4. _day = day;
  5. if (_year < 1 || _month>13 || _month<1 ||
  6. _day>getMonthday(_year, _month)) {
  7. assert(false);
  8. }
  9. }

在CPP中还给了另外一种定义构造函数的方法 

初始化列表

 为什么要用初始化列表

函数体无法初始化引用成员,const和自定义类型成员没有默认构造

什么是初始化列表

初始化列表是每个成员定义的地方

以前的Date类的三个私有成员变量没有在初始化列表显示定义,但是其实它也是有定义的,只是C++内置类型默认给随机值,而自定义类型会去调用它的默认构造函数

有些自定义成员想要显示定义,即,用自己的数值初始化

如下代码

  1. class A {
  2. public:
  3. A(int a = 0)
  4. :_a(a)
  5. {
  6. cout << "我已完成A构造" << endl;
  7. }
  8. private:
  9. int _a;
  10. };
  11. namespace lhl {
  12. class Date {
  13. public:
  14. Date(int year, int month, int day)
  15. :_ref(year)
  16. , _n(1)
  17. , _aa(10)
  18. {
  19. _year = year;
  20. _month = month;
  21. _day = day;
  22. }
  23. private:
  24. int _year,_month,_day;
  25. A _aa;
  26. int& _ref;
  27. const int _n;
  28. };
  29. }
  30. int main() {
  31. lhl::Date d1(2024, 7, 17);
  32. return 0;
  33. }

那么有了初始化列表,但是不能只用初始化列表,因为有些初始化或者检查工作,初始化列表也不能全部搞定

初始化列表的顺序

初始化列表初始的顺序,跟初始化列表的顺序无关,只跟声明的顺序有关

  1. namespace lhl2 {
  2. class A
  3. {
  4. public:
  5. A(int a)
  6. :_a1(a)
  7. , _a2(_a1)
  8. {}
  9. void Print() {
  10. cout << "a1的值是" << _a1 << endl;
  11. cout << "a2的值是" << _a2 << endl;
  12. }
  13. private:
  14. int _a2;
  15. int _a1;
  16. };
  17. }
  18. int main() {
  19. lhl2::A aa(1);
  20. aa.Print();
  21. return 0;
  22. }

根据上述表达,可能我们会以为a1和a2的值都是1,1

但是结果并不是

 

显而易见,a1为1,a2为随机值 

为什么呢?

前面说过:初始化列表初始的顺序,跟初始化列表的顺序无关,只跟声明的顺序有关

这里先声明了_a2,后来才声明了_a1

所以先声明_a2,因为_a2(_a1),此时的_a1还没有被赋值,只是一个随机值,所以_a2也会变成随机值

后来声明_a1,_a1(a),a为1,所以_a1为1.

 

以上便是本篇博文的学习内容,如有错误,还望各位大佬指点出来,谢谢!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/844021
推荐阅读
相关标签
  

闽ICP备14008679号