【C++】类的默认成员函数

news2025/6/26 16:32:47

类的默认成员函数

  • 类的六个默认成员函数
  • 构造函数
    • 构造函数的概念
    • 构造函数的特性
  • 析构函数
    • 析构函数的概念
    • 析构函数的特性
  • 构造函数与析构函数的调用顺序
  • 拷贝构造
  • 拷贝构造的概念
  • 拷贝构造的特性
  • 赋值运算符重载
    • 运算符重载
    • 赋值运算符重载
    • 前置++与后置++重载
    • 输入输出流重载
  • const修饰成员
  • 实现完整的日期系统
  • 取地址操作符重载
  • const取地址操作符重载

类的六个默认成员函数

当一个类中什么成员都没有时被称为空类。
空类:即任何类在什么都不写时,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

默认成员函数
初始化和清理
构造函数主要完成初始化工作
析构函数主要完成清理工作
拷贝赋值
拷贝构造是使用同类对象初始化创建对象
赋值重载主要是把一个对象赋值给另一个对象
取地址重载
主要是普通对象和const对象取地址,这俩个很少会自己实现

构造函数

构造函数的概念

class Data
{
public:
	void Init(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main(void)
{
	Data d1;
	d1.Init();
	d1.Print();

	Data d2;
	d2.Init(2024,6,8);
	d2.Print();
	return 0;
}

每次创建代码时,都需有初始化代码,使得工作量加大,引入构造函数可以进行初始化工作。

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

构造函数的特性

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

其特征如下:
1.函数名与类名相同
2.无返回值(也不需要写void)
3.对象实例化时编译器会自动调用对应的构造函数

class Data
{
public:
	Data(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main(void)
{
	Data d1();
	return 0;
}

4.构造函数可以重载

class Data
{
public:
	//带参数的构造函数
	Data(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//不带参数的构造函数
	Data()
	{
		_year = 2000;
		_month = 1;
		_day = 1;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main(void)
{
	//调用带参数的构造函数
	Data d1(2024,6,8);
	//调用不带参数的构造函数
	Data d2;
	return 0;
}

构造函数支持重载的原因是:可以存在不同的初始化情况。
【注意】如果公共无参构造函数创建对象时,对象后面不用接括号,否则会变成函数声明。
在这里插入图片描述
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户定义编译器将不再生成。

class Data
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main(void)
{
	Data d1;
	d1.Print();

	return 0;
}

编译器默认初始化成随机值:
在这里插入图片描述
这里需要说明一下:
C++里将类型分成俩类:
1.内置类型
内置类型属于基本类型,是语言本身定义的基础类型,例如int、char、double、指针等等
2.自定义类型
用struct、calss等定义的类型

编译器自动默认生成构造函数,内置类型不做处理,自定义类型会去调用其默认构造。(这里需要注意的是,不同的编译器有不同的处理方法,有些编译器也会处理,但是至少部分编译器个性化处理,不是所有的编译器都会处理)

总结:
1.一般情况下,有内置类型成员的,就需要自己写构造函数,不能让编译器自己生成
2.全部都是自定义类型成员,可以考虑让编译器自己生成。(一个经典的算法题:使用俩个栈实现队列就可以使用默认构造函数)

6.C++11中,在成员声明的时候可以给缺省值

class Data
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year = 2000;
	int _month = 1;
	int _day = 1;
};

【注意】这里不是初始化
由于这里只能存在成员变量的声明,没有开空间,这里给的是默认的缺省值,给编译器生成默认构造函数用。

7.无参的构造函数和全缺省的构造函数都称为默认构造函数、并且默认构造函数只能有一个。
【注意】无参构造函数、全缺省默认构造函数、如果我们自己没写,编译器默认生成的构造函数都可以认为是默认构造函数。

class Data
{
public:
	//无参构造函数
	Data()
	{
		_year = 2000;
		_month = 1;
		_day = 1;
	}
	//全参构造函数
	Data(int year = 2000,int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	Data d1;

	return 0;
}

在这里插入图片描述
不传参就可以调用的就是默认构造函数。

析构函数

析构函数的概念

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

析构函数的特性

1.析构函数名实在类名前面加上字符~
2.无参数无返回值

class Data
{
public:
	Data(int year = 2000, int month = 1, int day = 1)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	~Data()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};

3.一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数。【注意】析构函数不能重载。
4。对象声明周期结束时,C++编译器系统自动调用析构函数。

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 2000, int month = 1, int day = 1)
	{
		cout << "Data" << endl;
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	~Data()
	{
		cout << "~Data" << endl;
		_year = 0;
		_month = 0;
		_day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	Data d1;
	return 0;
}

在这里插入图片描述
5.系统自动生成的默认构造函数:
(1).内置类型成员不做处理。
(2).自定义类型会去调用他的析构函数。

下面这段代码是本人实现的:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_size = 1;
	}
	~Stack()
	{
		free(_arr);
		_arr = nullptr;
		_size = 1;
		_capacity = 0;
	}
	void Push(int x)
	{
		if (this->_capacity == this->_size)
		{
			int* arr = (int*)realloc(this->_arr, sizeof(int) * this->_capacity * 2);
			if (arr == nullptr)
			{
				perror("realloc fail");
				return;
			}
			this->_arr = arr;
			this->_capacity *= 2;
		}
		this->_arr[this->_size - 1] = x;
		this->_size++;
	}
	void Pop()
	{
		this->_arr[this->_size - 1] = 0;
		this->_size--;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};
int main(void)
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	s1.Pop();

	return 0;
}

在这里插入图片描述

这里可以发现手动实现的析构函数可以根据自己的要求满足实现。

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_size = 1;
	}
	void Push(int x)
	{
		if (this->_capacity == this->_size)
		{
			int* arr = (int*)realloc(this->_arr, sizeof(int) * this->_capacity * 2);
			if (arr == nullptr)
			{
				perror("realloc fail");
				return;
			}
			this->_arr = arr;
			this->_capacity *= 2;
		}
		this->_arr[this->_size - 1] = x;
		this->_size++;
	}
	void Pop()
	{
		this->_arr[this->_size - 1] = 0;
		this->_size--;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};
int main(void)
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	s1.Pop();

	return 0;
}

在这里插入图片描述
所以,一般情况下如果没有动态内存申请,析构函数可以不写,例如:Data类;但是如果有动态内存申请,就需要显式写析构函数释放资源,否则会造成内存泄漏。例如:栈的实现。

构造函数与析构函数的调用顺序

  • 类的析构函数调用一般按照构造函数调用的相反顺序调用,但是需要注意static对象的存在,因为static改变了对象的生存作用域,需要等待程序结束后才可析构释放对象。

  • 全局对象先于局部对象进行构造。

  • 局部对象按照顺序进行构造,无论是否为static对象。

  • static修饰的对象会在局部对象析构后进行析构。

拷贝构造

拷贝构造的概念

创建对象时,需要创建一个与已存在对象一模一样的新对象,就需要用到拷贝构造。

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

拷贝构造的特性

拷贝构造函数也是特殊的成员函数,其特征如下:
1.拷贝构造函数时构造函数的一个重载形式,所有书写格式与构造函数类似,但是参数类型不同。

2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器之间报错,因为会引发无穷递归调用。

观察下面代码:

class Data
{
public:
	Data(int year = 2000, int month = 1, int day = 1)
	{
		cout << "Data" << endl;
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	~Data()
	{
		cout << "~Data" << endl;
		_year = 0;
		_month = 0;
		_day = 0;
	}
	//拷贝构造函数
	//传值
	Data(Data d) 
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	Data d1;
	Data d2(d1);

	return 0;
}

在这里插入图片描述
首先编译器会自动报错,原因是:

当我们以传值方式传参时,需要先建立一个临时拷贝,而在建立临时拷贝也需要传参,再以传值方式传参时,又需要建立一个临时拷贝…
在这里插入图片描述

这种无穷递归,编译器会强制检查,解决的办法有俩种:
1.使用指针(内置类型)
2.使用引用(推荐)
【注意】使用引用时需要注意赋值方向(d._year = _year);就是错误代码。
由于传引用的值不用改变,所有可以使用const缩小权限。
正确代码如下:

class Data
{
public:
	Data(int year = 2000, int month = 1, int day = 1)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	~Data()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}
	//拷贝构造函数
	//传值
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	Data d1;
	Data d2(d1);

	return 0;
}

在这里插入图片描述

3.如果没有显式定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫浅拷贝,或者值拷贝。
(1)内置类型成员完成值拷贝、浅拷贝。
(2)自定义类型成员会调用他的拷贝构造。

观察代码:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_size = 1;
	}
	void Push(int x)
	{
		if (this->_capacity == this->_size)
		{
			int* arr = (int*)realloc(this->_arr, sizeof(int) * this->_capacity * 2);
			if (arr == nullptr)
			{
				perror("realloc fail");
				return;
			}
			this->_arr = arr;
			this->_capacity *= 2;
		}
		this->_arr[this->_size - 1] = x;
		this->_size++;
	}
	void Pop()
	{
		this->_arr[this->_size - 1] = 0;
		this->_size--;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};
int main(void)
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	s1.Pop();
	Stack s2(s1);

	return 0;
}

在这里插入图片描述

以栈为例,栈不可以使用默认拷贝构造,栈默认生成的拷贝构造会将俩个指针指向同一个栈,在析构时,后面拷贝的指针会先析构,而前面被拷贝的指针会后析构,一个堆区被析构俩次,编译器会报错,同时如果修改其中一个值,会影响另一个。

栈拷贝构造的正确代码是:

