详解C++操作符的重载

操作符函数重载

  • 什么是操作符函数:在C++中对类类型的对象的运算符,由于它们肯定不支持真正的运算操作,因此编译器会将它们翻译成函数,这种就叫操作符函数(运算符函数),

  • 编译器把运算翻译成运算符函数,可以针对自定义的类类型设计它独有的运算功能

  • 其实各种运算符已经具备一些功能,再次实现它就是运算符的重载

  • 双目运算符:
    a+b

    • 成员函数

      a.operator+(b)

    • 全局函数

      operator+(a,b)

  • 单目运算符:
    !a

    • 成员函数

      a.operator!(void)

    • 全局函数

      operator!(a)

双目操作函数重载

  • 成员函数:

    const 类 opreator#(const 类& that) const
    {
    }
    

    注意

    • 双目运算符的运算结果是个右值,因此返回值应该加const
    • 在一个类的函数后面加上const后,就表明这个函数是不能改变类的成员变量的(加了mutable修饰的除外)。实际上,也就是对这个this指针加上了const修饰。
    • 后面加const是为了能让const对象调用
  • 全局函数:

    const 类 operator#(const 类& a,const 类& b)
    {
    }
    

    注意 全局函数不是成员函数,可能会需要访问类的私有成员,解决这种问题可以把函数声明为类的友元函数(友元函数不是成员)

  • 友元

    在类的外部某个函数中想访问类的私有成员时(public/protected/private),需要所在的函数声明为友元,但友元只是朋友,没有实际的拥有权,只有访问权限(其根本原因是它没有this指针)

    友元声明 把函数的声明写一份到中,然后在声明前加上friend关键字,使用友元既可以把操作符函数定义为全局的,也可以确保类的封装性

    注意 友元函数与成员函数不会构成重载关系,因为它们不在同一个作用域内

赋值类型的双目运算符

  1. 获取单参构造造成赋值运算的调用方式
    String str = "haha";//会调用单参构造,不会调用赋值运算符
  1. 左操作数据不能具有const属性
    1. 成员函数不能是常函数
    2. 全局函数第一个参数不能右const属性

单目操作函数重载

-,~,!,&,*,->,.

  • 成员
const 类 operator#(void) const
{
}
  • 全局
const 类& operator#(const 类& that)
{
}

  • 前++/–
//成员
类& operator#(void)
{
}
//全局
类& operator#(类& that)
{
}
  • 后++/–(哑元)
//成员
const 类& operator#(int)
{
}
//全局
const 类& operator#(类& that,int)
{ 
}

输入输出操作符重载

cout 是 ostream 类型的对象
cin 是 istream 类型的对象

如果输入输出运算(<< >>)符实现为成员函数,那么调用者应该是ostream/istream,而我们无权增加标准库代码,因此输入/输出运算符只能定义为全局函数

  • 注意 在输入输出过程中,cin/cout会记录错误标志,因此不能加const属性
ostream& operator<<(ostream& os,const 类& p)
{
}

istream& operator>>(istream& is,类& p)
{
}

特殊操作符的重载

  1. 下标操作符[]
  • 常用于再容器类型中以下标获取元素
    int& operator[](int i)
    {
    }
  1. 函数操作符()
  • 如果一个重载函数操作符,那么他的对象就可以像函数一样使用,参数的个数,返回值类,可以不确定,它是唯一一个可以参数有缺省参数的操作符
    void operator()(void){
        cout << "我是函数操作符的重载" << endl;
    }
  1. 解引用操作符*,成员访问操作符->
  • 如果一个类重载了*和->,那么它的对象就可以像指针一样使用
  • 所谓的智能指针就是一种类对象,它支持解引用和成员访问操作
  1. 智能指针auto_ptr
  • 标准库中封装好的智能指针,实现了常规指针的基本功能,头文件#include <memory>
    • 用法: auto_ptr<指向的类型> 指针变量名(对象的地址)
    • 局限性:
      • 不能跨作用域使用,一旦出作用域,指针变量会释放,它指向的对象也会释放
      • 不能放入标准容器
      • 不能指向对象数组
  • 常规指针的缺点
    • 当一个常规指针离开它的作用域时,只有该指针所占的空间被释放,而它指向的内存空间能否被释放就不一定了,在一些特殊情况下,(人为,业务逻辑特殊),free/delete没有执行,就会造成内存泄漏
  • 智能指针的优点
    • 是一个封装了常规指针的类类型对象,当它离开作用域时,它的析构函数会自动执行,它的析构函数会负责释放常规指针所指向的动态内存(以正确方式创建智能指针,它的析构函数才会正确执行)
  • 智能指针和常规指针的相同点
    • 都支持*和->运算
  • 智能指针和常规指针的不同点
    • 任何时候一个对象只能使用一个指针指针来使用,而常规指针能够指向多次
    • 智能指针的赋值操作需要经过拷贝构造和赋值构造特殊处理(深拷贝)
