自学内容网 自学内容网

C++运算符重载

运算符重载的本质是函数重载

1. 重载入门

1.1. 语法格式

返回值类型 operator 运算符名称(行参表列)
{
重载实体;
}

operator 运算符名称在一起构成了新的函数名,比如:

const Complex operator+(const Complex &c1, const Complext &c2);

我们会说,operator+重载了运算符+

1.2. 友元重载

class Complex
{
public:
  Complex(int r = 0, int i = 0) : real(r), imag(i) {}
  friend Complex operator+(Complex &a, Complex &b);
  void dis()
  {
    cout << "(" << real << "," << imag << ")" << endl;
  }

private:
  int real;
  int imag;
};
Complex operator+(Complex &a, Complex &b)
{
  Complex t;
  t.real = a.real + b.real;
  t.imag = a.imag + b.imag;
  return t;
}
int main()
{
  Complex c1(1, 2), c2(2, 3);
  Complex c3 = c1 + c2;
  c1.dis();
  c2.dis();
  c3.dis();
  return 0;
}

1.3. 成员重载

class Complex
{
public:
  Complex(float x = 0, float y = 0) : _x(x), _y(y) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }
  friend Complex operator+(Complex &a, Complex &b);
  const Complex operator+(Complex &other);

private:
  float _x;
  float _y;
};
Complex operator+(Complex &a, Complex &b)
{
  Complex t;
  t._x = a._x + b._x;
  t._y = a._y + b._y;
  return t;
}
const Complex Complex::operator+(Complex &other)
{
  Complex t;
  t._x = this->_x + other._x;
  t._y = this->_y + other._y;
  return t;
}

int main()
{
  Complex c1(1, 2), c2(2, 3);
  // Complex c3 = c1 + c2;
  // Complex c3 = c1.operator+(c2);
  Complex c3 = operator+(c1, c2);

  c1.dis();
  c2.dis();
  c3.dis();

  // int i, j, k;
  // (i = j) = k;
  // (i + j) = k;

  // (c1 + c2) = c3;
  return 0;
}

备注:关于返回值

int a = 3;
int b = 4;
(a + b) = 100; // 这种语法是错误的,所以函数重载+的返回值加const来修饰

string a = "china", b = "is china", c;
(c = a) = b; // 此时的语法,是重载=,返回值不需要加const

所以重载运算符,不要破坏语意

1.4. 重载规则

  1. c++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载
  2. c++允许重载的运算符:
    在这里插入图片描述
    不能重载的运算符:
    在这里插入图片描述
    前两个运算符不能重载是为了保护访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特性。
  3. 重载不能改变运算符运算对象(即操作数)的个数,如:>和<都是双目运算符,重载后仍为双目运算符,需要两个参数,运算符+,-,*,&,等既可以作为单目运算符,也可以作为双目运算符,可以分别将他们重载为单目运算符和双目运算符
  4. 重载不能改变运算符的优先级别
  5. 重载不能改变运算符的结合性,例如,=是从右至左赋值的,重载后依然是
  6. 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数
  7. 重载运算符的运算中至少有一个操作数是自定义类
    重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)
    也就是说,参数不能全是c++标准类型,以防用户修改用于标准类型数据成员的运算符的性质,如下是错误的:
int operator+(int a, int b) 
{
return (a - b);
}

原来运算符+的作用是对两个数相加,现在企图通过重载使他的作用改为两个数相加。如果允许这样重载的话,如果有表达式4+3,那么结果是7还是1呢,显示,这是禁止的。

  1. 不必重载的运算符(= &)
    用于类对象的运算符一般必须重载,例外:=和&不必用户重载。
    赋值运算符=可以用于每一个类对象,可以用它在同类对象之间相互赋值。因为系统已为每一个新声明的类重载了一个赋值运算符,他的作用是逐个赋值类中的数据成员。
    &也不必重载,它能返回类对象在内存中的其实地址
  2. 对运算符的重载,不应该失去其原有的意义
    应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。

2. 重载举例

2.1. 双目运算符例举

2.1.1. 双目运算符重载格式

形式

L#R

全局函数

operator#(L,R);

成员函数

L.operator#(R)

2.1.2. 重载+=实现

class Complex1
{
public:
  Complex1(float x = 0, float y = 0) : _x(x), _y(y) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }
  Complex1 &operator+=(const Complex1 &another);

