Lesson 02 类与对象 (下)

C++:渴望力量吗,少年?


一、赋值运算符重载

1. 运算符重载

  C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
(1)函数名字为:关键字operator后面接需要重载的运算符符号。
(2)函数原型:返回值类型 operator操作符 (参数列表)
但是要注意的是:
(3)不能通过连接其他符号来创建新的操作符:比如operator@
(4)重载操作符必须有一个类类型参数
(5)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
(6).*   ::sizeof? :.  注意以上5个运算符不能重载。

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

	 //d1 < d2  或者  d1.operator<(d2)
	bool operator<(const Date& d)//列表隐含了第一个参数this,在这里d1就是this,d2就是d
	{ //可以把这个重载定义在类中,这样可以解决private成员无法在类外使用的问题
		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;
		}
		else
		{
			return false;
		}
	}

private:
	// 内置类型
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023, 7, 21);
	Date d2(2022, 8, 21);
	cout << (d1 < d2) << endl;//这两条语句等价
	cout << (d1.operator<(d2)) << endl;

	return 0;
}

2. 赋值运算符重载

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

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

	// d1 < d2  
	// d1.operator<(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;
		}
		else
		{
			return false;
		}
	}

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

	// d1 <= d2:this就是d1,d就是d2
	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 monthArray[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 monthArray[month];
	}

	Date& operator+=(int day)//修改了原来的日期
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			// 月进位
			_day -= GetMonthDay(_year, _month);
			++_month;

			// 月满了
			if (_month == 13)
			{
				++_year;
				_month = 1;
			}
		}

		return *this;//注意this只是一个指针,需要解引用
	}

	Date operator+(int day)//没有修改原来的日期
	{//函数名不可以加 & ,因为函数结束tmp就销毁了
		Date tmp(*this);//调用拷贝构造函数,复制出一份新的日期
		tmp += day;//复用上面的代码
		return tmp;
	}

private:
	// 内置类型
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023, 7, 21);
	Date d2(2022, 8, 21);
	cout << (d1 < d2) << endl;
	// cout << (d1.operator<(d2)) << endl;
	cout << (d1 == d2) << endl;

	//Date ret = d1 += 50;
	//ret.Print();
	//d1.Print();

	Date ret = d1 + 50;
	ret.Print();
	d1.Print();

	return 0;
}

(2)赋值运算符只能重载成类的成员函数,不能重载成全局函数。

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


	int _year; 
	int _month; 
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year; left._month = right._month; left._day = right._day;
	}


	return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

  原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
(3)用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

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

二、const成员

  将 const修饰的“成员函数” 称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
在这里插入图片描述
  注意:const修饰成员函数时要放在函数的末尾(也就是必须以上图左边的代码形式书写,右边是表示实际等价于左边的代码),另外内部不涉及修改成员的都是只读函数,这样的函数可以加const,这样是有利于避免权限上冲突的问题的。

三、取地址及const取地址操作符重载

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

    // 不想被取到有效地址的时候才会考虑重载取地址运算符 &
    Date* operator&()
    {
    	// return (Date*)0x01202;
    	return nullptr;//不想返回自己的地址,返回了空指针
    }

    const Date* operator&() const
    {
    	return this;//返回自己的地址,其实也不用写,默认生成的就是这样的
    }

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

四、构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

class Date
{
public:
	/*Date()//无参构造函数
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}*/
	
	//Date(int year, int month, int day)//有参构造函数,和上面的函数重载
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	Date(int year = 1, int month = 1, int day = 1)//采用全缺省,相当于上面的两个函数合并
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

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

class Stack
{
public:
	/*Stack()
	{
		a = nullptr;
		top = capacity = 0;
	}*/

	Stack(size_t n = 4)
	{
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if(a == nullptr)
			{
				perror("realloc fail");
				exit(-1);//以异常的形式结束程序,剩下的程序不会继续执行
			}

			top = 0;
			capacity = n;
		}
	}

	// 成员函数
	//void Init()
	//{
	//	a = nullptr;
	//	top = capacity = 0;
	//}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a) //根据首地址验证是原地扩容还是异地扩容
			{
				cout << capacity << "原地扩容" << endl;
			}
			else //异地扩容比较麻烦,需要找到一块新的空间,然后拷贝数据,最后释放旧的空间
			{
				cout << capacity << "异地扩容" << endl;
			}

			a = tmp;
			capacity = newcapacity;
		}

		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}
private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);
	
	while (!st1.Empty())
	{
		cout << st1.Top() << " ";
		st1.Pop();
	}
	cout << endl;

	st1.Destroy();

	Stack st2(1000);//利用缺省参数提前开好了足够大的空间,避免了多次异地扩容,比下面的语句效率更高
	//Stack st2;
	for (size_t i = 0; i < 1000; i++)
	{
		st2.Push(i);
	}

	while (!st2.Empty())
	{
		cout << st2.Top() << " ";
		st2.Pop();
	}
	cout << endl;

	st2.Destroy();

	// 调用无参构造函数不能像下面这么写,不需要加括号,因为声明函数就是Date Func()的形式
	/*Date d1();
	d1.Print();*/

	Date d1;//自动调用无参构造函数或者上面全缺省的函数
	d1.Print();

	Date d2(2023, 7, 20);
	d2.Print();

	Date d3(2023);
	d3.Print();

	Date d4(2023, 7);
	d4.Print();

	return 0;
}

  虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。所以就需要我们学习下一篇文章提到的初始化列表。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值