C++ 类和对象 赋值运算符重载

前言:

在上文我们知道数据类型分为自定义类型和内置类型,当我想用内置类型比较大小是非常容易的但是在C++中成员变量都是在类(自定义类型)里面的,那我想给类比较大小那该怎么办呢?这时候运算符重载就出现了

一 运算符重载概念:

允许用户为自定义类定义或重新定义运算符的行为,使这些类在使用运算符时表现得像内置数据类型一样,从而提高代码的可读性和简洁性。

1.2 运算符重载与函数重载的区别:

我第一次听到这两个重载都傻傻的分不清楚,以为是一个意思。其实他们的区别可大了

函数重载:

函数重载是指在同一作用域中有多个同名函数,但它们的参数列表(参数的类型和数量)不同。编译器通过参数列表来决定调用哪个函数。函数重载的目的是为了提高代码的可读性和灵活性,使同一操作可以应用于不同类型的参数。

运算符重载:

运算符重载允许我们为用户定义的类型(如类)定义或重新定义特定运算符(如 +、-、*、/ 等)的行为。运算符重载函数的名称为 operator 后跟运算符符号。尽管这些函数的返回类型和参数列表与普通函数类似,但它们的目的是使自定义类型能够使用像内置类型一样的运算符。

1.3 运算符特点:

1 定义运算符重载函数 :运算符重载是通过定义特殊的成员函数或全局函数来实现的

2 运算符重载函数的返回类型:通常是运算符操作后的结果类型。

3 运算符重载函数的参数:根据运算符的类型,参数可以是一个或多个。

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

3.1 代码解析:

运算符重载成员函数代码示例:

//成员函数 运算符重载
class Date 
{
public:
	int _x = 5;
	int _y = 4;
	int operator+(const Date& b)
	{
		return this->_x + b._x + this->_y + b._y;
	}

	//错误写法
	//int operator+(const Date& a , const Date& b)
	/*{
		return a.x + b.x + a.y + b.y;
	}*/
};
int main()
{
	//成员函数 运算符重载
	Date d1;
	Date d2;
	int sum = d1 + d2;
	std::cout << "d1 d2总和:" << sum << std::endl;
	return 0;
}

错误写法分析:

因为它是Date类里面的成员函数 又因为成员函数会自带一个隐含的this指针所以成员函数版本的 operator+ 只能有一个显式参数。如果需要两个参数,则应使用全局函数版本的运算符重载。

//全局函数 运算符重载
class Point
{
public:
	//默认构造函数
	Point()
	{
		this->_x = 5;
		this->_y = 15;
	}
//private:
	int _x;
	int _y;
};
bool operator==(const Point& b, const Point& a)
{
	return (b._x == a._x) && (b._y == a._y);
}

int main()
{
	Point f1;
	Point f2;
	int B = f1 == f2;
	std::cout << "1相同 0相否:" << B << std::endl;
	return 0;
}

全局函数的运算符重载是在类外部实现的,不属于任何类,因此没有 this 指针。全局函数可以通过参数访问所有操作数。

假如我把成员函数变成私有的话那在全局函数里面就找不到他们了所以想改变就只能把运算符变量改为成员函数或者用友元函数或getter方法。

二 赋值运算符重载概念:

赋值运算符重载用于定义对象之间的赋值操作,即如何将一个对象的值赋给另一个对象。赋值运算符是 =,它在赋值时被调用。通常我们需要重载赋值运算符来处理深拷贝,以防止浅拷贝带来的问题。

2.1 赋值运算符重载和拷贝构造的区别:

通过赋值运算符重载的概念我们知道它主要的功能是将一个对象的值赋给另一个对象,而这和拷贝构造又非常相似,然而赋值运算符重载与运算符重载只有两字相差却又是不同的内容,这就让我很想知道他们之间的区别到底是什么,接下来让我们一起来解密吧!

概念:

1. 赋值运算符重载

定义对象之间的赋值操作,即如何将一个对象的值赋给另一个对象。

2. 运算符重载:

定义或重新定义自定义类型的运算符行为,使其与内置类型的运算符行为一致。

3. 拷贝构造:

创建一个新的对象,并将其初始化为现有对象的副本。

区别:

拷贝构造函数和赋值运算符重载的主要区别在于它们的使用场景和目的。拷贝构造函数在对象创建时用于初始化新对象,目的是创建一个新的副本。赋值运算符重载在对象已存在时用于赋值操作,目的是修改现有对象的状态,使其与另一个对象的状态相同。拷贝构造函数通常接收一个对同类对象的常引用,而赋值运算符重载通常返回对象的引用,并接收一个对同类对象的常引用作为参数。而运算符重载和赋值运算符重载也真是差了两个字而已,并没有什么区别。

2.1.0 代码解析:

//赋值运算符重载
class Date1
{
public:
	//默认构造函数
	Date1(int year = 2005, int month = 5, int date = 25)
	{
		this->_year = year;
		this->_month = month;
		this->_date = date;
	}

	//拷贝构造函数
	Date1(const Date1& other)
	{
		this->_year = other._year;
		this->_month = other._month;
		this->_date= other._date;
	}