private:
  float _x;
  float _y;
};

Complex1 &Complex1::operator+=(const Complex1 &another)
{
  this->_x += another._x;
  this->_y += another._y;
  return *this;
}
int main()
{
#if 0
  int a = 10, b = 20, c = 30;
  a += b;
  b += c;
  cout << a << " " << b << " " << c << endl; // 30 50 30
  cout << "--------------------------------" << endl;

  Complex1 x(10, 0), y(20, 0), z(30, 0);
  // (1)此时的+=重载函数返回 void
  x += y;
  y += z;
  x.dis(); // (30,0)
  y.dis(); // (50,0)
  z.dis(); // (30,0)

  int a = 10, b = 20, c = 30;
  a += b += c;
  cout << a << " " << b << " " << c << endl; // 60 50 30
  cout << "--------------------------------" << endl;

  Complex1 x(10, 0), y(20, 0), z(30, 0);
  // (2)此时重载函数+=返回的是 Complex
  x += y += z;
  x.dis(); // (60,0)
  y.dis(); // (50,0)
  z.dis(); // (30,0)
#endif

  int a = 10, b = 20, c = 30;
  // (3)此时重载函数+=返回的是 Complex &,一定要注意在连等式中,返回引用和返回对象的区别
  (a += b) += c;
  cout << a << " " << b << " " << c << endl; // 60 20 30
  cout << "--------------------------------" << endl;

  Complex1 x(10, 0), y(20, 0), z(30, 0);
  (x += y) += z;
  x.dis(); // (60,0)
  y.dis(); // (20,0)
  z.dis(); // (30,0)
  return 0;
}
  • operator-=
friend Complex &operator-=(Complex &c1, const Complext &c2) 
{
}

2.2. 单目运算符距离

2.2.1. 重载单目运算符格式

形式

#M 或 M#

全局函数

operator#(M)

成员函数

M.operator#()

2.2.2. 重载-运算符

class Complex2
{
public:
  Complex2(float x = 0, float y = 0) : _x(x), _y(y) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }
  Complex2 operator-()
  {
    return Complex2(-this->_x, -this->_y);
  }

private:
  float _x;
  float _y;
};

int main()
{
  int n = 5;
  cout << n << endl;     // 5
  cout << -n << endl;    // -5
  cout << -(-n) << endl; // 5
  // -n = 30; // Error

  Complex2 c(1, 1);
  Complex2 t = -c;
  Complex2 m = -(-c);
  c.dis(); // (1, 1)
  t.dis(); // (-1, -1)
  m.dis(); // (1, 1)

  Complex2 i(2, 2); // 编译不通过
  -c = i;
  return 0;
}

2.2.3. 重载++(前++)

class Complex4
{
public:
  Complex4(float x = 0, float y = 0) : _x(x), _y(y) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }
  /* Complex4 &operator++(void)
  {
    _x++;
    _y++;
    return *this;
  } */
  friend Complex4 &operator++(Complex4 &c);

private:
  float _x;
  float _y;
};
Complex4 &operator++(Complex4 &c)
{
  c._x++;
  c._y++;
  return c;
}
int main()
{
  int n = 10;
  cout << n << endl;      // 10
  cout << ++n << endl;    // 11
  cout << n << endl;      // 11
  cout << ++ ++n << endl; // 13
  cout << n << endl;      // 13

  Complex4 c(10, 10);
  c.dis(); // (10,10)
  Complex4 c2 = ++c;
  c2.dis(); // (11,11)
  c.dis();  // (11,11)
  c2 = ++ ++c;
  c2.dis(); // (13,13)
  c.dis();  // (13,13)
  return 0;
}

2.2.4. 重载++(后++)

class Complex5
{
public:
  Complex5(float x = 0, float y = 0) : _x(x), _y(y) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }
  /* const Complex5 operator++(int) // 友元
  {
    Complex5 t = *this;
    _x++;
    _y++;
    return t;
  } */

  friend const Complex5 operator++(Complex5 &c, int);