	Stack(const Stack& s)
	{
		_arr = (int*)malloc(sizeof(int) * s._capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		memcpy(_arr, s._arr, sizeof(int) * s._size);
		_size = s._size;
		_capacity = s._capacity;
	}

在这里插入图片描述
需要动态开辟的都需要自己实现深拷贝,而像日期类可以不写拷贝构造,默认生成的拷贝构造就可以用。

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

  • 内置类型可以通过编译器计算,而自定义类型也可以向内置类型一样,需要通过运算符重载,进行加、减、比较等。

以Date日期类举例:是否使用重载运算符,是观察这个运算符对这个类是否有意义,例如:日期相减可以计算相差天数,而日期相加却没有意义。

下面演示日期比较大小:

class Date
{
public:
	//Date构造
	Date(int year = 2000,int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date默认析构,默认拷贝
	//
//private:
	int _year;
	int _month;
	int _day;
};

bool operator>(const Date& d1, const Date& d2)
{
	if (d1._year > d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month > d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
	{
		return true;
	}

	return false;
}

int main(void)
{
	Date d1(2024, 6, 10);
	Date d2(2024, 5, 20);
	if (d1 > d2)
	{
		cout << "d1 > d2" << endl;
	}
	else
	{
		cout << "d1 < d2" << endl;
	}
	return 0;
}

这里需要注意的是编译器在编译期间将d1>d2转换成operator>(d1,d2),所以将d1>d2写成operator>(d1,d1)是相同的,第二种方式相当于调用函数。

int main(void)
{
	Date d1(2024, 6, 10);
	Date d2(2024, 5, 20);
	//第一种方式
	if (d1 > d2)
	{
		cout << "d1 > d2" << endl;
	}
	else
	{
		cout << "d1 < d2" << endl;
	}
	//第二种方式
	if (operator>(d1,d2))
	{
		cout << "d1 > d2" << endl;
	}
	else
	{
		cout << "d1 < d2" << endl;
	}
	return 0;
}

在这里插入图片描述
在反汇编的角度观察:
在这里插入图片描述
在这里插入图片描述
本质上二者都是相同的,编译器在编译期间将第一种方式转换为第二种方式,然后进行call

但是观察刚才的代码,如果在类中将成员变量改为私有,那么编译器会报错,在类外的函数是无法访问私有(private)和保护(protected),解决的办法有俩种:1.使用友元(建议能不用就不用,友元会破坏封装);2放在类内,将其视为成员函数。

下面演示将运算符重载视为成员函数:

class Date
{
public:
	//Date构造
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date默认析构,默认拷贝
	//比较大小的运算符重载
	bool operator>(const Date& d)
	{
		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;
		}

		return false;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main(void)
{
	Date d1(2024, 6, 10);
	Date d2(2024, 5, 20);
	//第一种方式
	if (d1 > d2)
	{
		cout << "d1 > d2" << endl;
	}
	else
	{
		cout << "d1 < d2" << endl;
	}
	//第二种方式
	if (d1.operator>(d2))
	{
		cout << "d1 > d2" << endl;
	}
	else
	{
		cout << "d1 < d2" << endl;
	}
	return 0;
}

将运算符重载函数放入类内时,参数只有一个,另外一个时隐藏的this。同时d1>d2准换为函数为d1.operator(d2),和使用成员函数时相同。

值得注意的是,写出成员函数更加方便,也更加容易理解。

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

赋值运算符重载

如何在类中实现一个赋值运算符重载?

	//实现赋值运算符重载
	void operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

在这里可以发现赋值运算符重载与拷贝构造类似,但是值得注意的是,二者有本质上的区别,我们需要理一理二者的概念:
拷贝构造函数:函数用一个已经存在的对象初始化另一个对象
运算符重载函数:已经存在的俩个对象之间的复制拷贝
通过理解二者的概念,那么通过哪种方式调用呢?

int main(void)
{
	//调用拷贝构造函数
	Date d1(2024, 6, 10);
	Date d2 = d1;

	//调用运算符重载函数
	Date d3(2024, 5, 20);
	Date d4(2003, 2, 3);
	d3 = d4;

	return 0;
}

初步认识赋值运算符函数,那么赋值运算符是如何使用的呢?

	int i , j , k;
	i = j = k = 10;

赋值运算符可以进行连续赋值:
在这里插入图片描述
所以我们可以将代码进一步更改:

	//实现赋值运算符重载
	Date operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

this指针不可以再形参出现,但是可以在成员函数内部出现。
但是在使用Date作为返回值时,每次赋值都会调用拷贝构造,所以建议直接使用引用返回。

代码如下:

	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

因为传值返回,值在函数结束后会销毁,返回的是值的拷贝,而传引用返回,返回的是别名,是*this这个指针在未被销毁时所在地址的值的别名。

this是参数,也是进行传参的,this的生命周期实在函数调用结束后销毁,相当于在函数建立时先进行push,压栈,然后函数在结束后销毁,this也会被pop,虽然this这个指针被销毁了,但是this这个指针所指向的值没有被销毁。

会不会存在一种情况,是自己给自己赋值:

	d1 = d2;

所以为了防止这种情况,需要对代码进行更改:

	//实现赋值运算符重载
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

总结:

1.赋值运算符重载格式:

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

2.赋值运算符只能重载成类的成员函数不能重载成全局函数,因为类中存在默认赋值运算符重载,会与之冲突。

3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值得方式逐字节拷贝。

【注意】默认生成赋值重载跟拷贝构造行为一样:内置类型成员仅值拷贝,浅拷贝;自定义类型成员会去调用其赋值重载。

例如:Date日期类、Myqueue不需要自己实现赋值重载,stack需要自己实现,因为默认生成的是浅拷贝。

前置++与后置++重载

	//前置++
	Date& operator++()
	{
		_day = _day + 1;
		return *this;
	}

【注意】使用前置++时,需要注意前置++时返回++以后的对象。

	//后置++
	Date operator++(int)
	{
		Date tmp(*this);
		_day = _day + 1;
		return tmp;
	}

前置++与后置++都是一元运算符为了让让前置++与后置++形参正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时,该参数不用传递,编译器会自动传递。
【注意】使用后置++时,需要注意后置++返回++以前的对象,所以需要tmp保存一份,tmp是临时变量,返回时必须需要进行拷贝构造。

输入输出流重载

	int i = 10;
	printf("%d", i);

我们知道printf可以打印内置类型,但是不可以打印自定义类型。

如果不使用调用函数的方式,是如何实现打印自定义函数呢?

在C++中支持使用流插入流提取运算符重载的方式进行打印自定义类型。

在这里插入图片描述
在C++reference中介绍cout是ostream类型的对象,cin是istream类型的对象。

在C++中,内置类型是初创C++时使用流插入和流提取定义的成员函数。

运算符重载是为了让自定义类型支持运算符:
1.可以直接支持内置类型的是库里实现的
2.可以直接支持自定义识别类型是因为函数重载

实现流插入自定义:

//自定义流插入
void Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}
void TestDate2() 
{
	int i = 10;
	cout << i << endl;

	Date d1(2024,6,12);
	d1 << cout;
}

在这里插入图片描述
从我们初步实现流插入可以发现我们所实现的运算符不符合运算重载。

cout << d1;

在这里插入图片描述

由于cout是终端、控制台,要实现只能在库里实现,流插入不能写成成员函数,因为Date类对象默认占用第一个参数,就是主操作数,这种写法写出来只能是d1<<cout,但是这不符合使用习惯。

而全局函数不会占用第一个参数,没有默认参数,但是访问不了私有问题,解决办法有俩个:
1.写成公有的成员函数

class Date
{
public:
	//公有的成员函数
	int GetYear()
	{
		return _year;
	}
	int GetMonth()
	{
		return _month;
	}
	int GetDay()
	{
		return _day;
	}
private:
	int _year;
	int _month;
	int _day;
};
//全局的流插入操作符
void operator<<(ostream& out, Date& d)
{
	out << d.GetYear() << "年" << d.GetMonth() << "月" << d.GetDay() << "日" << endl;
}

在这里插入图片描述

2.使用友元函数(后续讲解)

class Date
{
public:
	//友元函数声明
	friend void operator<<(ostream& out, Date& d);
private:
	int _year;
	int _month;
	int _day;
};
//全局的流插入操作符
void operator<<(ostream& out, Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

在这里插入图片描述
这里使用友元函数比调用公有的函数方便一些。

在实际使用流插入的时候可以进行连续插入

	cout << d1 << d2 << d3;

则需要更换返回值来进行连续插入

//全局的流插入操作符
ostream& operator<<(ostream& out, Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

在这里插入图片描述
同时ostream对象可以使用const修饰,因为其在流入控制台过程中没有被修改。

最后整合一下流提取运算符重载函数:

class Date
{
public:

	//友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
private:
	int _year;
	int _month;
	int _day;
};
//全局的流插入操作符
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" ;
	return out;
}

在这里插入图片描述
通过对流插入的了解,我们可以试着写出流提取运算符重载:

class Date
{
public:
	//友元函数声明
	friend istream& operator>>(istream& in,  Date& d);
private:
	int _year;
	int _month;
	int _day;
};
//全局的流提取操作符
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

在这里插入图片描述
值得注意的是:流提取是的参数是不可以加const的。istream不使用const修饰的原因是:当我们从输入流中读取数据时,流的状态会发生变化,例如读取位置会向前移动;对象不使用const修饰的原因是:输入流是可变的、输入流应该允许被修改。

总的来讲,流是支持任何形式的输入与输出。

const修饰成员

	Date d1(2022,2,2);
	d1.print();

	const Date.d2(2011,10,10);
	d2.print();

假设d1与d2俩个对象都调用print成员函数时

	void print();

d1可以调用成功,而d2却调用不成功。
d1在调用print成员函数时,Date的权限平移;而d2在调用print成员函数时,Date的权限被放大,由起初的Date的不可被修改,变成可修改时不被允许的。

那么如何可以更改呢?

	void print(const Date* this);

如果可以这样写就可以实现d1调用成员函数,但是this指针是不能作为形参出现的。
所以在C++中对此做了新的说明:
将const修饰的成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

	void print() const;

const修饰的是*this,如果成员函数加上const,普通与const对象都可以调用,但并不是所有的成员函数都可以加上const修饰,要修改成员变量的成员函数不可以加,只要成员函数内部不修改成员变量,都应该加const,这样const对象和普通对象都可以调用。

实现完整的日期系统

  • Date.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	//全缺省的构造函数
	Date(int year = 2000, int month = 1, int day = 1);
	//析构函数
	~Date();
	//拷贝构造函数
	Date(const Date& d);
	//赋值运算符重载
	Date& operator=(const Date& d);
	//打印
	void Print() const
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	//获取某年某月的天数
	int GetMonthDay(int year, int month) const;
	//日期+=天数
	Date& operator+=(int day);
	//日期+天数
	Date operator+(const int day) const;
	//日期-=天数
	Date& operator-=(int day);
	//日期-天数
	Date operator-(const int day) const;
	//前置++
	Date& operator++();
	//后置++
	Date operator++(int);
	//前置--
	Date operator--();
	//后置--
	Date operator--(int);
	//>运算符重载
	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;
	//日期-日期
	int operator-(Date& d) const;
	//友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
	//全局的流提取操作符
	friend istream& operator>>(istream& in,  Date& d);
private:
	int _year;
	int _month;
	int _day;
};

//全局的流插入操作符
ostream& operator<<(ostream& out, const Date& d);
//全局的流提取操作符
istream& operator>>(istream& in, Date& d);



  • Date.cpp
#include"Date.h"

//全缺省的构造函数
Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13
		&& day > 0 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期输入故障" << endl;
		assert(false);
	}
}
//析构函数
Date::~Date()
{
	_year = 0;
	_month = 0;
	_day = 0;
}
//拷贝构造函数
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//赋值运算符重载
Date& Date::operator=(const Date& d)
{
	if(this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}
//获取某年某月的天数
int Date::GetMonthDay(int year, int month) const
{
	int MonthArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2 && ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
	{
		return 29;
	}
	return MonthArr[month];
}
//日期+=天数
Date& Date::operator+=(int day)
{
	if (day <= 0)
	{
		day = -day;
		*this -= day;
		day = 0;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month > 12)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}
//日期+天数
Date Date::operator+(int day) const
{
	Date tmp = *this;
	tmp += day;
	return tmp;
}
//日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		day = -day;
		*this += day;
		day = 0;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month < 1)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
//日期-天数
Date Date::operator-(int day) const
{
	Date tmp = *this;
	tmp -= day;
	return tmp;
}

//前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
//后置++
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}
//前置--
Date Date::operator--()
{
	*this -= 1;
	return *this;
}
//后置--
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}
//>运算符重载 
bool Date::operator>(const 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;
	}

	return false;
}
//==运算符重载
bool Date::operator==(const Date& d) const
{
	if (_year == d._year && _month == d._month && _day == d._day)
	{
		return true;
	}
	return false;
}
//>=运算符重载
bool Date::operator>=(const Date& d) const
{
	if (*this > d || *this == d)
	{
		return true;
	}
	return false;
}
//<运算符重载
bool Date::operator<(const Date& d) const 
{
	if (!(*this >= d))
	{
		return true;
	}
	return false;
}
//<=运算符重载
bool Date::operator<=(const Date& d) const
{
	if (!(*this > d))
	{
		return true;
	}
	return false;
}