	//赋值运算符重载
	Date1 operator=(const Date1& d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_date = d._date;
		return *this;
	}
	//输出
	void print()
	{
		std::cout << _year << "-" << _month << "-" << _date << std::endl;
	}

private:
	int _year;
	int _month;
	int _date;
};

int main()
{
	//构造函数
	Date1 q1(2024 , 7 , 12);
	Date1 q2(2021 , 6 , 26);

    //拷贝构造
	Date1 q3(q2);

    //赋值运算符重载
	Date1 q4;
	q4 = q1;
    
    //输出
	q1.print();
	q2.print();
	q3.print();
	q4.print();

	return 0;
}

从上面代码可以知道q3是在被创建的时候就直接被调用拷贝构造初始化而q4是先定义好之后在被调用赋值运算符重载初始化的。

那既然赋值运算符重载就是对象之间的赋值那和C语言中的赋值整体意思还是一样的但就是赋值的对象变了,我不知道大家是否还记得在C语言中可以连续赋值,让我们来试试在C++中的赋值运算符重载是否也可以实现呢?

#include <iostream>
class Date 
{
public:
    int _year, _month, _day;

    Date(int year = 2005, int month = 5, int day = 25)
    {
        this->_year = year;
        this->_month = month;
        this->_day = day;
    }

    // 赋值运算符重载
    Date& operator=(const Date& d) 
    {
        // 自赋值检查
        if (this != &d) 
        { 
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
       
        return *this;   // 返回当前对象的引用
    }

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

int main() {
    Date d1(2023, 7, 10);
    Date d2;
    Date d3;

    // 链式赋值
    d3 = d2 = d1;

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

    return 0;
}

输出:

虽然输出的结果确实是链式但是又有非常多的疑惑比如为什么要传*this 还有执行顺序是什么......

让我们详细的来解答一下:

首先d2传给隐函数this指针然后d1传给 Date& d那这时d1就是d的引用了,执行到下面就是判断成员变量的地址是否相同(最开始它们之间的地址还是不一样的)跳出循环之后就返回*this即d2的引用返回值类型 Date& (这个我之前一直没看到所以就很困惑)

2.2 默认赋值运算符重载:

编译器生成的默认赋值运算符重载也和默认构造函数中分自定义和内置类型那它与默认构造函数有什么区别,让我们一探究竟吧

内置类型:

概念:编译器生成的默认赋值运算符会直接逐字节拷贝内置类型成员变量的值。

代码演示:

//默认赋值运算符重载(内置类型)
class Date1
{
public:
	//默认构造函数
	Date1(int year = 2005, int month = 5, int date = 25)
	{
		this->_year = year;
		this->_month = month;
		this->_date = date;
	}
	void print()
	{
		std::cout << _year << "-" << _month << "-" << _date << std::endl;
	}

private:
	int _year;
	int _month;
	int _date;
};

int main()
{
    Date1 q1(2024 , 7 , 12);

	Date1 q5;
	q5 = q1;
	q5.print();
	return 0;
}

输出:

就如上图所示,我并没有写赋值运算符重载但是它却给我打印出和q1对象中的成员变量一样的值,所以我们可以得出结论编译器生成的默认赋值运算符会直接逐字节拷贝内置类型成员变量的值。俗称浅拷贝。

自定义类型:

代码演示:

//默认赋值运算符重载(自定义类型)
class Date2
{
public:
   // 赋值运算符重载
    Date2& operator=(const Date2& d)
    {
        if (this != &d) 
        { // 自我赋值检查
            _a = d._a;
        }
        return *this;
    }

private:
    int _a = 10;
};

class Date3
{
public:
    // 默认构造函数
    Date3(int year = 2005, int month = 5, int date = 25)
        {
          this->_year = year;
          this->_month = month;
          this->date = date;
        }
    void print()
    {
        std::cout << _year << "-" << _month << "-" << _date << std::endl;
    }

private:
    int _year;
    int _month;
    int _date;
    Date2 c; // 包含 Date2 类型的成员变量
};

int main()
{
    Date3 q1(2024, 7, 12);

    Date3 q5;
    q5 = q1; // 使用编译器生成的默认赋值运算符

    q5.print();

    return 0;
}

输出:

如图所示编译器生成的默认赋值运算符会调用自定义类型的赋值运算符重载。

相关推荐
不想当程序猿_4 分钟前
【蓝桥杯每日一题】分糖果——DFS
c++·算法·蓝桥杯·深度优先
cdut_suye16 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
波音彬要多做41 分钟前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
捕鲸叉41 分钟前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
只做开心事2 小时前
C++之红黑树模拟实现
开发语言·c++
程序员老冯头3 小时前
第十五章 C++ 数组
开发语言·c++·算法
程序猿会指北4 小时前
【鸿蒙(HarmonyOS)性能优化指南】启动分析工具Launch Profiler
c++·性能优化·harmonyos·openharmony·arkui·启动优化·鸿蒙开发
无 证明8 小时前
new 分配空间;引用
数据结构·c++
别NULL12 小时前
机试题——疯长的草
数据结构·c++·算法
CYBEREXP200813 小时前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos