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引用该隐藏的对象,并不认为这个表达式是错的,从而导致赋值语句上的混乱。因此,双目赋值运算符重载为成员函数,而不能重载为友元函数。

时间: 2024-10-24 23:57:07

C++:运算符重载函数之"++"、"--"、"[ ]"的应用的相关文章

C++:运算符重载函数之成员运算符重载函数

5.2.3 成员运算符重载函数 在C++中可以把运算符重载函数定义为某个类的成员函数,称之为成员运算符重载函数. 1. 定义成员运算符重载函数的语法形式 (1)在类的内部,定义成员运算符重载函数的格式如下: 函数类型 operator 运算符(形参表) {       函数体 } (2)成员运算符重载函数也可以在类中声明成员函数的原型,在类外定义. 在类的内部,声明成员运算符重载函数原型的格式如下: class X{      ...      函数类型 operator运算符(参数表); };

C++:运算符重载函数之友元运算符重载

5.2.2 友元运算符重载函数 运算符重载函数一般采用两种形式定义: 一是定义为它将要操作的类的成员函数(简称运算符重载函数): 二是定义为类的友元函数(简称为友元运算符重载函数). 1.定义友元运算符重载函数的语法如下: 在类的内部: friend 函数类型 operator运算符(形参表) { 函数体 } 在类的内部声明,在类外定义: class X{ ... friend 函数类型 operator运算符(形参表): }; 函数类型 X::operator运算符(形参表) { 函数体 }

前置后置单目运算符重载函数返回值用法

Clock& Clock::operator ++() //前置单目运算符重载函数{Second++;if(Second>=60){Second=Second-60;Minute++;if(Minute>=60){Minute=Minute-60;Hour++;Hour=Hour%24;}}return *this;}//后置单目运算符重载Clock Clock::operator ++(int) //注意形参表中的整型参数{Clock old=*this;++(*this);retu

C++:友元运算符重载函数

运算符重载函数:实现对象之间进行算数运算,(实际上是对象的属性之间做运算),包括+(加号).-(减号).*./.=.++.--.-(负号).+(正号) 运算符重载函数分为:友元运算符重载函数.成员运算符重载函数 运算符运算符重载函数按运算类型为:双目运算符重载函数,如加.减.乘.除.赋值:   单目运算符重载函数:自加.自减.取正负号 切记:成员运算符. 和->,sezeof等不能重载.运算符重载函数的参数至少有一个是类类型或引用类型, 下面为友元运算符重载函数举例: 1 #include<i

运算符重载(作为普通函数)

运算符重载---基本概念 C++程序设计 郭炜 刘家瑛 1 #include<iostream> 2 using namespace std; 3 class Complex{ 4 public: 5 double real; 6 double imaginary; 7 Complex(double a=0.0,double b=0.0) : real(a),imaginary(b) {}//初始化 8 ~Complex(){} 9 void print(); 10 }; 11 Complex

C++运算符重载(成员函数方式)

一.运算符重载 C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型,也需要有类似的运算操作.如果将C++中这些现存的运算符直接作用于用户自定义的类型数据上,会得到什么样的结果呢?编译器无法给出正常的结果,因为我们需要运算符重载,给运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为,增强了运算符的普适性. 运算符重载的实质是函数重载.在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型

C++运算符重载(友元函数方式)

我们知道,C++中的运算符重载有两种形式:①重载为类的成员函数(见C++运算符重载(成员函数方式)),②重载为类的友元函数. 当重载友元函数时,将没有隐含的参数this指针.这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数.但是,有些运行符不能重载为友元函数,它们是:=,(),[]和->. 重载为友元函数的运算符重载函数的定义格式如下: [cpp] view plaincopy 一.程序实例 [cpp] view plaincopy 1 //运算符重载:友元函数方式 2

为什么operator&lt;&lt;&gt;&gt;运算符重载一定要为友元函数呢?

如果是重载双目操作符(即为类的成员函数),就只要设置一个参数作为右侧运算量,而左侧运算量就是对象本身...... 而 >>  或<< 左侧运算量是 cin或cout 而不是对象本身,所以不满足后面一点........就只能申明为友元函数了... 如果一定要声明为成员函数,只能成为如下的形式: ostream & operator<<(ostream &output) { return output; } 所以在运用这个<<运算符时就变为这种形

运算符重载(作为成员函数)

运算符重载---基本概念 C++程序设计 郭炜 刘家瑛 1 #include<iostream> 2 using namespace std; 3 class Complex{ 4 private: 5 double real; 6 double imaginary; 7 public: 8 Complex(double a=0.0,double b=0.0) : real(a),imaginary(b) {}//初始化 9 ~Complex(){} 10 Complex operator+(