C++ ------运算符重载:字符串和数组对象

C++ ------运算符重载:字符串和数组对象

一、简介

1、将展示如何使用C++中的运算符与类对象结合在一起使用,这个功能称为运算符的重载。
2、通过实现用户自定义的类phoneNumber,Array,String,Date来演示如何重载运算符。

二、运算符重载的基础知识

1、运算符的重载允许程序员在用户自定义类型上使用运算符。
2、运算符重载实现的功能可以通过函数的调用来实现,只不过使用运算符更清晰、方便。
3、运算符重载由运算符函数实现,其函数名是关键字operator后接重载的运算符,例如:

Complex Complex::operator+( const Complex &c)//运算符函数

4、C++不允许创造新的运算符,不过它允许重载大部分现有的运算符。
5、运算符重载函数可以是非static成员函数,也可以是全局函数(如main函数)。
6、要在类的对象上使用运算符,该运算符必须重载(因为编译器只知道运算符作用在标准类型上)。但是有三个例外:& = , 这三个可以直接用在对象上的。

三、运算符重载的限制

1、C++中出现的可重载和不可重载的运算符。
在这里插入图片描述
在这里插入图片描述
2、重载不能改变运算符的优先级、结合律、以及运算符的操作个数。
3、不能创建新的运算符,只有现有的运算符可以重载。
4、运算符的重载不能改变运算符对于基本类型对象操作的含义。

四、作为类成员函数和全局函数的运算符函数之比较。

1、运算符函数可以是成员函数或者全局函数
(1)、成员函数用this指针隐式获得类对象的某个参数,对二元运算符而言即左操作数。

例如:c1+c2;左操作数c1由this指针隐式获得,右操作数c2是需要参数传进去的。

(2)、全局函数中 ,二元运算符的两个操作数必须显示列出

即两个操作数都要作为参数传到函数里面去。
所以全局函数中的参数往往比成员函数的参数多

下面分别举例重载两种函数进行 c1+c2:
···重载为成员函数:

Complex Complex::operator+(const Complex &c)/*c1+c2依然是一个Complex对象,所以要返回的是一个Complex对象.这里的c就是c2*/
{
   //body;/*数据成员名字(成员函数可直接访问数据成员)+c.数据成员名字*/
}

注释:当编译器遇到c1+c2表达式时,其实会给c1发送一个消息,一个operator+的消息,然后右操作数当做参数传进去。即c1.operator+(c2); ,这个过程用户不用参与。

···重载为全局函数:

Complex operator+( const Complex &,const Complex &)
{
     //body;/*c1.~+c2.~*/
}

注释:遇到c1+c2时,马上进行一个函数的调用,左操作数作为第一个参数,又操作数作为第二个参数传进去。

下面以一个未重载的函数和两个重载的函数进行对比分析:
(1)未重载的函数:

//Complex.cpp
Complex Complex::add(const Complex &right)
{
   return Complex(realPart + right.realPart,imaginaryPart + right.imaginaryPart);
}//end function add,返回一个临时对象

注:编译器遇到 ::add 就允许进行 c1.add(c2) 操作

(2)重载成成员函数

//Complex.cpp
Complex Complex::operator+(const Complex &right)
{
    return Complex(realPart + right.realPart,imaginaryPart + right.imaginaryPart);
}//返回一个临时对象

注:编译器遇到 operator 时,就允许进行c1+c2操作。这种操作编译器也会转换成c1.operator+(c2) 的操作

(3)重载成全局函数

//必须为友元函数
Complex operator+(const Complex &left,const Complex &right)//声明 在类外,执行operator(c1,c2)的函数调用
{
  return Complex(left.realPart+right.realPart,left.imaginaryPart+right.imaginaryPart);
}

2、结论
(1)、无论运算符函数是通过成员函数还是全局函数实现,运算符表达式中的用法都是一样的。
(2)、重载为成员函数,左操作数必须为该类的对象或其一个引用(左操作数要发消息)。
(3)、下面的运算符必须重载为成员函数 (),[ ],->
(4)、如果左操作数必须是一个不同类的对象或者一个基本类型对象,那么该运算符函数必须重载为全局函数来实现。
(5)、全局函数重载运算符可以使 运算符具有交换性。

