C++类和对象中篇

在这里插入图片描述

🐇

🔥博客主页: 云曦
📋系列专栏:[C++]

💨路漫漫其修远兮 吾将而求索
💛 感谢大家👍点赞 😋关注📝评论

文章目录

  • 📔前言
  • 📔1、类的六个默认成员函数
  • 📔2、构造函数
    • 📙2.1、概念
    • 📙2.2、特性
  • 📔3、析构函数
    • 📙3.1、概念
    • 📙3.2、特性
  • 📔4、拷贝构造函数
    • 📙4.1、概念
    • 📙4.2、特性
  • 📔5、运算符重载
    • 📙5.1、运算符重载
    • 📙5.2、赋值运算符重载
    • 📙5.3、前置++和后置++重载
  • 📔6、日期类的实现
    • 📙6.1、日期类比较运算符重载
      • 📄6.1.1、等于运算符重载
      • 📄6.1.2、不等于运算符重载
      • 📄6.1.3、大于运算符重载
      • 📄6.1.4、小于运算符重载
      • 📄6.1.5、大于等于运算符重载
      • 📄6.1.6、小于等于运算符重载
    • 📙6.2、日期类加天数的运算符重载
      • 📄6.2.1、日期+=天数
      • 📄6.2.2、日期+天数
      • 📄6.2.3、日期-=天数
      • 📄6.2.4、日期-天数
      • 📄6.2.5、GetMonthDay函数
    • 📙6.3、日期-日期运算符重载
    • 📙6.4、前置和后置++/- -运算符重载
      • 📄6.4.1、前置++
      • 📄6.4.1、后置++
      • 📄6.4.1、前置- -
      • 📄6.4.1、后置- -
    • 📙6.5、流插入/流提取函数重载
      • 📙6.5.1、<<运算符重载
      • 6.5.2、>>运算符重载
    • 📙6.6、解决日期类的一些bug
      • 📄6.6.1、构造函数
      • 📄6.6.2、日期-=、-、+=、+天数
  • 📔7、const成员函数
  • 📔8、取地址及const取地址运算符重载

📔前言

  • 上期讲解到了C++一个类是如何定义的即对象的实例化等。这期将讲解类的六个默认成员函数及其实现一个日期类。

📔1、类的六个默认成员函数

  • 如果一个类没有任何成员,那么这个类就称为空类。
  • 空类就真的什么都没有嘛?其实不是,任何一个什么都没有的类里,编译器其实默认生成了6个默认成员函数。
  • 默认成员函数:用户没有写,编译器会自动生成的成员函数叫作默认成员函数。
    在这里插入图片描述

📔2、构造函数

📙2.1、概念

对于Date类:

#include<iostream>
using namespace std;

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;
	Date d2;
	d1.Init(2024, 5, 2);
	d2.Init(2024, 5, 5);

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

	return 0;
}
  • 对于日期类来说,可以通过Init来初始化日期,但每创建一个对象都要自己调用一次,这样未免太过于麻烦了,哪有没有在创建对象时就给这个对象初始化好数据的方法呢?
  • 构造函数是一个特殊的函数,函数名与类名相同,创建对象时由编译器自动调用,以此来保证每个对象都有一个合适的初始值,并且在每个对象的生命周期里只能调用一次

📙2.2、特性

  • 构造函数之所以是一个特殊的函数那是因为,构造函数的名字叫构造但它的作用其实并不是开辟空间,而是给调用它的对象进行初始化工作
  • 构造函数的特征:
  1. 函数名与类名相同。
  2. 没有返回值(不用写返回类型)。
  3. 对象在实例化的同时,会自动调用对应的构造函数 。
  4. 构造函数可以重载。
#include<iostream>
using namespace std;

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

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

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

int main()
{
	Date d1;//会去调用无参构造

	Date d2(2024, 5, 2);//会去调用有参构造

	return 0;
}
  • 注意: 在调用无参构造时,对象后面不能带():Date d3();(加了()这样就成了函数声明了)。
  1. 如果类内没有显示写构造函数,那么C++编译器会自动生成一个无参的构造函数,一旦用户显示写构造函数时,编译器就不会再生成了。
#include<iostream>
using namespace std;

