C++ 大学MOOC 北大课程(郭炜老师)听课整理 第四周(运算符重载)

运算符重载基本概念

1)目的是拓展原C程序运算符的作用范围,使程序看起来更加简洁
2)本质是函数,可以称之为运算符函数
3)可以定义为普通函数,也可定义为成员函数
4)把含运算符的表达式转换成函数的调用
5)运算符操作数转换为函数的参数
6)运算符函数可以重载,调用时根据参数类型选择
例如:

class complex{
public:
 double real, imag;
 complex(double r = 0.0, double i = 0.0) :real(r), imag(i){}
 complex operator - (const complex & r);
};
complex operator+(const complex &c1, const complex &c2){
 return complex(c1.real+c2.real,c1.imag+c2.imag);
}
complex complex::operator-(const complex &r){
 return complex(real-r.real,imag-r.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.operator-(b);
 return 0;
}

赋值运算符重载

1)考虑需要将两个不同类型的变量相互赋值,比如将 char* 类型的字符串常量赋值给字符串对象 complex
2)只能是成员函数
如示例:实现 s=“hello" 功能

class String{
private:
 char * str;
public:
 String() :str(new char[1]){
  str[0] = 0;
 }
 ~String(){
  delete[] str;//析构函数 负责当对象消亡时将str指向的空间释放
 }
 const char * c_str(){
  return str;
 }
 String& operator=(char * s);//声明赋值运算符成员函数
};
String& String::operator=(char* s){//主义函数返回值为对象的引用
 delete[]str;//先将成员指针str指向的空间delete掉
 str = new char[strlen(s) + 1];//再new出一片新的字符数组内存空间,空间大小为 s指向的空间大小加一
 strcpy(str, s);//然后将S指向的空间复制到str指向的空间 完成赋值
 return *this;//最后返回被赋值的对象
}
int main(){
 String s1, s2;
 s1 = "hello";
 s2 = "nice to meet you";
 cout << s1.c_str() << endl << s2.c_str() << endl;
 return 0;
}

输出:

hello
nice to meet you

3)如果需要实现 s1=s2 的赋值语句,则上诉代码存在问题
4)我们希望将s2.str指向的空间赋值给s1.str指向的空间,而单纯 s1=s2的结果是使得s1.str与s2.str相同,即s1.str与s2.str同指向同一片内存空间
5)这样就会导致当s1消亡时,及时s2没有消亡,但s2.str所指向的空间已经被析构函数释放了,这样不妥
6)改进方式:重新定义类string的赋值运算符函数
例如:

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

7)如果出现 s1=s1 的语句 则上诉代码会出现问题
8)这个语句会使得 s1.str 所指向的空间先释放掉 从而导致无法进行后面的赋值
所以改进:

String& String::operator=(const String & r){
 if (this==&r)//判断赋值语句两端的对象是否相同
  return *this;
 delete[] str;
 str = new char[strlen(r.str) + 1];
 strcpy(str, r.str);
 return *this;
}

9)如果出现调用复制构造函数的情况,就会出现如上诉 s1=s2 一样的问题
10)需要重新定义新的复制构造函数
例如:

String(const String& e){
  str = new bar[strlen(e.str)+1];
  strcpy(str,e.str);
 }

11)为了实现类似 (s1=s2)=s3(使得s1=s3) 的功能,赋值运算符函数的返回值需要是对象的引用(只有返回值是引用的函数可以放在等号的左侧)

运算符重载为友元函数

1)有时需要将运算符函数定义为普通函数
例如:

class complex{
 double real, imag;
public:
 complex(double r, double i) :real(r), imag(i){}
 complex operator+(int r){
  return complex(real + r, imag);
 }
};

2)如上的运算符函数operator+可以解释类似于 c=c+5; 的代码,但不能解释类似于 c=5+c 的代码
3)所以需要将运算符函数定义为普通函数来实现上诉功能
例如:

complex operator+(double c,const complex & s){
 return complex(s.real+c,s.imag);
}

4)但为了使得普通函数可以访问对象的私有成员变量,需要将其声明为友元
例如:

class complex{
 double real, imag;
public:
 complex(double r, double i) :real(r), imag(i){}
 void c_print(){
  cout << real << ',' << imag << endl;
 }
 complex operator+(double r){
  return complex(real+r,imag);
 }
 friend complex operator+(double c, const complex & s);
};

赋值运算符重载应用 可变长数组实现

1)在应用中,如何能使得一个数组的长度可以改变,例如可以实现如下整型数组类的功能

int main(){
 CArray a;//定义一个类CArray对象,其代表一个空数组(需要定义构造函数)
 for (int i = 0; i < 5;++i)
  a.push_back(i);//可以通过函数为数组向后增加一个整型元素 i(需要定义push_back函数)
 CArray a2, a3;
 a2 = a;//可以通过赋运算符 = 将 a 数组内容赋值给 a2 数组内容(需要赋值运算符重载函数)
 for (int i = 0; i < a2.length(); ++i)//可以用函数返回数组长度(需要定义length函数)
  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;
}

2)类 CArray 定义如下

class CArray{
private:
 int size;//用于储存数组元素的个数
 int * p;//用于指向整型数组空间
public:
 CArray(int s = 0){//构造函数可以根据所给参数完成数组空间的分配
  size = s;//储存数组元素的个数
  if (s == 0)//当参数为零 则代表数组为空
   p = NULL;//指针置空
  else//否则 数组元素为 s 
   p = new int[s];//动态分配一片大小为sizeof(int)*s的空间 将其地址赋值给指针
 }
 CArray(CArray& p1){//复制构造函数
  if (!p1.p){//如果要复制的数组为空
   p = NULL;//则指针置空
   size = 0;//元素数为零
   return;//返回
  }
  p = new int[p1.size];//否则 重新分配空间
  memcpy(p, p1.p, sizeof(int)*p1.size);//并完成内存字节的复制
  size = p1.size;//储存元素数
 }
 ~CArray(){//析构函数
  if (p)//如果指针不为空
   delete[] p;//则将所指空间释放
 }
 void push_back(int i);//push_back函数声明
 int length();//length()函数声明
 CArray & operator=(const CArray p1);//赋值运算符重载函数声明
 int & operator[](int i){//[]运算符重载函数 返回是int &(为使得a[1]=3合法)
  return p[i];//返回相应数组元素 引用使得返回值和数组元素一回事
 }
};

