运算符重载(下)

简介: 运算符重载

🖊④-=和-


//借位-天数
Date& Date::operator-=(int day)
{
  if (day < 0)
  {
  return *this += -day;
  }
  _day -= day;
  while (_day <= 0)
  {
  --_month;
  if (_month == 0)
  {
    --_year;
    _month = 12;
  }
  _day += GetMonthDay(_year, _month);
  }
  return *this;
}
Date Date::operator-(int day)
{
  Date ret(*this);
  ret -= day;
  return ret;
}


🖊⑤日期-日期


如果我想知道两个日期之间的差值,该如何计算呢?

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


👓2.2前置++和后置++重载


我们知道前置++返回的是++之后的结果,后置++返回的是++之前的结果。这里C++的标准形式是:


1、前置++:类型&   operator++()


2、后置++:类型      operator++(int)


以日期类为例:

//前置++
Date& operator++()
 {
     _day += 1;
     return *this;
 }
//后置++
Date operator++(int)
 {
     Date temp(*this);
     _day += 1;
     return temp;
 }

1、为什么后置++会有参数int呢?有什么含义呢?


它并无多大意义,甚至说毫无用处,这是C++规定的,后置++重载时多增加一个int类型的参数,但调用参数时该参数不用传递,编译器自动传递。


2、前置++为什么返回引用,而后置++返回值呢?


①、前置++返回+1之后的结果,因为this指向的对象函数结束之后不会销毁,故以引用方式返回提高效率。


②、后置++是先使用后++,需要返回+1之前的旧值,故需在实现时先将this拷贝一份,然后给this++。


当然,前置--和后置--也与之类似:


//后置--
Date operator--(int)
 {
     Date temp(*this);
     _day -= 1;
     return temp;
 }
//前置--
Date& operator--(int)
 {
     _day -= 1;
     return *this;
 }


🏆三、<<重载和>>重载


int main()
{
  int a = 10;
  double d = 2.28;
  char c = 'f';
  cout << a <<" "<< d <<" "<< c << endl;
  return 0;
}

之前博主讲述的在C++阶段,引入了cout 和<<用于打印,cin和>>用于输出,它们拥有自动识别类型的功能。那么,它们真的有这么神奇,可以自动识别吗?当然不是,它们本身也是用C语言封装的。他们的头文件分别是<ostream>和<istream>。合起来也就是我们常引的


<iostream>头文件。


1669269333044.jpg


通过头文件,我们也可以看出来C++的cout和cin可以对内置类型(int,double,float,char等)类型进行识别,如果我现在想打印一个类对象,或者输入一个类对象成员,编译器本身是不支持,而我们现实是有这种需求的,所以就有了<<重载和>>重载用于解决这类问题。当然,我们要想实现也需要解决一些问题。


👓3.1 <<重载和>>重载在全局定义


可能有的老铁困惑了,之前讲的重载都在类里面定义,怎么到了这里不在类里面定义呢?这是有原因的。我们可以在类里面定义,只不过它不太符合习惯,没可读性。为什么呢?


1669269349183.jpg


当我们写到类里面的时候,我们按照正常去调用,(cout<<d1)会报错。


1669269360545.jpg


当我们采用这种别扭的调用方式时(d1.operator << (cout)或者d1 << cout),可以调用成功,为什么呢?这里就要说到一个问题:类成员函数存在隐式传参,也就是之前提及的this,this默认以第一个参数传递,所以我们如果定义在类里面,只能以这样别扭的形式调用。怎么解决?定义到全局!!


这里博主为了方便后面的讲解,将会以三个文件来讲解:


1669269369593.jpg


这是博主创建的三个文件。之后的说明将围绕这三个文件展开。


定义到全局的问题:


1、无法访问私有,C++规定在类外无法访问private内的内容。


2、定义到头文件的函数如果在两个以及两个以上的文件中包含这个头文件在链接时会有重定义错误,怎么解决。


👓3.2无法访问私有


当定义到全局我们无法访问私有怎么办?常用的方式有两种:


1、在类里面定义一个函数,通过这个函数把private内的成员带出来,我们再通过这个函数访问到private,这是Java常喜欢的方式,我们C++也可以,但我们不这么干。


2、友元声明。友元的关键字是friend,当我们哪个函数需要访问到private内部时,我们对这个函数的声明加一个friend即可。


1669269385700.jpg


当我们加上友元之后,就可以访问private了,但是这里还有报错,这就涉及到另一个问题:重定义链接的问题。


👓3.3重定义链接问题


这是一个在C语言预处理常说的老问题:定义到头文件的函数,如果在两个及两个以上头文件包含这个头文件,就会在合并符号表,链接的时候出现重定义问题。


解决方案有三种。


1、声明和定义分离,声明在头文件,定义在实现函数的文件。


2、定义为static。


3、内联函数


第一个方案是常用的一种手段,一般的函数都是这么处理的,这里主要谈一下第二和第三种方案。


🖊①定义为static


1669269405086.jpg


为什么定义为static可以呢?


