【C++之类和对象篇002】

C++知识类和对象篇

前言:
前面篇章学习了C++对于C语言的语法优化,接下来继续学习,C++的类和对象中类的6个默认成员函数的知识。
/知识点汇总/

1、类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class A{ };

1.初始化和清理
初始化:构造函数主要完成初始化工作;
清理:析构函数主要完成释放/清理工作。
2.拷贝和复制
拷贝:拷贝函数使用同类对象初始化创建对象;
赋值重载:主要把一个对象赋值给另一个对象。
3.取地址和重载(相对于前4个做个了解)
主要是普通对象和const对象取地址,这两个很少会自己实现(由编译器实现)。

2、构造函数

概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.1、构造函数的特性

特性:
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。(不是void,直接不用写)
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
#include <iostream>
using namespace std;

class Date
{
public:
	//构造函数,不写构造函数,编译器默认对于内置类型不执行操作,为随机值
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//构造函数支持函数重载,支持缺省参数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//构造函数,支持缺省参数,语法上可以与无参的构造函数重载,但是编译会有歧义,所以可以重载但有歧义。
	//编译报错显示:对重载函数调用不明确。
	//Date(int year = 1, int month = 2, int day = 3)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();

	//构造函数,特殊的函数,对象后面跟的参数,但是参数不能为空,因为需要区分无参函数。
	Date d2(2024,1,27);
	d2.Print();

	return 0;
}

2.2、内置类型和自定义类型

默认成员函数之一,构造函数,我们不写编译器就会生成一个,它并不会对内置类型执行什么操作(由编译器不同也有不同),会对自定义类型初始化0操作。
C++分为内置类型/基本类型,即语言自身定义的类型;int/char/doubl…还有自定义类型,即struct/class/enum…
默认生成的构造函数,对于内置类型不做处理,自定义类型会去调用他的默认构造。

#include <iostream>
using namespace std;
class A
{
public:
	//构造函数,对自定义类型的默认处理
	A()
	{
		cout << "print_A()" << endl;
		_a = 0;
		cout << _a << endl;
		_a = 2;
	}
private:
	int _a;
};

class Date
{
public:
	//构造函数,不写构造函数随机值
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	//这里是声明,不是初始化,给的是缺省值
	int _year = 1;
	int _month = 1;
	int _day;
	A _a;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}

所以具体还是需要,分析一个类型成员是否需要初始化需求;
需要就写构造函数,不需要就交给编译器执行默认动作。
结论:一般情况下,都是自己写更适用。如果没写构造函数,编译器才会生成;否则,一旦写了编译器就不会写了。

#include <iostream>
using namespace std;
class stack
{
public:
	stack()
	{
		cout << "Stack_Init()..." << endl;
	}
};
class MyQueue
{
private:
	stack str1;
	stack str2;
};
int main()
{
	//构造函数适用于自定义类型
	MyQueue q;
	return 0;
}

2.3、什么是默认构造函数?

默认的构造函数,不完全等于编译器生成的构造函数,还包括无参的和全缺省的构造函数。
一般构造函数只需要写一个,否则存在编译歧义。

所以什么是默认构造函数?
答:1.无参构造函数 2.全缺省构造函数 3.没写构造而由编译器默认生成的构造函数

简述:不需要传参就可以调用的构造函数,都可以叫默认构造函数。

使用构造函数通常满足三个条件:
1.条件1,因为我们写了与类同名且无返回类型的构造函数,所以编译器就不会写了。
2.条件2,但是我们自己写的额构造函数不成立,因为构造函数有2中,除了编译器生成的以外就是无参和全缺省,所以报错:没有合适的默认构造函数可用。
3.条件3,3种构造函数又只能写一种(推荐写全缺省),否则编译会与其它2种构造函数存在歧义。

#include <iostream>
using namespace std;
class Date
{
public:
	//构造函数的条件不成立,所以报错
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//无参的构造函数
	Date()
	{
		_year = 2024;
		_month = 1;
		_day = 27;
	}
	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	//这里是声明,不是初始化,给的是缺省值
	//注意:是因为C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,
	//即:内置类型成员变量在类中声明时可以给默认值。不等于初始化
	int _year = 1;
	int _month = 1;
	int _day;
};
int main()
{
	//构造函数的条件不成立,所以报错
	//1.条件1,因为我们写了与类同名且无返回类型的构造函数,所以编译器就不会写了。
	//2.条件2,但是我们自己写的额构造函数不成立,因为构造函数有2中,除了编译器生成的以外就是无参和全缺省,所以报错:没有合适的默认构造函数可用。
	//3.条件3,3种构造函数又只能写一种(推荐写全缺省),否则编译会与其它2种构造函数存在歧义。
	Date d1;
	d1.Print();

	//构造函数支持函数重载,直接对象传参
	Date d2(2024, 2, 11);
	d2.Print();

	return 0;
}

