首页 > 其他分享 >3.类与对象(中篇)介绍了类的6个默认构造函数,列举了相关案例,实现了一个日期类

3.类与对象(中篇)介绍了类的6个默认构造函数,列举了相关案例,实现了一个日期类

时间:2024-04-09 16:59:37浏览次数:13  
标签:中篇 const int 默认 month Date day 构造函数

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

  • 如果一个类中什么成员都没有,简称为空类。

  • 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

  • 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

默认成员函数是一种特殊成员函数:

​ 1**.我们不写,编译器会自己生成一个;我们写了,编译器就不会生成。(例如构造函数,析构函数)**

​ 2.隐含意思:对于有些类,需要我们自己写;对于另一些类,编译器默认生成就可以用。

class Date {};

image-20221209171257787

2. 构造函数

2.1 概念

对于下面的Date类:

class Date
{
public:
	/*void Init(int year, int month, int day)    // 普通的初始化函数
	{
		_year = year;
		_month = month;
		_day = day;
	}*/

	// 初始化(构造函数)
    // 1.带参构造函数
    // 可以使用缺省;使用缺省参数就可以不用传参
	Date(int year = 1, int month = 1, int day = 1)  
	{
		_year = year;
		_month = month;
		_day = day;
	}

    /*
    // 构造函数可以重载,但是对于这两个构造函数,如果不传参数,会造成二义性(因为两个构造函数,都可以不用进行传参,但是语法上这么写是正确的,但是函数调用时,会有二义性)
    // 2.无参构造函数
	Date()                     
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	*/

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

private:
	int _year;     // 年
	int _month;    // 月
	int _day;      // 日
};


int main()
{
	Date d1(2022, 9, 21);  // 给构造函数传参,在创建的对象后写需要传递的参数
	Date d2(2022, 9, 21);
	Date d3;  // 不传参的形式
	
	// 无参的不要像下面这样写
	// Date d4();
	// Date func();    // 编译器无法判断func()是函数还是对象

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

	return 0;
}
  • 对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

2.2 特性

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

其特征如下:

  1. 函数名与类名相同。

  2. 无返回值。

  3. 对象实例化时编译器自动调用对应的构造函数。

  4. 构造函数可以重载。

class Date
{
public:
	// 1.无参构造函数
	Date()
	{}

	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void TestDate()
{
    // 调用无参构造函数
	Date d1; 
    // 调用带参的构造函数
	Date d2(2015, 1, 1); 

	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	
    // 以下代码的函数:声明了d3函数(并不是d3对象),该函数无参,返回一个日期类型的对象
	// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
	 Date d3();  // 错误演示
}

int main()
{
    TestDate();
    return 0;
}
// case1:

class Stack
{
public:
    // 构造函数
	Stack(int capacity = 4)    
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	~Stack()               // 析构函数
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};


int main()
{
	// 有了构造函数和析构函数,我们不在需要主动对创建的对象进行初始化; 而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作(和Destory的功能类似)。
    Stack st;          
	st.Push(1);
	st.Push(2);
	st.Push(3);

	return 0;
}
  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date
{
public:
	/*
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	*/

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

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

int main()
{
	// 将Date类中我们自己定义的构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	
    // 将Date类中我们自己定义构造函数放开(则不会生成默认构造函数),代码编译失败(由于自己写的构造函数需要传参,但Date d1并没有传参,因此匹配不到合适的构造函数),因为一旦显式定义任何构造函数,编译器将不再生成
	
    // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	
    Date d1;
	return 0;
}
  1. 关于编译器生成的默认成员函数,很多人会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象year/_month/_day ,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

    解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认构造函数。

// case1:编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认构造函数

class Time
{
public:
    //  内置类型,需要自己定义构造函数
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
    
    // 内置类型 (默认构造函数对其不进行处理,因此需要我们自己来写构造函数)
private:
	int _hour;
	int _minute;
	int _second;
};


class Date
{
private:
    // 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	
    // 自定义类型(类名为Time的自定义类型,不需要我们来写构造函数,编译器会直接调用默认构造函数)
    // 但是内置类型又无法处理
    // 因此,需要用到初始化列表 ==> 类和对象下篇再解释初始化列表
	Time _t;
};

int main()
{
	Date d;
	return 0;
}
// case2:编译器生成默认的构造函数会对自定类型成员_pushST,_popST调用的它的默认构造函数

class Stack
{
public:
    // 自己创建的默认构造函数
	Stack(int capacity = 4)      
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	~Stack()                 // 自己创建的析构函数
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};



class MyQueue 
{
public:
	void push(int x)
	{
		_pushST.Push(x);
	}

	Stack _pushST;
	Stack _popST;

	//size_t _size;     如果myQueue还存在一个内置类型,又该怎么办呢?
};

 // 面向需求:编译器默认生成就可以满足,就不用自己写,不满足就需要自己写
 // Stack的构造函数需要自己写
 // MyQueue构造函数就不需要自己写,默认生成就可以用

 // Stack的析构函数,需要我们自己写
 // MyQueue 就不需要自己写析构函数,默认生成就可以用

int main()
{

	MyQueue mq;
	mq.push(1);
	mq.push(2);

	return 0;
}

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
    
private:
	int _hour;
	int _minute;
	int _second;
};


class Date
{
private:
	// 基本类型(内置类型)   
    //此处,为内置类型:内置类型成员变量在类中声明时可以给默认值(则默认构造函数,可以不用考虑内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	
    // 自定义类型 (内置类型已经给了缺省值,因此,默认构造函数不用考虑内置类型)
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

再例如:

class Stack
{
public:
    /*
    // 如果构造函数的参数不是全缺省值,则不是默认构造函数,编译器会报错
    // 在class MyQueue中,会调用Stack初始化Stack _pushST;Stack _popST, 但是Stack的构造函数不是全缺省值,所以会报错
	Stack(int capacity)     
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
    */

