运算符重载基础
零、强调
拷贝构造函数,是构造函数!!!!是在创建一个新的,的时候用的!这个新的就不用原先那个构造函数了!而是用这个拷贝构造函数。
#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(可选) {
// 实现逻辑
}
二、实现方式
-
是在成员函数中写;
-
是在外部函数,然后搞个友元全局函数,去重载;
三、代码实现(一个复数类,只有加法)
#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);
实际执行:
- 分配内存:调用 operator new(sizeof(CGirl))获取内存 相当于买一块地皮
- 构造对象:在获取的内存上调用 CGirl的构造函数 相当于去建房子,并且怎么建,怎么装修
delete表达式的两步操作:
delete p;
实际执行:
- 析构对象:调用 p->~CGirl()析构函数 清理家具,拆掉房子,怎么清理?
- 释放内存:调用 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;
}

浙公网安备 33010602011771号