我们知道static可以改变变量的生命周期,将其存储到静态区,但是我们很容易忽略的一个很重要的特点是:


在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。也就是说,被static关键字修饰的函数,只能在当前文件展开,不能在其他文件展开,也就避免了链接重定义的问题。


🖊②定义为内联函数


内联函数也是有效避免重定义问题的方法,为什么这里要把<<重载和>>重载定义为内联函数呢?除了重定义,还有一个关键是这两个重载函数是经常被调用使用的,而这一点契合内联函数的使用场景。

/*Date.h*/
class DateB
{
  friend void operator<<(ostream& out, const DateB& d);
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
inline void operator<<(ostream& out, const DateB& d)
{
  out << d._year << d._month << d._day << endl;
}
/*test.c*/
#include"Date.h"
void TestDate1()
{
  DateB d1(2022, 10, 11);
  //d1.operator<<(cout);
  cout<<d1;
}
int main()
{
  TestDate1();
  return 0;
}

这里还有最后一个问题没解决,就是和前面讲的赋值重载问题一样,返回值问题,返回void只能输出或输入一个类对象,我们应该让其返回类类型还是cout/cin类型呢?


1669269432673.jpg


这是我们常见的连续打印的模式,如果很显然从左往右打印,当打印完d1之后,很显然cout<<d1返回cout才能继续打印d2,所以返回的应该是ostream&.


>>重载和<<重载大致一样,只不过>>重载cin的类型是istream,同时传参的时候也不能加const限制,否则无法修改。


以日期类为例,>>重载和<<重载的形式:

class DateB
{
  friend ostream& operator<<(ostream& out, const DateB& d);
  friend istream& operator>>(istream& in, DateB& d);
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
inline ostream& operator<<(ostream& out, const DateB& d)
{
  out << d._year <<"年"<< d._month <<"月"<< d._day <<"日"<< endl;
  return out;
}
inline istream& operator>>(istream& in, DateB& d)
{
  in >> d._year >> d._month >> d._day;
  return in;
}

🏆四、const 成员


/*Date.h*/
class DateB
{
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  void Print()
  {
  cout << _year << " " << _month << " " << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
/*test.c*/
void TestDate1()
{
  DateB d1(2022, 10, 11);
  const DateB d2(2022, 10, 8);
  d1.Print();
  d2.Print();
}
int main()
{
  TestDate1();
  return 0;
}

1669269466068.jpg

当我const定义一个类对象d2,当我去调用类成员函数的时候,会发现会报错,这是为什么呢?


调用类成员函数会默认传this,this的定义是什么呢?


Date * const this,而我们隐式传参传递的是const Date*d2,这里就考察const的理解,我们this这个const只是限定不能去修改this名称,所以这里涉及到权限的放大问题,我们知道权限放大是被禁止的,可以缩小不能放大,那么这里怎么解决呢?


我们只需在函数后面加个const就可以缩小this的权限。


C++规定:将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰的是该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。


1669269475389.jpg


那么我们可以这样操作:

/*Date.h*/
class DateB
{
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  void Print()const
  {
  cout << _year << " " << _month << " " << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
/*test.c*/
void TestDate1()
{
  DateB d1(2022, 10, 11);
  const DateB d2(2022, 10, 8);
  d1.Print();
  d2.Print();
}
int main()
{
  TestDate1();
  return 0;
}

1669269492605.jpg


这里还有一个问题,为什么d1也可以调用呢?因为指针和引用的权限授予可以缩小而不能放大.所以我们在以后的类成员函数中可以在后面加const限制this,以提高代码的安全性。


🏆五、取地址及const取地址操作符重载


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

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


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

相关文章
|
1月前
|
C++
运算符重载
运算符重载
22 1
|
7月前
|
编译器 程序员 C++
C++的运算符重载介绍
所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。 实际上,我们已经在不知不觉中使用了运算符重载。例如,+号可以对不同类型(int、float 等)的数据进行加法操作;<<既是位移运算符,又可以配合 cout 向控制台输出数据。 C++ 本身已经对这些运算符进行了重载。C++ 也允许程序员自己重载运算符,这给我们带来了很大的便利。 下面的代码定义了一个复数类,通过运算符重载,可以用+号实现复数的加法运算:
47 0
|
7月前
|
存储 编译器 C++
C++运算符重载
C++运算符重载
|
27天前
|
C++
7. C++运算符重载
7. C++运算符重载
15 0
|
1月前
|
C++
【C++】——运算符重载
【C++】——运算符重载
33 1
|
1月前
|
算法 C++ 开发者
40运算符重载
40运算符重载
10 0
|
2月前
|
编译器 C++
【c++】运算符重载
【c++】运算符重载
【c++】运算符重载
|
4月前
|
C++
【C++】运算符重载
【C++】运算符重载
12 0
|
10月前
|
算法 安全 程序员
【为什么】C++ 中需要运算符重载
【为什么】C++ 中需要运算符重载
|
11月前
|
C++
【C++运算符重载】运算符重载(二)
【C++运算符重载】运算符重载(二)