5+c2----operator+(int,complex);
c2+5----operator+(complex,int);

(6)、运算符的参数至少有一个必须是用户自定义类型的对象或者引用。这样使程序员无法改变运算符作用在基本类型上的方式。

五、重载流插入运算符和流提取运算符

1、必须重载成全局函数

cout << classObject;//左操作数是ostream类
cin >> classobject;//左操作数是istream类型

下面以一个PhoneNumber的实际例子来说明:

//PhoneNumber.h
#ifndef PHONENUMBER_H_INCLUDED
#define PHONENUMBER_H_INCLUDED

#include<iostream>
using namespace std;

#include<string>
using std::string;

class PhoneNumber
{
     friend ostream &operator<<( ostream &,const PhoneNumber &);//返回ostream的引用,即cout,使连续的输出成为可能。
     friend istream &operator>>( istream &,PhoneNumber &);
private:
     string areaCode;
     string exchange;
     string line;
};

#endif // PHONENUMBER_H_INCLUDED
//PhoneNumber.cpp
#include<iomanip>
using std::setw;

#include "PhoneNumber.h"

ostream &operator<<(ostream &output,const PhoneNumber &number)
{
     output << "("  << number.areaCode << ")" << number.exchange << "-" << number.line;

     return output;//output即为cout的引用。
}
istream &operator>>(istream &input,PhoneNumber &number)
{
     input.ignore();
     input >> setw(3) >> number.areaCode;
     input.ignore();
     input >> setw(3) >> number.exchange;
     input.ignore();
     input>> setw(3) >> number.line;
     return input;
}

//PhoneNumberTest.cpp

```cpp
#include<iostream>
using namespace std;

#include "PhoneNumber.h"

int main()
{
     PhoneNumber phone;
     cout << "Enter phone number in the form (123) 456-7890:" << endl;

     cin >> phone;//operator >> (cin,phone);
     cout << phone << endl;//operator << (input.phone);
     cout << phone <<endl;
     return 0;
}

六、重载一元运算符

`1、类的一元运算符可以重载为不带参数的非静态成员函数或者带有一个参数的全局函数。
例如:

//复数取反
class complex
/*不带参数的非静态成员函数*/
complex operator-();//-c--->c.operator-();用户---->编译器;

/*带有一个参数的全局函数*/
friend complex operator-(const complex &);//-c---->operator-(c);进行函数的调用

2、前置和后置自增运算符
(1)、前置返回实际自增过的新对象,这个对象可以在表达式中作为左值。++a
(2)、后置返回自增前的原始值,这个对象只能作为左值。a++,(这块空间不能操作,因为在返回后又进行了+1的操作)。
3、如何区分前置和后置自增运算符
(1)、重载成非静态的成员函数

/*重载成非静态的成员函数*/
class counter
{
public:
      counter(int = 0);//初始化
      counter &operator++();//两个函数名相同的函数的匹配与参数的不同相关。
      counter operator++(int);
      void disp();
private:
      int count;
};

++c1------>c1.operator++();无参数,与第一个函数匹配。
c1++------>c1.operator++(0);有参数,与第二个函数匹配,此处0是伪参数,没有具体用,它的作用就是区分前置与后置。

--------前置加的实现:

/*++c*/
counter &counter::operator++()
{
   count++;
   return *this;//指当前句柄这个对象。
}

--------后置加的实现:

/*c++*/
counter counter::operator++(int i)//i为伪参数
{
  counter temp;//定义temp临时对象
  temp =*this;//对象赋值,相当于赋值0给temp
  count++;//加1
  return temp;//返回的是加之前的值,返回后被回收。
}

用的时候实现:

int main()
{
   counter c1(5);//初始值是5
   (++(++c1)).disp();//5--->6---->7
   (c1++).disp();//返回加之前的7,这条语句执行完c1变成8.
   c1.disp();//返回8.
   return 0;
}

