运算符重载基础

零、强调

拷贝构造函数,是构造函数!!!!是在创建一个新的,的时候用的!这个新的就不用原先那个构造函数了!而是用这个拷贝构造函数。

#include <iostream>
using namespace std;

class A {
public :
	int m_age;
	A() {
		m_age=15;
		cout<<"use gouzao\n";
	}
	A(const A& a) { // 
		m_age=a.m_age;
		cout<<"use copy gouzao\n";
	}
};

int main() {
	A a1;
	cout<<"-------------\n";
	A a2=a1;
	return 0;
}

输出

use gouzao
-------------
use copy gouzao

一、基本语法:

给运算符 重载:给运算符拓展它的意思。

你发现,他都是按照所在的位置,去填入参数的;如果是用成员函数的方式,那么第一个参数就是 *this

很多时候要返回引用!

定清楚谁第一个,第二个,运算符是谁;这一块就不难了!

返回类型 operator运算符符号(参数列表) const(可选) {
    // 实现逻辑
}

二、实现方式

  1. 是在成员函数中写;

  2. 是在外部函数,然后搞个友元全局函数,去重载;

三、代码实现(一个复数类,只有加法)

#include <iostream>
using namespace std;

class Complex {
	friend ostream& operator << (ostream &os,const Complex &c);
public: 
	int m_real,m_img;
	Complex(int real=0,int img=0) {
		m_real=real; m_img=img;
	}
	Complex operator + (const Complex &other) {
		Complex result;
		result.m_real=m_real+other.m_real;
		result.m_img=m_img+other.m_img;
		return result;
	}
};

// cout << c1
// cout,c1 都是一个对象,<< 是运算符,我们要来重载这个!
ostream& operator << (ostream &os,const Complex &c) {
	os<<c.m_real<<" + "<<c.m_img<<"i\n";
	return os; // 确保能够链式调用
}

int main() {
	Complex c1(1,1),c2(2,1);
	c1=c1+c2;
	cout<<c1;
	return 0;
}

四、补充

cout 是一个类为 std::ostream 的对象,最后返回一个 os 的引用是为了能够链式地输出;

cout<<c1<<c2; 我们先执行 cout<<c1,然后返回一个 cout,接下来,相当于执行 cout<<c2,所以说,我们可以加括号!(cout<<c1)<<c2;

五、重载下标运算符

g[2]: (g,2) 做一个 [] 的运算,我们去重载这个运算就好了;一般是要传引用。

对于常对象,要额外提供一个返回为常量引用的,这样他就修改不了了!

基础语法:

返回一个引用 operator [] (int pos) {

}
const 返回一个引用 operator [] (int pos) const {

}
#include <iostream>
using namespace std;
class Girl {
public: 
	int a[3];
	Girl() {
		a[0]=10;
		a[1]=20;
		a[2]=30;
	}
	void show() const {
		cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<'\n';
	}
	// 返回类型 operator 操作符(参数)
	// g[2]: (g,2) 做一个 [] 的运算
	int& operator [](int pos) {
		cout<<"use first one []\n";
		return a[pos];
	}
	const int& operator [](int pos) const {
		cout<<"use const[]\n";
		return a[pos];
	}
};

int main() {
	Girl g1;
	g1.show();
	g1[2]=999;
	g1.show();
	// 常量一定创建的时候就给值!
	const Girl g2=g1;
	cout<<g2[2];
}

六、重载赋值运算符

首先,编译器给我们提供一个默认的赋值函数,就是一车子对应赋值。

当然,我们也可以重载它,为了满足 a=b=10,相当于 a=(b=10),这个从右到左结合的特性,我们需要每次都返回左值的引用!也是为了能够链式调用下去!否则你返回个 void 的话,那 a=void 是没有意义的!

返回右值也很显然没有意义!(a=9)=10,是相当于 a=10 而不是 9=10,因为左值一定能当右值!反过来不是。

赋值运算和拷贝构造不同:拷贝构造是指原来的对象不存在,用已存在的对象进行构造;赋值运算是指已经存在了两个对象,把其中一个对象的成员变量的值赋给另一个对象的成员变量。

核心,拷贝构造本质上是 构造,所以在创建的时候顺手的事情。

而赋值,就是两个已经创建完的对象的事情了。