private:
  float _x;
  float _y;
};
const Complex5 operator++(Complex5 &c, int)
{
  Complex5 t(c._x, c._y);
  c._x++;
  c._y++;
  return t;
}
int main()
{
  int n = 10;
  cout << n << endl;   // 10
  cout << n++ << endl; // 10
  cout << n << endl;   // 11
  // cout << n++++ << endl; // 后++表达式不能连用

  Complex5 c(10);
  c.dis(); // (10,0)
  Complex5 c2 = c++;
  c2.dis(); // (10,0)
  c.dis();  // (11,1)

  return 0;
}

2.3. 流输入输出运算符重载

函数形式

istream &operator>>(istream &自定义类&);
ostream &operator<<(ostream &自定义类&);

通过友元来实现,避免修改c++标准库

  • operator<< vs operator >>
class Complex3
{
public:
  Complex3(float x = 0, float y = 0) : _x(x), _y(y) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }
  friend ostream &operator<<(ostream &os, const Complex3 &c);
  friend istream &operator>>(istream &is, const Complex3 &c);
  // 成员必须保证左值为该类的对象

private:
  float _x;
  float _y;
};
ostream &operator<<(ostream &os, const Complex3 &c)
{
  os << "(" << c._x << "," << c._y << ")" << endl;
}
istream &operator>>(istream &is, const Complex3 &c)
{
  is >> c._x;
  is >> c._y;
  return is;
}
int main()
{
  Complex3 c(2, 3);
  cout << c << endl; // (2,3)
  cin >> c;
  cout << c << endl;
  return 0;
}

补充MyString类的流输入于输出

friend ostream &operator<<(ostream &os, const MyString &outStr);
friend istream &operator>>(istream &in, MyString &inStr);
ostream &operator<<(ostream &os, const MyString &outStr) 
{
os<<outStr._str;
return os;
}
istream &operator>>(istream &in, MyString &inStr) 
{
delete []inStr._str;
char bufp[1024];
scanf("%s", buf);  // scanf fgets in getline(buf, 1024)
int len;
len = strlen(buf);
inStr._str = new char[len + 1];
strcpy(inStr._str, buf);
return in;
}

3. 运算符重载小结

3.1. 重载格式

在这里插入图片描述

3.2. 不可重载的运算符

在这里插入图片描述

3.3. 只能重载为成员函数的运算符

在这里插入图片描述

3.4. 常规建议

在这里插入图片描述

3.5. 友元还是成员?

  • 引例
    假设,有类Sender和Mail,实现发送邮件的功能
Sender sender;
Mail mail;
sender << mail;

sender左操作数,决定了operator << 为Sender的成员函数,而mail决定了operator << 要作Mail类的友元

class Mail;
class Sender
{
public:
  Sender(string addr) : _addr(addr) {}
  Sender &operator<<(Mail &mail);

private:
  string _addr;
};
class Mail
{
public:
  Mail(string title, string contents, string t) : _title(title), _contents(contents), _time(t) {}
  friend Sender &Sender::operator<<(Mail &mail);

private:
  string _title;
  string _contents;
  string _time;
};
Sender &Sender::operator<<(Mail &mail)
{
  cout << _addr << endl;
  cout << mail._title << endl;
  cout << mail._contents << endl;
  cout << mail._time << endl;
  return *this;
}
int main()
{
  Sender sender("wyb@163.com");
  Mail mail1("开会", "123", "2023/01/01"), mail2("取消开会", "345", "2023/02/01");
  sender << mail1 << mail2;
  return 0;
}
  • 结论:
  1. 一个操作符的左右操作数不一定是相同类型的对象,这就涉及到该操作符函数定义为谁的友元,谁的成员问题
  2. 一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左操作数)
  3. 一个操作符函数,被声明为哪个类的成员,取决于该函数的参数对象(通常是右操作数)

4. 类型转换

4.1. 标准类型间转换

  • 隐式类型转换
5/8 5.0/8
  • 显式类型转换
static_cast<float>(5)/8

对于上述两类,系统有章可循,而对于用户自定义的类型。编译系统则不知道怎么进行转化,解决这个问题的办法:自定义专门的函数

4.2. 用类型转换构造函数进行类型转换

实现其他类型和本类类型的转化

  • 转换构造函数格式
class 目标类
{
目标类(const 源类 &源类对象引用) 
{
根据需求完成从源类型到目标类型的转换
}
}
  • 特点
    转换构造函数,本质是一个构造函数。是只有一个参数的构造函数。如有多个参数,只能称为构造函数,而不是转换函数
