const成员,流插入,流提取重载,初始化列表! 流插入,流提取的重载(6千字长文详解!)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了const成员,流插入,流提取重载,初始化列表! 流插入,流提取的重载(6千字长文详解!)相关的知识,希望对你有一定的参考价值。

c++详解之const成员,流插入,流提取重载,初始化列表!

<< 流插入 和 >> 流提取的重载

#include <iostream>
using namespace std;
int main()

    int a = 0;
    double b = 1.1111;
    char c = w;
    cout << a << b << c <<endl;
    return 0;

我们使用流插入的时候可以看出来 << 可以兼容多种的类型,其实这个本质上就是一个函数重载和运算符重载!

我们可以看到官方库里面就内置了内置类型的输入流重载!但是如果我们想要对自定义类型使用<<的我们就得自己手动的对输入流进行重载!

例如日期类

//date.h
#include <iostream>
using namespace std;
class Date

public:
	Date(int year = 10, int month = 10, int day = 10)
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;
void operator<<(const Date& d, ostream& out)

	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;

class Date

public:
	Date(int year = 10, int month = 10, int day = 10)
	
		_year = year;
		_month = month;
		_day = day;
	
	void operator<<(ostream& out)
	
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	//cout是ostream类的函数!
private:
	int _year;
	int _month;
	int _day;
;
//date.cpp
int main()

	Date d1;
	Date d2;
	cout << d1 << endl;
	d1 << cout;
	d1.operator<<(cout);

	return 0;

我们会发现如果直接在类里面怎么写会导致正常我们的使用会出错 cout必须在右边才行!

这是因为this指针默认在左边!如果我们要在类里面使用则必须反正使用!

所以 为了符合我们正常的使用习惯我们一般都会将>> 和<< 的重载定义在类外面!

//date.h
#include <iostream>
using namespace std;
class Date

public:
	Date(int year = 10, int month = 10, int day = 10)
	
		_year = year;
		_month = month;
		_day = day;
	
	int _year;
	int _month;
	int _day;
;
void operator<<(ostream& out,const Date& d)

	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;


//date.cpp
#include "date.h"
int main()

	Date d1;
	Date d2;
	cout << d1 << endl;
	d1 << cout;
	d1.operator<<(cout);

	return 0;


// date2.cpp
#include "date.h"

但是这引入了一个问题当我们有多个源文件引用了这个头文件会出现

这是什么原因?答案是因为头文件会在每一个引用它的源文件中展开!也就是说这个operator<<函数重载出现多次,导致发生了重定义!

解决的办法有三种

  1. 使用静态的函数
  1. 声明和定义分离!
  1. 使用inline 修饰为内联函数

解决共有成员函数问题

链式访问的问题

从上面来看我们的流插入基本是完成了最后剩下的问题就是如果遇到

 cout  << d1 << d2<<endl;

这种情况的时候,如果我们使用的是void类型作为返回值会报错,为了解决这个问题我们使用cout的类作引用返回!

//date.h
inline ostream& operator<<(ostream& out,const Date& d)

	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
//cout是全局函数出了全局作用域后仍然可以存在,所以可以使用引用返回

>> 流插入的重载

//date.h
#include <iostream>
using namespace std;
class Date

	friend istream& operator>>(istream& in, const Date& d);
public:
	Date(int year = 10, int month = 10, int day = 10)
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;
inline istream& operator>>(istream& in,Date& d) 

	in >> d._year>> d._month >> d._day;

//实际调用就会转换成
//operator>>(cin,d);

const 成员

//date.h
class Date

public:
	Date(int year = 10, int month = 10, int day = 10)
	
		_year = year;
		_month = month;
		_day = day;
	
	void print()
	
		cout << _year << " " << _month << " " << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
//date.cpp

int main()

	Date d1;
	const Date d2;
	d1.print();
	d2.print();//这个无法运行!
	return 0;

会发生这样的问题的原因是因为发生了权限的扩大!

在类里面this指针的全貌为 Date* const this,后面的const是用来修饰this保证this本身不被修改!所以this指针的真实类型为 *Date **

d2.print();//等价于
print(&d2);

此时d2的类型为 const Date ,则d2的指针类型为**const Date* **

将const Date *传给Date * 这就发生了权限的放大!

那我们该如何解决这个问题呢?我们都知道this指针的参数都是由编译器自行添加的!我们不可以手动的进行显示添加!

所以这就引入了const成员!

class Date

public:
	Date(int year = 10, int month = 10, int day = 10)
	
		_year = year;
		_month = month;
		_day = day;
	
	void print() const//const成员!
	
		cout << _year << " " << _month << " " << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	Date d1(2022,1,1);
	const Date d2(2022,1,2);
	d1.print();
	d2.print();
	return 0;

我们发现不仅d2可以使用了,而且d1也仍然可以使用!

这是因为指针虽然不能从**const Date *传给Date ***

但是可以 **Date *传给const Date ***,原理是因为==权限可以缩小但是不能放大!==

	void print() const//只用来修饰this指针!
	
		cout << _year << " " << _month << " " << _day << endl;
	

==记住一点 const是用来修饰this指针的!不会修饰其他函数变量!==

修饰后的this指针的全貌为 const Date * const this ,类型为**const Date ***

	void print(int* x) const
	
		cout << _year << " " << _month << " " << _day << endl;
	