#include <iostream>
using namespace std;
class Girl {
public: 
	int m_age;
	Girl(int age) {
		m_age=age;
	}
	Girl& operator = (const Girl& other) {
		m_age=other.m_age;
		return *this;
	}
	Girl& operator = (int age) {
		m_age=age;
		return *this;
	}
	// 其实你也可以这样子,就跟函数重载(多态)没区别,就是特征不一样!名字(都对=重载)一样!
	void show() {
		cout<<"my m_age: "<<m_age<<'\n';
	}
};
int main() {
	int a=5;
	(a=8)=10; // 也就是说,我们重载完也要让这个赋值的链式能实现,这也是为啥返回的是引用
	cout<<"a="<<a<<'\n';
	Girl g1(15),g2(20);
	(g1=g2)=17;
	// = 是从右往左的!
	// g1=(g2=17)
	g1.show();
	g2.show();
	return 0;
}

七、重载new&delete运算符

为什么要重载 new和 delete?

  • 主要目的:自定义内存管理策略
  • 内存池:预分配一大块内存,从中分配小对象,减少内存碎片,提高分配速度
  • 性能优化:避免频繁向系统申请/释放内存
  • 调试和监控:跟踪内存分配,检测内存泄漏
  • 特殊需求:对齐要求、共享内存、持久化存储等

工作原理:

我们抽象成建房子。

new表达式的两步操作:

CGirl* p = new CGirl(3, 8);

实际执行:

  1. 分配内存:调用 operator new(sizeof(CGirl))获取内存 相当于买一块地皮
  2. 构造对象:在获取的内存上调用 CGirl的构造函数 相当于去建房子,并且怎么建,怎么装修

delete表达式的两步操作:

delete p;

实际执行:

  1. 析构对象:调用 p->~CGirl()析构函数 清理家具,拆掉房子,怎么清理?
  2. 释放内存:调用 operator delete(p)释放内存 归还地皮

注意:我们能够控制,去哪里找地皮(获取内存),怎么样建房子(构造函数),怎么清理(析构函数),怎么样归还地皮(转卖还是租出去),但是我们没办法控制顺序:装修要在找地皮之后,归还地皮要在清理之后,这个顺序是由编译器决定的!

基本语法:

会自动给你传进来一个 size,表示需要多少字节。

// 分配函数
void* malloc(size_t size);
void* operator new(size_t size) { // size_t 相当于 ull
    void* ptr = malloc(size);
    return ptr;
}

// 释放函数  
void free(void* ptr);
void operator delete(void* ptr) {
    free(ptr);
}

简单的例子:

会优先调用类中重载的 new & delete,再考虑全局的。

#include <iostream>
using namespace std;

void* operator new(size_t size) {
	cout<<"use new bytes: "<<size<<'\n';
	void* ptr=malloc(size);
	cout<<"ok address: "<<ptr<<'\n';
	return ptr;
}

void operator delete(void* ptr) {
	cout<<"use delete\n";
	if(ptr==nullptr) return ;
	free(ptr);
}

class Girl {
public:
	int m_age,m_xw;
	Girl(int age,int xw) {
		m_age=age; m_xw=xw;
		cout<<"use gouzaohanshu\n";
	}
	~Girl() {
		m_age=0; m_xw=0;
		cout<<"use xigouhanshu\n";
	}
	void* operator new(size_t size) {
		cout<<"111 use new bytes: "<<size<<'\n';
		void* ptr=malloc(size);
		cout<<"ok address: "<<ptr<<'\n';
		return ptr;
	}

	void operator delete(void* ptr) {
		cout<<"111 use delete\n";
		if(ptr==nullptr) return ;
		free(ptr);
	}
};

int main() {
	//int* p=new int(3); // int(3) 也是一个构造函数!
	//cout<<p<<" "<<*p<<'\n';
	//delete p;
	// 会优先调用类中重载的 new & delete
	Girl* p=new Girl(3,99);
	cout<<p<<" "<<p->m_age<<" "<<p->m_xw<<'\n';
	delete p;
	return 0;
}

简单的内存池:

注意,为一个类重载new和delete时,尽管不必显式地使用static,但实际上仍在创建static成员函数。那你只能在里面访问静态成员变量了!