(2)、重载成全局函数

/*重载成全局函数*/
class counter
{
friend counter &operator++(counter &);//++c1;++c1--->oprator++(c1);
friend counter operator++(counter &,int);//int 后面的伪参数,对应c1++;c1++--->operator++(1,0);
public:
      counter(int = 0);//初始化
      void disp();
private:
      int count;
};

--------前置加的实现:

/*++c*/
counter &operator++(counter &c)
{
   c.count++;
   return c;
}

--------后置加的实现:

/*c++*/
counter operator++(counter &c,int i)//i为伪参数
{
  counter temp;
  temp = c;
  c.count++;//加1
  return temp;//返回的是加之前的值,返回后被回收。
}

用的时候实现:

int main()
{
   counter c1(5);//初始值是5
   (++(++c1)).disp();//5--->6---->7
   (c1++).disp();//返回加之前的7,这条语句执行完c1变成8.
   c1.disp();//返回8.
   return 0;
}

七、类型转换

1、转换构造函数(一个单参数的构造函数可以作为一个转换构造函数):将其他类型(包括基本类型)的对象转换成特定类的对象的单参数构造函数。(转换成当前类:构造函数所在的类型)

2、强制类型转换运算符:将某一类的对象转换成另一类的对象,或者转换成基本类型的对象。

单参数构造函数在特定情况下会被编译器征用当做转换构造函数
Array integers1(7),integers2;
integers2 = 8;//左操作数是一个Array类型,右操作数是一个int类型,所以单参数构造函数征用为转换函数,将8转换成一个临时对象,这个临时对象的长度为8。
integers2 = 8;
------>
Array(8);
integers2.operator=((Array(8));//调用单参数的构造函数。

不幸的是。编译器可能在并非期望的情形中使用了隐式转换,以至于产生歧义的表达式,造成编译错误或者执行期逻辑错误。

#include<iostream>
using namespace std;

#include"Array.h"

void outputArray(const Array &);

int main()
{
   Array integers1( 7 );//生成一个Array类型的对象integers1;
   outputArray( integers1 );//integers1当做参数调用全局函数
   outputArray( 3 );//3的类型与Array类型不符,自动调用转换构造函数生成临时对象存3,再调用全局函数。
   return 0;
}

void outputArray( const Array &arrayToOutPut )
{ 
   cout << "The Array received has " << arrayToOutPut.getSize()
   << "elements.The contents are:\n" << arrayToOutPut << endl;
}

运行结果:
在这里插入图片描述
3、防止无意中将单参数函数用作转换构造函数:在构造函数前加关键字explicit,此时构造函数仅仅是单纯的构造函数。

//构造函数前加关键字explicit时;
 Array integers1( 7 );
 outputArray( integers1 );
 
 outputArray( 3 );//此操作将会错误;
/*下面形式将被允许*/
 outputArray( Array(3));

试图调用explicit构造函数以进行隐式转换会导致编译错误。

explicit只能用在单参数构造函数前。

对于那些不应被编译器用来执行隐式转换的单参数构造函数,请务必记住使用关键字explicit;

4、转换运算符:本类对象转换成其他类型或者基本类型,转换运算符函数类的非static成员函数。
转换运算符函数:
在这里插入图片描述

1、以opetator开头;
2、函数名为要转换成的类型;
3、参数表基本为空;
4、一般为const 函数。

转换函数没有返回值,没有参数:
`

class Smalllnt
{
public:
      Smalllnt(int i = 0) : val(i)
      {  
      }
      operator int() const//没有返回类型,没有参数。
      { 
         return val;
      }
private:
       int val;
}
Smalllnt si;
double dval;
si >= dval;//Smalllnt类型转换成int类型,然后都是基本类型,编译器再将int转换成double。
/*operator int() const因为提供了此函数*/
发布了10 篇原创文章 · 获赞 227 · 访问量 4004

猜你喜欢

转载自blog.csdn.net/m0_46518461/article/details/105351170