C++类和对象(中)

文章目录

      • 类的六个默认构造函数
        • 构造函数
          • 概念
          • 特性
          • 总结
        • 析构函数
          • 概念
          • 特性
          • 总结
        • 拷贝构造函数
          • 概念
          • 特性
          • 总结
        • 赋值运算符重载
          • 运算符重载
          • 赋值运算符重载
          • 总结
        • const成员函数
          • const修饰类的成员函数
        • 取地址及const取地址操作符重载(了解即可)
        • 日期类的实现

类的六个默认构造函数

上一次的内容我们了解了什么是类,类成员函数的this指针以及类的对象的计算。如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?其实并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数

class Date
{};

C++类和对象(中)_第1张图片

接下来就让我来为你们逐个介绍这些默认成员函数吧

构造函数

概念

在未正式开讲之前我们先来看一段代码。

class Date
{
public:
	void Init(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 d1;
	d1.Init(2020, 5, 1);
	d1.Print();

	Date d2;
	d2.Init(2020, 6, 23);
	d2.Print();
	return 0;

}

在前面学习数据结构的时候我们知道,创建一个变量之后一般都会调用一下初始化函数,如果忘记初始化,程序就有可能会出问题。对于上面Date类,可以通过Init公有的方法给对象设置内容,那么C++是否像C语言那样每次创建一个变量都必须要调用一次初始化函数呢?

答案是否定的,C++中为了使每个对象实例化时都被初始化就有了构造函数

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

特性

构造函数是特殊的成员函数,大家听这个名字可能都以为是构造一个对象出来,但是其实不是的。构造函数虽然名称叫做构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下:

  • 函数名与类名相同。
  • 无返回值(无返回值并不表示返回类型是void,void是有返回值的只不过返回值为空
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数可以重载(可以存在多种初始化方式)

下面我们通过代码来一起看一下构造函数

class Date
{
public:
	//1.无参构造函数
	Date()
	{
		cout << "Date()" << endl;
	}
	
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1;
    //需要注意的是:通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。
    //以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    Date d2();
    
	return 0;
}

C++类和对象(中)_第2张图片

通过上面的调试我们可以看到编译器自动调用了无参的构造函数,但是类里面的成员变量都是随机值。

class Date
{
public:
	//2.带参构造函数
	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 d1(2020,5,1);
	return 0;

}

C++类和对象(中)_第3张图片

通过上面的调试我们可以看到,当类里面有带参构造函数时,并且我们给了对象一个值,对象就会按照我们想要的样子去初始化。

class Date
{
public:
    //3.全缺省构造函数
	Date(int year = 2020, int month = 5, 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()
{
	Date d1;
	return 0;

}

C++类和对象(中)_第4张图片

通过上面的调试我们可以发现这里编译器通过调用了全缺省的构造函数成功创建了一个对象。

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 d1;
	return 0;

}

C++类和对象(中)_第5张图片

可以看到我们没有定义构造函数,对象也创建成功了,这是因为此处调用的是编译器默认生成的构造函数。如果我们显示定义了构造函数,编译器将不再生成。

总结

默认构造函数,很多同学都会以为是我们不写,编译器默认生成的那一个,但是这中理解是不全面的

  • 我们不写,编译器默认生成的

  • 我们自己写的无参的构造函数

  • 我们自己写的全缺省的构造函数

    总结:不用参数就可以调用的构造函数统称为默认构造函数

通过对上面代码的调试我们可以发现,我们显示的写构造函数的情况下,编译器生成的默认构造函数好像没有起什么作用,成员变量依旧是随机值。

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法以及定义好的类型:比如int/char等等,自定义类型就是我们使用class/struct/union自己定义的类型。我们不写,编译器默认生成的构造函数对内置类型不做处理,对于自定义类型会去调用他们的默认构造函数初始化。

大部分情况,都需要我们自己写构造函数因为自动生成的那个不一定好用。如果一定得自己写的话建议写全缺省的构造函数。

析构函数

概念

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

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

特性

析构函数是特殊的成员函数

其特征如下:

  • 析构函数名是在类名前加上字符~
  • 无参数无返回值(不能重载)
  • 一个类有且只有一个析构函数。若为显示定义,编译器会自动生成默认的析构函数
  • 对象生命周期结束时,C++编译器自动调用析构函数

下面我们通过代码来看一下析构函数

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//有同学就会想,Date的析构函数好像没啥意义?->是的
	~Date()
	{
		//资源的清理
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

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

C++类和对象(中)_第6张图片

我们可以看到对象生命周期结束时,C++编译器自动调用了析构函数。但是有的同学就可能会想,Date的析构函数好像没啥意义呀,对于内置类型没有处理。是的,这里Date的析构函数确实没什么意义。那析构函数在哪里有意义呢?像Stack这样的类,析构函数具有重大的意义,我们再来看一段代码

class Stack
{
public:
	Stack(int capacity = 4)
	{
		if (capacity == 0)
		{
			_a = nullptr;
			_size = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int)*capacity);
			_size = 0;
			_capacity = capacity;
		}
        cout << "Stack()" << endl;
	}
	void Push(int x)
	{

	}

	//像Stack这样的类,析构函数具有重大意义
	//因为不清理资源就会造成内存泄漏
	~Stack()
	{
		cout << "~Stack()析构函数" << endl;
		//清理资源
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};

int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	Stack st2;
	st2.Push(4);
	st2.Push(5);
	st2.Push(6);

	return 0;
}

C++类和对象(中)_第7张图片

C++类和对象(中)_第8张图片

通过调试我们可以发现,析构函数在Stack这样的类中具有重大意义,因为不需要我们手动的去调用,对象生命周期结束时,编译器会自动调用,这样就不会发生在C语言中由于我们忘记了调用destroy函数而出现内存泄漏的风险了。

因为对象是定义在函数中,函数调用会建立栈帧。栈帧中的对象构造和析构也要符合后进先出。因此上面代码的顺序是:s1先构造->s2后构造->s2先析构->s1后析构。

讲到这个我们就再来说一个东西吧

数据结构的栈和堆和我们讲的内存分段区域(操作系统)也有一个叫栈和堆,他们之间有什么区别和联系呢?

1.他们之间没有绝对的联系,因为他们属于两个学科的各自的一些命名

2.数据结构栈和系统分段栈(函数栈帧)中的对象都符合后进先出

总结

一个类有且只有一个析构函数。析构函数与构造函数是类似的,我们不写编译器默认生成的析构函数对于内置类型不做处理,对于自定义类型会去调用他们的析构函数清理资源。析构函数满足后进先出的特性

拷贝构造函数

概念

在我们现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。那在创建对象的时候,可否创建一个与一个对象一模一样的新对象呢?

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

特性

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

  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个,且必须使用引用传参,使用传值方式会引发无穷递归调用。

下面我们来说明一下拷贝函数为什么不能使用引用传参

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

	// 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
	Date(const Date& d)
	{
		防止自己这样写错 可以在形参里面加上一个const
		//d._year = _year;
		//d._month = _month;
		//d._day = _day;

		//
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

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


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

int main()
{
	Date d1;
	Date d4(d1);

	return 0;
}

C++类和对象(中)_第9张图片

如果未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储字节序完成拷贝,这种拷贝我们叫做浅拷贝,也称为值拷贝。

class Date
{
public:
	Date(int year = 2020, int month = 5, 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()
{
	Date d1;
	Date d2(d1);
	d1.Print();
	d2.Print();
    
	return 0;
}

C++类和对象(中)_第10张图片

C++类和对象(中)_第11张图片

通过打印结果我们可以看到,d2的打印结果和d1的打印结果一模一样。我们通过调试可以发现,我们不写编译器默认生成的默认构造函数对内置类型也做了处理,内置类型完成了浅拷贝。

我们不写编译器默认生成的拷贝构造函数对于内置类型也会处理,那么这么说来我们是不是就不用写拷贝构造函数了呢?我们接下来再来看一段代码

class Stack
{
public:
	Stack(int capacity = 4)
	{
		if (capacity == 0)
		{
			_a = nullptr;
			_size = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int)*capacity);
			_size = 0;
			_capacity = capacity;
		}
	}
	~Stack()
	{
		free(_a);
		_size = _capacity = 0;
		_a = nullptr;
	}

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

int main()
{
	Stack st;

	Stack copy(st);

	return 0;
}

C++类和对象(中)_第12张图片

运行程序之后我们发现程序崩了,为什么会出现这种情况呢?

我们结合上面学的构造函数和析构函数来分析一下吧。我们不写编译器默认生成的拷贝构造函数对于内置类型是处理的,对于内置类型完成了浅拷贝。**那么会先构造st对象,然后再构造copy对象,先析构copy,再析构st。**我们知道这里的指针a是动态开辟的,对象声明周期结束后,编译器就会先析构copy,再析构st。浅拷贝也就是值拷贝,那么就证明copy对象里面的a与st里面的a是指向同一块空间的,那么先析构copy后,a已经被析构函数置成nullptr了,这时再去析构st就相当于同一块空间被free了两次,所以我们的程序就会崩溃。其次由于指向的是同一块空间,其中一个对象插入删除数据,都会导致另一个对象也插入删除了数据。

总结

默认生成的拷贝构造函数对内置类型完成了浅拷贝,对于自定义类型会去调用它的拷贝构造函数。

像Date这样的类,需要的就是浅拷贝,那么默认生成的拷贝构造函数就已经够用了,不需要我们自己写。

但是像Stack这样的类、需要的是深拷贝,浅拷贝会导致析构两次,程序崩溃等问题。

赋值运算符重载

运算符重载

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

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

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过链接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整形+,不能改变其含义
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1。成员函数的操作符有一个默认的形参this,限定为第一个形参
  • .::sizeof?:.* 注意以上五个运算符不能重载。这个经常在笔试选择题中出现。

下面再说一个东西:

运算符重载跟函数重载,都有用重载这个词,但是两个地方直接没有关联。

1.函数重载时支持定义同名函数

2.运算符重载是为了让自定义类型可以像内置类型一样去使用运算符

下面我们来看一下==运算符重载的代码吧

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

	//bool operator==(Date* this,const Date& d);
	//这里左操作数是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(2021, 1, 1);
	Date d2(2021, 1, 2);

	//内置类型,语言层面就支持运算符
	//自定义类型,默认不支持。C++可以用运算符重载来让类对象支持用某个运算符
	d1 == d2; // d1.operator==(d2); 
	cout << (d1 == d2) << endl; //这里<<优先级高于==,因此需要加一个括号
	//d1 < d2;

	return 0;
}
赋值运算符重载

赋值运算符主要有四点

  • 参数类型
  • 返回值
  • 检测是否自己给自己赋值
  • 返回*this
  • 与拷贝构造函数类似,一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,对于内置类型完成浅拷贝,对于自定义类型会去调用它的赋值运算符重载。
class Date
{
public:
	Date(int year = 2020, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
	Date(const Date& d)
	{
		防止自己这样写错 可以在形参里面加上一个const
		//d._year = _year;
		//d._month = _month;
		//d._day = _day;

		//
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void operator=(const Date& d) // void operator=(Date* this,const Date& d)
	{
			_year = d._year;
			_month = d._month;
			_day = d._day;
	}

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

	//析构函数,我们不需要写,编译器默认生成就够用,对象内没有资源清理
	//默认生成的析构函数也是基本不做什么事情,release下优化,就没了

private:
	int _year;
	int _month;
	int _day;

};

int main()
{
    Date d1(2020, 5, 26);
	Date d2;
	d1.Print();
	d2.Print();

	d1 = d2;
	d1.Print();
	d2.Print();
}

C++类和对象(中)_第13张图片

通过打印结果来看这里好像真的完成了把d1赋给d2,但是事实真的如此吗?这里的赋值运算符重载是不完整的,因为它不能够连续赋值,或者说如果是自己给自己赋值的话就不再需要一步一步的去赋值了,因此上面的代码还可以完善一下。

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

	// 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
	Date(const Date& d)
	{
		防止自己这样写错 可以在形参里面加上一个const
		//d._year = _year;
		//d._month = _month;
		//d._day = _day;

		//
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	
	//d1 = d2; d1.operator(&d1, d2);
	//d1 = d1; //自己给自己赋值
	Date& operator=(const Date& d)  // Date& operator=(Date* this,const Date& d)
	{
		if (this != &d)//检查如果不是自己给自己复制,才需要拷贝
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

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

	//析构函数,我们不需要写,编译器默认生成就够用,对象内没有资源清理
	//默认生成的析构函数也是基本不做什么事情,release下优化,就没了

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

int main()
{
    Date d1(2020, 5, 26);
	Date d2;
    Date d3(2020,10,1);
	d1.Print();
	d2.Print();
    d3.Print();
    
    //连续赋值
	d1 = d2 = d3;
	d1.Print();
	d2.Print();
    d3.Print();
}

C++类和对象(中)_第14张图片

如此一来这个赋值运算符重载才算是真正的大功告成了。

下面我们再通过一段代码来理解对比拷贝构造函数与赋值运算符重载吧

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

	// 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
	Date(const Date& d)
	{
		防止自己这样写错 可以在形参里面加上一个const
		//d._year = _year;
		//d._month = _month;
		//d._day = _day;

		//
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	
	//d1 = d2; d1.operator(&d1, d2);
	//d1 = d1; //自己给自己赋值
	Date& operator=(const Date& d)  // Date& operator=(Date* this,const Date& d)
	{
		if (this != &d)//检查如果不是自己给自己复制,才需要拷贝
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

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

	//析构函数,我们不需要写,编译器默认生成就够用,对象内没有资源清理
	//默认生成的析构函数也是基本不做什么事情,release下优化,就没了

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

int main()
{
    Date d1(2020, 5, 26);
	Date d2;
    
    Date d5(d1);
	d1 = d2; 
	Date d6 = d1;
    
    return 0;
}

你认为main函数里面的这三个是拷贝构造还是赋值运算符重载呢?

第一个Date d5(d1):这个是一个拷贝构造,因为这是拿一个已经存在的对象去构造初始化另一个要创建的对象

第二个 d1 = d2 这是一个赋值运算符重载,赋值运算符重载也是拷贝行为,但是不一样的是,拷贝构造是创建一个对象时,拿同类对象初始化的拷贝。这里的复制拷贝是两个对象已经都存在了,都被初始化过了,现在想把一个对象,复制拷贝给另一个对象。因此是赋值运算符重载

第三个 Date d6 = d1: 这是一个拷贝构造,与第一个一样这是拿一个已经存在的对象去构造初始化另一个要创建的对象

总结

赋值运算符它也是一个默认成员函数,也就是说我们不写编译器会自动生成一个。

编译器默认生成的赋值运算符跟拷贝构造函数的特性是一样的

  • 针对内置类型,会完成浅拷贝,也就是说像Date这样的类不需要我们自己写赋值运算符重载,Stack就得自己写
  • 针对自定义类型,也一样,它会调用它的赋值运算符重载完成拷贝。

拷贝构造与赋值运算符重载的区别:

赋值运算符与拷贝构造都是一种拷贝行为。但是拷贝构造是拿一个已经存在的对象去构造初始化另一个要创建的对象,而赋值运算符重载是两个对象已经都存在了,都被初始化过了,现在想把一个对象,复制拷贝给另一个对象。

const成员函数

const修饰类的成员函数

将const修饰的类成员函数称之为cosnt成员函数,const修饰成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

下面我们来看一段代码

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

	//bool operator==(Date* this,const Date& d);
	//这里左操作数是this指向的调用函数的对象
	bool operator==(const Date& d)
	{
		return (_year == d._year)
			&& (_month = d._month)
			&& (_day == d._day);
	}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

int main()
{
	Date d1(2021, 1, 1);
	Date d2(2021, 5, 2);

	//内置类型,语言层面就支持运算符
	//自定义类型,默认不支持。C++可以用运算符重载来让类对象支持用某个运算符
	cout << (d1 == d2) << endl; //这里<<优先级高于==,因此需要加一个括号
	d1.Print();
	d2.Print();

	return 0;
}

C++类和对象(中)_第15张图片

看到打印结果我们会感到很奇怪,明明这里我们只是通过运算符重载比较了一下d1与d2是否相等,怎么打印出来之后d1的月还改变了呢?

细心的你肯定发现了问题所在,成员函数bool operator==(const Date& d)的实现有问题,平时写代码的时候也可能会烦这种将==写成=的情况。我们都知道编译器能查出来的问题都不是问题,查不出来的问题才叫做大问题这里编译器是不会报错的,但是会影响结果和this指向的对象。

那我们如何解决这个问题呢?

大家可能会想在this指针前面加const不就可以了嘛,但是我们再回过头来想想这个this指针是隐含的,我们不能显示的写在形参里面。那么我们该如何做呢?

为了防止this指针指向的对象被修改,C++类中可以在函数的后面加上const,表示this指针指向的对象不可被修改。

好处:函数中不小心改变的成员变量,编译时就会被检查出来。

建议:成员函数中,不需要改变成员变量,建议都加上const。

bool operator==(const Date& d)const

下面我们再来看几个问题吧

  • const对象可以调用非const成员函数吗?
  • 非const对象可以调用const成员函数吗?
  • const成员函数内可以调用其它的非const成员函数吗?
  • 非const成员函数内可以调用其它的const成员函数吗?

答案:第二个和第四个是可以的,第一个和第三个是不可以的。

这个还是我们与我们前面所学的知识有关——权限可以不变与缩小,但是权限不能放大

取地址及const取地址操作符重载(了解即可)

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 了解一下
	// 他们基本没有被自己实现的价值
	// 除非你不想让别人获取Date类对象的地址,才有必要自己实现
	Date* operator&()
	{
		return this;
	}

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

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

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

日期类的实现

接下来我们通过实现一下日期类来巩固我们上面所学的知识吧。有兴趣的小伙伴可以下去自己实现一下

Date.h

#pragma once
#include
#include

using std::cout;
using std::cin;
using std::endl;

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 0);
	void Print();
	//析构、拷贝构造、赋值重载,可以不写,默认生成的就够用了
	//像Stack这样的类才需要自己写这三个

	// 拷贝构造函数
	//拷贝构造函数的参数只有一个且必须使用引用传参,切记使用传值传参,使用传值方式会引发无穷递归调用。
	// d2(d1)
	Date(const Date& d);

	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);

	
	//d += 100
	Date& operator+=(int day);

	//d + 100
	Date operator+(int day);

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

	//d - 100
	Date operator-(int day);


	//++d -> d.operator++();
	Date& operator++();

	//d++ -> d.operator++(int);
	//int参数不需要给实参,因为没用,它的作用是为了跟前置++构成函数重载
	Date operator++(int);

	//--d -> d.operator--();
	Date& operator--();

	//d-- -> d.operator--(int);
	//int参数不需要给实参,因为没用,它的作用是为了跟前置--构成函数重载
	Date operator--(int);

	// >运算符重载
	bool operator>(const Date& d);

	// ==运算符重载
	bool operator==(const Date& d);

	// >=运算符重载
	bool operator >= (const Date& d);

	// <运算符重载
	bool operator < (const Date& d);

	// <=运算符重载
	bool operator <= (const Date& d);

	// !=运算符重载
	bool operator != (const Date& d);

	// 日期-日期 返回天数
	int operator-(const Date& d);

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

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

//算出这一年这一月的天数
int GetMonthday(int year,int month)
{
	// 数组存储平年每个月的天数
	int dayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int day = dayArray[month];

	//如果是闰年2月就得是29天
	if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
	{
		day = 29;
	}
	return day;
}

//缺省参数不能在声明和定义里面同时出现
Date::Date(int year, int month, int day)
{
	//检查日期的合法性
	if (year >= 0 && month > 0 && month <= 12 && day > 0 && day <= GetMonthday(year,month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期不合法" << endl;
		cout << year << "年" << month << "月" << day << "日" << endl;
	}
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
	this->_year = d._year;
	_month = d._month;
	_day = d._day;
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)  // void operator=(Date* this,const Date& d)
{
	if (this != &d)//检查如果不是自己给自己复制,才需要拷贝
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

//d += 100
Date& Date:: operator+=(int day)
{
	// day是负数,怎么处理?
	//复用-=
	if (day<0)
	{
		*this -= -day;
	}
	else
	{
		//先将原来日期的天数加上我们要加的天数
		_day += day;

		while (_day > GetMonthday(_year, _month))
		{
			_day -= GetMonthday(_year, _month);
			//如果当前天数已经大于该月的最大天数,月进位
			_month++;
			//如果当前月数已经大于12,年进位,并将月置为1
			if (_month > 12)
			{
				_year++;
				_month = 1;
			}
		}
	}
	return *this;
}

d + 100
//Date Date::operator+(int day)
//{
//	Date temp = *this;
//	temp._day += day;
//	while (temp._day > GetMonthday(temp._year, temp._month))
//	{
//		temp._day -= GetMonthday(temp._year, temp._month);
//		temp._month++;
//		while (temp._month > 12)
//		{
//			temp._year++;
//			temp._month = 1;
//		}
//	}
//	return temp;
//}

//d + 100
// 复用
Date Date::operator+(int day)
{
	Date ret(*this);
	// 复用operator+=
	ret += day;

	return ret;
}

//d -= 100
Date& Date::operator-=(int day)
{
	// day是负数,怎么处理?
	//复用+=
	if (day < 0)
	{
		*this += -day;
	}
	else
	{
		//先用当前日期的天数减去我们要减的天数
		_day -= day;
		while (_day <= 0)
		{
			//如果当前天数小于0,我们得从上一月借位
			--_month;
			//如果当前月数等于0,则向年借位,并且将月置为12
			if (_month == 0)
			{
				_year--;
				_month = 12;
			}
			_day += GetMonthday(_year, _month);
		}
	}
	return *this;
}


d - 100
//Date Date:: operator-(int day)
//{
//	Date temp(*this);
//	//先用当前日期的天数减去我们要减的天数
//	temp._day -= day;
//	while (temp._day <= 0)
//	{
//		//如果当前天数小于0,我们得从上一月借位
//		--temp._month;
//		//如果当前月数等于0,则向年借位,并且将月置为12
//		if (temp._month == 0)
//		{
//			temp._year--;
//			temp._month = 12;
//		}
//		temp._day += GetMonthday(temp._year,temp._month);
//	}
//	return temp;
//}

//d1 - 100
//复用
Date Date:: operator-(int day)
{
	//调拷贝构造
	Date temp(*this);
	//复用Date& Date::operator-=(int day)
	temp -= day;
	return temp;
}


// ++d -> d.operator++(&d)
Date& Date:: operator++()
{
	*this += 1;
	return *this;
}

// d++ -> d.operator++(&d,0)
//int参数不需要给实参,因为没用,它的作用是为了跟前置++构成函数重载
Date Date:: operator++(int)
{
	Date temp(*this);
	temp += 1;
	return temp;
}

// --d -> d.operator--(&d)
Date& Date:: operator--()
{
	*this -= 1;
	return *this;
}

//d-- -> d.operator(&d,0)
//int参数不需要给实参,因为没用,它的作用是为了跟前置--构成函数重载
Date Date:: operator--(int)
{
	Date temp(*this);
	temp -= 1;
	return temp;
}


// >运算符重载
//d1 > d2->d1.operator>(&d1, d2)
bool Date:: operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year)
	{
		if (_month > d._month)
		{
			return true;
		}
		else if (_month == d._month)
		{
			if (_day >= d._day)
			{
				return true;
			}
		}
	}
	return false;
}

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


// >=运算符重载
bool Date:: operator >= (const Date& d)
{
	return !(*this < d);
}

// <运算符重载
bool Date:: operator < (const Date& d)
{
	//复用>与=
	return !(*this>d || *this == d);
}

// <=运算符重载
bool Date:: operator <= (const Date& d)
{
	//复用>
	return !(*this > d);
}

// !=运算符重载
bool Date:: operator != (const Date& d)
{
	//复用
	return !(*this == d);
}

// 日期-日期 返回天数
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 day = 0;
	while (Min != Max)
	{
		++Min;
		++day;
	}
	return day * flag;
}

你可能感兴趣的:(C++,c++,开发语言,后端)