【C++杂货铺】运算符重载(一)

简介: 【C++杂货铺】运算符重载(一)

2da17ae87e714ba9bf486200af524612.gif

前言

本文将以日期类为基础,去探寻运算符重载的特性与使用方法,下面先给出日期类的基础定义:

class Date
{
public:
  Date::Date(int year, int month, int day)
  {
    if (month > 0 && month <= 12
      && day > 0 && day <= GetDay(year, month))
    {
      _year = year;
      _month = month;
      _day = day;
    }
    else
    {
      cout << "非法日期" << endl;
      assert(false);
    }
  }
private:
  int _year;//年
  int _month;//月
  int _day;//日
};

备注:拷贝构造函数和析构函数,均可以不写,因为当前日期类的三个成员变量都是内置类型,没有动态申请空间,使用浅拷贝就可以。

一、运算符重载

📖如何比较两个日期的大小?

int main()
{
  Date d1(2023, 7, 21);
  Date d2(2023, 6, 21);
  return 0;
}

现如今,定义了两个日期类的对象d1和d2,该如何比较这两个对现象的大小呢?首先想到的是,写一个函数来比较他俩的大小,向下面这样:

//以小于比较为例
bool Less(const Date& x, const Date& y)
{
  if (x._year > y._year)
  {
    return false;
  }
  else if (x._year == y._year && x._month > y._month)
  {
    return false;
  }
  else if (x._year == y._year && x._month == y._month && x._day > y._day)
  {
    return false;
  }
  else
  {
    return true;
  } 
}

存在的问题:首先这个函数是写在类外面的,意味着,日期类的成员变量如果是private私有的话,在类外面就无法访问,所以在这个函数里面是访问不到对象的年、月、日这三个成员变量,即x._year等都是非法的,要想实现该函数的功能,日期类的成员变量必须是public公有。

其次,在比较两个日期类对象大小的时候,需要写成Less(d1, d2),这和我们平时直接用<符号比较大小,比起来不够直观。

📖为什么日期类不能直接使用<

因为日期类是我们自己定义的,属于一种自定义类型,它的大小比较方式,只有定义它的人知道,而像int、double等内置类型,是祖师爷创造C++语言时就定好的,祖师爷当然知道该如何比较两个内置类型变量的大小,所以提前帮我们设置好了,我们可以直接用<去比较两个内置类型变量的大小,而至于祖师爷是怎么设置的,这里先埋一个伏笔。

📖运算符重载

为了解决上面Less函数存在的问题,C++引入了运算符重载,它可以让我们直接使用<来比较两个日期类的大小。

运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字、参数列表、返回值类型都和普通函数类似。

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

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

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

上面就是对<运算符的一个重载,它的两个形参是Data类型的引用,此时两个日期类对象就可以直接用<来比较大小啦,d1 < d2本质上就是调用运算符重载函数,但是由于上面的运算符重载函数还是写在类外面,所以当日期类的成员变量是private私有的时候,该运算符重载函数还是用不了。

//下面两条语句是等价的本质都是调用运算符重载函数
d1 < d2;
operator<(d1, d2);//d1 < d2的本质

3af0266425be4621a7a539bb16ab37a1.png

📖将运算符重载函数写成成员函数

为了解决上面的私有成员变量在类外面无法访问的问题,可以把运算符重载函数写成类的成员函数或者友元,这样就能访问到私有的成员变量,但是友元一般不建议使用,因为友元会破坏封装。

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

上面就是把<运算符重载成类的成员函数,此时参数只有一个,因为<是一个双目运算符,类的非静态成员函数有一个隐藏的形参this指针,所以形参就只需要一个。

//它们俩是等价的
d1 < d2;
d1.operator<(d2);//d1 < d2的本质

038ddf8457ad4ccebc1cd0c0f96a8ff1.png

