C++:运算符重载函数之"++"、"--"、"[ ]"、"=="的应用

简介:

5.2.5 "++"和"--"的重载

复制代码
 对于前缀方式++ob,可以用运算符函数重载为:
            ob.operator++()         //成员函数重载
operator++(X &ob)       //友元函数重载,其中ob为类X的对象的引用 
            
           
          对于后缀方式++ob,可以用运算符函数重载为:
            ob.operator++(int)      //成员函数重载
operator++(X &ob,int)   //友元函数重载,其中ob为类X的对象的引用 
            
         调用时,参数int一般被传递给值0,例如:
         class X{
           ...
           public:
           ...
            X operator++();         //前缀方式 
            X operator++(int);      //后缀方式
         };
         int main()
         {
           X ob;
           ...
           ++ob;                //隐式调用ob.operator++() 
           ob++;                //隐式调用ob.operator++(int)
           ob.operator++();     //显式调用ob.operator++(),意为++ob 
           ob.operator++(0);    //显式调用ob.operator++(int),意为ob++
         }
         
         类似的,也可以重载为友元函数,例如:
         
         class Y{
           ...
           public:
           ...
            firend Y operator++(Y &);         //前缀方式 
            friend Y operator++(Y &,int);      //后缀方式
         };
         int main()
         {
           Y ob;
           ...
           ++ob;                //隐式调用ob.operator++(Y&) 
           ob++;                //隐式调用ob.operator++(Y&,int)
           operator++(ob);      //显式调用ob.operator++(Y&),意为++ob 
           operator++(ob,0);    //显式调用ob.operator++(Y&,int),意为ob++
         }
复制代码

//例5.8 使用成员函数以前缀方式后后缀方式重载运算符"--"

复制代码
#include<iostream>
using namespace std;
class Three{
  public:
   Three(int I1=0,int I2=0,int I3=0)
   {
    i1 = I1;
    i2 = I2;
    i3 = I3;
   }
   Three operator--();              //声明自减运算符--重载成员函数(前缀方式) 
   Three operator--(int a);        //声明自减运算符--重载成员函数(后缀方式)
   void show(); 
  private:
   int i1,i2,i3;
};
Three Three::operator--()           //定义自减运算符--重载成员函数(前缀方式)
{
  --i1;
  --i2;
  --i3;
  return *this;   //返回自减后的当前对象 
}
Three Three::operator--(int a)          //定义自减运算符--重载成员函数(后缀方式)
{
  Three temp(*this);    
  i1--;
  i2--;
  i3--;
  return temp;  //返回自减前的当前对象
} 
void Three::show()
{
 cout<<"i1="<<i1<<",";
 cout<<"i2="<<i2<<",";
 cout<<"i3="<<i3<<endl;
}
int main()
{
 Three t1(4,5,6),t2,t3(11,12,13),t4;
 t1.show();
 
 --t1;                       //隐式调用(前缀方式)
 t1.show();                  //显示执行--t1后的值 
 t2=t1--;                      //隐式调用(后缀方式),将t1自减前的值赋给t2 
 t2.show();                  //显示t2保存的是执行t1--之前的t1的值 
 t1.show();                  //显示执行t1--之后的t1的值 
 cout<<endl;
 
 t3.show();
 t3.operator--();           //显示调用(前缀方式)
 t3.show(); 
 t4=t3.operator--(0);       //显示调用(后缀方式)
 t4.show();
 t3.show();
 
 return 0; 
}
复制代码

//例5.8 使用友元函数以前缀方式和后缀方式重载运算符"++"

