C++类与对象3

接上篇,我们了解了6个默认函数的构造函数,析构函数,拷贝构造函数。

现在我们来讲解一下:赋值运算符的重载。

赋值运算符的重载

1.概念目的

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,运算符重载是指在编程语言中,对已有的运算符重新定义赋予其新的功能,使其能够操作自定义的数据类型(如类、结构体)。
例如,在没有重载运算符的情况下,加法运算符“+”通常用于基本数据类型(如两个整数相加)。但如果有一个自定义的复数类,重载“+”运算符后,就可以像使用基本数据类型相加一样,方便地实现两个复数对象相加的操作。这样能使代码更简洁直观,符合人们的编程习惯。


注意:

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

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

<返回类型> operator<运算符号>(参数)
{
    <函数体>

}
bool operator=(Date&d)   //赋值
{

}

bool operator==(Date&d)   //等于
{

}

注意: 

不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数

用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

.*   ::   sizeof   ?:   . 注意以上5个运算符不能重载。

 现在我们来举个例子:以比两个日期 

类内部的: 


class Date
{

public:
	Date(int year=1, int month=1, int day=1)
    {
	    _year = year;
	    _month = month;
	    _day = day;
    }
	
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	bool operator<(const Date& d)const  //这里看似只有一个参数,实际有两个,只是this指针隐藏了而已
    {
	    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; 
    }
private:
	int _year;
	int _month;
	int _day;
};

类外部的:

class Date
{

public:
	Date(int year=1, int month=1, int day=1)
    {
	    _year = year;
	    _month = month;
	    _day = day;
    }
	
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}


//private:   //这里要改成共公有的,因为私有的话,在类外面无法使用,符号运算符就无法使用
public:
	int _year;
	int _month;
	int _day;
};

//这里参数类型要传&:因为如果传值的话,要多拷贝构造函数一次,效率降低了。
//加const:因为我们在函数体内是不进行修改的,所以最好是加上const,防止意外。
	bool operator<(const Date& d1,const Date& d)
    {
	    if (d1._year < d._year)
	    {
	    	return true;
    	}
        	else if (d1._year == d._year && d1._month < d._month)
    	{
    		return true;
    	}
    	else if (d1._year == d._year && d1._month == d._month && d1._day < d._day)
    	{
    		return true;
    	}
    	return false; 
    }

上面我们想要通过编译,是把private改成了public,那么我们有没有办法使他不用改呢?

class Date
{

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

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	bool operator<(const Date& d1, const Date& d)
	{
		if (d1._year < d._year)
		{
			return true;
		}
		else if (d1._year == d._year && d1._month < d._month)
		{
			return true;
		}
		else if (d1._year == d._year && d1._month == d._month && d1._day < d._day)
		{
			return true;
		}
		return false;
	}

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


int main()
{
	Date d1(2022, 2, 2);
	Date d2(2023, 3, 3);
	d1 < d2;
	return 0;
}

当我们把它放到类里面去的时候,我们会发现出现下面的问题: 

 

为什么呢?是因为这里实际上由三个参数(隐藏起来了一个this指针的参数),所以会出现参数太多的情况。 我们修改成下面的就可以了。

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;
	}
int main()
{
    Date d1(2022,2,2);
    Date d2(2023,3,3);
    d1<d2;       --1 ==operator<(d1, d2);
    d1.operator<(d2);  --2.
    return 0;
}

 上面的两种都是一样的原理,下面是它的汇编角度观察:

 

1. 赋值运算符重载格式

1.参数类型: 

const Date&,传递引用可以提高传参效率(减少拷贝,提高效率)(后面我们就可以体现到)

加&传引用是为了减少传值时多了拷贝构造的拷贝。加const是因为我们在函数体中是不修改的,加了之后可以避免错误(权限放大等等)。

2.返回值类型

Date&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

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

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

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

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

int main()
{
	Date a(2022, 2, 2);
	Date a2(2023, 2, 2);
	a2 = a;
	a2.Print();
	a.Print();
	return 0;
}

 

我们先来区分一下:

1.已经存在的两个对象之间复制拷贝 -- 运算符重载函数 

d1 = d2;

 2.用一个已经存在的对象初始化另一个对象 -- 构造函数

Date d3(d1);

当我们去连续赋值的时候:

 

 上面的代码是错误的,为什么运行错误呢?就是因为它(赋值重载的类型)是void类型,赋值的话它需要生成一个临时变量,而void无法返回,所以不行。

那么怎么才行?

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

什么时候才要&呢?出了函数作用域,对象就不在了,就不能用引用返回,若还在就可以用引用返回,回到这里,是不是返回*this,出了作用域还在。

3.检测是否自己给自己赋值

若是自己赋值给自己,这是没有必要的,所以要检查。

4.返回*this

像int ret=10的话;返回的是ret,即赋值运算符的左操作数,那么像在我们类的函数成员中,我们只能通过this指针来访问到类的内置成员,所以返回的是*this。

要复合连续赋值的含义

int main()
{
    Date d1,d2,d3;
    d1=d2=d3
}

赋值运算符只能重载成类的成员函数不能重载成全局函数

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
 

 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值(这里就是我们上面讲的)。(规则跟拷贝构造一样)

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

注意:日期,MyQueue类是可以不写赋值运算符重载的。

但是Stack栈必须要写:为什么呢?涉及到了资源管理(这里的原因跟Stack必须要写析构函数的原因非常相似)

class Stack
{
public:
	
	Stack(int Decapacity=4)
	{
		cout << "Stack()" << endl;
 
		_a = (int*)malloc(sizeof(int) * Decapacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_size = _capacity = 0;
	}

    void Push(const int& x)
    {
        _a[_size] = data;
        _size++;
    }
	~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);
    st1.Push(4);
    Stack st2;
    st2=st1;
	return 0;

这时候,代码会崩溃

原因:

代码中,st1对象调用了构造函数,并开辟了4个空间,并存进了1,2,3,4数字

st2对象调用了构造函数,并开辟了4个空间,没有存储数据。

由于它没有显示的赋值运算符重载,所以它只能调用默认的赋值运算符重载。并以浅拷贝的形式实现赋值运算符重载,也就是:它们会直接相互赋值,将一个对象原封不动的赋值到另一个对象。

到了st2=st1时,st1的内容原封不动的拷贝到st2,那么就会导致st1的内容丢失,造成了内存泄漏

而且,最后的析构函数释放时也会释放两次而造成崩溃。

好了,本次笔记到处结束了。

最后,到了我们的鸡汤部分:

记住你的价值,它不因你的外观的不雅而贬值,是金子总有发光的一天。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值