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 ,如需转载请自行联系原作者
相关文章
|
2月前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
61 6
|
2月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
26 0
C++ 多线程之线程管理函数
|
2月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
33 3
|
2月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
260 1
|
2月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
39 1
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
47 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
59 2
|
3月前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
26 3