复制代码
#include<iostream>
using namespace std;
class Three{
  public:
   Three(int I1=0,int I2=0,int I3=0)
   {
    i1 = I1;
    i2 = I2;
    i3 = I3;
   }
   friend Three operator++(Three &);              //声明自加运算符++重载成员函数(前缀方式) 
   friend Three operator++(Three &,int );         //声明自加运算符++重载成员函数(后缀方式)
   void show(); 
  private:
   int i1,i2,i3;
};
Three operator++(Three &T)                //声明自加运算符++重载友元函数(前缀方式)
{
  ++T.i1;
  ++T.i2;
  ++T.i3;
  return T;   //返回自加后的对象 
}
Three operator++(Three &T,int a)          //声明自加运算符--重载友元函数(后缀方式)
{
  Three temp(T);    
  T.i1++;
  T.i2++;
  T.i3++;
  return temp;  //返回自加前的对象
} 
void Three::show()
{
 cout<<"i1="<<i1<<",";
 cout<<"i2="<<i2<<",";
 cout<<"i3="<<i3<<endl;
}
int main()
{
 Three t1(4,5,6),t2,t3(14,15,16),t4;
 t1.show();
 
 ++t1;                       //隐式调用(前缀方式)
 t1.show();                  //显示执行++t1后的值 
 t2=t1++;                      //隐式调用(后缀方式),将t1自加前的值赋给t2 
 t2.show();                  //显示t2保存的是执行t1++之前的t1的值 
 t1.show();                  //显示执行t1++之后的t1的值 
 cout<<endl;
 
 t3.show();
 operator++(t3);           //显示调用(前缀方式)
 t3.show(); 
 t4=operator++(t3,0);       //显示调用(后缀方式)
 t4.show();
 t3.show();
 
 return 0; 
}
复制代码

说明:

(1)由于友元运算符重载函数没有this指针,所以不能引用this指针所指的对象,使用友元函数重载增运算符"++"或自减"--"时,应采用对象引用传递数据。

例如: 
friend Three operator++(Three &); //声明自加运算符++重载成员函数(前缀方式) 
friend Three operator++(Three &,int ); //声明自加运算符++重载成员函数(后缀方式)


(2)前缀方式和后缀方式的函数内部语句可以相同,也可以不同,取决于编程的需要。

 

5.2.7 下标运算符"[]"的重载
在C++中,在重载下标运算符[]时,认为它是一个双目运算符,例如X[Y]可以看成:
[]---------双目运算符
X----------左操作数
Y----------右操作数 
其相应的运算符重载函数名为operator[]。
设X是一个类的对象,类中定义了重载"[]"的operator[]函数,则表达式
  X[Y]   被解释为    X.operator[](y);

下标运算符重载函数只能定义成员函数,其形式如下:
返回类型 类名::operator[](形参)

      //函数体 
 }

注意:形参在此表示下标,C++规定只能有一个参数

//例5.12 使用下标运算符重载函数的引例

复制代码
#include<iostream>
using namespace std;
class Vector4{
 public:
  Vector4(int a1,int a2,int a3,int a4)
  {
   v[0]=a1;
   v[1]=a2;
   v[2]=a3;
   v[3]=a4;
  }
  private:
    int v[4];
}; 
int main()
{
 Vector4 ve(1,2,3,4);
 cout<<v[2];   //运行错误,v[2]是类Vector4的私有成员,即使是公有成员,输出格式应为ve.v[2]; 
 return 0;
}

 可是,如果对[]进行重载,即使v[2]是私有成员,也可以运行成功,直接访问。即:
   int &Vector::operator[](int bi)
   {
       if(bi<0||bi>=4)
       {
       cout<<"Bad subscript!\n";
          exit(1);
       }
       return v[bi];  //v[bi]被解释为v.operator[](2) 
   }
复制代码

//例5.13 使用下标运算符[]重载函数

复制代码
#include<iostream>
using namespace std;
class Vector4{
 public:
  Vector4(int a1,int a2,int a3,int a4)
  {
   v[0]=a1;
   v[1]=a2;
   v[2]=a3;
   v[3]=a4;
  }
  int &operator[](int ); 
  private:
    int v[4];
}; 
int &Vector4::operator[](int bi)  //返回一个int型的引用 
{
  if(bi<0||bi>=4)
   {
    cout<<"Bad subscript!\n";
       exit(1);
   }
  return v[bi];  //v[bi]被解释为v.operator[](2) 
}
int main()
{
 Vector4 v(1,2,3,4);
 int i=0;
 for(;i<4;i++)
 cout<<"v["<<i<<"]="<<v[i]<<"\n";  //v[i]被解释为v.operator[](i); 
 cout<<endl;
 
 v[3]=v[2];        //v[2]被解释为v.operator[](2); 
 cout<<"v[3]="<<v[3]<<endl; //v[3]被解释为v.operator[](3);
 
 v[2]=22;
 cout<<"v[2]="<<v[2]<<endl;
    
 return 0;
}
/*
运行结果: 
v[0]=1
v[1]=2
v[2]=3
v[3]=4

v[3]=3
v[2]=22
*/
复制代码

 