//日期-日期
int Date::operator-(Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = 1;
		flag = -1;
	}
	int n = 0;
	while (min < max)
	{
		++min;
		++n;
	}
	return n * flag;
}

//全局的流插入操作符
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" ;
	return out;
}
//全局的流提取操作符
istream& operator>>(istream& in, Date& d)
{
	int year = d._year;
	int month = d._month;
	int day = d._day;
	if (month > 0 && month < 13
		&& day > 0 && day <= d.GetMonthDay(year, month))
	{
		in >> year >> month >> day;
	}
	else
	{
		cout << "日期输入故障" << endl;
		assert(false);
	}
	return in;
}

取地址操作符重载

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

const取地址操作符重载

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

取地址与const取地址操作符一般不需要重载,使用编译器默认取地址重载即可,只有存在默认情况才才需要重载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1822772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32开发过程中碰到的问题总结 - 2

文章目录 前言1、Hex文件和Bin文件的区别2. STM32的boot0 在0和1有什么区别3. keil下的translate和builder有什么区别4. 为什么RTOS中区分中断内调用和中断外调用5. STM32中中断内运行的代码应该注意哪些事项6. STM32的定时器中断中 使用printf没有任何输出7. 将makefile编译的…

浅谈C++基本框架内涵及其学习路线

目录 一.C的内涵本质 1. 面向对象编程&#xff08;OOP&#xff09; 2. 低级控制 3. 模板编程 4. 标准库&#xff08;STL&#xff09; 5. 多范式支持 二.学习路线 1. 基础阶段 C基础语法 函数 数组和指针 2. 面向对象编程 类和对象 继承和多态 运算符重载 3. 高级…