==x的类型仍然为int* 不会变成 const int*==

const在类函数中的普遍使用

我们经常看到在类成员函数中const漫天飞的普遍情况,原因就是因为只要不涉及对于类本身的修改,使用const成员可以是的类的成员函数有更好的泛用性!

例如日期类涉及比大小的运算符重载例如

class Date

public:
    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)const;
	Date operator+(int day)const;
private:
	int _year;
	int _month;
	int _day;
;

如果不加const的反例

class Date

public:
    bool operator < (const Date& d);
    bool operator>(const Date& d);
    int operator-(const Date& d) const
	
		if (*this < d)
        
            ///...
        //这样写没有问题!
        if (d > *this)
        
            ///....
        
        //这样写会报错!
	
private:
	int _year;
	int _month;
	int _day;
;

因为这样会变成!operator>(d, this) 发生了经典的权限放大!因为第一个类型为const Date 但是operator>的第一个参数类型为Date* 后面加 const 使得this的类型变成cosnt Date* 就让使用范围更广了!**

==总结凡是内部不改变成员变量,也就是*this对象数据的,这些成员函数都应该加const==

取地址重载

class Date

public:
    Date* operator&()
	
		return this;
	
	const Date* operator&()const
	
		return this;
	
private:
	int _year;
	int _month;
	int _day;
;


int main()

	Date d1(2022,1,1);
	const Date d2(2022,1,2);
	Date* d3 = &d1;
	const Date* d4 = &d2;

	return 0;

这两个一般不用写,编译器会自动默认生成!

只有特别特殊的要求才可能写——比如不让某个类的对象取地址!

   Date* operator&()
	
		return NULL;
	
	const Date* operator&()const
	
		return NULL;
	

可以重载成这样的样子!

再谈构造函数——初始化列表

初始化列表

我们首先来看初始化列表的格式

class Date

public:
	Date(int year = 10, int month = 10, int day = 10)//可以全部都是初始化列表
		:_year(year),
		_month(month),
		_day(day)
	
	
private:
int _year;
int _month;
int _day;
;

class stack

public:
    //..
	stack(int newcapcacity = 4)
		:_capacity(newcapcacity),
		_top(0)
	
		_a = (int*)malloc(sizeof(int) * newcapcacity);
		if (_a == nullptr)
		
			perror("malloc fail");
			exit(-1);
		
		memset(_a, 0, sizeof(int) * _capacity);
	//可以初始化列表和构造函数混合使用!
    //..
private:
	int* _a;//都是声明!
	int _top;
	int _capacity;

那么初始化列表有什么用呢?

2.没有默认构造函数的自定义类型变量

class A

public:
	A(int a)//没有默认构造!
		:_a(a)
	
private:
	int _a;
;
class B

public:
	B()
		:_n()
		,_m(100)
	
	
private:
	const int _n;
	int _m = 10;
    A _aa;
;
int main()

	B b;
	return 0;

这种情况A就必须走初始化列表!_a就无法初始化,因为没有默认构造让他调用就必须要显示的写出来!

class A

public:
	A(int a)
		:_a(a)
	
private:
	int _a;
;
class B

public:
	B()
		:_n()
		,_m(100)
		,a(1)
	
	
private:
	const int _n;
	int _m = 10;
	A _aa;
;
int main()

	B b;

	return 0;

**如果我们不写 A 类的构造函数仅仅使用A的默认构造函数,也是可以的,B在初始化列表初始化 _aa 的时候会去调用A类的默认构造初始化 _aa。而又在A类的默认构造函数下面的初始化列表下 _a被初始化成了一个随机值 **

class A

public:
private:
	int _a;
;
class B

public:
	B()
		:_n()
		,_m(100)
	
	
private:
	const int _n;
	int _m = 10;
	A _aa;
;
int main()

	B b;
	return 0;

再我们认识了初始化列表后让我们看一下下面的代码

class stack

public:
	stack(int newcapcacity = 4)
		:_capacity(newcapcacity),
		_top(0)
	
		_a = (int*)malloc(sizeof(int) * newcapcacity);
		if (_a == nullptr)
		
			perror("malloc fail");
			exit(-1);
		
		memset(_a, 0, sizeof(int) * _capacity);
	
	void Push(int x);
private:
	int* _a;
	int _top;
	int _capacity;
;

class MyQuene

public:
	MyQuene()
	
    //这样写是没有问题的!
    //因为我们没有在初始化列表显性的写所以它会去调用stack类的默认构造去初始化!
    //如果没有了stack的默认构造就会出现报错!
	void Push(int x)
	
		_PushST.Push(x);
	
	//......剩下读者可以加自己实现
private:
	stack _PopST;
	stack _PushST;
;

存在stack类默认构造的情况

stack没有默认构造!

使用初始化列表!

3.引用类型的成员函数

初始化列表的初始化顺序

以上是关于const成员,流插入,流提取重载,初始化列表! 流插入,流提取的重载(6千字长文详解!)的主要内容,如果未能解决你的问题,请参考以下文章

c++模拟实现string类

重载流提取运算符

C++ 输入/输出运算符重载

使用非成员函数从输入流中提取类对象

输入输出运算符重载

类和对象(二)——6个默认成员函数