C++面向对象程序设计(四)

C++面向对象程序设计(四)

1.运算符重载的基本概念

运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使统一运算符作用于不同类型的数据时导致不同类型的行为。

运算符重载的目的是:扩展C++中提供的运算符的使用范围,使之能作用于对象。

同一个运算符,对不同类型的操作数,所发生的行为不同。

  • 运算符重载的形式

运算符重载的实质是函数重载。

可以重载为普通函数,也可以重载为成员函数。

把含运算符的表达式转换成对运算符函数的调用。

把运算符的操作数转换成运算符函数的参数。

运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

class Complex{
  public:
  double real,imag;
  Complex(double r=0.0,double i=0.0):real(r),imag(i){}
  Complex operator-(const Complex &c);
};
Complex operator+(const Complex &a,const Complex &b){
  return Complex(a.real+b.real,a.imag+b.imag);
}
Complex Complex::operator-(const Complex &c){
  return Complex(real-c.real,imag-c.imag);
}

重载为成员函数时,参数个数为运算符目数减一。

重载为普通函数时,参数个数为运算符目数。

int main(){
  Complex a(4,4),b(1,1),c;
  c=a+b;
  //等价于c=operator+(a,b); 普通函数重载
  cout<<c.real<<","<<c.imag<<endl;
  cout<<(a-b).real<<","<<(a-b).imag<<endl;
  //a-b等价于a.operator-(b)	//成员函数重载
  return 0;
}

习题:

如果将[ ]运算符重载成一个类的成员函数,则该重载函数有几个参数?

(1个)

2.赋值运算符的重载

有时希望赋值运算符两边的类型可以不匹配,比如:把一个int类型变量赋值给一个Complex对象,或把一个char*类型的字符串赋值给一个字符串对象,此时就需要重载运算符"="。

赋值运算符“=”只能重载为成员函数

class String{
  private:
  char *str;
  public:
  String():str(new char[1]){str[0]=0;}
  const char * c_str(){return str;}
  String &operator=(const char *s);
  ~String(){delete [] str};
};
String & String::operator=(const char *s){
  delete [] str;
  str=new char[strlen(s)+1];
  strcpy(str,s);
  return *this;
}
int main(){
  String s;
  s="Good Luck,";
  //等价于 s.operator=("Good Luck,");
  cout<<s.c_str()<<endl;
  //String s2="hello!";这条会报错,这是初始化语句,不是赋值语句,初始化语句是用构造函数,因为我们没有写这样的构造函数,会报错。
  s="SHenzhou 8!";
  //等价于 s.operator=("Shen zhou 8!");
  cout<<s.c_str()<<endl;
  return 0;
}
  • 浅拷贝和深拷贝
class String{
  private:
  cahr *str;
  public:
  String():str(new char[1]){str[0]=0;}
  const char * c_str(){return str;};
  String &operator=(const cahr *s){
    delete[] str;
    str=new char[strlen(s)+1];
    strcpy(str,s);
    return *this;
  };
  ~String(){delete[]str;}
};
String s1="this";
String s2="that";
s1=s2;

如果不定义自己的赋值运算符,那么s1=s2实际上导致s1.str和s2.str指向同一个地方

如果s1对象消亡,析构函数将释放s1.str指向的空间,则s2消亡时还要释放一次,不妥。

另外,如果执行s1=“other”;会导致s2.str指向的地方被delete

因此要在class String里添加新的成员函数:

String &operator=(const String&s){
  delete[] str;
  str=new char[strlen(s.str)+1];
  strcpy(str,s.str);
  return *this;
}

以上函数仍需要改进

String s;
s="hello";
s=s;

在=运算时,先delete了s的str成员指向的内存,导致出现了问题

String &operator=(const Sting&s){
  if(this==&s)
    return *this;
  str=new char[strlen(s.str)+1];
  strcpy(str,s.str);
  return *this;
}

对operator=返回值类型讨论

对运算符重载的时候,好的风格是应该尽量保留原运算符原本的特性。

考虑 a=b=c; (a=b)=c;

分别等价于:

a.operator=(b.operator=©);

(a.oprator=(b)).operator=©;

为String类编写复制构造函数的时候,会面临和=同样的问题,用同样的方法处理。

在复制时,调用的默认的复制构造函数,只是将str复制过去,即指向同一个地方,析构时就会产生问题。

String(String & s){
  str=new char[strlen(s.str)+1];
  strcpy(str,s.str);
}