【elementui源码解析】如何实现自动渲染md文档-第二篇

目录 1.概要 2.引用文件 1&#xff09;components.json 2&#xff09;json-template/string 3&#xff09;os.EOL 3.变量定义 4.模版填充 5.MAIN_TEMPLATE填充 6.src下的index.js文件 1&#xff09;install 2&#xff09;export 7.总结 1.概要 今天看第二个命令no…

Blender骨骼创建

骨骼系统 建立 使用Shift A添加骨骼或在添加|骨架中添加一段骨骼 骨骼的三种模式 -物体模式&#xff1a;做动画&#xff0c;摆人物pose时在该模式 -编辑模式&#xff1a;进行骨骼搭建&#xff08;选择一段骨骼&#xff0c;然后按E挤出一段骨骼并进行调整&#xff09; -姿…

解析ISP许可证:构建安全可靠的网络空间

在当今数字化时代&#xff0c;互联网已成为人们生活和工作中不可或缺的一部分。作为连接世界的纽带&#xff0c;互联网服务提供商&#xff08;ISP&#xff09;承担着重要的责任&#xff0c;为用户提供稳定、高效的网络接入和服务。而ISP许可证&#xff0c;则是保障这些服务合法…

Python学习打卡:day05

day5 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day538、函数的初体验39、函数的基础定义语法函数的定义注意事项 40、函数的基础定义案例练习41、函数的传入参数42、函数的传入参数案例练习——升…

【智能家居控制系统项目】一、项目系统镜像烧录与系统登录

前言 完成本章节将可以获得本项目的系统UI界面功能。本章节主要介绍如何烧录项目系统镜像以及进入系统。配套的视频介绍可以点击跳转到智能家居项目复刻配套视频 1.系统功能页面介绍 完成本章全部步骤&#xff0c;我们将可使用以下项目系统功能界面。 1.1 家居总览界面 主界面…

2分钟用手机开发一个ChatBot

前言&#xff1a; 在上一期&#xff0c;我们测评了CodeFlying&#xff0c;用它开发出了一个复杂推文管理系统&#xff0c;然后体验了一下它的热门应用&#xff1a;AI智能机器人。今天咱就继续用CodeFlying来开发一个属于我们自己的聊天机器人。 老规矩&#xff0c;我们先在手机…

【elementui源码解析】如何实现自动渲染md文档-第一篇

文章目录 目录 背景 获取源码 代码分析 背景 之前基于vant3的源码开发过二次开发过组件&#xff0c;其中vant实现了将md文档渲染到界面上&#xff0c;有天突发奇想想知道这是如何实现的将md文档渲染到界面上的&#xff0c;因为平时开发中使用elementui占多数&#xff0c;所…

并发、多线程、HTTP连接数有何关系?