    // 析构函数
	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
    
    // 再声明内置类型时,给到缺省值,则相当于默认构造
private:
	int* _a = nullptr;
	int _top = 0;
	int _capacity = 0;
    
    /*
 	// 只有创建对象之后,才会调用默认构造函数,才会malloc空间
 	// 下面这种方法无法检测malloc是否成功开辟空间
	int* _a = (int*)malloc(sizeof(int)*4); 
	int _top = 0;
	int _capacity = 4;
	*/
};


// 用两个栈来实现一个队列
class MyQueue {
public:
    /*
	// 初始化列表 -- 类和对象下篇再讲
	MyQueue(size_t capacity = 8)
		:_popST(8)
		, _pushST(8)
		, _size(0)
	{
		_size = 0;
	}
    */

	void push(int x)
	{
		//_pushST.Push(x);
	}

private:
    // 调用Stack的默认构造函数来初始化
	Stack _pushST;
	Stack _popST;
    
    // 下面这种在定义时给缺省值的方式,不建议(建议使用初始化列表,后续讲解)
    // 这里不是初始化,给的缺省值,但并不会开辟空间(则默认构造函数不用考虑这个内置类型,默认构造函数就会使用其进行初始化)
	size_t _size = 0; 
};                 

int main()
{
	MyQueue q;

	return 0;
}

7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。(也就是不需要传参数,就可以调用的构造函数,就叫默认构造函数)

class Date
{  
public:
    // 无参构造函数(可以将其看做是默认构造函数)
	Date()             
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
    
    // 全缺省构造函数(可以将其看做是默认构造函数)
	Date(int year = 1900, int month = 1, int day = 1)   
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

// 以下测试函数能通过编译吗?
void Test()
{
	Date d1;
}

// 答:不能够通过测试,因为无参构造函数和全缺省构造函数都可以接收参数,因此,由于d1对象,没有传参,导致二义性(也就是没有默认构造函数)

3.析构函数

3.1 概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

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

3.2 特性

  1. 析构函数名是在类名前加上字符 ~。

  2. 无参数无返回值类型。

  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

typedef int DataType;
class Stack
{
public:
    // 自己写的默认构造函数(全缺省)
	Stack(size_t capacity = 3)   
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	
    // 其他方法...
	
    // 自己写的析构函数
    ~Stack()            
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
    
    // 内置类型(默认析构函数对其不进行处理,因此需要我们自己来写析构函数)
    // 内置类型(默认构造函数对其不进行处理,因此需要我们自己来写构造函数)
private:
	DataType* _array;
	int _capacity;
	int _size;
};

void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
    
}

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

5.关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

#include<iostream>
#include <assert.h>
using namespace std;

// 栈的类
class Stack
{
public:
    // 默认构造函数
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

   // 内置类型
private:
	int* _a;
	int _top;
	int _capacity;
};

// 队列的类
class MyQueue
{
public:
	void push(int x)
	{
		//_pushST.Push(x);
	}

    // 自定义类型
private:
	Stack _pushST;
	Stack _popST;
};

// 面向需求:编译器默认生成就可以满足,就不用自己写,不满足就需要自己写

// 用两个栈来实现一个队列
// Stack的构造函数需要自己写  (都是内置类型)
// MyQueue构造函数就不需要自己写,默认生成就可以用(都是自定义类型)

// Stack的析构函数,需要我们自己写   (都是内置类型)
// MyQueue 就不需要自己写析构函数,默认生成就可以用   都是自定义类型)

int main()
{
	Date d1;
	MyQueue q;

	return 0;
}

两个栈实现一个队列

实现上述oj题,深刻体会编译器生成析构函数的作用。

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4. 拷贝构造函数

4.1 概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎

image-20221209213910413

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

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

4.2 特性

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