小Tips:一个双目运算符如果重载成类的成员函数,会把它的左操作数传给第一个形参,把右操作数传给第二个形参。以上面为例,this指针接收的是d1的地址,d接收的是d2。

📖注意事项:

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

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

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

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

.*、::、sizeof、? :、.这五个运算符不能重载。

二、赋值运算符重载

📖区分赋值运算符重载和拷贝构造

Date d1(2020, 5, 21);
Date d2(2023, 6, 21);
d1 = d2;//需要调用赋值运算符重载
Date d3 = d1;//这里是调用拷贝构造函数
//Date d3(d1);//和上一行等价调用拷贝构造

要区分赋值运算符重载和拷贝构造,前者是针对两个已存在的对象,将一个对象的值,赋值给另一个,而后者是用一个已存在的对象去初始化创建一个新对象。

赋值运算符重载格式:

参数类型:const T&(T是类型),传引用返回可以提高效率。

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

检测是否自己给自己赋值。

返回*this:要符合连续赋值的含义。

Date& operator=(const Data& d)
{
  if (this != &d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  return *this;//出了作用域*this还在,所以可以用引用返回
}

📖只能是类的成员函数

上面的<运算符,最开始我们是在类外面把它重载成全局的,后来为了保证类的封装性,才把它重载成类的成员函数,而赋值运算符天生只能重载成类的成员函数,因为赋值运算符重载属于类的默认成员函数,我们不写,编译器会自动生成,所以,如果我们把赋值运算符重载写在类外面,就会和编译器生成的默认赋值运算符重载发生冲突。

📖编译器生成的干了些什么工作?

用户没有显式实现时,编译器生成的默认赋值运算符重载,对内置类型的成员变量是以值的方式逐字节进行拷贝(浅拷贝),对自定义类型的成员变量,调用其对应类的赋值运算符重载。

三、完善日期类

有了上面的基础,接下来完善一下日期类,重载其他的运算符。

3.1 重载关系运算符

关系运算符有<、>、==、<=、>=、!=,由于它们之间存在的逻辑关系,可以通过复用来实现,即:要想知道a是否大于b,可以通过判断a是否小于等于b来实现。因此,我们只要写一个<和==的比较逻辑,其他的直接复用即可。

📖重载<

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 Date::operator==(const Date& d)
{
  return _year == d._year
    && _month == d._month
    && _day == d._day;
}

📖重载<=

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

📖重载>

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

📖重载>=

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

📖重载!=

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


目录
相关文章
|
编译器 C++
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
142 1
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
316 5
C++(十五) 运算符重载
C++中的运算符重载允许对已有运算符的功能进行重新定义,从而扩展语言功能、简化代码并提升效率。重载遵循特定语法,如 `friend 类名 operator 运算符(参数)`。重载时需注意不可新增或改变运算符数量、语义、优先级、结合性和返回类型。常见示例包括双目运算符 `+=` 和单目运算符 `-` 及 `++`。输入输出流运算符 `&lt;&lt;` 和 `&gt;&gt;` 也可重载。部分运算符只能作为成员函数重载。
|
存储 编译器 C++
【C++】:拷贝构造函数和赋值运算符重载
【C++】:拷贝构造函数和赋值运算符重载
199 1
|
C++ 索引
C++核心技术要点《运算符重载》
C++核心技术要点《运算符重载》
165 2
|
自然语言处理 程序员 C++
C++基础知识(五:运算符重载)
运算符重载是C++中的一项强大特性,它允许程序员为自定义类型(如类或结构体)重新定义标准运算符的行为,使得这些运算符能够适用于自定义类型的操作。这样做可以增强代码的可读性和表达力,使得代码更接近自然语言,同时保持了面向对象编程的封装性。
177 0
|
编译器 C++
【C++】详解运算符重载,赋值运算符重载,++运算符重载
【C++】详解运算符重载,赋值运算符重载,++运算符重载
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
c++进阶篇(一)——运算符重载
c++进阶篇(一)——运算符重载
110 0