在计算机领域&#xff0c;"并发"、"多线程"和"HTTP连接数"是三个重要的概念&#xff0c;它们之间存在着密切的关系。本文将探讨这三者之间的联系以及它们在现代计算机系统中的作用。 一、并发的概念 并发是指系统能够同时处理多个任务或事件的能…

thinkphp6 入门(23)--如何给上传图片的服务器目录授权

开发网站&#xff0c;上传图像时提示”上传图片失败&#xff0c;Impossible to create the root directory /var/www/html/xxxxx/public/uploads/avatar/20240608.“ 在Ubuntu上&#xff0c;你可以通过调整文件夹权限来解决这个问题。首先&#xff0c;确保Web服务器&#xff08…

填报志愿选大学专业,文科生如何选专业?

读文科的同学接触的专业知识相对广泛&#xff0c;往往被认为是“万金油”&#xff0c;他们仿佛什么都能做&#xff0c;但是和专业技能类知识不同&#xff0c;缺乏技术支持&#xff0c;从而使得文科专业的就业方向和前景远远比不上理科专业那么明朗&#xff0c;对于众多文科生而…

带头+双向+循环链表的实现

目录 1. 链表1.1 带头双向循环链表 2. 链表的实现2.1 结构体2.2 初始化2.3 打印2.4 判断空不能删2.5 尾插2.6 头插2.7 尾删2.8 头删2.9 查找2.10 在pos之前插入2.11 删除pos位置的值2. 12 销毁2.13 创建节点 3. test主函数4. List.c文件5. List.h文件 1. 链表 1.1 带头双向循环…

