c++运算符重载,友元

c++运算符重载

与C语言有所区别的是,在C++中会把运算符当做函数处理,一个表达式,其实是调用了很多的运算符函数完成计算,我们可以重新设计函数的实现达到自己想要的形式,这种特性对于内建类型是没有意义的,但是对于自建类型的数据,可以大大地提高代码的可读性、易用性,例如string这个类中就是运用了重载运算符的特性,使得我可以很方便的进行字符串的相加等功能……

string s1("hello "),s2("world");
s1+=s2;	//s1此时就是"hello world",在功能上类似strcat函数
//这样子的写法看起来简洁明了,可以增加易读性

在下文中,我们统一定义#为运算符,O为类的对象进行说明举例

友元

在此之前,我们需要先了解一下友元,什么是友元?
我们给出这样一个背景,在实现类的全局运算符函数时,可能会使用类内的私有成员,此时全局运算符函数是没有访问权限,如果把私有成员变成public会打破类的封装性,或者实现public接口的get函数会很麻烦,C++提供了友元这种方式来为全局的运算符函数提供独家授权,该函数就称为友元函数。
使用方法也非常的简单,只需要在类内对全局运算符函数进行声明,并且在声明前加 friend 关键字即可,友元打破了类的封装性,我不建议在除了重载运算符之外的地方使用友元

重载单目运算符

单目运算符: #O 或者 O#(例如:!a s++等)
作为类中成员函数重载的形式:[] O::operator#(void){}

注意:[]表示返回值,根据实际情况给定,运算对象就是函数的调用者,没有参数

作为全局函数配合友元实现的形式:[] operator#(O& o){}

全局运算符函数不属于任何类,因此需要把调用者作为参数传递过来
注意:运算符成员函数、全局函数,只能实现一个,不能同时实现,编译器会产生歧义

重载双目运算符

双目运算符: a # b
注意:一定是运算符左边的对象(a)发起的函数调用
作为类中成员函数重载的形式:

[] A::operator#(B& b)//B表示一个类
{
	// 类A对象必须是运算符函数的调用者,
}

作为全局函数配合友元实现的形式:

[] opeartor#(A& a,B& b)
{
	//函数调用者放第一个参数
}

小练习:实现一个坐标类,并实现 (1,1)+(2,2) = (3,3)的功能
实际上就是重载了一个+运算符
接下来在这个类的基础上分析运算符重载问题

class Point
{
	int x;
	int y;
	Point(int x,int y):x(x),y(y) {}
};

重载输入输出运算符

在C++中 << >> 运算符不光是按位左移、右移,同时它们还是 cout \ cin 的输出、输入运算符
输出运算符:

cout << 10 << endl;
Test t;
cout << t << endl;

此时 << 运算符的调用者是 cout,所以我们无法在cout类中实现 << 运算符函数重载,只能实现全局的<< 运算符函数

ostream& operator<<(ostream& os,const Test& t)
{
	return os << t.x;   //不建议换行
}

同理,我们可以对输入运算符重载

istream& operator>>(istream& is,Test& t)
{
	return is >> t.x;
}

以下有几个需要注意的点:

  1. 由于输入、输出运算符是可以连续调用,因此返回值应该还是cin、cout ,应该返回istream& ostream&(返回的是流的引用)
  2. 由于输入、输出运算符的调用者是左边的cin、cout,我们无法实现它们的成员运算符函数,只能实现全局的输入输出运算符函数
  3. 如果在全局运算符函数中使用了私有成员,需要声明全局运算符函数为友元函数

运算类的单目运算符

单目运算符:++/-- ! ~ * & sizeof -
成员函数: ! ~ -
例如负号运算符:

const Test operator-(void)const
{
	return Test(-x,-y);
}

注意:单目运算类的运算符对象都可以带常属性,因此重载的单目运算符函数必须是常函数,并且运算过程中都不会改变自身的值,而是产生一个临时的结算结果,并且是右值,只能返回带const的临时对象
使用全局函数重载负号运算符:

const Test operator-(const Test& t)
{
	return Test(-t.x,-t.y);
}

自变运算符函数(前++,后++)

前自变运算符:++i/ --i

在C语言中无论前、后自变得到的结果都是右值,但是在C++中前自变的结果是左值,后自变的结果是右值

int num = 10;
++(num++)   C C++ 报错
(++num)++   C++可以 C报错

前自变运算符重载相对来说比较简单,接下来给出前自变运算符的两种实现形式

//成员函数:
T& operator++(void)//必须不能是常函数
{
	x++,y++;
	return *this;   //一定要要返回引用
}

//全局函数:
T& operator++(Test& t)//必须不能是常形参
{
	t.x++,t.y++;
	return t;   //一定要要返回引用
}

后自变运算符函数: i++ / i--
先说一个哑元的概念,哑元就是在参数列表中增加一个不会使用的哑元类型参数,唯一目的就是为了区分前自变还是后自变 (哑元唯一用处),在这里可以用来区分前自变和后自变
下面给出后自变的函数实现,大概就是返回原来的拷贝,自身值自变

//成员函数:
const T operator++(int)
{
	return T(x++,y++);//必须返回临时对象,不能是引用
}

//全局函数:
const T operator++(T& t,int)
{
	return T(t.x++,t.y++);//必须返回临时对象,不能是引用
}

特殊的运算符函数

  1. 下标运算符[]
    当想让一个类对象当做数组使用时,可以考虑重载下标运算符,例如vector类中就重载了[]

  2. 函数运算符()
    当想让一个类对象当做函数一样使用时,可以考虑重载该运算符
    需要注意的是,[]`` ()只能重载为成员函数,不能重载成全局函数

  3. 解引用* 和 访问成员运算符->
    重载这两个运算符时可以让一个类对象像指针一样使用
    C++的智能指针就是重载了它们来实现的

  4. newdelete
    为什么要重载new和delete运算符?
    因为我们可以在重载该运算符函数时记录每次分配、释放内存的地址、时间、行数等信息到日志中,可以检查哪里出现了内存泄漏、什么时候出现的,这在大型工程开发中是很有必要的

成员函数与全局函数格式一样:

void* operator new(size_t size)//
{
	//  size是要申请内存的字节数,编译器会自动计算并传递过来
	//  做一些自己的操作
	void* ptr = malloc(size);
	return ptr;
}

void operator delete(void* ptr)
{
	//  做一些自己的操作
	free(ptr);
}
//注意:如果只是针对某个类重载它的new\delete,那么只需要实现成员函数即可,如果想要所有类型进行new/delete时都是用重载的函数,则实现为全局函数
//在网上搜索new和delete的重载你会发现,重载只能做重载申请内存和释放的部分功能,该调用构造还是得调用构造,该调用析构还是得调用析构

重载运算符的规则

  1. 有些运算符是不能重载
    :: 域限定符
    . 直接访问成员的运算符
    ? : 三目运算符
    sizeof 计算字节数的运算符
    typeid 获取类型信息的运算符
  2. 只能重载成全局函数的运算符
    << 输出运算符
    >> 输入运算符
  3. 只能重载成成员函数的运算符
    [] 下标运算符
    () 函数运算符
    = 赋值操作运算符 类内一定有一个=成员函数
    -> 间接访问成员的运算符
  4. 运算符重载可以自定义运算的过程,但是无法改变运算符的优先级
  5. 运算符的运算对象数量和格式不能改变
  6. 不能发明新的运算符

牢记本心,我们不要忘了重载运算符得初衷是为了方便,而不是炫技!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值