  1. 拷贝构造函数是构造函数的一个重载形式

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

#include<iostream>

using namespace std;

class Date
{
public:
    // 自己写的默认构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

    // 拷贝构造函数
	Date(const Date& d)            
	{
		cout << "Date 拷贝构造" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;

		// 形参加const,防止写反了,下面问题就可以检查出来(因为d被const修饰,所以d是无法被修改的)
		//d._day = _day;
	}

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

void Func1(Date d)
{
	cout << "Func1()" << endl;
}

void Func2(Date& d)
{
	cout << "Func2()" << endl;
}

int main()
{
    // 构造 - 初始化
	Date d1(2022, 9, 22); 
     
    // 为传值传参,因此传参,需要先调用拷贝函数拷贝d1,再将拷贝的值传参给void Func1(Date d)
	Func1(d1); 
    // 为引用,因此不需要调用拷贝函数
	Func2(d1);   

    // 拷贝一份d1
    // 拷贝构造 -- 拷贝初始化
	// Date d2(d1); 
	
	return 0;
}

//  打印结果
// Date 拷贝构造
// Func1()
// Func2()

思考:为什么调用func1时,会先调用拷贝函数?

答:由下图可知,将d1传参,需要先调用拷贝函数 拷贝d1,再将其传参给Func1(Date d),而Date& d,不需要拷贝传参,dd1的别名,不需要拷贝d1,也就不需要调用拷贝函数

image-20221209215755558

  1. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 我们不写拷贝函数,则编译器会默认生成拷贝函数	
    
private:
	int _year;
	int _month;
	int _day;
};



int main()
{
    // 构造 - 初始化
	Date d1(2022, 9, 22); 

	// 拷贝一份d1
    // 拷贝构造 -- 拷贝初始化
	Date d2(d1); 
	return 0;
}

image-20221209221954378

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

class Stack
{
public:
    // 默认构造函数
	Stack(int capacity = 4)   
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
    /*
    // 我们不写拷贝函数,则默认生成拷贝函数
    //默认生成的函数,就是类似于这样的实现
    Stack(const Stack& st)   
	{
		_a = st._a;
		_top = st._top;
		_capacity = st._capacity;
	}
	*/
    
    // 析构函数
	~Stack()            
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{	
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	
    //浅拷贝,st1和st2在堆上会共用一块空间;两个对象,因此会调用两次析构函数,所以会报错
	Stack st2(st1); 
	return 0;
}

运行之后我们会发现程序崩溃了

image-20221209222029829

思考:

由下图可以得出:st1和st2共用一块空间,被调用两次析构函数,同一块空间被释放两次,因此会发生报错。

image-20221209222313438

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

  • 我们应该如何解决浅拷贝的问题呢?

  • 为了解决浅拷贝,一旦涉及到资源申请时,则拷贝构造函数我们自己需要写,这也就是深拷贝

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
	// 我们自己写的拷贝构造函数(这样拷贝时才是深拷贝,不会出现浅拷贝的问题)
	Stack(const Stack& st)
	{
		cout << "Stack(const Stack& st)" << endl;

        // 申请一块空间,这块空间是给拷贝的新的栈对象用的
        // 这样就不会出现,两个站对象用一块空间的问题了
		_a = (int*)malloc(sizeof(int)*st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int)*st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
 
// 需要写析构函数的类,都需要写深拷贝的拷贝构造              Stack
// 不需要写析构函数的类,默认生成的浅拷贝的拷贝构造就可以用   Date/MyQueue

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

int main()
{
	Stack st1;  // 调用默认构造函数
	st1.Push(1);
	st1.Push(2);

	Stack st2(st1); // 调用拷贝函数
	st2.Push(10);

    // MyQueue为自定义类型,调用默认的拷贝构造函数(如果默认拷贝构造函数,满足需求,则我们不用自己写)
	// 调用两次默认构造函数(因为队列,是由两个栈来实现的,所以会调用两次栈的默认构造函数)
    MyQueue q1;   
    // 调用两次拷贝函数(因为队列,是由两个栈来实现的,所以会调用两次栈的默认拷贝函数)
	MyQueue q2(q1); 


	return 0;   // 调用6次析构函数
}


// 打印结果如下:
/*  
Stack(int capacity = 4)
Stack(const Stack& st)
Stack(int capacity = 4)
Stack(int capacity = 4)
Stack(const Stack& st)
Stack(const Stack& st)
~Stack()
~Stack()
~Stack()
~Stack()
~Stack()
~Stack() 
*/

**5.**赋值运算符重载

5.1 运算符重载

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

函数名字为:关键字operator后面接需要重载的运算符符号

函数原型:返回值类型 operator操作符(参数列表);如:bool operator==(const Date& d1, const Date& d2)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@

  • 重载操作符必须有一个类类型参数

  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针

  • .*  ::  sizeof  ?:  .   注意这5个运算符不能重载。
    
// 全局的operator==   ;判断两个日期类是否相等

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

//private:
	int _year;
	int _month;
	int _day;
};

// 这里会发现运算符重载成全局的,就需要成员变量是公有的,那么问题来了,封装性如何保证?
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

int main()
{
	Date d1(2022, 9, 22);
	Date d2(2023, 1, 1);

	//d1 > d2;
	cout << (d1 == d2) << endl; // 程序运行时会转换成operator == (d1, d2);

	// 也可以显示调用,一般不这样
	operator==(d1, d2);
	return 0;
}
// 将operator== 定义在类里面

#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==(Date* this, const Date& d2)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象(==操作符应该刚好是两个操作数)
    // 成员函数都是放在公共代码区
    bool operator==(const Date& d)    
    {
        return _year == d._year
        && _month == d._month
            && _day == d._day;
    }
    
private:
    int _year;
    int _month;
    int _day;
};


int main()
{
    Date d1(2022, 9, 22);
    Date d2(2023, 1, 1);

    //d1 > d2;
    // (d1 == d2) 一定要加括号,因为<< 的优先级比+高
    // 运行时会转换成 d1.operator==(d2)
    cout << (d1 == d2) << endl; 

    // 也可以显示调用,一般不这样
    cout << (d1.operator == (d2)) << endl;

    return 0;
}

5.2 赋值运算符重载

1.赋值运算符重载格式

