C++类和对象中篇

news2024/11/17 13:44:32

在这里插入图片描述

🐇

🔥博客主页: 云曦
📋系列专栏:[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;
}
  • 这两个函数不需要重载,使用编译器生成的默认取地址重载即可,除非是特殊情况,才需要重载,比如:想让别人获取特定的内容!

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

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

相关文章

微服务学习笔记

微服务学习笔记 文章目录 微服务学习笔记认识微服务微服务技术栈微服务学习要点微服务远程调用1)注册RestTemplate2) 服务远程调用RestTemplate Eureka注册中心简介操作过程搭建EurekaServer注册user-service在order-service完成服务拉取 Ribbon负载均衡IRule负载均衡策略饥饿加…

在线扭蛋机小程序:商家稳占市场的新突破口

近几年&#xff0c;扭蛋机进入了爆发期&#xff0c;动漫、游戏的发展更是推动了市场的发展&#xff0c;我国扭蛋机正在蓬勃发展中。 不过&#xff0c;在市场规模扩大下&#xff0c;扭蛋机行业的竞争力也在同时加大&#xff0c;企业商家需要在市场竞争中寻求发展新思路&#xf…

SEQUENTIAL CONSISTENCY----SC

SC模型是最直观的memory consistency model; 什么是single core sequential? 真正的执行顺序&#xff0c;和PO的顺序&#xff0c;是相同的&#xff1b; 什么是multi core sequential consistent? the operations of each individual processor (core) appear in this seq…

【知识碎片】2024_05_08

记录了两个代码和一个C语言switch的题。代码【错误的集合】使用到了hash&#xff0c;【密码检查】是对多个字符串输入的检查&#xff0c;有一些条件细节。两个代码都是关于数组内容操作的题目。 每日代码 1.错误的集合 错误的集合-力扣&#xff08;LeetCode 也是数组的操作&…

EOS智慧营销系统:突破传统,引领智慧营销新潮流

在数字化时代&#xff0c;智慧营销被誉为企业提升竞争力的关键。而EOS智慧营销系统作为一款领先于时代潮流的创新软件&#xff0c;正以其卓越的功能和不可替代的优势&#xff0c;引领着营销智能化的新时代。 EOS智慧营销系统的独特之处在于其全面而细致的市场分析能力。通过大数…

印染工厂5G智能制造数字孪生可视化平台,推进行业数字化转型

印染工厂5G智能制造数字孪生可视化平台&#xff0c;推进行业数字化转型。印染工厂正迈入一个全新的时代&#xff0c;这个时代以5G智能制造数字孪生可视化平台为核心&#xff0c;推动整个行业的数字化转型。不仅是一场技术革命&#xff0c;更是一次产业变革&#xff0c;为印染工…

IP SSL证书申请教程:实现HTTPS加密访问

随着网络安全意识的提高&#xff0c;HTTPS加密访问已经成为网站安全性的重要标准。通过安装SSL证书&#xff0c;网站可以实现数据的加密传输&#xff0c;有效保护用户隐私和数据安全。本文将详细介绍如何为IP地址申请SSL证书&#xff0c;并实现HTTPS加密访问。 一、准备工作 …

常用目标检测算法介绍

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

【JAVA进阶篇教学】第十一篇:Java中ReentrantLock锁讲解

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第十篇&#xff1a;Java中ReentrantLock锁讲解。 在Java并发编程中&#xff0c;保证多线程环境下的数据安全是至关重要的。ReentrantLock 是Java中用于实现线程安全的一种锁机制。本篇博客将深入介绍 ReentrantLock 的原…

商场超市会员日活动怎么群发短信营销

商场超市会员日活动怎么群发短信营销 短信营销的优势 短信营销是一种直接有效的市场推广方式&#xff0c;它具有以下优势&#xff1a; 高开放率&#xff1a;相比于其他营销方式&#xff0c;具有较高的开放率&#xff0c;因为手机用户几乎每天都会查看。 即时性&#xff1a;群…

[单片机课设]十字路口交通灯的设计