一定要注意指针变量的复制。

3.运算符重载为友元函数

一般情况下,将运算符重载为类的成员函数,是较好的选择。

但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

class Complex{
  double:real,imag;
  public:
  Complex(double x,double y):real(r),imag(i){};
  Complex operator+(double r);
};
Complex Complex::operator+(double r){
  return Complex(real+r,img);
}

Complex c;

c=c+5;有定义,相当于c.operator+(5);

c=5+c;会出错

所以,为了使上述表达式成立,需要将+重载为普通函数。

Complex operator+(double r,const Complex &c){
  return Complex(c.real+r,c.imag);
}//但是普通函数又不能访问私有成员,所以,需要将运算符+重载为友元。
class Complex{
  double real,imag;
  public:
  Complex(double r,double i):real(r),imag(i){};
  Complex operator+(double r);
  friend Complex operator+(double r,const Complex &c);
};

4.可变长整型数组

可实现如下的操作

int main(){
  CArray a;
  for(int i=0;i<5;++i)
    a.push_back(i);//要用动态分配的内存来存放数组元素,需要一个指针成员变量
  CArray a2,a3;
  a2=a;//需要重载=
  for(int i=0;i<a.length();++i)
    cout<<a2[i]<<" ";//需要重载[]
  a2=a3;
  for(int i=0;i<a2.length();++i)
    cout<<a2[i]<<" ";
  cout<<endl;
  a[3]=100;
  CArray a4(a);//需要自己完成复制构造函数,实现深拷贝
  for(int i=0;i<a4.length();++i)
    cout<<a4[i]<<" ";
  return 0;
}
class CArray{
  int size;
  int *ptr;
  public:
  CArray (int s=0);
  CArray(CArray & a);
  ~CArray();
  void push_back(int v);
  CArray &operator =(const CArray &a);
  int length(){return size;}
  int &CArray::operator[](int i){//返回值不能是int,如果只是int,不能实现a[i]=4这种语句,只有引用作为函数的返回值才能放在赋值号的左侧。
    return ptr[i];
  }
}
CArray::CArray(int s):size(s){
  if(s==0)
    ptr=NULL;
  else
    ptr=new int [s];
}
CArray::CArray(CArray &a){
  if(!a.ptr){
    ptr=NULL;
    size=0;
    return;
  }
  ptr=new int [a.size];
  memcpy(ptr,a.ptr,sizeof(int)*a.size);
  size=a.size;
}
CArray::~CArray(){
  if(!ptr) delete [] ptr;
}
CArray & CArray::operator=(const CArray&a){
  if(ptr==a.ptr)
    return *this;
  if(a.ptr==NULL){
    if(ptr) delete [] ptr;
    ptr=NULL;
    size=0;
    return *this;
  }
  if(size<a.size){
    if(ptr)
      delete [] ptr;
    ptr=new int [a.size];
  }
  memcpy(ptr,a.ptr,sizeof(int)*a.size);
  size=a.size;
  return *this;
}
void CArray::push_back(int v){
  if(ptr){
    int *tmpPtr=new int [size+1];
    memcpy(tmpPtr,ptr,sizeof(int)*a.size);
  	delete [] ptr;
    ptr=tmpPtr;
  }
  else
    ptr=new int[1];
  ptr[size++]=v;
}

习题:

在可变长数组类中,重载了哪些运算符或编写了哪些成员函数(B)

A) = , [] , ++

B) = , [] , 复制构造函数

C) = , [] , ++ , 复制构造函数

D) = , [] , & , 复制构造函数

5.流插入运算符和流提取运算符的重载

cout是在iostream中定义的,ostream类的对象

<<能作用在cout上是因为,在iostream中对<<进行了重载

cout<<5 即 cout.operator<<(5);

cout<<“this” 即 cout.operator<<(“this”);

怎样才能cout<<5<<“this”

ostream & ostream::operator<<(int n){
  return *this;
}
ostream & ostream::operator<<(const char * s){
  return *this;
}

cout<<5<<“this”

本质上的调用形式如下:

cout.operator<<(5).operator<<(“this”);

class CStudent{
  public:
  int nAge;
};
int main(){
  CStudent s;
  s.nAge=5;
  cout<<s<<"hello";//重载<<使其能够接收 对象 参数
  return 0;
}
ostream & operator(ostream & o,const CStudent &s){
  o<<s.nAge;
}

例:

假定c是Complex复数类的对象,现在希望写cout<<c,就能以a+bi的形式输出c的值,写cin>>c,就能从键盘接收a+bi,形式的输入,并且使得c.real=a,c.imag=b。

int main(){
  Complex c;
  int n;
  cin>>c>>n;
  cout<<c<<","<<n;
  return 0;
}
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Complex{
  double real,imag;
  public:
  Complex(double r=0,double i=0):real(r),imag(i){};
  friend ostream &operator<<(ostream &os,const Complex &c);
  friend istream &operator>>(istream &is,Complex &c);
};
ostream & operator<<(ostream &os,const Complex &c){
  os<<c.real<<"+"<<c.imag<<"i";
  return os;
}
istream & operator>>(istream &is,Complex &c){
  string s;
  is>>s;
  int pos=s.find("+",0);//分离实部和虚部
  string sTmp=s.substr(0,pos);
  c.real=atof(sTmp.c_str());
  sTmp=s.substr(pos+1,s.length()-pos-2);//
  c.imag=atof(sTmp.c_str());
  return is;
}

习题:

重载“<<”用于自定义的对象通过cout输出时,以下说法哪个是正确的?(C)

A)可以将“<<”重载为ostream类的成员函数,返回值类型是ostream &

B)可以将"<<"重载为全局函数,第一个参数以及返回值,类型都是ostream

C)可以将"<<"重载为全局函数,第一个参数以及返回值都是ostream &(正确)

D)可以将"<<"重载为ostream类的成员函数,返回值类型是ostream

6.重载类型转换运算符

#include <iostream>
using namespace std;
class Complex{
  double real,imag;
  public:
  Complex(double r=0,double i=0):real(r),imag(i){};
  operator double(){return real;}
  //重载强制类型转换运算符
};
int main(){
  Complex c(1.2,3.4);
  cout<<(double)c<<endl;
  double n=2+C;//double n=2+c.operator double()
  cout<<n;
  return 0;
}

7.自增自减运算符的重载

自增运算符++、自减运算符–有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:

前置运算符作为一元运算符重载

T & operator++();
T & operator--();
T1 & operator++(T2);
T1 & operator--(T2);

后置运算符作为二元运算符重载,多写一个没用的参数:

T operator++(int);
T operator--(int);
T1 operator++(T2,int);
T1 operator--(T2,int);
class CDemo{
  private:
  int n;
  public:
  CDemo(int i=0):n(i){}
  CDemo &operator++();
  CDemo operator++(int);
  operator int(){return n;}
  friend CDemo & operator--(CDemo &);
  friend CDemo operator--(CDemo &,int);
};
CDemo & CDemo::operator++(){//前置++
  n++;
  return *this;
}
CDemo CDemo::operator++(){//后置++
  CDemo tmp(*this);
  n++;
  return tmp;
}
CDemo & operator--(CDemo & d){//前置--
  d.n--;
  returrn d;
}
CDemo operator--(CDemo & d,int){//后置--
  CDemo tmp(d);
  d.n--;
  return tmp;
}
int main(){
  CDemo d(5);
  cout<<(d++)<<",";//d.operator++(0);
  cout<<d<<",";
  cout<<(++d)<<",";//d.operator++();
  cout<<d<<endl;
  cout<<(d--)<<",";//d.operator--(d,0);
  cout<<d<<",";
  cout<<(--d)<<",";//d.operator--(d);
  cout<<d<<endl;
  return 0;
}

operator int(){return n;}

这里,int作为一个类型强制转换运算符被重载,伺候

Demo s;

(int) s;//等效于s.int();

类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就是该类型强制转换运算符代表的类型。

习题:

如何区分自增运算符重载的前置形式和后置形式?

A)重载时,前置形式的函数名是++operator,后置形式的函数名是operator++(错误)

B)后置形式比前置形式多一个int类型的参数(正确)

C)无法区分,使用时不管前置形式还是后置形式,都调用相同的重载函数(错误)

D)前置形式比后置形式多了一个int类型的参数(错误)

运算符重载的注意事项

  1. C++不允许定义新的运算符

  2. 重载后运算符和含义应该符合日常习惯

  3. 运算符重载不改变运算符的优先级

  4. 以下运算符不能被重载

    ​ “.” “.*” “::” “?:” “sizeof”

  5. 重载运算符()、[]、->后者赋值运算符=时,运算符重载函数必须声明为类的成员函数

小建议:建议使用前置自增,前置自增相较后置自增更高效。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值