3)push_back函数

void CArray::push_back(int i){//再数组后再增加一个元素 i
 if (p){//判断原数组不为空
  int * tmp;//定义一个中间指针
  tmp = new int[size + 1];//使中间指针指向一个大小比原数组大一个元素的数组空间
  memcpy(tmp, p, sizeof(int)*size);//将原数组内容复制到中间指针指向的数组空间
  delete[]p;//将原数组空间释放
  p = tmp;//将中间指针赋值给作用数组 此时原数组的空间多了一个元素空间的大小
 }
 else//原数组为空
  p = new int[1];//则直接动态分配一个元素大小的空间
 p[size++] = i;//将size自加 并将最后的元素赋值为 i
}

4)length()函数

int CArray::length(){
 return size;//直接返回size
}

5)赋值运算符重载函数

CArray & CArray::operator=(const CArray p1){
 if (p == p1.p)//判断赋值运算符两端为同一个对象
  return *this;//则直接返回
 if (p1.p == NULL){//判断赋值右端数组为空数组 则将被赋值数组置空
  if (p)//判断被赋值数组不是空
   delete[] p;//则将所指空间释放
  p = NULL;//再将指针置空
  size = 0;//然后将元素数置零
  return *this;//最后返回
 }
 if (size < p1.size){//判断原数组空间大小比赋值数组小
  if (p)//原数组不为空
   delete[] p;//则将所指空间释放
  p = new int[p1.size];//并重新分配一个与赋值数组空间一样大小的空间
 }
 memcpy(p, p1.p, sizeof(int)*p1.size);//空间分配后 或空间够用 则完成两数组空间内容的复制
 size = p1.size;//修改数组元素数
 return *this;//最后返回
}

左移右移运算符重载

1)cout是 iostream 文件中定义的 ostream类的对象
2)cin是istream类的对象
3)运算符"<<",">>“分别在ostream类与istream类中进行了重载
4)故"cout<<5"的调用形式是"cout.operator(5)”
5)为了能够实现类似于"cout<<5<<“hello”"的语句,重载函数ostream::operator()的返回值类型应该是ostream类的引用:ostream &
例1:实现如下代码 使能够输出 5hello

class student{
public:
 int age;
};
ostream & operator<<(ostream & o, const student & s){//需要定义为普通函数 函数参数等于运算符操作数(2) 为了减小开销函数参数使用对象的引用
 o << s.age;
 return o;
}
int main(){
 student s;
 s.age = 5;
 cout << s << "hello";
 return 0;
}

输出:

5hello

例2:假定c是complex复数类的对象,现在希望写"cout<<c",就能以"a+bi"的形式输出c的值,写"cin>>c",就能从键盘中读入"a+bi"并赋值给c,使得c.real=a,c.imag=b。

class complex{
private:
 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);  //find()函数 找到字符'+'在字符串中的位置
 string stmp = s.substr(0, pos);  //将'+'前的字符串剥离出
 c.real = atof(stmp.c_str());  //将剥离出的字符串转换成flaot类型
 stmp = s.substr(pos + 1, s.length() - pos - 2);
 c.imag = atof(stmp.c_str());
 return is;
}
int main(){
 complex c;
 int n;
 cin >> c >> n;
 cout << c << ' ' << n;
 return 0;
}

输入:

123+13.1i 456

输出:

123+13.1i 456

重载类型转换运算符

1)重载的类型转换运算符有显性和隐性
2)声明定义时不用写返回值类型 因为返回值类型和运算符相同
3)单目运算符 操作数为一 声明成员函数时不用写参数

class complex{
private:
 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.2);
 cout << (double)c << endl;//显性作用 输出1.2
 double n = 2 + c;//隐性作用 等价于2+c.operator double ();
 cout << n << endl;//输出3.2
 return 0;
}

输出:

1.2
3.2

自增自减运算符重载

1)自增自减运算符有前置和后置之分
2)C++规定 前置时为一元运算符 后置时为二元运算符
3)前置运算符重载函数返回引用
例如:

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;//返回原对象
}//++s等价于s.operator++()
CDemo CDemo::operator++(int i){//后置++
 CDemo tmp(*this);//保存修改前的值
 n++;
 return tmp;//返回修改前的值
}//s++等价于s.operator(0)
CDemo & operator--(CDemo & r){//前置-- 参数得是引用
 r.n--;
 return r;//返回修改后的对象
}//--s等价于s.operator(s)
CDemo operator--(CDemo & r, int k){//后置--
 CDemo tmp(r);//保存修改前的值
 r.n--;//修改
 return tmp;//返回修改前的值
}//s++等价于s.operator(s,0)
int main(){
 CDemo d(5);
 cout << d++ << ',';
 cout << d << ',';
 cout << ++d << ',';
 cout << d << endl;
 cout << d-- << ',';
 cout << d << ',';
 cout << --d << ',';
 cout << d << endl;
 return 0;
}

输出:

5,6,7,7
7,6,5,5
发布了16 篇原创文章 · 获赞 17 · 访问量 757

猜你喜欢

转载自blog.csdn.net/weixin_45644911/article/details/104161705