  • 参数类型const T&,传递引用可以提高传参效率

  • 返回值类型T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

  • 检测是否自己给自己赋值

  • 返回*this :要复合连续赋值得含义

我们自己写的运算符重载(日期类)


#include<iostream>
using namespace std;

class Date
{
public:
	int GetMonthDay(int year, int month)   // 得到对应月份的天数
	{
		// 将数组定义在静态区,这样每次创建对象时,都不需要重新创建一个数组
		static int monthDayArray[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;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	Date(int year = 1, int month = 1, int day = 1)    // 默认构造函数
	{
		_year = year;
		_month = month;
		_day = day;

		// 检查日期是否合法
		if (!(year >= 1
			&& (month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}


	
    // cout << (d1 == d2) << endl; 
    // 转换成d1.operator==(d2)
    // 判断两个日期类是否相等
	bool operator==(const Date& d)             
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	
    
    // d1 > d2
	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;
	}

	
    
    // d1 >= d2
	bool operator >= (const Date& d)
	{
		return *this > d || *this == d;   // *this 也就是d1
	}

	// ....  <  <=  !=    都可以按照如上的方式来实现

	
    
    // d1 += 100
     // 使用传引用返回,则不会拷贝*this来返回,栈帧销毁后d1还存在
	Date& operator += (int day) 
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month)) 
		{
			_day -= GetMonthDay(_year, _month);    
			_month++;

			if (_month == 13)
			{
				++_year;
				_month = 1;
			}
		}

		return *this;
	}

	
    
    
    // d1 + 100   不会改变d1的值
    // 由于栈帧销毁后,对ret没有访问权限,所以不能够使用传引用返回
    // ret是一个临时创建的日期类对象,只在当前运算符重载函数中存活
	Date operator+(int day)   
	{
        // 调用拷贝构造函数来创建ret这个日期类对象
		Date ret(*this);  
		ret += day;
        
		return ret;
	}

	void print()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}

    
    // d1 = d2;(赋值重载)
	Date& operator=(const Date& d)
	{
	    if (this != &d)
	    {
	        _year = d._year;
			_month = d._month;
			_day = d._day;
		}

			return *this;
	}
    
    
private:
	int _year;
	int _month;
	int _day;
};


void TestDate1()
{
	Date d1;
	Date d2(2022, 10, 8);
	
    // 初始化时,可以检测出来为非法日期
	Date d3(2022, 2, 40);  
    // 初始化时,可以检测出来为非法日期
	Date d4(2022, 2, 29);  
	d3.Print(); 
	d4.Print();
}

void TestDate2()
{
	Date d0;
	Date d1;
	Date d2(2022, 10, 8);

    // 拷贝构造(初始化)  将d2拷贝来初始化d3
	Date d3(d2);  
     // 拷贝构造(将d2拷贝给d4)
	Date d4 = d2; 

	d1.Print();
    // 赋值重载(复制拷贝) 已经存在两个对象之间拷贝
	d0 = d1 = d2; 
	d1.Print();

	int i, j;
	(i = j = 10)++;
	cout << i << " " << j << endl;  // 打印结果为  11   10
}


int main()
{
    TestDate1();
	Date d1(2022, 9, 22);
	Date d2(2022, 9, 22);

	d1 += 8;
	d2 += 100;
	Date d3 = d1 + 9;
	d1.print();
	d3.print();


	/*d1 - d2;*/

	return 0;
}

系统默认生成的运算符重载(栈)

class Stack
{
public:
    // 默认构造函数
	Stack(int capacity = 4)   
	{
		cout << "Stack(int capacity = )" <<capacity<<endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
    // 拷贝构造函数
	// st2(st1);
	Stack(const Stack& st)     
	{
		cout << "Stack(const Stack& st)" << endl;

		_a = (int*)malloc(sizeof(int)*st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int)*st._top);
		_top = st._top;
		_capacity = st._capacity;
	}
    
