【C++技能树】令常规运算符用在类上 --类的六个成员函数II

简介: C++中为了增强代码的可读性,加入了运算符的重载,与其他函数重载一样

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。


cb63638ae1e949778936fff68b606c5a.png


0.运算符重载


C++中为了增强代码的可读性,加入了运算符的重载,与其他函数重载一样。其命名格式如下:


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


Date operator<(Date&d1)


但并不是所有运算符都可以重载的:


1.不可以自定义出一个全新的操作符,如@

2..* :: sizeof ?: . 以上操作符不能重载,特别是第一个.*

接下来我们定义一个日期类来作为操作符重载的实践:

class Date{
public:
    Date(int day=1,int month=1,int year=1)
    {
        if(month>0&&month<13&&day>0&&day<getmonth(year,month))
            _day=day,_month=month,_year=year;
        else
            cout<<"输入错误";
    }
    Date(const Date&d)
    {
        _day=d._day;
        _month=d._month;
        _year=d._year;
        cout<<"copy";
    }
private:
    int _year;
    int _month;
    int _day;
}


这是一个最基本的类函数,其包括一个默认构造函数,以及一个拷贝构造函数,拷贝构造函数会在调用时输出:“copy”,这将有助于我们后期分析函数.


1.赋值运算符 = 重载


先来看看他是怎么定义的吧.同样符合上方的定义法则:


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


为什么需要返回值调用呢?因为这样做可以节省空间。以引用的方式传参、返回值不需要调用拷贝构造函数


那this不是形式参数嘛?为什么出作用域还能被引用呢?*this本身是形式参数,但是 this指向的是date对象,其栈帧存储在Main函数中,所以this会被销毁,而返回的是this指向的对象,并不会被销毁


加入const只是一个保险,让我们不会改变传入的参数


我们可以以一段代码来演示一下:


class Date{
public:
    Date(int day=1,int month=1,int year=1)
    {
        if(month>0&&month<13&&day>0&&day<getmonth(year,month))
            _day=day,_month=month,_year=year;
        else
            cout<<"输入错误";
    }
    Date(const Date &d)
    {
        _day=d._day;
        _month=d._month;
        _year=d._year;
        cout<<"copy";
    }
    Date& operator=(const Date &d)
    {
        _day=d._day;
        _month=d._month;
        _year=d._year;
        return *this;
    }
int main()
{ 
    Date d1(2,5,2024);
    Date d2;
    d2=d1;
}


先将两个引用都去掉,运行会发现,调用了两次拷贝构造,分别为传值以及返回值:


bf488e83dff6471c86eb67ed1383e2b9.png


**加上后则不会调用拷贝构造,所以这样写节省空间与时间。**在编译阶段,改代码会被转变成


d2.operator=(d1)


这也方便我们对这个操作符的具体执行方式进行理解。


至于为什么需要返回Date类型呢?考虑以下场景:


int a;
int b;
int c;
a=b=c=1;


这段代码运行的实质为:


5d3b92684a0e4e9b993bd4ddafb263e5.jpg


所以我们需要令其返回Date类型来满足这种情况:


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


赋值运算符如果不自己定义,系统就会自动生成一个赋值运算符(这与之前提到的构造函数、拷贝构造函数、析构函数),其调用规则业余之前一样,若没有定义则内置类型完成浅拷贝,自定义类型调用其自定义的赋值运算符重载,所以与调用拷贝构造函数相同,涉及空间管理的内置类型,我们需要自己定义拷贝构造函数。这也导致赋值运算符不能定义在全局,只能定义在类中,若在自己在全局定义一个就会和类中定义的冲突。


2.比较运算符 == 重载


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


三个都相同则返回true,否则返回false,这整体逻辑很好理解。这个函数并不是默认函数,所以我们可以把它定义在全局,也可以把他定义在类中。

若定义在全局会出现类中私有成员无法访问的问题(这之后会解决,但现在我们为了规避这个问题我们先将其放在类中)


那这个const为什么放在外面呢?这里是对this指针,也就是本体的一个修饰,因为this指针是隐式的存在,且在这个重载中我们并不会改变本体,所以对其加上修饰

this指针原来的模样:无法改变this指针指向的内容,但可以改变其值


Date * const this


现在修饰后:既无法改变this指针指向的内容,也无法改变其值,对其起到了一个保护的作用


const Date * const this 


3.比较运算符 != 重载


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


这里直接复用了上方的结果,也证明了重载运算符的强大之处hhhh


4.比较运算符 < 重载


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


主要为逻辑的实现:


1.若年小,则直接返回true

2.若年相同,月小,直接返回true

3.若年月都相同,天数小,直接返回true


若不满足以上情况,则说明不小于,则直接返回false


5.比较运算符 <= 重载


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


也是对之前写过的==以及<的一个复用.若小于或者等于则满足小于等于


6. 比较运算符 > 重载


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


若不小于且不等于则满足小于等于

7.比较运算符 >= 重载


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


若不小于则满足大于等于


8. 赋值运算符 += 与 + 重载


在实现接下来的重载中,为了方便获得每一个月份的具体日期,包括闰年平年的判断。在类中实现了一个getmonth函数用来返回每一个月具体天数。


int getmonth(int year,int month)
    {
        static int monthday[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
        if((year%4==0&&year%100!=0)||year%400==0)
        {
            if(month==2)return 29;
        }
        return monthday[month];
    }


传入年与月,进行判断具体日期数,其中闰年满足四年一闰,百年不闰,四百年一闰


接下来进行+=的实现:这里需要先判断下若Day<0,则等价于计算*this-=Day


Date& operator+=(int Day)
    {
      if(Day<0)
        {
            *this-=(-Day);
            return *this;
        }
        _day+=Day;
        while(_day>getmonth(_year,_month))
        {
            _day-=getmonth(_year,_month);
            ++_month;
            if(_month>12)
            {
                _month=1;
                _year++;
            }
        }
        return *this;
    }


具体实现逻辑如下:


a23a2fe948a045818b1d4a2a421920dc.jpg


那么我们来看看+的逻辑:


Date operator+(int day)
    {
        Date tmp=*this;
        tmp+=day;
        tmp.print();
        return tmp;
    }


为了不改变Date对象的本身,需要先将Date复制一份(这里使用拷贝构造函数,而不是赋值运算符重载,因为其在初始化对象时赋值),然后复用+=,最后返回其本身.因为是局部参数,所以并不能返回引用,因为tmp出作用域会被销毁.


9.赋值运算符 -= 与 - 重载:


-=:


 Date& operator-=(const int Day)
    {
        if(Day<0)
        {
            *this+=(-Day);
            return *this;
        }
        _day-=Day;
        while(_day<=0)
        {
            --_month;
            if(_month<1)
            {
                _year--;
                _month=12;
            }
            _day+=getmonth(_year,_month);
        }
        return *this;
    }


-:


Date operator-(const int Day)
    {
        Date tmp=*this;
        return tmp-=Day;
    }


整体逻辑与加法大差不差,不过多赘述.


10. 前置++与后置++


前置++


Date& operator++()
    {
        *this+=1;
        return *this;
    }


若我想要将一个Date对象执行++操作,则直接:++Date即可.


后置++:为了和前置++再一次发生重载,加入了一个参数,这个参数在重载过程中并不会起到什么作用,仅为分辨具体是哪个重载,使用时编译器会自己在后置++中传入0,我们仅需正常使用即可,

Date operator++(int)
    {
        Date tmp=*this;
        *this+=1;
        return tmp;
    }


在刚学习c的阶段,总会看到讨论关于前置++与后置++的讨论,现在看重载就知道了,因为后置++会拷贝一份原始值,所以效率会低一些.


不过在内置类型中,这种效率变换可以忽略不计.

11.前置–与后置–


 Date& operator--()
    {
        *this-=1;
        return *this;
    }
    Date operator--(int)
    {
        Date tmp;
        *this-=1;
        return tmp;
    }


12.逻辑运算符-的重载


为两个日期类间想减,计算日期间的差值


int operator-(Date &d)
    {
        Date max=*this;
        Date min=d;
        int flag=1;
        if(max<min)
        {
            max=d;
            min=*this;
            flag=-1;
        }
        int n=0;
        while(min!=max)
        {
            ++min;
            ++n;
        }
        return n*flag;
    }


先找出最大的日期,通过将最小的++与计数天数++,一步步逼近最大的天数,最后返回,注意传值需要传引用,提高效率


13.流运算符重载


为什么在c++中cout/cin可以输出/输入任意内置类型的对象,而不用带对象类型呢?因为在c++中cout与cin也为一个流对象,其底层实现了多个类型的重载

此重载需要定义在全局中,若定义在类中,根据上方的转换法则,则会变成d1<<cout,但定义在全局中,会出现访问不了私有变量的情况,所以我们提供了一个友元函数的声明.


  friend ostream& operator<<(ostream& out,const Date&d);
  friend istream& operator>>(istream& in, Date&d);


放在类中哪里都可以,表示这个函数是这个类的朋友,可以直接访问其中的变量与函数(关于友元的具体含义之后会细讲,)


13.1输出流重载:


ostream& operator<<(ostream& out,const Date&d)
{
   out<<"day: "<<d._day<<" month: "<<d._month<<" year: "<<d._year<<endl;
   return out;
}


ostream为输出流对象,其返回值会放进输出流在输出


13.2输入流重载:


istream& operator>>(istream& in,Date&d)
{
    int year, month, day;
  in >> day >> month >>year;
  if (month > 0 && month < 13
    && day > 0 && day <= d.getmonth(year, month))
  {
    d._year = year;
    d._month = month;
    d._day = day;
  }
  else
  {
    cout << "非法日期" << endl;
  }
  return in;
}


整体与上方相同


14.完整代码:


#include<iostream>
using namespace std;
class Date{
public:
    friend ostream& operator<<(ostream& out,const Date&d);
    friend istream& operator>>(istream& in, Date&d);
    Date(int day=1,int month=1,int year=1)
    {
        if(month>0&&month<13&&day>0&&day<getmonth(year,month))
            _day=day,_month=month,_year=year;
        else
            cout<<"输入错误";
    }
    Date(const Date&d)
    {
        _day=d._day;
        _month=d._month;
        _year=d._year;
        cout<<"copy"<<endl;
    }
    int getmonth(int year,int month)
    {
        static int monthday[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
        if((year%4==0&&year%100!=0)||year%400==0)
        {
            if(month==2)return 29;
        }
        return monthday[month];
    }
    void print()
    {
        cout<<"day: "<<_day<<" month: "<<_month<<" year: "<<_year<<endl;
    }
    Date operator=(const Date d)
    {
        _day=d._day;
        _month=d._month;
        _year=d._year;
        return *this;
    }
    Date& operator+=(int Day)
    {
        if(Day<0)
        {
            *this-=(-Day);
            return *this;
        }
        _day+=Day;
        while(_day>getmonth(_year,_month))
        {
            _day-=getmonth(_year,_month);
            ++_month;
            if(_month>12)
            {
                _month=1;
                _year++;
            }
        }
        return *this;
    }
    Date operator+(int day)
    {
        Date tmp=*this;
        tmp+=day;
        tmp.print();
        return tmp;
    }
    Date& operator++()
    {
        *this+=1;
        return *this;
    }
    Date operator++(int)
    {
        Date tmp=*this;
        *this+=1;
        return tmp;
    }
    int operator-(Date &d)
    {
        Date max=*this;
        Date min=d;
        int flag=1;
        if(max<min)
        {
            max=d;
            min=*this;
            flag=-1;
        }
        int n=0;
        while(min!=max)
        {
            ++min;
            ++n;
        }
        return n*flag;
    }
    Date& operator-=(const int Day)
    {
        if(Day<0)
        {
            *this+=(-Day);
            return *this;
        }
        _day-=Day;
        while(_day<=0)
        {
            --_month;
            if(_month<1)
            {
                _year--;
                _month=12;
            }
            _day+=getmonth(_year,_month);
        }
        return *this;
    }
    Date operator-(const int Day)
    {
        Date tmp=*this;
        return tmp-=Day;
    }
    Date& operator--()
    {
        *this-=1;
        return *this;
    }
    Date operator--(int)
    {
        Date tmp;
        *this-=1;
        return tmp;
    }
    bool operator==(const Date&d)const
    {
        if(_year==d._year&&_month==d._month&&_day==d._day)
        {
            return true;
        }
        return false;
    }
    bool operator!=(const Date&d)const
    {
        if(*this==d)return false;
        return true;
    }
    bool operator<(const Date &d)const
    {
        if(_year<d._year)
        {
            return true;
        }
        if(_year==d._year&&_month<d._month)
        {
            return true;
        }
        if(_year==d._year&&_month==d._month&&_day<d._day)
        {
            return true;
        }
        return false;  
    }
    bool operator<=(const Date &d)const
    {
        return *this<d||*this==d;
    }
    bool operator>(const Date &d)const
    {
        return (!(*this<d)&&(*this!=d))?true:false;
    }
    bool operator>=(const Date &d)const
    {
        return !(*this<d);
    }
private:
    int _year;
    int _month;
    int _day;
};
ostream& operator<<(ostream& out,const Date&d)
{
   out<<"day: "<<d._day<<" month: "<<d._month<<" year: "<<d._year<<endl;
   return out;
}
istream& operator>>(istream& in,Date&d)
{
    int year, month, day;
  in >> day >> month >>year;
  if (month > 0 && month < 13
    && day > 0 && day <= d.getmonth(year, month))
  {
    d._year = year;
    d._month = month;
    d._day = day;
  }
  else
  {
    cout << "非法日期" << endl;
  }
  return in;
}
int main()
{
    Date d1(2,5,2024);
    d1+=100;
    d1.print();
}


至此日期类整体完成


15. 取地址运算符重载


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


默认情况下,编译器会自动生成这个重载,大多数情况下我们也不需要写这个重载.


若不想让一个人获得可修改地址,仅想让其获得不可修改的const地址,可以这样写


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


16.至此六个内置类型成员函数完结


a52dd519ebf44cad93dc66e19330f051.png

目录
相关文章
|
5天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
25 5
|
11天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
40 4
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
36 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
24 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1