类和对象下

目录

日期类的前置--和后置--

前置--

日期类相减:

cin和cout

 ​编辑

流插入:

友元声明:

流提取:

 const成员

取地址重载:

const修饰的取地址重载:

初始化列表

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

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

 

初始化顺序


日期类的前置--和后置--

前置--

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张图片

 对于自定义对象,最好用前置:

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和cout

 类和对象下_第2张图片

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

类和对象下_第3张图片

istream是一个类,ostream也是一个类。

 为什么cin和cout可以自动识别参数的类型:

类和对象下_第4张图片

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

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

类和对象下_第5张图片

因为库中有这些内置类型的函数重载。

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 << "/" <

类和对象下_第6张图片

注意:在头文件中尽量不要定义全局变量和全局函数,否则会导致重定义的问题。 

 我们如何解决这个问题:

静态函数:

static修饰全局变量和全局函数时会改变其链接属性,使其只在当前文件中可见,其他文件不可见,也无法使用。

声明和定义分离:

我们在cpp文件中进行定义,在.h文件中进行声明。

头文件Date.h会在两个cpp文件中展开,但是Date.h的文件中只有声明没有定义,所以无法进入符号表。

void TestDate1()
{
	Date d1, d2;
	/*cin >> d1;
	cout << d2;*/
	cout << d1;
}

int main()
{
	TestDate1();
	return 0;
}

 需要优化的问题:

类和对象下_第7张图片

无法像cout调用内置类型一样连续调用。 

从左往右进行调用,cout<

ostream& operator<<(ostream&out, const Date&d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}
void TestDate1()
{
	Date d1, d2;
	/*cin >> d1;
	cout << d2;*/
	cout << d1 << d2 << endl;
}  

int main()
{
	TestDate1();
	return 0;
}

 我们还可以用内联函数解决:

注意:内联函数直接定义在头文件中即可,不能声明和定义分离:

inline ostream& operator<<(ostream&out, const Date&d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}

内联的原理:内联会在调用时直接展开,不会进入符号表。

但是以上的方法都是在我们的成员变量为公有的前提下,假如成员变量为私有呢?

第一种方法:创建函数取成员变量。

int GetYear() const
	{
		return _year;
	}
	int GetMonth() const
	{
		return _year;
	}
	int GetDay() const
	{
		return _year;
	}
inline ostream& operator<<(ostream&out, const Date&d)
{
	out <

友元声明:

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

我们在类中进行声明,表示我们这个运算符重载函数是一个友元函数,友元函数可以直接访问成员变量。

流提取:

inline istream& operator>>(istream& in,  Date&d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

类和对象下_第8张图片

对于流提取函数,我们也要进行友元声明。

 类和对象下_第9张图片

 const成员

void TestDate2()
{
	const Date d1(2022, 9, 10);
	d1.Print();
}

int main()
{
	TestDate2();
	return 0;
}

我们发现,用const修饰的对象无法调用Print函数:

void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

原因如下:

在我们的Print函数中,有一个隐藏的this指针,该指针的类型是Date*,const用来修饰this,防止对this本身进行修改。

而const修饰的是d2,我们传参的时候传递的也是d2,d2用const修饰是只读的,而对于this,我们却可以访问this指向的数据。

更简单的理解:

 

把d2的地址传递给this,发生了权限的扩大。

我们如何避免这种问题呢?

我们可以后置const

后置const表示把this的类型由Date*this修改为const Date*this的形式。

void TestDate2()
{
	const Date d1(2022, 9, 10);
	d1.Print();
	Date d2(2022, 9, 11);
	d2.Print();
}

int main()
{
	TestDate2();
	return 0;
}

并且对于普通的对象也可以调用print函数,因为权限的缩小是不受限制的。

我们再举一个例子:

类和对象下_第10张图片

同样属于权限的放大,我们后置const。
 

凡是函数内部没有改变成员变量的,这些成员函数的声明和定义都要加上const修饰。

取地址重载:

Date*operator&()
	{
		return this;
	}
void TestDate2()
{
	const Date d1(2022, 9, 10);
	d1.Print();
	Date d2(2022, 9, 11);
	d2.Print();
	cout << &d2 << endl;
}

int main()
{
	TestDate2();
	return 0;
}

这里的取地址相当于调用取地址重载函数:

const修饰的取地址重载:

const Date*operator&() const
	{
		return this;
	}

对于const类型的对象,我们调用该函数。

void TestDate2()
{
	const Date d1(2022, 9, 10);
	d1.Print();
	Date d2(2022, 9, 11);
	d2.Print();
	cout << &d2 << endl;
	cout << &d1 << endl;
}

int main()
{
	TestDate2();
	return 0;
}

类和对象下_第11张图片

这两个函数,我们即使不写,编译器也会自动生成:

void TestDate2()
{
	const Date d1(2022, 9, 10);
	d1.Print();
	Date d2(2022, 9, 11);
	d2.Print();
	cout << &d2 << endl;
	cout << &d1 << endl;
}

int main()
{
	TestDate2();
	return 0;
}

我们注释掉取地址重载函数进行运行:

类和对象下_第12张图片

我们如何使用取地址重载呢?

假如我们规定,在日期类中不能使用取地址,我们可以这样写:

Date*operator&()
	{
		return nullptr;
	}
	const Date*operator&() const
	{
		return nullptr;
	}

初始化列表

类和对象下_第13张图片

我们可以在构造函数中使用初始化列表,构造函数可以什么都不用写(针对的是日期类)

 写一个栈类的初始化列表:

Stack(int capacity=4)
		:_a(((int*)malloc(sizeof(int)*_capacity)))
		, _capacity(capacity)
		, _top(0)
	{
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
        memset(_a, 0, sizeof(int)*capacity);
	}

也可以这样写:

Stack(int capacity=4)
		: _capacity(capacity)
		, _top(0)
	{
		_a = ((int*)malloc(sizeof(int)*_capacity));
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memset(_a, 0, sizeof(int)*capacity);
	}

所以初始化列表和函数体内初始化可以混着一起使用。

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

class B
{
public:
	B()
	{
		_n = 10;
	}
private:
	const int _n;
};
int main()
{
	B b;
	return 0;
}

_n是cons类型的对象,我们知道const类型的对象只能在定义的时候初始化,我们的构造函数是无法对_n进行修改的。

const修饰的对象定义的时候必须初始化。

在这里是_n的声明,创建对象时才会定义。

 

这里是定义,是整体定义,我们无法对类内的一些const修饰的对象定义。

 对象的每一个成员变量是在初始化列表时定义的。

我们可以用初始化列表来定义const修饰的成员变量:

B()
		:_n(10)
	{
		;
	}

每一个成员变量都会走初始化列表,就算没有显示,也会走。

class B
{
public:
	B()
		:_n(10)
	{
		;
	}
private:
	const int _n;
	int _m;
};

这里的_m也会走初始化列表:

如果我们没有在初始化列表中显示的写,对于内置类型,默认是随机值,对于自定义类型调用其默认构造。

 对于这个随机值,c++打了一个补丁:

我们可以在成员变量的声明位置给缺省值。

 这个缺省值是在初始化列表中使用的,假如我们的初始化列表没有显示写成员变量,就用缺省值。

类和对象下_第14张图片

总结:

 

class A
{
public:
	A(int a)
	{
		;
	}
private:
	int _aa;
};

class B
{
public:
	B()
		:_n(10)
		, _m(2)
	{
		;
	}
private:
	const int _n;
	int _m=1;
	A _a;
};
int main()
{
	B b;
	return 0;
}

 _a走初始化列表时,_a是自定义类型,对于自定义类型调用默认构造,因为A类没有默认构造,所以报错。

有两种方法解决:

1:注释掉A的构造函数,使用系统给的默认构造:

这时候的_aa是随机值。

方法2:

 

class A
{
public:
	A(int a)
	{
		;
	}
private:
	int _aa;
};

class B
{
public:
	B()
		:_n(10)
		, _m(2)
		, _a(11)
	{
		;
	}
private:
	const int _n;
	int _m=1;
	A _a;
};
int main()
{
	B b;
	return 0;
}

我们可以在初始化列表给_a一个值,相当于调用_a的构造函数。

再举一个例子:

class MyQueue
{
public:
	MyQueue()
	{
	}
	void push(int x)
	{
		_pushST.Push(x);
	}
private:
	Stack _pushST;
	Stack _popST;
	size_t _size = 0;
};
int main()
{
	MyQueue m1;
	return 0;
}

这样写不会报错:对于自定义类型Stack,调用其默认构造,对于内置类型我们有缺省值,就给缺省值0.

类和对象下_第15张图片

当我们把栈的函数写成是非默认构造就会报错。

 类和对象下_第16张图片

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

 

class B
{
public:
	B()
		:_n(10)
		, _m(2)
		, b(_m)
	{
		;
	}
private:
	const int _n;
	int _m=1;
	int&b;
};
//class MyQueue
//{
//public:
//	MyQueue()
//	{
//	}
//	void push(int x)
//	{
//		_pushST.Push(x);
//	}
//private:
//	Stack _pushST;
//	Stack _popST;
//	size_t _size = 0;
//};
int main()
{
	//MyQueue m1;
	B b;
	return 0;
}

初始化顺序

类和对象下_第17张图片

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{

	}
	void Print()
	{
		cout << a1 << "" << a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main()
{
	A aa(1);
	aa.Print();
}

 初始化顺序是按照成员变量的声明顺序的:

先对_a2进行定义,_a1还不存在,所以报错原因是编译不通过。

你可能感兴趣的:(数据结构)