//一个简单的自定义智能指针
#include <iostream>
#include <unistd.h>

using namespace std;

class Int
{
	int val;
public:
	Int(int val=0):val(val) { }
	/*
	void set_val(int val)
	{
		this->val = val;
	}

	int get_val(void)
	{
		return val;
	}
	*/

	Int& operator=(const int val)
	{
		cout << "----" << endl;
		this->val = val;
		return *this;
	}

	~Int(void)
	{
		cout << "我是Int的析构函数" << endl;
	}

	friend ostream& operator<< (ostream& os,Int& n);
};

ostream& operator<< (ostream& os,Int& n)
{
	return os << n.val;
}

class IntPointer
{
	Int* ptr;
public:
	IntPointer(Int* ptr):ptr(ptr) { }
	Int& operator*(void)
	{
		return *ptr;
	}
	~IntPointer(void)
	{
		delete ptr;
	}
};

int main()
{
	Int* num = new Int(100);

	IntPointer p = num;
	cout << *p << endl;
	*p = 20;

	cout << *p << endl;
}
//智能指针的使用
#include <iostream>
#include <memory>

using namespace std;

class A
{
public:
	A(void)
	{
		cout << "我是构造函数" << endl;
	}
	~A(void)
	{
		cout << "我是析构函数" << endl;
	}
	void show(void)
	{
		cout << "我是类A的show函数" << endl;
	}
};

int main()
{
	auto_ptr<A> ptr(new A);
	(*ptr).show();
}

  1. new/delete/new[]/delete[]运算符重载
    1. C++缺省的堆内存管理器速度比较慢,重载new/delete底层使用malloc/free可以提高运行速度
    2. new在失败时会产生异常,而每次使用new都应该为了安全进行异常捕获,而重载new操作符只需要在操作符函数中进行一次错误处理即可
    3. 在一些占字节数比较小的类,频繁使用new有可能产生大量内存碎片,而重载new操作符后可以适当扩大每次申请的字节数,减少内存碎片产生的几率
    4. 重载new/delete可以记录堆内存使用信息
    5. 当使用delete释放内存失败时,重载delete可以检测到释放失败时的信息,检测到内存泄漏

重载操作符的一些限制

  1. 不能重载的操作符
    1. 域限定符 ::
    2. 直接成员访问操作符 .
    3. 条件表达式(3目运算符) ?:
    4. 字节长度操作符 sizeof
    5. 类型信息操作符 typeid
  2. 不能休修改操作符的优先级
  3. 不能重载基本类型的操作符运算
  4. 不能修改操作符的参数个数
  5. 不能发明新的操作符

关于操作符重载的建议

  1. 在重载操作符时要根据操作符实际的功能和意义来确定具体参数,返回值,是否有const属性,返回值是否是引用或者是临时对象
  2. 重载操作符要合情合理(要有意义),要以实际用途为前提
  3. 重载操作符的意义是为了让对象的操作更简单,提高代码的可读性
  4. 要追求一致性(与默认操作符功能,运算规则一致),不要出现反人类的操作

附测试代码:

#include <iostream>
#include <cstdlib>
using namespace std;

class Point
{
    int x;
    int y;
public:
    Point(int _x=0,int _y=0)
    {
        x = _x;
        y = _y;
    }
    void show(void) const//const:为了能够让const对象调用,加了之后该函数无法修改成员变量(mutable修饰的除外)
    {
        cout << "x:" << x << ",y:" << y << endl;
    }
    //成员函数双目操作符-的重载
    const Point operator-(const Point& that) const//const:例如Point p = p1-p2,p1无需也不能被修改所以加const
    {
        cout << "成员函数双目操作符-的重载" << endl;
        return Point(x-that.x,y-that.y);
    }
    //成员函数双目操作符*的重载
    const Point operator*(const int n) const//Point p = p1*2
    {   
        cout << "成员函数双目操作符*的重载" << endl;
        return Point(x*n,y*n);
    }
    //成员函数双目操作符+=的重载
    const Point& operator-=(const Point& that)// p1-=p2,p1被修改,所以不能加const
    {
        cout << "成员函数双目操作符-=的重载" << endl;
        x -= that.x;
        y -= that.y;
        return *this;//Point& p5 = *(&pa);pa = *(&pa);
    }
    
    const Point operator-(void)
    {
        cout << "成员函数单目运算符-的重载" << endl;
        return Point(-x,-y);
    }