    // st1 = st2
    // 此处我们不自己写运算符重载的函数,使用系统默认生成的运算符重载函数

	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

    
    
private:
	int* _a;
	int _top;
	int _capacity;
};


void TestSatck()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);

	Stack st2(10);
	st2.Push(10);
	st2.Push(20);
	st2.Push(30);
	st2.Push(40);

	st1 = st2;
}

int main()
{
	TestSatck();

	return 0;
}

运行之后我们会发现使用默认生成的运算符重载,系统崩溃了

image-20221210183726521

系统为什么会崩溃呢,出现了那些问题?

1.通过下图可知,赋值重载之后,st1和st2中_a的地址是相同的

image-20221210183136357

2.通过详细的分析得出下面的结论

image-20221210183628579

我们自己写的运算符重载(栈)

/*
1.怎样才可以保证不发生如上所说的内存泄漏,且对同一块空间多次析构?
第一步:首先释放st1的空间
第二步:为st1申请一块和st2同样大小的空间
第三步:将st2的数据拷贝到st1中
*/
    
// st1 = st2;
Stack& operator=(const Stack& st)
{
    cout << "	Stack& operator=(const Stack& st)" << endl;
    
    // 考虑到会有本身给本身赋值的情况,所以在赋值前,要确保this != &st为真
    if (this != &st)   
    {
        // 1.首先释放st1的空间
        free(_a);
        
        // 2.为st1申请一块和st2同样大小的空间
        _a = (int*)malloc(sizeof(int)*st._capacity);
        if (_a == nullptr)
        {
            perror("malloc fail");
            exit(-1);
        }
        
        // 3.将st2的数据拷贝到st1中
        memcpy(_a, st._a, sizeof(int)*st._top);
        _top = st._top;
        _capacity = st._capacity;
    }

    // 将st1对象返回
    return *this;
}
  • 使用我们自己写的运算符重载,再来运行一下
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = )" <<capacity<<endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
    
	
	// st2(st1)
	Stack(const Stack& st)
	{
		cout << "Stack(const Stack& st)" << endl;

		_a = (int*)malloc(sizeof(int)*st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int)*st._top);
		_top = st._top;
		_capacity = st._capacity;
	}
    
    

	// st1 = st2;
	// st1 = st1;
	Stack& operator=(const Stack& st)     // 我们自己写的运算符赋值重载
	{
		cout << "	Stack& operator=(const Stack& st)" << endl;
		if (this != &st)
		{
			free(_a);
			_a = (int*)malloc(sizeof(int)*st._capacity);
			if (_a == nullptr)
			{
				perror("malloc fail");
				exit(-1);
			}
			memcpy(_a, st._a, sizeof(int)*st._top);
			_top = st._top;
			_capacity = st._capacity;
		}

		return *this;
	}
    
    

	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

    
    
	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}
    
    

private:
	int* _a;
	int _top;
	int _capacity;
};

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



void TestSatck()
{
	//Stack st1(10000);
    // 运行结果证明,自己写的运算符重载,不会再导致程序崩溃
	// 默认生成operator= 不行 需要自己实现
	Stack st1;
	st1.Push(1);
	st1.Push(2);

	Stack st2(10);
	st2.Push(10);
	st2.Push(20);
	st2.Push(30);
	st2.Push(40);

	st1 = st2;
	st1 = st1;

	// MyQueue 默认生成operator= 是可以的,我们不需要自己去写
	MyQueue q1;
	q1.push(1);
	q1.push(2);
	q1.push(3);

	MyQueue q2;
	q2.push(10);
	q2.push(20);
	q2.push(30);

	q1 = q2;
}

int main()
{
	TestSatck();

	return 0;
}

6.日期类的实现(完善日期类,并将它的声明与定义分离)

Date.h