5.2.6 赋值运算符"="的重载
对于任一类X,如果没有用户自定义的赋值运算符函数,那么系统将自动地为其生成一个默认的
赋值运算符函数,例如:
X &X::operator=(const X &source)
{
//成员间赋值 

若obj1和obj2是类X的两个对象,obj2已经建立,则编译程序遇到如下语句;
obj1=obj2;
就调用默认的赋值运算符函数,将对象obj2的数据成员逐域复制到obj1中。

采用默认的赋值运算符函数实现的数据成员逐一赋值的方法是一种浅层复制非方法。通常,默认的赋值运算符函数是能够胜任工作的。但是,对于许多重要的实例类来说,仅有默认的赋值运算符函数还是不够的,还需要用户根据实际需要自己对赋值元算法进行重载,以解决遇到的问题。指针悬挂就是这方面的一个典型问题。

1.指针悬挂问题
在某些特殊情况下,如类中有指针类型时,使用默认的赋值运算符函数会产生错误。 
//例 5.10 关于浅层复制的例子。

复制代码
#include<iostream>
using namespace std;
class STRING{
  public:
   STRING(char* s)
   {
    cout<<"Constructor called."<<endl;
    pt = new char(strlen(s)+1);
    strcpy(pt,s); 
   }
   ~STRING()
   {
    cout<<"Destructor called."<<pt<<endl;
    delete pt;
   }
  private:
   char* pt;
};
int main()
{
 STRING p1("book");
 STRING p2("jeep");
 p2=p1;
 return 0;
}
复制代码

运行结果: 
Constructor called. (1) 
Constructor called. (2) 
Destructor called.book (3) 
Destructor called.*q (4)

结果出现了指针悬挂问题。

原因分析:对象p1和p2建立时,分别调用构造函数,输出(1)(2)。通过new运算符分别从内存动态分配一块空间,对象p1的字符指针pt指向book,对象p2的字符指针pt指向jeep;执行p2=p1时,由于用户没有定义赋值运算符函数,系统于是就会调用默认的赋值运算法函数。使对象p1和p2的字符指针pt都指向new开辟的同一块内存空间,该内存空间里所存放的内容是book;

主程序结束时,系统逐一撤销建立的对象,因此第一次调用析构函数,撤销对象p2,输出(3),并用delete释放new开辟的动态空间。再进行第二次调用析构函数,撤销对象p1,由于p1和p2的指针pt是指向同一内存的,在撤销p2时,已经释放了pt指向的空间,此时,尽管对象p1的指针pt存在,可是却无法访问此空间了。所以输出的(4)中pt指向的内容是随机字符,而不是book.同一空间当是不允许用delete释放两次的,这就是所谓的指针悬挂问题。

由于本例的类中含有指向动态空间的指针pt,执行语句"p2=p1"时,调用的就是默认的赋值运算符函数,采用的是浅层复制方法,使两个对象p1和p2的指针pt都指向new开辟的同一个空间,于是出现了指针悬挂现象。

 

2.用深层复制解决指针悬挂问题
为了解决浅层复制出现的错误,必须显示地定义一个自己的赋值运算符重载函数,使之不但复制数据成员,而且为对象p1和p2分配了各自的内存空间,这就是所谓的深层复制。 

//例5.11 关于深层赋值的例子

复制代码
#include<iostream>
using namespace std;
class STRING{
  public:
   STRING(char* s)
   {
    cout<<"Constructor called."<<endl;
    pt = new char(strlen(s)+1);
    strcpy(pt,s); 
   }
   STRING &operator=(const STRING &s);     //声明赋值运算符重载函数 
   ~STRING()
   {
    cout<<"Destructor called."<<pt<<endl;
    delete pt;
   }
  private:
   char* pt;
};
STRING &STRING::operator=(const STRING &s) //定义赋值运算符重载函数 
{
  if(this==&s)  return *this;         //防止s=s的赋值 
  delete pt;                          //释放掉原区域 
  pt = new char(strlen(s.pt)+1);      //分配新区域 
  strcpy(pt,s.pt);                    //字符串复制 
  return *this;
}
int main()
{
 STRING p1("book");
 STRING p2("jeep");
 p2=p1;
 return 0;
}
复制代码

 

运行结果:
Constructor called.
Constructor called.
Destructor called.book
Destructor called.book

结果解决了指针悬挂问题。
原因分析:对象p1和p2建立时,分别调用构造函数,输出(1)(2)。通过new运算符分别从内存动态分配一块空间,对象p1的字符指针pt指向book,对象p2的字符指针pt指向jeep;执行p2=p1时,由于用户自己定义了赋值运算符函数,释放掉了p2指针pt所指的旧区域,又按照新的长度分配新的内存空间给p2,再把对象p1的指针pt所指向的数据book赋给p2对象的指针pt所指向的区域内。也即 p1的指针pt指向book,p2的指针pt也指向book。 

主程序结束时,系统逐一撤销建立的对象,虽然对象p1和对象p2的指针pt都指向了相同内容book,但是它们却分别有自己的动态分配的空间。所以delete释放空间时,就不会出现指针悬挂现象了。

说明:类的赋值运算符"="只能重载为成员函数,而不能把它重载为友元函数,因为若把上述赋值运算符"="重载为友元函数

friend string &operator=(string &p2,string &p1)

表达式
p1="book" 将被解释为 operator=(p1,book) 这显然是没有问题的,

但是对于表达式"book"=p1 将被解释为 operator=(book,p1),即C++编译器首先将book转换成一个隐藏的string对象,然后使用对象p2引用该隐藏的对象,并不认为这个表达式是错的,从而导致赋值语句上的混乱。因此,双目赋值运算符重载为成员函数,而不能重载为友元函数。

 

程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!

本文转自当天真遇到现实博客园博客,原文链接:http://www.cnblogs.com/XYQ-208910/p/4912596.html ,如需转载请自行联系原作者
相关文章
|
12天前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
17 2
|
23天前
|
安全 算法 C++
【C/C++ 泛型编程 应用篇】C++ 如何通过Type traits处理弱枚举和强枚举
【C/C++ 泛型编程 应用篇】C++ 如何通过Type traits处理弱枚举和强枚举
46 3
|
24天前
|
设计模式 安全 算法
【C/C++ 类型转换 】深入理解C++向上转型:从基础到应用
【C/C++ 类型转换 】深入理解C++向上转型:从基础到应用
41 0
|
24天前
|
算法 编译器 C语言
【C++ 异常】C++ 标准库异常类及其应用
【C++ 异常】C++ 标准库异常类及其应用
17 0
|
24天前
|
算法 C++ 开发者
【C++运算符重载】深入理解C++中的流运算符 >>和<<重载
【C++运算符重载】深入理解C++中的流运算符 >>和<<重载
35 0
|
3天前
|
存储 编译器 C++
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
|
8天前
|
编译器 C语言 C++
【C++初阶(九)】C++模版(初阶)----函数模版与类模版
【C++初阶(九)】C++模版(初阶)----函数模版与类模版
16 0
|
12天前
|
程序员 C++
C++语言模板学习应用案例
C++模板实现通用代码,以适应多种数据类型。示例展示了一个计算两数之和的模板函数`add&lt;T&gt;`,可处理整数和浮点数。在`main`函数中,展示了对`add`模板的调用,分别计算整数和浮点数的和,输出结果。
11 2
|
18天前
|
存储 缓存 C++
C++链表常用的函数编写(增查删改)内附完整程序
C++链表常用的函数编写(增查删改)内附完整程序
|
20天前
|
存储 安全 编译器
【C++】类的六大默认成员函数及其特性(万字详解)
【C++】类的六大默认成员函数及其特性(万字详解)
35 3