    //前++
    Point& operator++(void)//不加const ++++n
    {
        cout << "成员函数单目运算符前++的重载" << endl;
        ++x;
        ++y;
        return *this;
    }
    //后++  (哑元)
    const Point operator++(int)//int:区分前++
    {
        cout << "成员函数单目运算符后++的重载" << endl;
        Point temp(x,y);
        x++;
        y++;
        return temp;//后++返回的是一个临时对象
    }



    friend const Point operator+(const Point& a,const Point& b);
    friend const Point& operator+=(Point& a, const Point& b);//如果同时定义了参数类型一样的类成员函数+=的重载,二者不构成重载关系,会造成无法选择
    friend Point operator--(Point& that,int);
    friend const Point& operator--(Point& that);
    friend const Point operator+(const Point& a,const Point& b);
    friend ostream& operator<<(ostream& os,const Point& p);
    friend istream& operator>>(istream& is,Point& p);

};
//友元函数双目运算符+的重载
const Point operator+(const Point& a,const Point& b)
{
    cout << "友元函数双目操作符+的重载" << endl;
    return Point(a.x+b.x,a.y+b.y);
}
//友元函数双目操作符+=的重载
const Point& operator+=(Point& a, const Point& b)
{
    cout << "友元函数双目操作符+=的重载" << endl;
    a.x+=b.x;
    a.y+=b.y;
    return a;
}
//友元函数单目运算符后--的重载
Point operator--(Point& that,int)
{
    cout << "友元函数单目操作符后--的重载" << endl;
    Point temp(that.x,that.y);
    that.x--;
    that.y--;
    return temp;
}
//友元函数单目运算符前--的重载
const Point& operator--(Point& that)
{
    cout << "友元函数单目操作符前--的重载" << endl;
    --that.x;
    --that.y;
    return that;
}
ostream& operator<<(ostream& os,const Point& p)
{
    cout << "友元函数输出操作符<<的重载" << endl;
    return os << p.x << "," << p.y << "\n";
}

istream& operator>>(istream& is,Point& p)
{
    cout << "友元函数输入操作符>>的重载" << endl;
    cout << "请输入x的值:";
    is >> p.x;
    cout << "请输入y的值:";
    is >> p.y;
    return is;
}


class Array{
    int* arr;
    size_t len;
public:
    Array(size_t len):len(len)
    {
        arr = new int[len];
    }

	void operator()(void)
	{
		cout << "我虽然是个对象,但我长的像函数" << endl;
	}

    int& operator[](int i)
    {
        if(i < 0 || i >= len)
        {
            cout << "下标错误" << endl;
            exit(0);
        } 
        return arr[i];
    }
};



int main(){

    const Point p1(1,1);
    p1.show();//由于成员函数show后面加了const,带const的对象和不带const的对象都可以调用
    
    Point pa(2,3);
    Point pb(4,5);
    
    //成员函数双目操作符-的重载
    Point p2 = pb-pa;
    p2.show();

    //测试成员函数双目操作符*-的重载
    Point p3;
    p3 = pa*3-pb;
    p3.show();

    //测试成员函数双目操作符-=的重载
    Point p5(5,8);
    p5-=pa;
    p5.show();

    //测试友元函数双目操作符+=的重载
    Point p4(6,7);
    p4+=pa;
    p4.show();

    //测试成员函数单目运算符前++与后++的重载
    Point p6 = pa;
    (++p6).show();
    p6++.show();

    //测试友元函数单目运算符前--与后--的重载
    Point p8  = pa;
    (--p8).show();
    p8--.show();

    //测试友元函数输入输出操作符的重载
    Point p7;
    cin >> p7;
    cout << p7;

    //测试下标操作符[]的重载
    Array arr(100);
    for(int i=0;i<10;i++){
        arr[i] = i;
        cout << arr[i] << endl;
    }
    //测试函数操作符的重载
    arr();
}
  • 运行结果
x:1,y:1
成员函数双目操作符-的重载
x:2,y:2
成员函数双目操作符*的重载
成员函数双目操作符-的重载
x:2,y:4
成员函数双目操作符-=的重载
x:3,y:5
友元函数双目操作符+=的重载
x:8,y:10
成员函数单目运算符前++的重载
x:3,y:4
成员函数单目运算符后++的重载
x:3,y:4
友元函数单目操作符前--的重载
x:1,y:2
友元函数单目操作符后--的重载
x:1,y:2
友元函数输入操作符>>的重载
请输入x的值:1
请输入y的值:1
友元函数输出操作符<<的重载
1,1
0
1
2
3
4
5
6
7
8
9
我虽然是个对象,但我长的像函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值