【五、运算符重载实例分析】运算符重载实例、重载的机制、步骤、返回值及参数确定、友元函数与成员函数

简介: 【五、运算符重载实例分析】运算符重载实例、重载的机制、步骤、返回值及参数确定、友元函数与成员函数

前言

在C++中有很多运算符,有单目运算符、双目运算符、三目运算符等,运算符重载使我们可以根据自己的需求实现加减乘除等各种运算,是不是所有运算符都可以重载呢?重载的运算符函数如何去确定函数原型呢?重载的方法分为成员函数法和友元函数法,那么他们有什么不同呢?下面就通过具体的运算符重载程序来详细分析如何确认重载函数的原型,如何选择成员函数或友元函数。


运算符重载知识点总结

1. 运算符重载为我们提供了对自定义数据进行运算的机制,让我们可以自定义加减等运算;

2. 运算符也是一个函数,运算符重载的本质是函数重载;

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

4. 运算符重载不改变原有的结合顺序(自左向右、自右向左);

5. 运算符重载不能改变操作数的个数;

6. 不能自己创建新的运算符;

7. 操作符. :: .* ?: sizeof不可重载;

8. 运算符重载重载有成员函数和友元函数两种,区别在于成员函数有this指针(可以减少一个函数参数),友元函数没有this指针;

9. =, [], ()和->操作符只能通过成员函数进行重载 ;

10. << >>左移右移只能用友元函数;

实例分析