实现一个内存池:
Girl 一个对象占 8 个字节
然后你内存池一共 18 个字节;分成两 part,每 part 第一个为标识位
这个内存池是这个类的,不是某个对象的
所以使用静态成员
0 1-8
9 10-17
#include <iostream>
using namespace std;

class Girl {
public: 
	int m_age,m_xw;
	static char* fir; // 内存池的首地址
	Girl(int age,int xw) {
		m_age=age; m_xw=xw;
	}
	static bool init_pool() {
		fir=(char*)malloc(18);
		if(fir==nullptr) return 0; // 申请内存失败
		memset(fir,0,18); // 把内存池的内容都清空为 0
		cout<<"first address: "<<(void*)fir<<'\n';
		return 1;
	}
	static void free_pool() {
		if(fir==nullptr) return ;
		free(fir);
	}
	void* operator new(size_t size) {
		if(fir[0]==0) {
			cout<<"use 1 neicun\n";
			fir[0]=1;
			return fir+1;
		} else if(fir[9]==0) {
			cout<<"use 2 neicun\n";
			fir[9]=1;
			return fir+10;
		} else {
			cout<<"not enough\n";
			void* p=malloc(size);
			cout<<"new address: "<<(void*)p<<'\n';
			return p;
		}
	}
	void operator delete(void* ptr) {
		if(ptr==fir+1) {
			fir[0]=0;
			cout<<"free 1\n";
			// 并不需要去 delete,因为我们内存池是不用去改的!
		} else if(ptr==fir+10) {
			fir[9]=0;
			cout<<"free 2\n";
		} else {
			free(ptr); // 说明不是内存池中的
		}
	}
};
char* Girl::fir=nullptr;
int main() {
	Girl::init_pool();
	Girl* g1=new Girl(1,99);
	Girl* g2=new Girl(2,88);
	cout<<"g1 "<<(void*)g1<<" "<<g1->m_age<<'\n';
	cout<<"g2 "<<(void*)g2<<" "<<g2->m_age<<'\n';
	Girl* g3=new Girl(3,77);
	delete g1;
	Girl* g4=new Girl(4,66);
	cout<<"g4 "<<(void*)g4<<" "<<g4->m_age<<'\n';
	delete g2;
	delete g3;
	delete g4;
	Girl::free_pool();
	return 0;
}
/*
实现一个内存池:
Girl 一个对象占 8 个字节
然后你内存池一共 18 个字节;分成两 part,每 part 第一个为标识位
这个内存池是这个类的,不是某个对象的
所以使用静态成员
0 1-8
9 10-17
*/

八、重载括号运算符

通过重载括号运算符,你会发现调用 对象名(),就长得跟函数一样;

核心思想:让对象像函数一样被调用;这样叫做函数对象,或者仿函数

#include <iostream>
using namespace std;

void show(string msg) {
	cout<<"call the global function "<<msg<<'\n';
}

class Girl {
public:
	void operator () (string msg,int qwq) {
		cout<<"call the member function "<<msg<<' '<<qwq<<'\n';
	}
};

int main() {
	::show("this is msg"); // :: 表示全局作用域
	Girl show;
	show("this is msg",15); // 把 () 看成运算符就好了;对象 (参数)
	return 0;
}

九、重载一元运算符

所谓一元,就是操作的对象只有一个;++a; *a; &a; ~a; 都是一元运算符。

一般是前置的,如果是后置的话要多传一个 int,便于编译器区分。

#include <iostream>
using namespace std;

// 一般是前置,要后置的话得加个

class Girl {
public:
	int m_value;
	// 一般是前置的
	// ++i,那你直接加完,返回 *this
	Girl() {
		m_value=0;
	}
	Girl& operator ++() {
		++m_value;
		return *this;
	}
	// 后置加个 int 区分
	Girl operator ++(int) {
		Girl tmp=*this;
		++m_value;
		return tmp;
	}
};

int main() {
	Girl g1;// 0
	Girl g2=++g1; // 1
	Girl g3=g1++; // 1
	cout<<g1.m_value<<'\n';
	cout<<g2.m_value<<'\n';
	cout<<g3.m_value<<'\n';
	return 0;
}
posted @ 2026-02-19 00:37  FxorG  阅读(11)  评论(0)    收藏  举报