题目要求&#xff1a; 模拟交通灯运行情况。南北绿灯亮30秒&#xff0c;南北黄灯亮3秒&#xff0c;东西红灯亮33秒&#xff1b;南北红灯亮33秒&#xff0c;东西绿灯亮30秒&#xff0c;东西黄灯亮3秒&#xff1b;要求数码管同步显示时间的倒计时&#xff0c;用定时器实现延时。…

DELL EMC unity存储系统如何初始化

在客户的存储使用过程中&#xff0c;经常会碰到一些场景需要对存储系统做重新初始化&#xff0c;就是回到出厂时候的配置。比如&#xff0c;客户设备要利旧&#xff0c;二次使用&#xff0c;一般都要回到出厂状态做重新配置的动作。存储严重故障&#xff0c;没有能力修复或者数…

Python AI库pandas读写数据库的应用操作——以sqlite3为例

Python AI库pandas读写数据库的应用操作——以sqlite3为例 本文默认读者具备以下技能&#xff1a; 熟悉python基础知识&#xff0c;vscode或其它编辑工具 已阅读Pandas基础操作文章,了解pandas常见操作 具备自主扩展学习能力 在数据分析和人工智能领域&#xff0c;pandas库和s…

用得助全媒体呼叫中心,让AI落到实处帮品牌做营销

怎么让人工智能落到实处的帮助到我们&#xff1f;我们今天来讲讲中关村科金得助全媒体呼叫中心是怎么让AI帮品牌。 这次聊的案例是知名的护肤品牌&#xff0c;该品牌在中国功能性护肤品市场占有率达到20.5%&#xff0c;这么高的市场占有率客户的咨询量也是非常庞大的&#xff0…

基于C++基础的函数模块

在C中&#xff0c;函数是一段封装了某种功能的代码块&#xff0c;可以在程序的不同地方重复使用。函数定义包含如下组成部分&#xff1a; 函数头&#xff1a;函数头包括函数返回类型、函数名和参数列表。函数返回类型规定了函数返回的数据类型&#xff0c;函数名是函数的唯一标…

【Git】Git在Gitee上的基本操作指南

文章目录 1. 查看 git 版本2. 从Gitee克隆仓库&#xff1a;3. 复制文件到工作目录&#xff1a;4. 将未跟踪的文件添加到暂存区&#xff1a;5. 在本地提交更改&#xff1a;6. 将更改推送到远程仓库&#xff08;Gitee&#xff09;&#xff1a;7. Windows特定提示&#xff1a; 1. …

中仕公考:你的专业在事业编招聘中适合报哪些岗位?

英语专业适合岗位:对外翻译、办公室行政助理、办公室秘书、文化和旅游厅、知识产权局对外纠纷、英语教师等部门 艺术设计适合岗位:电视台、艺术馆、美术馆、博物馆、建筑、市政规划、群艺馆、公共事业管理、水利厅等部门 计算机专业适合岗位:图书馆、气象部门、信息化中心、测…

python代码自动生成器原理 python 生成器原理

python生成器原理剖析 函数的调用满足“后进先出”的原则&#xff0c;也就是说&#xff0c;最后被调用的函数应该第一个返回&#xff0c;函数的递归调用就是一个经典的例子。显然&#xff0c;内存中以“后进先出”"方式处理数据的栈段是最适合用于实现函数调用的载体&…

TMS320F28335学习笔记-时钟系统

第一次使用38225使用了普中的clocksystem例程进行编译&#xff0c;总是编译失败。 问题一&#xff1a;提示找不到文件 因为工程的头文件路径没有包含&#xff0c;下图的路径需要添加自己电脑的路径。 问题二 找不到库文件 例程种的header文件夹和common文件夹不知道从何而来…

Windows下,基于Gradle用Docker发布自己的程序

方案1&#xff1a; windows下打包程序&#xff0c;然后&#xff0c;上传到linux下&#xff0c;生成docker镜像&#xff0c;然后执行。 首先&#xff1a; 由于是采用Gradle管理的项目&#xff0c;打包的时候需要执行build任务。执行完成后&#xff0c;再build\libs目录下应该…