【C++初阶】类和对象(三)

大家好我是沐曦希

文章目录

  • 1.赋值运算符重载
    • 1.1 运算符重载
      • 1.1.1 运算符重载实现
        • >
        • >=,<,<=,!=
        • +=,+
    • 1.2 赋值运算符重载
    • << 和>>重载
    • 1.3 前置++和后置++重载
  • 2.const成员
  • 3.取地址及const取地址操作符重载

1.赋值运算符重载

1.1 运算符重载

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

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

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

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
//全局operator==
class Date
{
public:
	Date(int year = 1970, 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, 10, 15);
	Date d2(2022, 10, 14);
	//cout << d1 == d2 << endl;
	//<<--流插入,运算符优先级大于==,所以d1==d2要加括号
	cout << (d1 == d2) << endl;//d1 == d2 会转换成operator==(d1,d2)
	cout << operator==(d1, d2) << endl;//也可以显示调用,一般不会这样
	return 0;
}

【C++初阶】类和对象(三)_第1张图片

class Date
{
public:
	Date(int year = 1970, 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;
		}
	}
	// bool operator==(Date* this, const Date& d2)
	// 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date& d2)
	{
		return _year == d2._year
			&& _month == d2._month
			&& _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

1.1.1 运算符重载实现

>

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;
	}

>=,<,<=,!=

bool operator>=(const Date& d)
{
	return *this == d || *this > d;
}
bool operator<(const Date& d)
{
	return !(*this >= d);
}
bool operator<=(const Date& d)
{
	return !(*this > d);
}
bool operator!=(const Date& d)
{
	return !(*this == d);
}

+=,+

int GetMonthDay(int year, int month)
{
	int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if ((((year % 4 == 0) && (year % 100 != 0)) 
		|| (year % 400 == 0)) && month == 2)
	{
		return 29;
	}
	else
	{
		return MonthDayArray[month];
	}
}
Date& operator+=(int day)
{
	_day += day;
	while (day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			_year += 1;
			_month = 1;
		}
	}
	return *this;
}
Date operator+(int day)
{
	Date ret(*this);
	ret._day += day;
	return ret;
}

1.2 赋值运算符重载

  1. 赋值运算符重载格式
    1.1 参数类型:const T&,传递引用可以提高传参效率
    1.2 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
    1.3 检测是否自己给自己赋值
    1.4 返回*this :要复合连续赋值的含义

赋值重载既是默认成员函数,又是运算符重载。

void TestDate()
{
	Date d1;
	Date d2(2022, 10, 9);

	Date d3(d2);//拷贝构造(初始化)  一个初始化另一个马上要创建的对象
	 
	d1 = d2;//赋值重载(复制拷贝)     已经存在两个对象之间拷贝
}

赋值重载实现:

//d1 = d2
Date& operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

对于赋值重载(复制拷贝)和拷贝构造的区别:
赋值重载(复制拷贝) 已经存在两个对象之间拷贝
拷贝构造(初始化) 一个初始化另一个马上要创建的对象

赋值运算符重载不写,编译器会生成一个默认的,对内置类型进行值拷贝(即浅拷贝),对自定义类型调用器赋值重载。
但是如果用编译器默认的赋值运算符重载又会发生什么?

#include
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(const Stack& st)
	{
		cout << "Stack(const Stack&st" << endl;

		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _a)
		{
			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;
};
void TestStack()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);

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

	st1 = st2;
}
int main()
{
	TestStack();
	return 0;
}

【C++初阶】类和对象(三)_第2张图片
发生了崩溃,实际上这里的问题还是析构了两次(直接把st2的空间拷贝给st1,st1和st2的_a指向了同一个)发生了崩溃,但是此时的st1的内存还发生了泄露。栈的赋值运算符重载实现:

//st1 = st2;
//st1 = st1;
Stack& operator = (const Stack& st)
{
	if (this != &st)
	{
		free(_a);
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}
	return *this;
}

那么要写析构函数的,就必须要显示写赋值构造函数。

<< 和>>重载

cin的类型是istream
cout的类型是ostream
其中cout,cerr和clog的功能相似
【C++初阶】类和对象(三)_第3张图片

对于<<和>>,我们一般不写成员函数,因为this默认抢了第一个参数位置,Date对象就是左操作数,不符合使用习惯和可读性,这点值得我们去关注哈。但是如果写在全局,又引发了另一个问题:

如何去访问类的私有属性?
1.直接把私有权限改为公共权限
2.在类中设置get和set方法,然后在类外直接调用即可
3.友元声明
4.加inline改成内联函数

同时,全局变量/全局函数在所有文件中(这里我们Date.cpp,Date.h,test.cpp)都可见的,在经过编译会生成Date.o,test.o,此时里面都会有全局函数的定义,放进符号表,此时就会产生冲突。所以声明与定义应该进行分离。(也可以加上static进行修饰,static修饰全局函数会改变链接属性,只在当前文件可见)。所以尽量在。h文件中不要定义全局的函数

同时,对于频繁调用,我们可以直接用内联函数,直接展开,不进符号表

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1970, 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;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};
//cout << dl << d2 << endl;
//返回cout是为了链式访问
static ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
int main()
{
	Date d1(2022, 10, 15);
	Date d2(2022, 10, 14);
	cout << d1 << d2;
	return 0;
}

【C++初阶】类和对象(三)_第4张图片

#include
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 1970, 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;
		}
	}
	int GetMonthDay(int year, int month)
	{
		static int monthDayArray[13] = { 0,31,30,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];
		}
	}
private:
	int _year;
	int _month;
	int _day;
};
//cout << dl << d2 << endl;
static ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1(2022, 10, 15);
	Date d2(2022, 10, 14);
	cout << "cout << d1 << d2结果是:" << d1 << d2 << endl;
	Date d3;
	cin >> d3 >> d1;
	cout << d3 << d1;
	return 0;
}

【C++初阶】类和对象(三)_第5张图片

1.3 前置++和后置++重载

对于自定义类型尽量使用前置++,因为后置++要进行深度拷贝。

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置+和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
		// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
		// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
		Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++; // d: 2022,1,13 d1:2022,1,14
	d = ++d1; // d: 2022,1,15 d1:2022,1,15
	return 0;
}

2.const成员

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

对于const的修饰,我们要注意权限可以进行缩小和平移,但是不能进行放大,这是在之前对于this指针( *const this)所说的。

简单来说,凡是内部不改变成员变量,其实也就是*this对象数据的,这些成员函数都应该加const

【C++初阶】类和对象(三)_第6张图片

using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()// Date* const this
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const//const Date* const this
	{
		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()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}
int main()
{
	Test();
	return 0;
}

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

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

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

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

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

你可能感兴趣的:(C++零基础学习,c++,开发语言)