详细的分析都在程序中,一定要多看程序、多写程序

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//操作符重载,操作符也是一个函数,我们可以根据需求对操作符重载,但是以下操作符不可重载
//  .   ::   .*   ?:   sizeof
//使用友元函数或成员函数重载操作符的本质是,友元函数没有this指针,成员函数有this指针
//因为 this 指针的存在,成员函数比友元函数少了一个参数
///
class PluralClass
{
public:
  PluralClass(int r = 0, int v = 0)
  {
    this->real = r;
    this->virt = v;
  }
  void print_data(void)
  {
    cout << real << "+" << virt << "i" << endl;
  }
public:
  //成员函数实现 - 号操作符 //二元操作符
  //调用语句   p2.operator-(p3);
  //1.先写出函数名 operator-()    
  //2.因为是类的成员函数,所以 - 操作符的左操作数转化为 this 指针并隐藏,只有一个函数参数即右操作数 operator-(PluralClass& p)
  //3.他应该返回一个匿名对象元素并转为 = 的左值
  PluralClass operator-(PluralClass& p)
  {
    PluralClass temp((this->real - p.real), (this->virt - p.virt));
    return temp;
  }
  PluralClass& operator--() //前置 --
  {
    this->real--;
    this->virt--;
    return *this;
  }
  PluralClass operator--(int) //后置 --
  {
    PluralClass temp = *this;
    this->real--;
    this->virt--;
    return temp;
  }
private:
  int real;
  int virt;
  friend PluralClass operator+(PluralClass& p1, PluralClass& p2); //友元函数不受 private public 的影响
  friend PluralClass& operator++(PluralClass& p); //前置++
  friend PluralClass operator++(PluralClass& p, int); //后置++
  friend ostream& operator<<(ostream& out, PluralClass& p);
};
//友元函数实现+重载 //二元操作符 //如果类的属性real virt是public就可以不用友元函数,直接使用全局函数即可
//友元函数就是为了在类的外部使用类的私有属性
//p4 = p2 + p3   先写出调用语句,然后进行操作符重载三部曲
//1.定义一个函数名为  operator+()  
//2.他应该有两个参数,把 + 的两个操作数传进去 operator+(PluralClass& p1, PluralClass& p2)
//3.他应该返回一个匿名对象元素并转为 = 的左值 //返回一个新的元素,用元素做返回值
//(判断返回值的时候,如果返回的是一个新的元素,就用 PluralClass 元素。如果返回的是操作数元素本身,就用引用PluralClass&)
PluralClass operator+(PluralClass& p1, PluralClass& p2)
{
  PluralClass temp((p1.real + p2.real), (p1.virt + p2.virt));
  //real和virt是类的私有属性,所以需要把该函数在类中声明为友元函数
  return temp;
}
//重载前置++
PluralClass& operator++(PluralClass& p) //返回的是函数参数本身,所以返回引用
{
  p.real++;
  p.virt++;
  return p;
}
//重载后置 ++ //先使用后++ 所以应返回一个新元素(没++的元素),而操作数已经加完了
PluralClass operator++(PluralClass& p, int) //和前置++函数参数相同,函数名相同,
{                  //而函数返回值不是判断函数重载的标准,怎么办?
                   //加占位符参数来区分,函数调用时不用管展位参数
  PluralClass temp = p; //调用默认拷贝构造函数//浅拷贝
  p.real++;
  p.virt++;
  return temp;
}
//使用友元函数和成员函数(优先使用成员函数)重载操作符入门
void FuncTest1(void)
{
  PluralClass p1; //构造函数中设置了默认参数,所以在定义对象的时候可以不加参数
  p1.print_data();
  PluralClass p2(1, 2), p3(1, 2);
  p2.print_data();
  p3.print_data();
  PluralClass p4;
  p4 = p2 + p3;
  p4.print_data();
  p1 = p4 - p2;
  p1.print_data();
  ++p1;
  p1.print_data();
  --p1;
  p1.print_data();
  p1++;
  p1.print_data();
  p1--;
  p1.print_data();
}
///
//调用语句 cout << p1 << endl;  
//左操作数 cout //转到定义查看 __PURE_APPDOMAIN_GLOBAL extern _CRTDATA2_IMPORT ostream cout, *_Ptr_cout;
//右操作数 p1
//void operator<<(ostream& out, PluralClass& p) //没有返回值,不支持链式编程
//cout << p << endl;  左移操作符从左向右结合, 
//应该是 cout << p 函数执行之后返回一个返回值 cout 它和 endl 结合执行 cout << endl;函数,实现链式编程。
//可理解为  (cout.operator<<(p)).operator<<(endl);
//所以左移操作符重载函数应返回一个 out 本身
//函数返回值当左值需要返回一个引用
ostream& operator<<(ostream& out, PluralClass& p)
{
  out << p.real << "+" << p.virt << "i";
  return out;
}
//如果用成员函数去实现  cout.operator<<(PluralClass& p); //左操作数.function(右操作数);
//这应该去 ostream 类中去实现,但我们看不到 ostream 类的源码,所以只能用友元函数
//友元函数重载操作符使用场景:<< >> 左移右移操作符  ( << >> 只能用友元函数)
//  =  ()  []  ->  不能用友元函数重载
//一般来说,左右操作数类型不同的时候要用友元函数
//比如上面的 - 号重载 使用了成员函数,
//如果我们     c = p - 4;  //  p.operator-(4); 可以
//若  c = 4 - p; // 4.operator-(p); 错 //4不是类对象,无法调用类的成员函数
void FuncTest2()
{
  PluralClass p1(1, 2);
  //cout << p1; //void operator<<(ostream& out, PluralClass& p)
  cout << p1 << endl; //ostream& operator<<(ostream& out, PluralClass& p)
}
///
class MyClassStr
{
public:
  MyClassStr(const char* str)
  {
    this->len = strlen(str);
    this->str = new char[len + 1];
    strcpy(this->str, str);
  }
  ~MyClassStr()
  {
    if (this->str != NULL)
    {
      delete[] this->str;
    }
    this->len = 0;
  }
public:
  void PrintStr()
  {
    cout << this->str << endl;
  }
public:
  //函数返回值当左值需要返回一个引用
  //void operator=(MyClassStr& s) //不支持链式编程   S1 = S2 = S3;
  MyClassStr& operator=(MyClassStr& s) //返回等号左操作数本身,支持链式编程
  {
    if (this->str != NULL)
    {
      delete[] this->str; //防止内存泄漏
      this->len = 0;
    }
    this->len = s.len;
    this->str = new char[this->len + 1];
    strcpy(this->str, s.str);
    return *this;
  }
private:
  int   len;
  char* str;
public:
  //char operator[](int index); //不支持左值  S1[0] = '1';
  char& operator[](int index) //函数返回值当左值需要返回一个引用
  {
    return this->str[index];
  }
public:
  bool operator==(MyClassStr& s)
  {
    if (this->len != s.len)
    {
      return false;
    }
    if (0 == strcmp(this->str, s.str))
    {
      return true;
    }
    else
    {
      return false;
    }
  }
  bool operator!=(MyClassStr& s)
  {
    return !(*this == s);
  }
public:
  void operator()(int a, int b) //可根据业务需要设置参数,返回值
  {
    cout << "hello word!" << endl;
    cout << a << " " << b << endl;
  }
public:
  int operator>(MyClassStr& s) const //this 指针不可修改
  {
    //相减,大于0则返回大于0的数,
    return strcmp(this->str, s.str);
  }
  int operator<(MyClassStr& s)
  {
    return strcmp(s.str, this->str);
  }
  int operator>(const char* p) const
  {
    return strcmp(this->str, p);
  }
  int operator<(const char* p) const
  {
    return strcmp(p, this->str);
  }
public:
  //技巧1,只分配内存的构造函数
  MyClassStr(int len = 0)
  {
    if (len == 0)
    {
      this->len = 0;
      this->str = new char[this->len + 1]; //多分配一个字节 \0 结束符
      strcpy(this->str, "");
    }
    else
    {
      this->len = len;
      this->str = new char[this->len + 1];
      memset(this->str, 0, this->len);
    }
  }
  //技巧2,把类的属性暴露出来,提供外部改变类私有属性的接口
  char* ret_str()
  {
    return this->str;
  }
  int ret_len()
  {
    return this->len;
  }
  friend istream& operator>>(istream& in, MyClassStr& s); //返回引用支持链式编程
};
//重载 = 操作符  //释放原内存//分配新内存//赋值数据//返回本身
void FuncTest3()
{
  MyClassStr S1("abcdefg");
  S1.PrintStr();
  MyClassStr S2("12345");
  S2.PrintStr();
  S2 = S1;
  S2.PrintStr();
}
//char& operator[](int index) //并须在类中,因为友元函数没有this指针
//错误(活动)  E0341 “operator[]”必须是成员函数
//char& operator[](MyClassStr& str, int index)
//重载 [] //函数返回值当左值,必须返回一个引用
void FuncTest4()
{
  MyClassStr S1("qwertyu");
  cout << S1[2] << endl;
  S1[0] = '1';
  S1.PrintStr();
}
//重载 == !=
void FuncTest5()
{
  MyClassStr S1("12345");
  MyClassStr S2("abcde");
  if (S1 == S2)
  {
    cout << "S1 = S2" << endl;
  }
  else
  {
    cout << "S1 != S2" << endl;
  }
  S1 = S2;
  if (S1 != S2)
  {
    cout << "S1 != S2" << endl;
  }
  else
  {
    cout << "S1 = S2" << endl;
  }
}
//其他特殊的重载  () && ||
// && || 内置了短路规则
void FuncTest6()
{
  MyClassStr s1("123");
  s1(1, 3);
  /*
  if ((表达式1) && (表达式2))
  {
    //短路规则
    //如果 表达式1 为假,那么直接执行 语句2 ,表达式2 将不再执行
    语句1;
  }
  else
  {
    语句2;
  }
  */
  /*
  class t1 = 0;
  class t2 = 1;
  if(t1 && (t1 + t2))
  {
    cout << "ture" << endl;
  }
  //相当于函数调用 t1.operator&&((t1+t2));  t1+t2 是函数参数,会先执行
  // && 是双目运算符,有两个参数,一个是 this 指针,t1+t2 作为实参,应先计算实参表达式
  //在这个函数中,会先执行 operator+() 这个函数调用,再执行 operator&&() 函数
  //得到的结果可能是错误的
  //按照短路规则,t1为0,不应该执行 t1 + t2 这个语句
  //所以  && || 可以进行运算符重载,但是无法实现短路规则
  //一般不对 && || 进行重载
  */
}
///
//重载大于小于号
void FuncTest7()
{
  MyClassStr s1("aaaaa"), s2("12");
  if (s1 > s2)
  {
    cout << "s1 > s2" << endl;
  }
  else
  {
    cout << "s1 < s2" << endl;
  }
  if (s1 > "a")
  {
    cout << "s1 > \"a\"" << endl;
  }
  else
  {
    cout << "s1 < \"a\"" << endl;
  }
}
///
istream& operator>>(istream& in, MyClassStr& s)
{
  cin >> s.str;
  return in;
}
//类编程技巧
void FuncTest8()
{
  MyClassStr s1(6);
  strcpy(s1.ret_str(), "abcde"); //在类的外部改变类的私有属性
  s1.PrintStr();
  cout << "请输入:";
  cin >> s1;
  s1.PrintStr();
}
int main(void)
{
  FuncTest1();
  FuncTest2();
  FuncTest3();
  FuncTest4();
  FuncTest5();
  FuncTest6();
  FuncTest7();
  FuncTest8();
  system("pause");
  return 0;
}