// Date.h
class Date
{ 
    // 友元声明(这个声明可以写在类的任意位置)
    // 有了这个友元声明,那么这两个运算符重载函数就可以访问类内的成员变量和成员函数
	friend ostream& operator<<(ostream& out, const Date& d);   // 流输出
	friend istream& operator>>(istream& in, Date& d);    // 流输入
    
public:
    // 这个函数可以得到,年份月份对应的天数,如2024-4,就有30天
	int GetMonthDay(int year, int month)
	{
		static int monthDayArray[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;
		}
		else
		{
			return monthDayArray[month];
		}
	}

    // 默认构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		// 检查日期是否合法
		if (!(year >= 1
			&& (month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}

	

	// d1 == d2
    bool operator==(const Date& d) const;
    
	// d1 > d2
	bool operator>(const Date& d) const;
    
	// d1 >= d2
	bool operator>=(const Date& d) const;
    
    // d1 <= d2
	bool operator<=(const Date& d) const;
    
    // d1 < d2
	bool operator<(const Date& d) const;
    
    // d1 != d2
	bool operator!=(const Date& d) const;
    
	
	// d1 += 100
	Date& operator+=(int day);

	// d1 + 100
	Date operator+(int day) const; // d1是不改变的

	// d1 -= 100
	Date& operator-=(int day);

	// d1 - 100
	Date operator-(int day) const;

	// 前置++
	Date& operator++();

	// 后置++
	Date operator++(int);
    
    // 前置--
	Date& operator--();

	// 后置--
	Date operator--(int);
    
    // d1 - d2
	int operator-(const Date& d) const;
    
    /*
    // 错误演示
    // d1.operator<<(cout); 
    // d1 << cout
    // << 和 >> 重载一般不写为成员函数,因为this默认抢了第一个参数的位置,Date对象就是左操作数,不符合使用习惯和可读性;    因此我们可以考虑将其定义为全局变量
	// 如果operator<<不是成员函数的话,那么第一个参数就不是this指针,就不需要写为d1 << cout
   
    // 错误演示
    // ostream 为流插入的类类型   
    // 不可以作为成员函数
	void operator<<(ostream& out)   
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	*/
    
    
    // 取类对象地址的重载
    // 要求这个类的对象不让取地址(需要自己写取地址及const取地址操作符重载的一种情况)
	Date* operator&()
	{
        // 自己写的重载,使用&,取类对象的地址,拿到的就是空指针
		return nullptr;
		//return this;             // 只是取类的对象的地址,则不需要自己写,系统默认生成的就可以
	}

    // 取类对象地址的重载(这个是被const修饰的)
	const Date* operator&() const
	{
		return nullptr;
		//return this;
	}

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


//  流插入的声明
ostream& operator<<(ostream& out, const Date& d); 
//  此处operator运算符重载必须声明与定义分离;因为其是全局的,不是类的成员函数


// 错误演示;如果都定义到Date.h中,由于Date.cpp和test.cpp都包含了Date.h那么他们生成的.o文件中都会包含这个函数,则在编译链接时,就会报错

/*
// 不可以直接定义到Date.h
ostream& operator<<(ostream& out, const Date& d)  
{
 	 out << d._year << "年" << d._month << "月" << d._day << "日" << endl;

	 return out;
}
*/

// 解决方法   
// 1.声明与定义分离     
// 2.用static来修饰 operator   
// 3.定义为内联函数   这样可以放到Date.h
// 注意:将函数定义为全局变量,那么想要访问类里面的私有成员变量,则需要在类里面添加友元声明

/*
// static修饰(则链接时,函数不进入符号表)
static ostream& operator<<(ostream& out, const Date& d) 
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;

    return out;
}
*/


// 内联函数(则链接时,函数不进入符号表)
// 流插入
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

// 流提取
// cin >> d1  operator(cin, d1)
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

Date.cpp

// Date.cpp
#include "Date.h"

// 运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// d1 > d2(被const修饰的运算符重载)
// 这种被const修饰的函数,是专门给被const修饰的对象调用的,普通对象也可以调用,只是不可以进行修改
// 注:最右侧的const是修饰隐藏的this指针的
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;
}

// d1 >= d2
bool Date::operator>=(const Date& d)  const
{
    // 大于 或上 等于,也就是大于等于,直接复用,不用自己重新写
    // 编译器会自行调用 bool Date::operator>(const Date& d)  const
    // bool Date::operator==(const Date& d)进行重载
	return *this > d || *this == d;   
}

// d1 <= d2
bool Date::operator<=(const Date& d)  const
{
	return !(*this > d);          // 大于取反,也就是小于等于
}

// d1 < d2
bool Date::operator<(const Date& d)  const
{
	return !(*this >= d);          // 大于等于取反也就是小于
}

// d1 != d2
bool Date::operator!=(const Date& d)  const
{
	return !(*this == d);
}

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		//return *this -= -day;
		return *this -= abs(day);
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;

		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

// d1 + 100
Date Date::operator+(int day)  const
{
	Date ret(*this);
	ret += day;
	return ret;
}

// d1 -= 100
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		//return *this -= -day;
		return *this += abs(day);
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

// d1 - 100
Date Date::operator-(int day)  const
{
	Date ret(*this);
	ret -= day;
    
	return ret;
}


// 前置
Date& Date::operator++()
{
	*this += 1;
	return *this;   // 前置++返回加加之后的值
}


// 后置++    括号中多一个int参数主要是为了和前置++  进行区分
// 构成函数重载
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;
}



// d1 - d2
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