class Point2D
{
public:
  Point2D(int x, int y) : _x(x), _y(y) {}
  friend class Point3D;
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }

private:
  int _x, _y;
};
class Point3D
{
public:
  Point3D(int x, int y, int z) : _x(x), _y(y), _z(z) {}
  Point3D(Point2D &p)
  {
    this->_x = p._x;
    this->_y = p._y;
    this->_z = 0;
  }
  void dis()
  {
    cout << "(" << _x << "," << _y << "," << _z << ")" << endl;
  }
  Point3D &operator=(const Point3D &another)
  {
    this->_x = another._x;
    this->_y = another._y;
    this->_z = another._z;
    return *this;
  }

private:
  int _x, _y, _z;
};
int main()
{
  Point2D p2(1, 2);
  p2.dis(); // (1,2)
  Point3D p3(3, 4, 5);
  p3.dis(); // (3,4,5)
  p3 = p2;  // 先把p2升级为3D类型,然后发生的是一种赋值
  p3.dis(); // (1,2,0)
  return 0;
}
  • explicit关键字的意义
    以显示的方式完成转化,static_cast<目标类>(源类对象)。否则会报错
explicit Point3D(Point2D &p) // implicit explicit
{
  this->_x = p._x;
  this->_y = p._y;
  this->_z = 0;
}
Point2D p2(1,2);
p2.dis();
Point3D p3a = static_cast<Point3D>(p2); // (Point3D)p2
p3a.dis();

4.3. 用类型转换操作符函数进行转换

  • 类型转化函数格式
class 源类
{
operator 转化目标类(void)
{
根据需求完成从源类型到目标类型的转换
}
}
  • 特点
    转换函数必须是类方法,转换函数无参数,无返回
  • 代码
class Point3D;
class Point2D
{
public:
  Point2D(int x, int y)
      : _x(x), _y(y) {}
  // 无参无返回
  operator Point3D();
  void dis()
  {
    cout << "(" << _x << "," << _y << ")" << endl;
  }

private:
  int _x;
  int _y;
};
class Point3D
{
public:
  Point3D(int x, int y, int z)
      : _x(x), _y(y), _z(z) {}
  void dis()
  {
    cout << "(" << _x << "," << _y << "," << _z << ")" << endl;
  }

private:
  int _x;
  int _y;
  int _z;
};
Point2D::operator Point3D()
{
  return Point3D(_x, _y, 0);
}
int main()
{
  Point2D p2(1, 2);
  p2.dis(); // (1,2)
  Point3D p3(3, 4, 5);
  p3.dis(); // (3,4,5)
  Point3D p3a = p2;
  p3a.dis(); // (1,2,0)
  return 0;
}

4.4. 小结

  1. 只有一个参数的类构造函数,可将参数类型转化为类类型。例如,将int类型的变量赋值给obj类型的对象。在构造函数中声明为explicit可以防止隐式转换,只允许显示转换
  2. 称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型。转换函数为类成员函数,没有返回,没有参数。例如:operator typename(); typename是对象将被转换成类型。将类对象赋值给typename变量时或将其强制转换为typename类型时,自动被调用
  3. 应用于构造及初始化,赋值,传参,返回等场合

5. 运算符重载提高

5.1. 函数操作符( () )-- 仿函数

把类对象像函数名一样使用
仿函数,就是使一个类的使用看上去像一个函数,其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了

  • 格式
class 类名
{
返回值类型 operator()(参数类型)
函数体
}
  • 应用
class Pow
{
public:
  int operator()(int i)
  {
    return i * i;
  }
};
int main()
{
  Pow pow;
  int i = 4;              // 把类对象当成函数名一样使用 需要重载()
  cout << pow(i) << endl; // pow.operator()(i); a.operator+(b); a+b; 仿函数
  return 0;
}

注:主要应用于STL和模板

bool myCmp(int i, int j)
{
  return i < j;
}
class Cmp
{
public:
  bool operator()(int i, int j)
  {
    return i < j;
  }
};
int main()
{
  int array[8] = {1, 2, 9, 10, 11, 3, 5, 32};
  vector<int> vi(array, array + 8);
  // sort(vi.begin(), vi.end(), myCmp);
  // sort(vi.begin(), vi.end(), Cmp());
  sort(vi.begin(), vi.end(), [=](int i, int j){ return i < j; }); // lambada本质就是仿函数
  for (int i = 0; i < vi.size(); i++)
  {
    cout << vi[i] << endl;
  }
  return 0;
}