class Date
{
public:
	/*Date() {};

	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/

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

int main()
{
	//可以正常运行,因为编译器自动生成了一个无参的构造函数
	Date d1;

	return 0;
}
  1. 关于编译器默认生成的成员函数:大家会有疑惑,用户没实现函数的情况下,编译器会自动生成默认的构造函数,但在我们打印时,打印出来的都是随机值且去调试时也是随机值,就好像编译器默认生成的构造函数什么事都没做一样。
  • 解答:编译器默认生成的构造函数,并不是什么都没做,C++把类型分为了内置类型和自定义类型,编译器默认生成的构造函数对于内置类型不做处理,对自定义类型会去调用它自己的构造函数。
#include<iostream>
using namespace std;

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
	}

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

//两个栈实现队列
class MyQueue
{
public:

private:
	Stack _pushst;
	Stack _popst;
	int size;
};

int main()
{
	MyQueue q;

	return 0;
}

在这里插入图片描述

  • 上述的两个栈实现队列的类就是一个很好的例子。
  • **注意:**在vs2019及之后的vs编译器里,编译器自动生成的构造函数堆内置类型都做了处理,但在vs2013里是没有处理的。这样对于学习C++的伙伴会有误导性,尽管编译器处理了我们还是当作没处理就好。
  • 在C++11中,针对内置类型成员不做初始化的缺陷,又打了补丁,即:内置类型成员变量在声明时,可以给缺省值。
#include<iostream>
using namespace std;

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
	}

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

//两个栈实现队列
class MyQueue
{
public:

private:
	//自定义类型
	Stack _pushst;
	Stack _popst;
	//内置类型
	int size = 1;
};

int main()
{
	MyQueue q;

	return 0;
}

在这里插入图片描述

  1. 无参构造函数和全缺省的构造函数都叫做默认构造函数,并且默认构造函数只能有一个存在(出现一个以上的默认构造函数会有二义性,编译器不知道调用哪个)。
  • **注意:**无参构造、全缺省构造函数、用户不写编译器自动生成的构造函数,都可以认为是默认构造函数。
#include<iostream>
using namespace std;

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

	//全缺省构造函数
	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;//编译报错:error C2668: “Date::Date”: 对重载函数的调用不明确

	return 0;
}

📔3、析构函数

📙3.1、概念

  • 对于构造函数的学习,我们知道了一个对象是怎么来的,那一个对象又是怎么没的呢?
  • 析构函数:与构造函数的功能相反,析构函数并不是完全给一个对象销毁,局部对象销毁工作是由编译器自动完成的,而对象在销毁时编译器会自动调用析构函数,完成对象的资源清理工作。

📙3.2、特性

  • 析构函数跟构造函数一样也是一个特殊的函数,其特征:
  1. 析构函数名是在类名的前面加上~。
  2. 析构函数没有返回值(不用写返回类型)。
  3. 一个类里只能有一个析构函数,若为显示定义,编译器会默认生成一个析构函数。(注:析构函数不能重载)
  4. 对象的生命周期结束时,编译器会自动调用析构函数。
#include<iostream>
using namespace std;

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

		_top = 0;
		_capacity = capacity;
	}

	void Push(int x)
	{
		if (_top == _capacity)
		{
			exit(-1);
		}

		_a[_top] = x;
		++_top;
	}

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



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

int main()
{
	Stack st;
	st.Push(1);
	st.Push(2);

	return 0;
}
  • 对象还存在时:
    在这里插入图片描述
  • 对象生命周期结束后:
    在这里插入图片描述
  1. 对于编译器自动生成的析构函数,是否完成过一些事情呢?
#include<iostream>
using namespace std;

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

		_top = 0;
		_capacity = capacity;
	}

	void Push(int x)
	{
		if (_top == _capacity)
		{
			exit(-1);
		}

		_a[_top] = x;
		++_top;
	}

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

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

class MyQueue
{
public:

private:
	//自定义类型
	Stack _pushst;
	Stack _popst;
	//内置类型
	int size = 1;
};

int main()
{
	MyQueue q;


	return 0;
}

在这里插入图片描述

  • 从上述可以看出,编译器生成的默认析构函数,对于自定义类型会去调用它自己的析构函数。
  1. 如果类中没有资源申请(不会在堆上开空间),我们可以不写析构函数,直接用编译器生成的默认析构函数,比如:Date类;有资源申请(会在堆上开空间)时,一定要写析构函数,否则会造成资源泄漏,比如:Stack类。

📔4、拷贝构造函数

📙4.1、概念

  • 我们在创建对象时,能否创建一个和已存在的对象一模一样的对象呢?
  • 拷贝构造函数:只有单个形参,该对象是对于本类对象的引用(一般常用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)//错误写法:编译报错,会引发无穷递归
	Date(const Date& d)//正确写法
	{
		cout << "Date(const Date& d)" << endl;
		_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(2023, 11, 11);
	Date d2(d1);
	
	d1.Print();
	d2.Print();

	return 0;
}

在这里插入图片描述

  • 拷贝构造函数传值传参的情况:
    在这里插入图片描述
  1. 若用户未显示写拷贝构造函数,编译器会生成一个拷贝构造函数,但编译器生成的拷贝构造函数里是按字节序完成拷贝的,这种拷贝叫作浅拷贝,或值拷贝。
//Date d1(d2);
//d1为:this;d2为d
Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
  • 浅拷贝对自定义类型带来的问题:
#include<iostream>
using namespace std;

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

		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}

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

class MyQueue
{
public:

private:
	Stack _pushst;
	Stack _popst;
	int size = 1;
};

int main()
{
	MyQueue q1;
	MyQueue q2(q1);
	
	return 0;
}

在这里插入图片描述

  • 上述代码在运行时,程序崩溃了。至于为什么,接下来为大家讲解:
    在这里插入图片描述
  • 解决方法就是把拷贝构造函数写成深拷贝。
#include<iostream>
using namespace std;

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

		_top = 0;
		_capacity = capacity;
	}

	//深拷贝
	Stack(const Stack& d)
	{
		_a = (int*)malloc(sizeof(int) * d._capacity);
		if (nullptr == _a)
		{
			perror("malloc fail");
			exit(-1);
		}

		_capacity = d._capacity;
		_top = d._top;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}

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

class MyQueue
{
public:

private:
	Stack _pushst;
	Stack _popst;
	int size = 1;
};

int main()
{
	MyQueue q1;
	MyQueue q2(q1);

	return 0;
}
  • 注意: 用户没写拷贝构造函数时,编译器会默认生成一个拷贝构造函数,这个拷贝构造函数内置类型成员会进行值拷贝,自定义类型成员会调用它自己的拷贝构造函数。
  • 关于深浅拷贝的问题,C++内存管理篇会详细讲解。
  1. 编译器默认生成的拷贝构造函数是值拷贝,那么我们还要显示实现吗?
  • 解答:像Date这样,类里没有申请资源的类,就没必要写了。
  • **注意:**一旦涉及申请资源的类,一定要写拷贝构造函数且实现的是深拷贝,否则就是浅拷贝。
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, 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()
{
	Date d1(2024, 5, 2);
	Date d2(d1);

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

	return 0;
}

在这里插入图片描述

  1. 拷贝构造函数的典型使用场景:
  • 使用已存在的对象去初始化一个新对象。
  • 函数参数类型为类类型对象(传值传参)
  • 函数返回值类型为类类型(传值返回)
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
	}

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

	~Date()
	{
		cout << "~Date()" << endl;
	}


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

Date TestDate(Date dd)
{
	Date tmp(dd);
	return tmp;
}

int main()
{
	Date d1(2023, 12, 12);
	Date d2(d1);
	cout << endl;

	TestDate(d1);

	return 0;
}

在这里插入图片描述

  • 为了提高程序的效率,一般对象传参时,尽量使用引用类型,返回时看实际场景,能用引用就尽量用引用。

📔5、运算符重载

📙5.1、运算符重载

  • 在C++中,为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数。
  • 函数名字为:关键字operator后面紧跟需要重载的运算符符号
  • 函数原型:返回类型 operator操作符(参数列表)。
  • 注意:
  • 不能连接其他的符号来创建新的运算符重载,比如:operator@、operator#。
  • 重载操作符必须有一个类类型参数。
  • 用于内置类型的运算符其含义不能改变,比如实现+运算符重载,内部却实现的是减法。
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数是一个隐含的this指针。
  • ( .*、::、sizeof、?:、. ) 注意以上5个运算符不能重载。
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==(Date& d1, Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
  • 既然在类外无法访问到类的成员变量,那么我们就定义到类里面,让运算符重载变成类的成员函数,这样就可以访问到类的成员变量了。
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//需要注意的是:左操作数是this,右操作数是指向调用的对象
	//bool operator==(Date* this, Date& d)
	bool operator==(Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};
  • 对于运算符的重载大家些许会有疑问,既然有运算符重载那么每个类都要把能实现的运算符重载都实现吗?
  • 解答:其实并不是,对于一个类来说:实现什么的运算符重载,是看这个运算符重载对于这个类有没有实现的意义。比如:日期类里实现日期+日期有用途吗?,日期+日期没啥用嘛就不需要实现了。
  • 但如果是:日期加天数呢?唉!日期加天数不就是看多少天后的日期嘛,嗯…,这个运算符重载对日期类是有意义的,那么就实现它。
  • 思路:
    在这里插入图片描述
#include<iostream>
using namespace std;

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

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

	//需要注意的是:左操作数是this,右操作数是指向调用的对象
	//bool operator==(Date* this, Date& d)
	bool operator==(Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	//因为12个月的天数除了二月其他都是固定的
	//所以写一个获取本年本月内天数的函数
	int GetMonthDaye(int year, int month)
	{
		int Month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		//判断本年的2月是否是闰年还是平年
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
		{
			return 29;
		}

		return Month[month];
	}

	//这里传值返回会生成临时对象返回,等于会调用一次拷贝构造函数
	//用引用返回就不一样,因为是返回this,
	//this出了函数还在引用返回没有问题且引用返回不用调用拷贝构造
	Date& operator+(int day)
	{
		_day += day;

		//循环结束条件:_day小于或等于当月的天数时,循环结束
		while (_day > GetMonthDaye(_year, _month))
		{
			_day -= GetMonthDaye(_year, _month);
			++_month;
			if (_month == 13)//判断月份是否大于12月
			{
				++_year;
				_month = 1;
			}

		}

		//this是d1对象的指针,*this就是d1
		return *this;
	}

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

int main()
{
	Date d1(2024, 5, 3);
	d1 + 50;
	d1.Print();

	return 0;
}

在这里插入图片描述

  • 上述的代码,仔细的朋友们可能已经发现问题了,这里实现的是什么,是+的运算符重载,+在内置类型里是不会改变变量本身的,而日期类实现的+运算符重载是把d1本身给修改了。显而易见这不是+运算符的重载,而是+=运算符的重载。
#include<iostream>
using namespace std;

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

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

	bool operator==(Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	int GetMonthDaye(int year, int month)
	{
		int Month[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 Month[month];
	}

	Date& operator+=(int day)
	{
		_day += day;

		while (_day > GetMonthDaye(_year, _month))
		{
			_day -= GetMonthDaye(_year, _month);
			++_month;
			if (_month == 13)
			{
				++_year;
				_month = 1;
			}

		}

		return *this;
	}

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

int main()
{
	Date d1(2024, 5, 3);
	d1 += 50;
	d1.Print();

	return 0;
}
  • 嗯…这样就好多了。
  • 那+运算符重载要咋样实现呢?这里有的人就会说,把上面的代码复制粘贴一下,然后定义一个临时对象修改这个临时对象即可,this指向的对象就不用修改,最后返回这个临时对象。
Date operator+(int day)
	{
		Date tmp(*this);
		tmp._day += day;

		while (tmp._day > GetMonthDaye(tmp._year, tmp._month))
		{
			tmp._day -= GetMonthDaye(tmp._year, tmp._month);
			++tmp._month;
			if (tmp._month == 13)
			{
				++tmp._year;
				tmp._month = 1;
			}

		}

		return tmp;
	}

在这里插入图片描述

  • 这样确实可以,但。。。是不是过于繁琐了,我们不是已经实现了+=运算符重载了吗,直接让+复用+=就好了。
Date operator+(int day)
	{
		Date tmp(*this);
		tmp += day;
		return tmp;
	}

📙5.2、赋值运算符重载

  • 下面的代码是拷贝构造还是赋值拷贝?
void TestDate1()
{
	Date d1(2024, 12, 12);
	Date d2 = d1;
}
  • 答案是:拷贝构造。
  • 讲解:
void TestDate1()
{
	Date d1(2024, 12, 12);
	//一个已存在的对象去拷贝初始化另一个对象,拷贝构造
	Date d2 = d1;

	//两个已存在的对象,赋值拷贝
	d1 = d2;
}
  1. 赋值运算符重载的格式:
  • 参数类型:const type&,传递引用提供程序的效率。
  • 返回类型:type&,因为返回的是this指针,出了函数还存在所以可以用引用返回。
  • 检测是否是自己给自己赋值。
  • 返回*this,复合连续赋值的含义。
  • 加const是避免一下的情况出现:
//*this为d2,d为d1
//原本是d2 = d1,加const是为了避免写成d1 = d2
Date& operator=(const Date& d)
	{
		d._year = _year;
		d._month = _month;
		d._day = _day;
	}
  • 赋值运算符重载
bool operator!=(Date& d)
	{
		//复用==运算符重载
		return !(*this == d);
	}

	Date& operator=(const Date& d)
	{
		//比较地址是否不相同
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}
  1. 赋值运算符只能重载成类的成员函数 ,不能重载成全局函数
//1.赋值运算符重载成全局函数,因为没有this指针需要给两个参数
//2.全局函数访问不到类内的成员变量,因为是私有的
//3.会编译报错:error C2801: “operator =”必须是非静态成员
//原因:赋值运算符重载是六个默认成员函数中的一个,用户不实现编译器会默认自动生成一个
//而编译器默认生成后的赋值运算符与我们实现的全局赋值运算符重载冲突了,
//所以赋值运算符重载只能是类的成员函数
Date& operator=(Date& d1, Date& d2)
{
	if (&d1 != &d2)
	{
		d1._year = d2._year;
		d1._month = d2._month;
		d1._day = d2._day;
	}

	return d1;
}
  1. 用户不实现赋值运算符重载,编译器会生成一个默认的赋值运算符重载,以值的方式逐字节拷贝过去。(注:编译器生成的默认赋值运算符,内置类型会进行值拷贝,对于自定义类型会去调用对于类的赋值运算符重载)。
class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	void Push(int x)
	{
		if (_top == _capacity)
		{
			exit(-1);
		}

		_a[_top] = x;
		++_top;
	}

	Stack& operator=(const Stack& st)
	{
		cout << "Stack & operator=(const Stack & st)" << endl;
		return *this;
	}

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

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

class MyQueue
{
public:
	MyQueue(int size = 1)
	{
		_size = size;
	}



private:
	//自定义类型
	Stack _pushst;
	Stack _popst;
	//内置类型
	int _size;
};


int main()
{
	MyQueue q1(10);
	MyQueue q2(20);
	q1 = q2;

	return 0;
}

在这里插入图片描述

  • 注意:如果实现的类里没有申请空间的成员函数,那么就不需要去实现赋值运算符重载里,用编译器默认生成的赋值运算符重载即可。

📙5.3、前置++和后置++重载

  • ++,嗯…简单直接去复用+=即可。
//前置++
	Date& operator++()
	{
		*this += 1;
		return *this;
	}
  • 但主要的问题是:后置++怎么进行重载呢?
  • 有的伙伴也许会直接想,直接把++放在operator前面就行Date& ++operator(),很可惜,编译错误,没有这样的语法。那要怎样才能重载前置++呢?
  • 其实是这样的,C++为了解决后置++这个问题,弄出了占位符这个语法:
  • 在函数的形参用一个类型来进行重载,一般使用int来当占位符,但其他类型也是可以的(只是正常都用int来当) 。Date operator++(int),这个函数的参数在实参传形参时可以传也可以不传。
//后置++
	Date operator++(int)
	{
		Date tmp = *this;
		*this += 1;
		return tmp;
	}
  • 注意:
  1. C++已经强制了,无参的重载为前置++,有参的重载为后置++。
  2. 内置类型的前置和后置++区别不大,但自定义类型进行++时,要考虑好再选择用前置还是后置,因为后置++的返回值必须是值返回,值返回会造成一次拷贝构造。

📔6、日期类的实现

  • 既然是实现日期这个类,那么先把日期类进行一下分文件实现。
  • Date.h(放声明)
#pragma once
#include<iostream>
using std::cout;
using std::cin;
using std::endl;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1);
	Date(const Date& d);

	bool operator==(Date& d);
	bool operator!=(Date& d);

	int GetMonthDaye(int year, int month);
	void Print();

	Date& operator+=(int day);
	Date operator+(int day);


	Date& operator=(const Date& d);

	Date& operator++();
	Date operator++(int);

private:
	int _year;
	int _month;
	int _day;
};
  • Date.cpp(放函数的定义)
#include"Date.h"

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

Date::Date(const Date& d)
{
	cout << "Date(const Date& d)" << endl;
}

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

bool Date::operator==(Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

int Date::GetMonthDaye(int year, int month)
{
	int Month[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 Month[month];
}

Date& Date::operator+=(int day)
{
	_day += day;

	while (_day > GetMonthDaye(_year, _month))
	{
		_day -= GetMonthDaye(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}

	}

	return *this;
}

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

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

Date& Date::operator=(const Date& d)
{
	//比较地址是否不相同
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}

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

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

📙6.1、日期类比较运算符重载

  • >、<、!=、==、>=、<=,这些比较运算符对日期类都是有用的。

📄6.1.1、等于运算符重载

bool Date::operator==(Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

📄6.1.2、不等于运算符重载

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

📄6.1.3、大于运算符重载

//把正确的都判断出来,剩下的都表示d1不大于d2
bool Date::operator>(Date& d)
{
	//年大于就返回true
	if (_year > d._year)
	{
		return true;
	}
	//年相等且月大于月就返回true
	else if (_year == _year && _month > d._month)
	{
		return true;
	}
	//年相等且月相等且天大于天就返回true
	else if (_year == _year && _month == d._month && _day > d._day)
	{
		return true;
	}

	return false;
}

📄6.1.4、小于运算符重载

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

📄6.1.5、大于等于运算符重载

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

📄6.1.6、小于等于运算符重载

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

📙6.2、日期类加天数的运算符重载

  • 一个日期+=或+天数,可以知道多少天后日期。
  • 一个日期-=或-天数,可以知道多少天前的日期。

📄6.2.1、日期+=天数

Date& Date::operator+=(int day)
{
	_day += day;

	while (_day > GetMonthDaye(_year, _month))
	{
		_day -= GetMonthDaye(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}

	}

	return *this;
}

📄6.2.2、日期+天数

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

📄6.2.3、日期-=天数

在这里插入图片描述

Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDaye(_year, _month);
	}

	return *this;
}

在这里插入图片描述

📄6.2.4、日期-天数

Date Date::operator-(int day)
{
	//和+运算符重载一样
	//复用-=运算符重载
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

在这里插入图片描述

📄6.2.5、GetMonthDay函数

int Date::GetMonthDaye(int year, int month)
{
	int Month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	//小细节:把2月放到前面判断,可以减少其他月的判断
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
	{
		return 29;
	}

	return Month[month];
}

📙6.3、日期-日期运算符重载

  • 这个运算符重载还是有用的,日期减日期得到相差的天数。
int Date::operator-(Date& d)
{
	//左大右小相差的天数是正数
	int flag = 1;
	//d1 - d2
	//假设左大右小
	Date Max = *this;
	Date Min = d;
	//假设错了,改一下
	if (*this < d)
	{
		Max = d;
		Min = *this;
		flag = -1;//左小右大相差的天数是负数
	}

	int n = 0;
	//循环累加天数
	while (Min != Max)
	{
		++Min;
		++n;
	}

	return n * flag;
}

📙6.4、前置和后置++/- -运算符重载

📄6.4.1、前置++

Date& Date::operator++()
{
	*this += 1;
	return *this;
}

📄6.4.1、后置++

Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

📄6.4.1、前置- -

Date& Date::operator--()
{
	*this -= 1;//复用-=
	return *this;
}

📄6.4.1、后置- -

Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

📙6.5、流插入/流提取函数重载

  • 在这个日期类的实现到现在,始终是在用实现的Print成员函数来输出日期,那么为什么不用cout打印呢。
  • 其实<<、>>,流插入和流提取也是可以进行运算符重载的,当我们要用流插入输出一个类时,编译器不知道你要怎么输出这个类,所以用流插入输出时需要先实现这个运算符的重载。
  • 注意:
  1. <<运算符重载的返回值是流插入,即:ostream
  2. >>运算符重载的返回值是流提取,即:istream

📙6.5.1、<<运算符重载

  • 类内实现
//引用返回,支持<<在一行内可以连续插入
ostream& Date::operator<<(ostream& out)
{
	out << _year << "-" << _month << "-" << _day << endl;

	return out;
}
  • 实现完后会发现问题:
void TestDate4()
{
	Date d1(2024, 12, 12);
	cout << d1 << endl;
	//编译报错:error C2679: 二元“<<”: 
	//没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
}
  • 编译报错,怎么回事!其实类内实现有个特点,语法上的传参:cout << d1 << endl; -> cout << d1.operator(cout) << endl; -> cout << d1.operator(&d1,cout) << endl;
  • 类的成员函数传参时,对象一定是占用第一个参数位置的(this指针永远是第一个参数)。
  • 类内实现的输入输出运算符重载,得这样调用d1<<cout<<endl,可这样就不易读懂,所以输出输入运算符重载,只能在类外实现,让插入或提取流做第一个参数。
  • 类外实现
ostream& operator<<(ostream& out, Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;

	return out;
}
  • 这样又出现问题了,类的成员变量是私有的无法访问到,写成公有的就不符合C++封装的概念了。
  • 解决方法:
  1. 写提供成员变量值的函数出来,例如:GetYear、GetMonst、GetDay。
  2. 友元(关键字:friend),举例:我的是我的,你的也是我的,但我的不是你的(这里只是让大家了解一下友元,具体内容会到类和对象下讲解)。
  • 这里用友元来解决这里的问题。
    在这里插入图片描述

6.5.2、>>运算符重载

  • 提取流运算符重载也跟插入流运算符一样,必须在类外实现成全局函数,通过友元方式访问类的成员变量。
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

📙6.6、解决日期类的一些bug

📄6.6.1、构造函数

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
  • 这里实现的构造函数有一个问题,年月日都没有小于等于0和月大于12,天大于当月的情况,所以构造函数这里要检查一下初始化的日期是否正确。
  • 不合法的日期我们可以采集一下任意一个措施:
  1. assert(false); 直接强制报错
  2. exit(-1); 直接终止程序
  3. 先打印一下日期,然后打印非法日期。
  • 这里我采用第三中措施。
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (_year <= 0 || _month <= 0 
		|| _month > 12 || _day <= 0 
		|| _day > GetMonthDaye(year, month))
	{
		Print();
		cout << "非法日期" << endl;
	}
	
}

📄6.6.2、日期-=、-、+=、+天数

  • 一个日期 + 天数可以得到这个天数后的日期,如果加的是负数,那就成这个天数前的日期了,提前处理一下比较好。
  • -=运算符重载处理
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}

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

		_day += GetMonthDaye(_year, _month);
	}

	return *this;
}
  • -运算符重载处理
Date Date::operator-(int day)
{
	Date tmp(*this);
	if (day < 0)
	{
		return tmp += (-day);
	}

	tmp -= day;
	return tmp;
}
  • +=运算符重载处理
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= (-day);
	}

	_day += day;

	while (_day > GetMonthDaye(_year, _month))
	{
		_day -= GetMonthDaye(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}

	}

	return *this;
}
  • +运算符重载处理
Date Date::operator+(int day)
{
	Date tmp(*this);
	if (day < 0)
	{
		return tmp -= (-day);
	}

	tmp += day;
	return tmp;
}

📔7、const成员函数

  • const修饰的“成员函数”叫作const成员函数,const修饰的成员函数实际上修饰的是成员函数隐含的this指针,表示在该成员函数内不能修改任何成员变量。
  • 注意:
  1. 非const对象也可以调用const成员函数。
  2. const成员函数是只读状态,建议大家实现成员函数时,如果成员变量不做修改那么就加上const,增加程序的安全性。
  3. 像流插入、流提取这样在类外重载的函数,就不用加const了,因为类外实现的函数没有隐含的this指针。
  • 在函数声明后面加const成为const成员函数。例如:日期类的比较运算符重载,不修改成员变量那么是可以加const的。
class Date
{
	friend ostream& operator<<(ostream& out, Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:
	Date(int year = 1, int month = 1, int day = 1);

	bool operator==(Date& d) const;
	bool operator!=(Date& d) const;
	bool operator>(Date& d) const;
	bool operator<(Date& d) const;
	bool operator>=(Date& d) const;
	bool operator<=(Date& d) const;

	int GetMonthDaye(int year, int month) const;
	void Print() const;

	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	int operator-(Date& d);
	Date& operator=(const Date& d);

	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);

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

ostream& operator<<(ostream& out, Date& d);
istream& operator>>(istream& in, Date& d);
  • 在类里加const时需要注意以下几个问题:
  1. const对象可以调用非const成员函数吗? – 不可以(权限的放大)
  2. 非const对象可以调用const成员函数吗?-- 可以(权限的缩小)
  3. const成员函数内可以调用其它的非const成员函数吗? – 不可以(权限的放大)
  4. 非const成员函数内可以调用其它的const成员函数吗? – 可以(权限的缩小)

📔8、取地址及const取地址运算符重载

	Date* operator&();
	const Date* operator&() const;
  • 这两个默认成员函数,一般不用实现,因为编译器会默认生成。
Date* Date::operator&()
{
	return this;
}

const Date* Date::operator&() const
{
	return this;
}
  • 这两个函数不需要重载,使用编译器生成的默认取地址重载即可,除非是特殊情况,才需要重载,比如:想让别人获取特定的内容!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

源代码怎么加密防泄漏?9种方法教会你

想做源代码加密防止泄漏&#xff0c;首先要了解程序员可以通过哪些方式将源代码传输出去&#xff01; 程序员泄密的常见方式 物理方法&#xff1a; — 网线直连&#xff0c;即把网线从墙上插头拔下来&#xff0c;然后和一个非受控电脑直连; — winPE启动&#xff0c;通过光盘…

怎么写毕业论文的? 推荐4个AI工具

写作这件事一直让我们从小学时期就开始头痛&#xff0c;初高中时期800字的作文让我们焦头烂额&#xff0c;一篇作文里用尽了口水话&#xff0c;拼拼凑凑才勉强完成。 大学时期以为可以轻松顺利毕业&#xff0c;结果毕业前的最后一道坎拦住我们的是毕业论文&#xff0c;这玩意不…

常用目标检测算法介绍

目录 1. 常用目标检测算法 2. R-CNN 模型 3. Fast R-CNN 模型 4. Faster R-CNN 模型 5. SSD 模型 1. 常用目标检测算法 在深度学习框架下&#xff0c;目标检测方法通常涉及图像定位和分类两个关键方面。有两种主要的解决方法&#xff1a;一种是一阶&#xff08;one-stage&…

去除快捷方式的箭头图标

文章目录 取消箭头显示恢复箭头显示结果展示 添加快捷方式之后&#xff0c;会有箭头图标&#xff0c;部分场景下看着较为难受&#xff1a; 可以通过如下方式取消/显示箭头&#xff1a; 取消箭头显示 新建一个.bat文件&#xff0c;内部加入如下命令&#xff1a; reg add "…

2024北京市人工智能大模型行业应用分析报告

来源&#xff1a;北京市科学技术委员会 方向一为基于AIGC技术的智能审计合规研究&#xff0c;由北京银行提出&#xff0c;以 提高审计工作效率和准确性为核心目标&#xff0c;需要参赛企业针对检查内容&#xff0c; 利用大模型技术寻找并给出相关现象涉及的制度名称及相关原文…

element ui的确认提示框按钮样式修改

修改确认提示框的默认按钮样式&#xff0c;使用css强制修改 例&#xff1a; js代码&#xff1a; deleteUser(params){this.$confirm("您确定要删除吗&#xff1f;此操作无法撤销并且将永久删除所有数据。", "提示", { type: "warning", cancel…

新款锐科达SV-2402VP SIP广播音频模块18123651365支持RTP流音频广播

一、模块介绍 SV-2402VP网络音频模块是一款通用的独立SIP音频播放模块&#xff0c;其带2*15W功放音频输出&#xff0c;可以轻松地嵌入到OEM产品中。该模块对来自网络的SIP协议及RTP音频流进行解码播放。 该模块支持多种网络协议和音频解码协议&#xff0c;可用于VoIP和IP寻呼…

解决Tomcat日志乱码问题

1、 修改apache-tomcat-10.1.23/conf/server.xml URIEncoding"UTF-8"2、 修改apache-tomcat-10.1.23/conf/logging.properties # java.util.logging.ConsoleHandler.encoding UTF-8 java.util.logging.ConsoleHandler.encoding GBK参考 https://www.jb51.net/ar…

一键接入电商API数据接口京东API通过商品ID、URL采集商品详情页实时数据API接入指南

要一键接入京东电商API数据接口并采集商品详情页的实时数据&#xff0c;您需要按照以下步骤操作&#xff1a; 注册账号&#xff1a;您需要注册一个账号。完成注册后&#xff0c;您将获得用于API认证的ApiKey和ApiSecret。选择API&#xff1a;根据自己的需求选择合适的API服务。…

域控安全 ----> Ntds.dit文件抓取

大家还记得内网渗透的初衷吗&#xff1f;&#xff1f;&#xff1f; 找到域馆&#xff0c;拿下域控&#xff01;&#xff01; 拿下了域控就是拿下了整个域&#xff01;&#xff01; 但是大家知道拿下域环境之后应该怎么操作吗(灵魂拷问)&#xff1f;&#xff1f;&#xff1f; …

科研综述写作技巧:三大要领与实战应用

​在科研工作中&#xff0c;综述不仅是研究者对既有知识体系的梳理与整合&#xff0c;更是为接下来的研究提供方向与思路的重要工具。写好一篇综述&#xff0c;需要掌握三大要领。 要领一&#xff1a;明确目标与定位 在开始综述写作之前&#xff0c;首先要明确综述的目标与定位…

Spring 常用的注入方式有什么?

Spring 是一个非常流行的 Java 开发框架&#xff0c;它提供了多种依赖注入&#xff08;Dependency Injection&#xff09;的方式&#xff0c;使得开发者可以轻松地管理应用程序中的组件依赖关系。在 Spring 中&#xff0c;常用的注入方式主要包括构造器注入、Setter 方法注入、…

全网最全:一文入门最热的LLM应用开发框架LangChain

f#### 1. LangChain 简介 1.1. LangChain 发展史 LangChain 的作者是 Harrison Chase&#xff0c;最初是于 2022 年 10 月开源的一个项目&#xff0c;在 GitHub 上获得大量关注之后迅速转变为一家初创公司。2017 年 Harrison Chase 还在哈佛上大学&#xff0c;如今已是硅谷的…

【Django项目】 通过AI实现视频转文字

使用Django构建一个视频—>文字 转换器&#xff1a; 输入视频地址&#xff0c;通过OpenAI的接口分析视频&#xff0c;返回视频内容的文字。 视频 代码 运行此程序&#xff0c;需要OpenAI-key和设置Postgres数据库。 &#xff08;吐槽一下&#xff0c;作者把这些信息都直接写…

【数据结构初阶】直接插入排序

最近浅学了直接插入排序&#xff0c;写个博客做笔记&#xff01;笔记功能除外若能对读者老爷有所帮助最好不过了&#xff01; 直接插入排序是插入排序的一种&#xff0c;那么介绍直接插入排序之前先介绍一下常见的排序算法&#xff01; 目录 1.常见的排序算法 2.直接插入排…

WPF鼠标拖拽的最佳实现

WPF鼠标拖拽的最佳实现 在很多项目中都会遇到鼠标拖拽控件移动的需求&#xff0c;常见的有从在列表中拖拽列表项移动&#xff0c;拖拽控件移动等。 本文将介绍2种拖拽的简单的实现 列表项的拖拽 本文将使用 gong-wpf-dragdrop 这个github上的库来实现列表的拖拽的效果&…

Python从0到POC编写--SQL注入

SQL注入POC编写。 环境&#xff1a; win10 &#xff0c;phpStudy &#xff0c;python3.7 &#xff0c;sqli-labs 虚拟域名&#xff1a; www.sql.com 简单的POC&#xff1a; 说起来也简单&#xff0c; 就是请求–>响应&#xff0c; 然后再判断返回信息是否存在注入。 本…

【高阶数据结构(二)】初识图论

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:高阶数据结构专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Go语言知识   &#x1f51d;&#x1f51d; 高阶数据结构 1. 前言2. 图的基…

Spring底层入门(七)

1、异常处理 在DispatcherServlet中&#xff0c;doDispatch(HttpServletRequest request, HttpServletResponse response) 方法用于进行任务处理&#xff1a; 在捕获到异常后没有立刻进行处理&#xff0c;而是先用一个局部变量dispatchException进行记录&#xff0c;然后统一由…

[Cpp]类和对象 | 实现日期类

标题&#xff1a;[Cpp]类和对象 | 实现日期类 水墨不写bug 正文开始&#xff1a; 类和对象是Cpp面向对象编程区别于C的面向过程编程的重要的一部分&#xff0c;因此打好坚实的类和对象的基础对于深入学习Cpp语言是比较明智的。 本文通过实现简单的日期类来加深对类和对象的理解…