    // 针对于if (d > *this)  
    // 如果 bool operator>(const Date& d) const; 没有加const修饰*this,则程序会报错
    // 因为d被const修饰,但是传参过去之后的*this并没有并const修饰,是一种权限放大,因此会报错
    // 加上const修饰*this之后;if (d > *this)  和   if (*this < d)   都是可行的
    
	if (*this < d)   
	//if (d > *this)  
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++n;
		++min;
	}
    
	return n*flag;
}
    
// 重载流插入
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;

	return out;
}

test.cpp

// test.cpp

#include "Date.h"

void TestDate1()
{
	Date d1(2022, 10, 8);
	Date d3(d1);
	Date d4(d1);

	d1 -= 10000;
	d1.Print();

	Date d2(d1);
	
    /*
    //  打印 d2 - 10000 的两种方法
    Date d3 = d2 - 10000;   
	d3.Print();*/
	(d2 - 10000).Print();
	d2.Print();

	d3 -= -10000;
	d3.Print();

	d4 += -10000;
	d4.Print();
}


void TestDate2()
{
	Date d1(2022, 10, 8);
	Date d2(d1);
	Date d3(d1);
	Date d4(d1);
	
	(++d1).Print(); // d1.operator++()
	d1.Print();

    // 后置++,会存在一个占位符
	(d2++).Print(); // d2.operator++(1)
	d2.Print();
    
    (--d1).Print(); // d1.operator--()
	d1.Print();

	(d2--).Print(); // d2.operator--(1)
	d2.Print();
}

void TestDate3()
{
	Date d1(2022, 10, 10);
	Date d2(2023, 7, 1);

	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;
}


void TestDate4()
{
	Date d1, d2;
	//cin >> d1;  // 流提取
	//cout << d1;   // 流插入
	
	//d1 << cout; // d1.operator<<(cout);

	cin >> d1 >> d2;
    
	cout << d1 << d2 << endl; // operator<<(cout, d1); 
    // 根据优先级,会先运行cout << d1; 因此operator<<的返回值类型应为ostream&;这样才可以支持d2进行插入
    
	cout << d1 - d2 << endl;
}



void TestDate5()
{
	Date d1(2022, 10, 10);
	d1.Print();

    // 此处详解看下图(被const修饰的类类型对象)
	const Date d2(2022, 10, 10); 
	d2.Print();     

	//d2 += 10;
	cout << d2 + 1000 << endl;

	cout << &d1 << endl;    // 取地址及const取地址操作符重载
	cout << &d2 << endl;
}

int main()
{
	TestDate2();

	return 0;
}
  • 下图是对d2.Print() 的详解

image-20221211004425096

7.const

  • 将const修饰的“成员函数"称之为const成员函数,

  • const修饰类成员函数,实际修饰该成员函数隐含的this指针(也就是const Date* this),表明在该成员函数中不能对类的任何成员变量进行修改。

// const 在*的左侧是修饰 *this;  (*this就是this指针指向的对象) 
// const Date* this;   Date const* this

// const 在*的右侧修饰 this;
// Date* const this

image-20221211012137069

我们来看看下面的代码:

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
    void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	
    void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

void Test()
{
    //调用 void Print()
	Date d1(2022, 1, 13);
	d1.Print();    
    
    //调用 void Print() const
	const Date d2(2022, 1, 13);
	d2.Print();     
}

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗?
    不可以。const 对象被视为只读对象,它们不允许修改对象的状态。因此,对 const 对象调用非 const 成员函数将违反 const-correctness 规则,因此编译器会报错。

  2. 非const对象可以调用const成员函数吗?
    是的。非 const 对象可以调用 const 成员函数。const 成员函数被设计用于不修改对象状态的情况,因此它们可以安全地被非 const 对象调用。

  3. const成员函数内可以调用其它的非const成员函数吗?
    不可以。const 成员函数被视为只读函数,它们保证不会修改对象的状态。因此,在 const 成员函数内部调用非 const 成员函数将破坏这一保证,编译器会报错。

  4. 非const成员函数内可以调用其它的const成员函数吗?
    是的。非 const 成员函数可以调用 const 成员函数。const 成员函数不会修改对象的状态,因此它们可以安全地被非 const 成员函数调用。