5.2. 堆内寸操作符(new delete)

适用于极个别情况需要定制的时候才用到,一般很少用

  • 格式
void *operator new(sizse_t);
void operator delete(void *);
void *operator new[](size_t);
void operator delete[](void *);

注:operator new中size_t参数是编译器自动计算传递的

  • 全局重载
void *operator new(size_t size)
{
  cout << "new " << size << endl;
  return malloc(size);
}
void operator delete(void *p)
{
  cout << "delete" << endl;
  free(p);
}
void *operator new[](size_t size)
{
  cout << "new[] " << size << endl;
  return malloc(size);
}
void operator delete[](void *p)
{
  cout << "delete[] " << endl;
  free(p);
}

测试

class A
{
public:
  A()
  {
    cout << "A constructor" << endl;
  }
  ~A()
  {
    cout << "A destructor" << endl;
  }

private:
  int a;
};
int main()
{
  int *p = new int;
  delete p;
  int *pa = new int[20];
  delete[] pa;
  A *cp = new A;
  delete cp;
  A *cpa = new A[20];
  delete[] cpa;
  return 0;
}
  • 类中重载
class A
{
public:
  A()
  {
    cout << "A constructor" << endl;
  }
  ~A()
  {
    cout << "A destructor" << endl;
  }
  void *operator new(size_t size)
  {
    cout << "new " << size << endl;
    void *p = malloc(size); // ((A*)p)->a = 100;
    return p;
  }
  void operator delete(void *p)
  {
    cout << "delete" << endl;
    free(p);
  }
  void *operator new[](size_t size)
  {
    cout << "new[] " << size << endl;
    return malloc(size);
  }
  void operator delete[](void *p)
  {
    cout << "delete[] " << endl;
    free(p);
  }

private:
  int a;
};
int main()
{
  // int *p = new int;
  // delete p;
  // int *pa = new int[20];
  // delete []pa;
  A *cp = new A;
  delete cp;
  A *cpa = new A[20];
  delete[] cpa;
  return 0;
}

5.3. 解引用与智能指针(-> / *)

常规意义上,new或是malloc出来的堆上的空间,都需要手动delete和free的。但在其他高级语言中,只需申请无需释放的功能是存在的
c++中也提供了这样的机制。实现原理:

  • 常规应用
class A
{
public:
  A()
  {
    cout << "A()" << endl;
  }
  ~A()
  {
    cout << "~A()" << endl;
  }
  void func()
  {
    cout << "func" << endl;
  }
};
void foo()
{
  A *p = new A;
  delete p;
}
  • 智能指针
#include <memory>
void foo()
{
  auto_ptr<A> p(new A);
  p->func(); // 两种访问方式
  (*p).func();
}
  • 推演
    1st step
class A
{
public:
  A()
  {
    cout << "A constructor" << endl;
  }
  ~A()
  {
    cout << "A destructor" << endl;
  }
};
class PMA
{
public:
  PMA(A *p)
      : _p(p) {}
  ~PMA()
  {
    delete _p;
  }

private:
  A *_p;
};
int main()
{
  A *p = new A;
  PMA pma(new A);
  return 0;
}

2sd step

#include <memory>
using namespace std;
class A
{
public:
  A()
  {
    cout << "A constructor" << endl;
  }
  ~A()
  {
    cout << "A destructor" << endl;
  }
  void dis()
  {
    cout << "in class A's dis" << endl;
  }
};
class PMA
{
public:
  PMA(A *p)
      : _p(p) {}
  ~PMA()
  {
    delete _p;
  }
  A &operator*()
  {
    return *_p;
  }
  A *operator->()
  {
    return _p;
  }

private:
  A *_p;
};
int main()
{
  A *p = new A;
  PMA pma(new A);
  // pma._p->dis(); private 的原因破坏了封装
  (*pma).dis(); //(pma*).dis(); (*pms).dis();
  pma->dis();   // pma->->dis(); pma->dis();
  return 0;
}
  • ->和*重载
类名 &operator*()
{
函数体
}
类名 &operator->()
{
函数体
}

原文地址:https://blog.csdn.net/qq_36816794/article/details/140243197

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!