【四、const与this指针详解】详解C与C++中const的异同,类中的const

六、继承/多继承与继承中的构造和析构函数


相关文章
|
3月前
|
编译器 C++
【C++11特性篇】新的类功能解读:新增加的[移动构造函数/移动赋值运算符重载]
【C++11特性篇】新的类功能解读:新增加的[移动构造函数/移动赋值运算符重载]
|
2月前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
|
11月前
|
存储 编译器 C语言
【C++基础】类与对象(中):默认成员函数、构造函数、析构函数、拷贝构造、赋值重载函数……
【C++基础】类与对象(中):默认成员函数、构造函数、析构函数、拷贝构造、赋值重载函数……
77 0
|
3月前
|
编译器 C语言 C++
C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法
C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法
52 0
|
存储 算法 安全
04-📝C++核心语法|面向对象2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转换、 C++标准、错误&&异常、智能指针】
复习`C++核心语法`,且适当进行汇编探索底层实现原理,进一步夯实基础,为以后的`底层开发`、`音视频开发`、`跨平台开发`、`算法`等方向的进一步学习埋下伏笔。
04-📝C++核心语法|面向对象2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转换、 C++标准、错误&&异常、智能指针】
|
8月前
|
存储 编译器 C语言
C++入门第三篇----类的默认6个成员函数【上篇】——构造函数 析构函数 拷贝构造函数 赋值重载
C++入门第三篇----类的默认6个成员函数【上篇】——构造函数 析构函数 拷贝构造函数 赋值重载
50 0
|
11月前
函数的重载
函数的重载
22 0
|
存储 编译器 C++
类的默认成员函数、赋值运算符重载(二)
如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
56 0
|
编译器 C++
类的默认成员函数、赋值运算符重载(一)
如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
79 0