总结:

  • const 成员函数可以访问对象的成员变量,但不能修改它们。
  • 非 const 成员函数可以访问并修改对象的成员变量。
  • const 成员函数之间可以互相调用,但不能调用非 const 成员函数。
  • 非 const 成员函数之间可以互相调用,也可以调用 const 成员函数。

8.取地址及const取地址操作符重载

class Date
{
public:
	Date* operator&()
	{
		return this;
	}

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

private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

//这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

标签:中篇,const,int,默认,month,Date,day,构造函数
From: https://blog.csdn.net/Q2691463021/article/details/137515033

相关文章

  • C#开发的绑定类型默认应用例子 - 开源研究系列文章
          这次在用C#编写一个看图软件小工具,然后其它的基本完成了,就是绑定看图软件到那些个图片扩展名的时候碰到了问题,就是如何将看图软件绑定图片文件的默认应用,以及解绑默认应用。这个涉及到注册表操作,但是找度娘和AI回答,都没得到良好的回复。于是就根据AI的提示,自己研究了......
  • 为什么索引结构默认使用B+Tree?为什么需要注意联合索引中的顺序?最左前缀原则是什么?
    (1)为什么索引结构默认使用B+Tree,而不是B-Tree,Hash,二叉树,红黑树?B-tree:B+Tree相比于B-Tree,所有的数据都存储在叶子节点,并且叶子节点之间用指针相连形成了一个有序链表,这有利于范围查询和全表扫描时连续地读取磁盘上的数据,极大地降低了磁盘I/O次数。而在B-Tree中,数据分布在所有节......
  • Ubuntu 22.04安装在大容量硬盘时默认只启用100G
    背景买了长江致钛的4T硬盘,重新安装Ubuntu22.04后,发现硬盘空间只有100G。本文就是解决这个问题。安装Ubuntu的过程安装到这一步的时候,默认是100G:进入修改界面:修改界面这里显示的是100G:修改成前一步中看到的最大容量1021.996G:在这一步中,可以看到这两行都是1021......
  • C++要点细细梳理——trivial:运算符优先级、switch、临时变量默认赋值等
    1.运算符优先级在C语言中,运算符的优先级决定了在表达式中各个运算符的执行顺序。当一个表达式中有多个运算符时,优先级高的运算符会先被计算。如果两个运算符的优先级相同,那么它们的结合性(从左到右或从右到左)会决定它们的计算顺序。以下是一些基本的C语言运算符优先级(从......
  • 前端【VUE】08-vue【插槽】【默认插槽】【具名插槽】【作用域插槽】
    1、默认插槽默认插槽对应的插槽的名字为default  1、组件目录下components/MyDialog.vue1<template>2<divclass="dialog">3<divclass="dialog-header">4<h3>友情提示</h3>5<spanclass="close......
  • 全栈的自我修养 ———— react router6默认二级路由配置?嵌套时候如何实现默认导航
    在组件嵌套时候小编定义了一个共同组件于/public地址下,小编发现如果直接访问public是只有外部组件的页面,小编目标是访问public时候直接访问index页面,小编找了很多资料最终自己使出来了一个办法如下!!小编自己发现的后来查找到的小编自己发现的即把{pat......
  • 全栈的自我修养 ———— react未知地址默认导航至404页面
    方法1在根目录上添加一个errorElement{path:'/',element:<Navigateto="/public/index"replace/>,errorElement:<div>errorPage</div>},方法2{path:'*',element:<div>errorPage</di......
  • Ubuntu22.04修改默认窗口系统为X11
    Ubuntu22.04安装默认窗口系统为Wayland(通过设置->关于可以看到)。一、用UbuntuonXorg会话登录用户登录时,点“未列出”,输入用户名后,在登录界面底部的齿轮图标中,选择"UbuntuonXorg"作为会话类型登录,系统将为当前会话使用Xorg。如果每次手动选择Xorg登录,系统应该记住选......
  • C++中的类与对象丶this指针和构造函数与析构函数 (一)
    C++中的类与对象和this指针(一)一丶类与对象1.类的引入2.类的实例化3.类的类型的大小I.计算类或对象的大小II.规定空类占一个字节大小4.类中的访问权限5.类中的构造函数和析构函数I.构造函数II.析构函数二丶this指针1.this指针的引出2.this指针的特性3.th......
  • 2024年4月7日-UE5-丰富怪物,结构体、数据表格、构造函数
    打开游戏基础文件夹,新建一个结构  打开后,新建一些变量,这里要看你的怪物用的皮肤是材质还是材质实例,选择不同的   然后再新建一个数据表格   打开怪物表填写一些怪物数据  打开怪物总类蓝图,打开构造函数ConstructionScript这个是预构造,游戏启动之前......