运算符重载

运算符重载

概念:对已有的运算符进行重新定义,可以对不同的数据结构进行不同处理


运算符重载的参数位置

一、运算符重载的两种方式

1. 成员函数重载

  • 特点:运算符重载函数是类的成员函数。
  • 参数位置
    • 左操作数是当前对象(this 指针指向的对象)。
    • 右操作数是函数的参数。
  • 适用运算符+-*/[]() 等。

2. 全局函数重载

  • 特点:运算符重载函数是全局函数(通常声明为类的友元函数)。
  • 参数位置
    • 左操作数是第一个参数。
    • 右操作数是第二个参数。
  • 适用运算符<<>>+- 等。

二、参数位置的具体规则

1. 二元运算符(如 +-\*/

  • 成员函数重载

    • 左操作数是当前对象(this),右操作数是参数。

    • 示例:

      class MyClass {
      public:
          MyClass operator+(const MyClass& other) const {
              MyClass result;
              // 实现加法逻辑
              return result;
          }
      };
      
      MyClass a, b, c;
      c = a + b; // a 是左操作数(this),b 是右操作数(参数)
      
  • 全局函数重载

    • 左操作数是第一个参数,右操作数是第二个参数。

    • 示例:

      class MyClass {
      public:
          int value;
          friend MyClass operator+(const MyClass& lhs, const MyClass& rhs);
      };
      
      MyClass operator+(const MyClass& lhs, const MyClass& rhs) {
          MyClass result;
          result.value = lhs.value + rhs.value;
          return result;
      }
      
      MyClass a, b, c;
      c = a + b; // a 是第一个参数,b 是第二个参数
      

2. 一元运算符(如 ++--!

  • 成员函数重载

    • 操作数是当前对象(this),无额外参数。

    • 示例:

      class MyClass {
      public:
          MyClass& operator++() { // 前置++
              // 实现自增逻辑
              return *this;
          }
      };
      
      MyClass a;
      ++a; // a 是操作数(this)
      
  • 全局函数重载

    • 操作数是唯一参数。

    • 示例:

      class MyClass {
      public:
          int value;
          friend MyClass operator++(MyClass& obj); // 前置++
      };
      
      MyClass operator++(MyClass& obj) {
          obj.value++;
          return obj;
      }
      
      MyClass a;
      ++a; // a 是参数
      

3. 流插入运算符(<<)和流提取运算符(>>

  • 必须使用全局函数重载

    • 左操作数是流对象(如 cout),右操作数是要输出的对象。

    • 示例:

      class MyClass {
      public:
          int value;
          friend ostream& operator<<(ostream& os, const MyClass& obj);
      };
      
      ostream& operator<<(ostream& os, const MyClass& obj) {
          os << obj.value;
          return os;
      }
      
      MyClass a;
      cout << a; // cout 是第一个参数,a 是第二个参数
      

4. 下标运算符([]

  • 必须使用成员函数重载

    • 左操作数是当前对象(this),右操作数是下标值。

    • 示例:

      class MyClass {
      public:
          int data[10];
          int& operator[](int index) {
              return data[index];
          }
      };
      
      MyClass a;
      a[3] = 42; // a 是左操作数(this),3 是右操作数(参数)
      

5. 函数调用运算符(()

  • 必须使用成员函数重载

    • 左操作数是当前对象(this),右操作数是参数列表。

    • 示例:

      class MyClass {
      public:
          int operator()(int x, int y) {
              return x + y;
          }
      };
      
      MyClass a;
      int result = a(3, 4); // a 是左操作数(this),(3, 4) 是参数列表
      

三、参数位置的总结

运算符类型 成员函数重载 全局函数重载
二元运算符 左操作数:this,右操作数:参数 左操作数:第一个参数,右操作数:第二个参数
一元运算符 操作数:this,无额外参数 操作数:唯一参数
流运算符 不支持 左操作数:流对象,右操作数:输出对象
下标运算符 左操作数:this,右操作数:下标 不支持
函数调用运算符 左操作数:this,右操作数:参数列表 不支持

四、注意事项

  1. 链式调用

    • 对于二元运算符(如 +<<),返回类型应为引用或值,以支持链式调用。

    • 示例:

      cout << a << b; // 链式调用
      
  2. 友元函数

    • 如果全局函数需要访问类的私有成员,必须声明为友元函数。

    • 示例:

      class MyClass {
          friend ostream& operator<<(ostream& os, const MyClass& obj);
      };
      
  3. 运算符优先级

    • 重载运算符的优先级与内置运算符一致,但需注意括号的使用以避免歧义。

五、总结

  • 成员函数重载:左操作数是当前对象(this),右操作数是参数。
  • 全局函数重载:左操作数是第一个参数,右操作数是第二个参数。
  • 特殊运算符
    • 流运算符(<<>>)必须使用全局函数重载。
    • 下标运算符([])和函数调用运算符(())必须使用成员函数重载。

加号减号重载(+、-)

语法:

类型 operator+/-(参数){
		对+的运算重新定义
}

类的成员函数可以实现运算符重载,全局函数也可以

 
 
/*
+
*/
//复数类
class Complex {
	friend Complex operator+(Complex& a, Complex& b);
	friend Complex operator-(Complex& a, Complex& b);
public:
	Complex(): real(0),image(0){
	}
	Complex(int real, int image) {
		this->real = real;
		this->image = image;
	}
	Complex add(Complex& other) {
		Complex ret;
		ret.real = this->real + other.real;
		ret.image = this->image + other.image;
		return ret;
	}
	/*可在成员函数
	Complex operator+(Complex& other) {
		Complex ret;
		ret.real = this->real + other.real;
		ret.image = this->image + other.image;
		return ret;
	}
	*/
	
	void Print() {
		cout << real << '+' << image << "i" << endl;
	}

private:
	int real;
	int image;
};

//可在全局函数 +
Complex operator+(Complex& a,Complex& b) {
	Complex ret;
	ret.real = a.real + b.real;
	ret.image = a.image + b.image;
	return ret;
}
// -
Complex operator-(Complex& a, Complex& b) {
	Complex ret;
	ret.real = a.real - b.real;
	ret.image = a.image - b.image;
	return ret;
}

int main() {
	Complex a(10, 20);
	Complex b(5, 8);
	Complex c = a.add(b);
	Complex d = a + b;
	Complex f = a - b;
	c.Print();
	d.Print();
	f.Print();
	return 0;
}

左移运算符重载(<<)

为什么要重载<<运算符:

默认情况下,cout无法直接输出自定义类对象,通过重载 <<,可以定义对象的输出格式

重载 << 的两种方式

1. 全局函数重载(推荐)

  • 适用场景:需要访问类的私有成员时,需声明为类的友元函数
class MyClass {
    // 声明友元函数
    friend ostream& operator<<(ostream& os, const MyClass& obj);
};

// 实现全局函数
ostream& operator<<(ostream& os, const MyClass& obj) {
    // 输出 obj 的成员
    return os;
}

2. 成员函数重载(不推荐)

  • 问题:成员函数重载时,左操作数必须是类的对象(而 << 的左操作数应为 ostream)。我们希望cout << c 而不是 c << cout
  • 语法(仅作了解,实际几乎不用):
class MyClass {
public:
    ostream& operator<<(ostream& os) { // 错误!实际应为 os << *this
        os << this->data;
        return os;
    }
};

// 错误用法:
MyClass obj;
obj << cout; // 不符合习惯,实际不可行

关键细节

1. 参数顺序与返回值

  • 参数顺序:必须为 (ostream& os, const T& obj),因为 << 的左操作数是 ostream
  • 返回值:返回 ostream& 以支持链式调用(如 cout << a << b)。

2. const 修饰

  • 对象参数:使用 const T& 避免拷贝,同时禁止修改对象。
  • 流参数:流对象 (os) 不能是 const,因为输出操作会修改流状态。

3. 友元函数必要性

如果类的成员是私有的,必须声明 operator<< 为友元函数,否则无法访问私有成员。

总结

  • 核心目的:让自定义类型支持 cout << obj 的直观输出。
  • 关键步骤
    1. 声明全局函数 operator<< 为类的友元(若需访问私有成员)。
    2. 函数参数顺序为 (ostream&, const T&)
    3. 返回 ostream& 以支持链式调用。
  • 适用场景:调试输出、日志记录、数据可视化等。

递增运算符重载(++)

C++中,递增运算符(++)分为前置(++obj)和后置(obj++)两种形式,它们的重载方式不同。

1. 前置递增运算符重载

  • 语法ClassName& operator++()
  • 特点
    • 直接修改对象本身,返回修改后的对象的引用
    • 允许连续调用(如 ++++obj)。
class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    
    // 前置递增运算符重载
    Counter& operator++() {
        ++count;
        return *this; // 返回当前对象的引用
    }
    
    int getCount() const { return count; }
};

// 使用示例
Counter c;
++c;        // count 变为 1
++++c;      // 合法,count 变为 3

2. 后置递增运算符重载

  • 语法ClassName operator++(int)
    • int 是哑元参数,仅用于区分前置和后置。
  • 特点
    • 先保存原对象的值,再修改对象,最后返回修改前的临时副本
    • 返回值是值类型(而非引用),禁止连续调用(如 c++++ 非法)。
class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    
    // 后置递增运算符重载
    Counter operator++(int) {
        Counter temp = *this; // 保存当前对象的值
        ++count;             // 修改当前对象
        return temp;         // 返回修改前的副本(临时对象)
    }
    
    int getCount() const { return count; }
};

// 使用示例
Counter c;
c++;        // 返回0,但 c.count 变为1
// c++++;   // 非法!返回的临时对象无法修改

3.完整代码示例

class Complex {
	friend ostream& operator<<(ostream& cout, Complex c);
private:
	int real;
	int image;
public:
	Complex():real(0),image(0){}
	Complex(int real, int image) {
		this->real = real;
		this->image = image;
	}
	Complex& operator++() {
		this->real += 1;
		return *this;
	}
	Complex operator++(int) {
		Complex c = *this;
		this->real += 1;
		return c;
	}
};

ostream& operator<<(ostream& cout,Complex c) {
	cout << c.real << '+' << c.image << "i" ;
	return cout;
}

int main() {
	Complex c(10, 12);
	cout << c << endl;
	cout << ++c << endl;
	cout << c++ << endl;
	cout << c << endl;
	return 0;
}

4. 关键注意事项

  1. 返回值类型
    • 前置返回引用(&),避免拷贝。
    • 后置返回值(非引用),因为返回的是临时对象。
  2. 哑元参数 int
    • 仅用于区分前置和后置,无实际用途。
  3. 效率
    • 前置递增通常更高效(无临时对象),推荐优先使用。

赋值运算符重载(=)

默认赋值运算符会把所有的成员变量赋值过去,包括指针。

class Hero {
public:
	int* m_Data;

	Hero():m_Data(NULL){}
	Hero(int data) {
		m_Data = new int;
		*m_Data = data;
	}
	~Hero() {
		if (m_Data) {
			delete m_Data;
			m_Data = NULL;
		}
	}
};
Hero h1(8);
Hero h2(5);
h1 = h2; //此时使用默认赋值运算符,把h2的m_Data指针也赋值给h1的m_Data,h1和h2的m_Data此时指向同一个地址

这样在析构时会出现double free问题,即析构h1时释放了h1的m_Data,在析构h2时释放h2的m_Data,但h1和h2的m_Data指向的是同一个地址,在h1析构时已经释放过,h2又释放一次,导致报错。

所以对于自定义类需要重构赋值运算符

class Hero {
public:
	int* m_Data;

	Hero():m_Data(NULL){}
	Hero(int data) {
		m_Data = new int;
		*m_Data = data;
	}
	~Hero() {
		if (m_Data) {
			delete m_Data;
			m_Data = NULL;
		}
	}
	Hero& operator=(Hero& h) {
		if (m_Data) {
			delete m_Data;
			m_Data = NULL;                         
		}
		m_Data = new int;
		*m_Data = *h.m_Data;
		return *this;
	}
};
int main() {
	Hero h1(8);
	Hero h2(5);
	Hero h3(4);
	h3 = h1 = h2;

	return 0;
}

关系运算符重载(>、<、==、...)

默认的关系运算符不能对自定义类进行比较,需要重载关系运算符。

因为只是用于比较,不需要修改,所以使用常函数常量引用来保证数据不被修改。

class Point {
public:
	Point(int x,int y):m_x(x),m_y(y){}

	bool operator==(const Point& p) const {//常函数和常量引用作为参数
		return this->m_x == p.m_x && this->m_y == p.m_y;
	}
	bool operator<(const Point& p) const {
		int d = this->m_x * this->m_x + this->m_y * this->m_y;
		int f = p.m_x * p.m_x + p.m_y * p.m_y;
		return d < f;
	}
	bool operator>(const Point& p) const {
		if (*this == p) {
			return false;
		}
		if (*this < p) {
			return false;
		}
		return true;
	}

private:
	int m_x;
	int m_y;
};
int main() {
	Point a(3, 4);
	Point b(1, 2);
	if (a == b) {
		cout << "a与b相等" << endl;
	}
	else if (a < b) {
		cout << "a比b更接近原点" << endl;
	}
	else if (a > b) {
		cout << "b比a更接近原点" << endl;
	}
	return 0;
}

函数调用运算符重载(())

重载函数调用运算符,实现仿函数,可以存储状态,用于其他目的

class AddFiction {
private:
	int m_acc;//状态
public:
	AddFiction():m_acc(0){}

	int operator()(int a,int b) {
		m_acc++;//状态改变
		return a + b;
	}
};

int Add(int a, int b) {
	return a + b;
}

int main() {

	AddFiction add;
	add(4, 5);		//仿函数
	Add(4, 5);		//函数	
	cout<<add(5,6)<<endl;
    Add(4, 5);
    Add(4, 5);		//多次调用函数,参数不变,结果相同
    add(5,6)
    add(5,6)		//多次调用仿函数,状态在改变

	return 0;
}
posted @   十四2001  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示