3、析构函数

主要完成的清理和释放资源(常用于malloc等开辟的空间释放)

3.1、什么是析构函数?

概念
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

#include <iostream>
using namespace std;
class Date
{
public:
	//无参的构造函数
	Date()
	{
		_year = 2024;
		_month = 1;
		_day = 27;
	}
	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
	//析构函数的格式,一般用户自定义类型,比如栈...
	~Date()
	{
		//验证编译器确实会在, 对象生命周期结束时默认调用执行
		cout << "~Date" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}

3.2、析构函数的特性

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
#include <iostream>
#include <assert.h>
using namespace std;

typedef int DataType;

class Date
{
public:
	//无参的构造函数
	Date()
	{
		_year = 2024;
		_month = 1;
		_day = 27;
	}
	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
	//析构函数的格式,一般用户自定义类型,比如栈...
	~Date()
	{
		//验证编译器确实会在, 对象生命周期结束时默认调用执行
		cout << "~Date" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

class Stack
{
public:
	Stack(int n = 4)
	{
		//放个打印,表示默认执行了的
		cout << "Stack" << endl;
		_array = (DataType*)malloc(sizeof(DataType) * n);
		if (_array == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_size = 0;
		_capacity = n;
	}
	void PushBack(DataType data)
	{
		//扩容
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			DataType* tmp = (DataType*)realloc(_array, sizeof(DataType) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				return;
			}
			_array = tmp;
			_capacity = newcapacity;
		}
		_array[_size] = data;
		_size++;
	}
	DataType GetTop()
	{
		return _array[_size-1];
	}
	void PopBack()
	{
		_size--;
	}
	//DataType& GetPopVal()
	//{
	//	return _array[_size-1];
	//}
	bool Empty()
	{
		return _size == 0;
	}
	~Stack()
	{
		//放个打印,表示默认执行了的
		cout << "~Stack" << endl;
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_size = 0;
			_capacity = 0;
		}
	}
private:
	DataType* _array;
	int _size;
	int _capacity;
};

int main()
{
	Date d1;
	d1.Print();

	//Stack s1(4);
	Stack s1;
	s1.PushBack(1);
	s1.PushBack(2);
	s1.PushBack(3);

	while (!s1.Empty())
	{
		cout << s1.GetTop() << endl;
		s1.PopBack();
	}

	return 0;
}

小结

1.可以发现,先打印的 ~Stack ,再打印的 ~Date,显然先创建的d1对象,后创建的s1对象;
说明,析构满足栈的特性。先进后出/后进先出。即:先创建的后析构。
2.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
3.编译器默认生成的析构与构造函数类似;内置类型不做处理,自定义类型则调用它的写的析构(适合Queue,stack)

3.3、析构函数的释放顺序

析构函数的释放顺序:
局部对象(后定义的先析构)–》局部静态–》全局对象(后定义的先析构)

补充:虽然编译器对于构造函数和析构函数对自定义函数处理,但自定义类型的底层还是内置类型的嵌套。

#include <iostream>
using namespace std;

class Date
{
public:
	//全缺省的构造函数
	Date(int year)
	{
		_year = year;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << _year <<  endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date d6(6);
static Date d7(7);

void Test()
{
	Date d4(4);
	static Date d5(5);
}
int main()
{
	Date d1(1);
	Date d2(2);
	static Date d3(3);

	Test();

	return 0;
}

4、拷贝构造函数

4.1、什么是拷贝构造函数?

拷贝构造函数概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

4.2、拷贝构造函数的特性

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
    注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
  4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。所以是根据实际的应用场景决定
    注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

4.3、拷贝构造函数典型调用场景

1.使用已存在对象创建新对象
2.函数参数类型为类类型对象
3.函数返回值类型为类类型对象

#include <iostream>
using namespace std;

class Date
{
public:
	//默认的成员函数,都别忘了隐式的this指针参数
	//全省参数构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数的基本格式
	//const目的理解为控制权限/权限保护
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d1(2024, 1, 28);
	d1.Print();

	//拷贝构造
	Date d2(d1);
	d2.Print();
	return 0;
}

4.4、理解传值传参和传引用传参

C++规定,执行自定义类型都会调用拷贝构造,执行了拷贝构造之后再进入自定义类型执行。

#include <iostream>
using namespace std;

class Date
{
public:
	//默认的成员函数,都别忘了隐式的this指针参数
	//全省参数构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数的基本格式
	//const目的理解为控制权限/权限保护
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
//传值传参
//由于C++规定的这个特性,就可能会导致造成“死递归”的循环去调用拷贝构造。
//所以通常会结合const修饰参数的权限/属性
void func1(Date d)
{

}
//传引用传参,引用不会调用拷贝构造,它是别名直接就可以操作参数。
void func2(Date& d)
{

}
int main()
{
	Date d1(2024, 1, 28);
	d1.Print();

	//拷贝构造
	Date d2(d1);
	d2.Print();

	//C++规定自定义类型都会先调用拷贝构造
	//调用拷贝构造之前会先传参,又因为是传值传参就会形成新的拷贝构造,依次类推的陷入死循环
	func1(d1);
	func2(d2);

	return 0;
}

4.5、const正确的适用

const正确的适用有利于保护数据/权限访问/参数检查

#include <iostream>
using namespace std;

class Date
{
public:
	//默认的成员函数,都别忘了隐式的this指针参数
	//全省参数构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数的基本格式
	//Date(Date& d)易错
	//const目的理解为控制权限/权限保护
	Date(const Date& d)
	{
		//const保护参数是否写法的检查
		//d._year = this->_year;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d1(2024, 1, 28);
	d1.Print();

	//拷贝构造
	Date d2(d1);
	d2.Print();

	Date d3(d2);
	d3.Print();

	return 0;
}

4.6、拷贝构造的默认处理

拷贝构造会对自定义类型进行默认处理
1.若没有显式定义的拷贝构造函数,那么编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按照内置类型成员按照内存存储的额字节依次完成拷贝,这种叫作浅拷贝。或叫值拷贝。
2.另外对于自定义的类型的拷贝函数称为深拷贝/引用拷贝。 拷贝构造函数的参数只能有一个并且必须是类类型对象的引用,因为传值是死递归直接报错。

#include <iostream>
using namespace std;

class Time
{
public:
	//强制编译器生成构造函数,因为后面写了拷贝构造函数,所以需要自己写,
	//但这里只是为了验证拷贝构造函数的深拷贝,所以构造函数就没什么意义,
	//就使用强制默认构造default
	Time() = default;
	//拷贝构造函数
	Time(const Time& t)
	{
		//打印验证
		cout << "Time()" << endl;
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
	}
	void Print()
	{
		cout << _hour << " - " << _minute << " - " << _second << endl;
	}
private:
	int _hour = 1;
	int _minute = 1;
	int _second = 1;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date(const Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//验证自定义类型
	Time _t;
};

int main()
{
	Date d1(2024, 1, 28);
	d1.Print();
	//Time t1;
	//t1.Print();
	//拷贝构造
	Date d2(d1);
	d2.Print();

	Date d3(d2);
	d3.Print();
	return 0;
}

小结
若未显式定义,编译器会自动生成默认的拷贝构造,传值拷贝会调用拷贝构造函数,称为浅拷贝;
对自定义类型的成员则调用它自己的拷贝构造,称为深拷贝。
补充
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

4.7、拷贝构造浅拷贝的无脑拷贝问题

那么既然编译器不管内置类型还是自定义类型,都会帮我们执行拷贝工作,那么还需要我们自己写?
答:当然,编译器不可能完成一些复杂的逻辑和操作,所以遇到栈等开辟额外空间的操作时,需要我们自己注意完善。

#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	//深拷贝处理
	//解决办法就是开辟另一块同样大小的空间进行相关的操作
	Stack(const Stack& s)
	{
		DataType* tmp = (DataType*)malloc(s._capacity * sizeof(DataType));
		if (nullptr == tmp)
		{
			perror("malloc申请空间失败");
			return;
		}
		//拷贝数据 -->size
		memcpy(tmp, s._array, sizeof(DataType) * s._size);
		_array = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	//拷贝构造函数
	Stack s2(s1);
	return 0;
}

这段代码的核心问题在于,拷贝构造函数会连同数组的地址一同无脑的拷贝,所以在程旭执行结束执行free释放时,由于两个数组地址指向了同一块地址空间,那么释放掉其中一个指针,那么另外一个再去释放,就造成了同一块区域空间的重复释放的问题。

解决办法就是我们自己编写深拷贝,处理正确的数组指针,正确的释放。
小结:一般来说,浅拷贝不适用于malloc开辟空间这种情况。所以一般交给深拷贝处理。

5、操作符重载,关键字operator

5.1、关键字operator

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
操作符重载为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

注意
1.不能通过连接其他符号来创建新的操作符:比如operator@。
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变, 例如:内置的整型 + ,不能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
4.注意:‘.*’ , ‘::’ , ‘sizeof’ , ‘?:’ , ‘.’ 注意以上5个运算符不能重载。

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};

//bool DateEqual(const Date& x, const Date& y)
//运算符重载:operator + 运算符
bool operator==(const Date& x, const Date& y)
{
	return x._year == y._year 
		&& x._month == y._month 
		&& x._day == y._day;
}
//日期不能整体比较
//2024 1 28
//2024 1 27
//2024 2 26

//bool DateLess(const Date& x, const Date& y)
//运算符重载:operator + 运算符
bool operator<(const Date& x, const Date& y)
{
	if (x._year < y._year)
	{
		return true;
	}
	else if (x._year == y._year)
	{
		if (x._month < y._month)
		{
			return true;
		}
		else if (x._month == y._month)
		{
			return x._day < y._day;
		}
	}
	//写完true,则其余情况false
	return false;
}

int main()
{
	Date d1(2024, 1, 28);
	Date d2(2024, 2, 26);

	//对象不能直接比较,不能strcmp
	//error:d1 < d2
	//error:strcmp(d1,d2)
	
	//cout << DateEqual(d1, d2) << endl;
	//cout << DateLess(d1, d2) << endl;

	//为了可读性和通识性,引入关键词operator
	//对运算符的需要重新控制
	//operator+运算符 作函数名

	cout << operator==(d1, d2) << endl;
	cout << operator<(d1, d2) << endl;

	//等价写法:但是注意,传参参数的和函数的参数顺序绑定的,不可任意交换参数顺序
	cout << (d1 ==  d2) << endl;//cout << operator==(d1, d2) << endl;
	cout << (d1 < d2) << endl;//cout << operator<(d1, d2) << endl;
	return 0;
}

重载的运算符通常是具备一定意义的运算符。
注意以上的访问操作是基于,类的成员变量处于pubilc状态的。
那么如何解决私有状态的正确访问呢?

5.2、私有限定符情况

1.写“回调函数” Getyear()/ Getmonth()/ Getday()
2.直接在类里写其相关函数即可

私有限定符情况,解决方法1:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//解决方法1:
	int Getyear()
	{
		return _year;
	}
	int Getmonth()
	{
		return _month;
	}
	int Getday()
	{
		return _day;
	}
private:
	int _year;
	int _month;
	int _day;
};

bool operator==(Date& x,  Date& y)
{
	return x.Getyear() == y.Getyear()
		&& x.Getmonth() == y.Getmonth()
		&& x.Getday() == y.Getday();
}

bool operator<(Date& x, Date& y)
{
	if (x.Getyear() < y.Getyear())
	{
		return true;
	}
	else if (x.Getyear() == y.Getyear())
	{
		if (x.Getmonth() < y.Getmonth())
		{
			return true;
		}
		else if (x.Getmonth() == y.Getmonth())
		{
			return x.Getday() < y.Getday();
		}
	}
	return false;
}

int main()
{
	Date d1(2024, 1, 28);
	Date d2(2024, 2, 26);

	cout << operator==(d1, d2) << endl;
	cout << operator<(d1, d2) << endl;

	//等价写法:但是注意,传参参数的和函数的参数顺序绑定的,不可任意交换参数顺序
	cout << (d1 == d2) << endl;//cout << operator==(d1, d2) << endl;
	cout << (d1 < d2) << endl;//cout << operator<(d1, d2) << endl;
	return 0;
}

私有限定符情况,解决方法2:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//解决方法2:直接放进类中,会报错,运算符函数的参数太多。
	//因为包括了隐含的成员函数this指针。
	//所以去掉多于参数,并且放进类之后,本身也就只需要被比较的参数了
	//bool operator==(const Date& x, const Date& y)
	bool operator==(const Date& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}
	//bool operator<(const Date& x, const Date& y)
	bool operator<(const Date& y)
	{
		if (_year < y._year)
		{
			return true;
		}
		else if (_year == y._year)
		{
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}
		//写完true,则其余情况false
		return false;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(2024, 2, 26);

	//cout << operator==(d1, d2) << endl;
	//cout << operator<(d1, d2) << endl;

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

	//等价写法:但是注意,传参参数的和函数的参数顺序绑定的,不可任意交换参数顺序
	cout << (d1 == d2) << endl;//cout << operator==(d1, d2) << endl;
	cout << (d1 < d2) << endl;//cout << operator<(d1, d2) << endl;
	
	return 0;
}

5.3、了解‘.*’操作符

'.’ 等价于 '->'属于成员指针运算符(不支持重载)

#include <iostream>
using namespace std;

class ob
{
public:
	void func()
	{
		cout << "void func()" << endl;
	}
};

typedef void(ob::* pobfunc)();

int main()
{
	pobfunc p = &ob::func;
	ob temp;
	//(*p)();
	//this
	(temp.*p)();
	return 0;
}

6、赋值运算符重载

6.1、 赋值运算符重载

1.参数类型:const T & ,传递引用可以提高传参效率
2.返回值类型:T & ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值
4.返回 * this :要复合连续赋值的含义

6.2、 拷贝构造函数与赋值运算符重载的区别

1.赋值运算符只能重载成类的成员函数不能重载成全局函数;
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。 此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
2.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?
答:当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
即与拷贝构造函数类似,涉及malloc动态开辟的空间操作就是自己写,编译器会把数组地址无脑拷贝

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}
	bool operator<(const Date& y)
	{
		if (_year < y._year)
		{
			return true;
		}
		else if (_year == y._year)
		{
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}
		return false;
	}

	//不写赋值重载,编译器会自动生成,但是只能是类的成员函数,
	//因为当用户在类外自己实现一个全局的赋值运算符重载时,就和编译器在类中生成的默认赋值运算符重载冲突了。
	//d1 = d2
	//void operator=(const Date& d)
	//满足,赋值运算符的连续赋值,添加Date返回值类型。
	//传值和传引用,返回又有一定的区别,传值只是拷贝,传引用才能改变变量
	Date& operator=(const Date& d)
	{
		//添加,if判断使其不会浪费空间资源去执行赋值操作。
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
			return *this;
		}
	}
	void Print()
	{
		cout << _year << " - " << _month << " - " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(2024, 2, 26);

	//拷贝构造
	Date d3(d1);//拷贝构造,同类型中一个已存在对象进行初始化要创建的对象
	//d1 = d2;//已经存在的对象,一个拷贝赋值给另一个存在对象
	d1 = d2;

	d1.Print();
	d2.Print();

	//连续赋值
	int i, j, k;
	i = j = k = 10;
	//优先级
	cout << ((i = j) = 10) << endl;
	cout << (i = j = 10) << endl;

	//那么赋值重载的运算符能实现连续赋值操作吗?
	//当然能,解决没有找到void类型的右操作数的运算符。
	//修改返回值为类类型并加引用。
	//不写赋值重载,编译器会自动生成
	d1 = d2 = d3;

	//偶尔会测试/失误的自己会给自己赋值
	//d1 = d1;
	//添加,if判断使其不会浪费空间资源去执行赋值操作。
	return 0;
}

小结:

1.用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝; 赋值重载与拷贝构造函数类似的,赋值重载内置类型可由编译器自动赋值,自定义类型调用它的赋值重载赋值。
2.不写赋值重载,编译器会自动生成,但是只能是类的成员函数,因为当用户在类外自己实现一个全局的赋值运算符重载时,就和编译器在类中生成的默认赋值运算符重载冲突了。

7、类的6个默认成员函数总结

1.构造函数和析构函数可归为一组,内置类型不做处理,自定义类型调用对应的构造函数和析构函数。
2.拷贝构造函数和赋值重载可归为一组,内置类型拷贝或赋值(编译器无法干一些复杂的逻辑操作,比如malloc开辟的空间处理等),自定义类型调用对应的拷贝构造函数和赋值重载

  • 32
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值