基于机器学习的CFD模型降阶

降阶模型 ROM 降阶模型ROM&#xff08;Reduced Order Models&#xff09;是一种对高保真度静态或动态模型的简化方法。模型降阶在保留了模型的基本特性与主导效应的同时&#xff0c;大大减少复杂模型的CPU计算时间及存储空间。 ROM的用途&#xff1a; 加速大规模系统的仿真速度…

每日一练:攻防世界:2-1

攻防世界&#xff1a;2-1 拿到一个打不开的图片&#xff0c;放到winhex里面&#xff0c;很直观的看到文件头损坏 修改以后还是打不开&#xff0c;继续往下分析 0000 000d说明IHDR头块长为13 4948 4452IHDR标识&#xff08;ascii码为IHDR&#xff09; 下面是IHDR数据块的实际…

【MySQL】事务的特性和隔离级别

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

报名进行中 | ISCSLP2024 对话语音克隆挑战赛(CoVoC)

晴数智慧(Magic Data)联合西北工业大学音频语音与语言处理研究组(ASLPNPU)、新加坡资讯通讯研究院(I2R)、深圳大数据研究院(SRIBD)、香港中文大学(深圳)等多家单位在2024年中文口语语言处理国际会议(ISCSLP2024)上推出对话语音克隆挑战赛(Conversational Voice Clone Challenge…

FRP 内网穿透 | 实现远程访问与安全管理

唠唠闲话 内网穿透简介 在互联网上&#xff0c;两个不同主机进行通信需要知道对方的 IP 地址。由于世界人口和设备众多&#xff0c;IPv4 资源相对紧缺&#xff0c;因此绝大部分情况下是通过路由器或交换机转换公网 IP 后才上网。 位于路由器或交换机后的设备通常是内网设备&…

Kettle根据分类实现Excel文件拆分——kettle开发31

将整理好的一份供应商付款明细Excel文件&#xff0c;按供应商拆分成多个Excel文件。 实现思路 本文我们首先将供应商付款明细表&#xff0c;按照“名称”拆分成多份Excel文件。拆分Excel文件打算用两个转换实现&#xff0c;一个用来将Excel数据读取到参数中&#xff0c;另外一…

2.4G低功耗无线收发SOC芯片-SI24R03

随着物联网产业对集成度的需求越来越高&#xff0c; 也在不断地完善公司产品生态。 “射频MCU”产品组合--无线SOC芯片&#xff08;MCU&#xff09;&#xff0c;简化了系统设计。只需要少量的外围器件&#xff0c;用户即可实现产品的开发&#xff0c;有效减少了PCB板的占用…