运算符重载基础&复数的重载&模板形参中传递函数指针
我们之前学习了C++的函数重载,但是不止函数可以重载,运算符也是可以重载的,运算符重载主要应用于对象,因为一般数据结构间的运算符没有重载的必要性。
运算符重载后,原有的基本语义不变,但是有几点需要注意:
- 不会改变运算符的优先级
- 不会改变运算符的结合性
- 不会改变运算符所需要的操作数的个数
- 不会改变运算符的原有的语法结构
- 不能重载的运算符:“.”,“.*”,“::”,“?:”,“sizeof”
我们在进行对象之间的运算的时候,编译器会自动调用运算符相应的函数进行处理,所以运算符重载有两种方式:成员函数和友元函数。
两者区别:
- 成员函数由于遵守_thiscall调用约定,所以有一个隐藏的this指针,所以我们传入的参数个数会少一个。
- 友元函数由于由于不遵循_thiscall调用约定,所以没有隐含的this指针。这样,对于双目运算符来说,友元函数有2个参数,对于单目运算符来说,友元函数只有一个参数。
- 注意:有些运算符不能重载为友元函数,比如:“=”,“()”,“[]”,“->”
一:我们这里主要看一看常用的运算符重载
(1)一般运算符重载
例如加减乘除四则运算运算符的重载:
代码如下:
#include <iostream>
class Test
{
public:
Test(int a):ma(a){}
Test operator+(const Test& rhs);//成员函数形式
Test operator-(const Test& rhs);
Test operator*(const Test& rhs);
Test operator/(const Test& rhs);
friend Test operator+(Test& rhs1, Test& rhs2);//友元函数形式
friend Test operator-(Test& rhs1, Test& rhs2);
friend Test operator*(Test& rhs1, Test& rhs2);
friend Test operator/(Test& rhs1, Test& rhs2);
void show()
{
std::cout << ma << std::endl;
}
private:
int ma;
};
Test Test::operator+(const Test& rhs)//成员函数形式
{
return Test(ma + rhs.ma);
}
Test Test::operator-(const Test& rhs)
{
return Test(ma - rhs.ma);
}
Test Test::operator*(const Test& rhs)
{
return Test(ma * rhs.ma);
}
Test Test::operator/(const Test& rhs)
{
return Test(ma / rhs.ma);
}
Test operator+(Test& rhs1, Test& rhs2)//友元函数形式
{
return Test(rhs1.ma + rhs2.ma);
}
Test operator-(Test& rhs1, Test& rhs2)
{
return Test(rhs1.ma - rhs2.ma);
}
Test operator*(Test& rhs1, Test& rhs2)
{
return Test(rhs1.ma * rhs2.ma);
}
Test operator/(Test& rhs1, Test& rhs2)
{
return Test(rhs1.ma / rhs2.ma);
}
int main()
{
Test tst1(10);
Test tst2(20);
Test tst3(30);
tst1 = tst2 + tst3;
tst2 = tst1.operator*(tst3);//两种方式皆可
tst1.show();
tst2.show();
tst3.show();
return 0;
}
这时,我们就可以进行成员之间的加减乘除运算了,我们看一看运行结果:
我们可以看到,结果和我们预想中的一致。
(2)关系运算符重载
关系运算符有:==,!=,<=,>=,<,>等
代码如下:
我们运行一下代码,看一看测试结果:
我们可以看到50是不等于1500的,结果显式正确。
(3)自增自减运算符重载
我们运行一下代码,看一看测试结果:
(4)特别注意:指向运算符(->)是单目运算符,不是双目运算符
当这样编写时:
换句话说,我们想要调用的是point->action求值的结果。
编译器会这样对该代码进行求职:
1.如果point是一个指针(该指针指向一个对象,该对象具有名称为action的成员),则编译器会调用该对象的action成员。
2.如果point是一个对象(且point指向的对象重载了operator->运算符),则编译器会调用这个对象的action成员。
对指向运算符重载的返回值约束:
- 指向运算符重载必须返回指向类类型的指针,或者返回指向定义了自己的箭头运算符的类类型对象
- 如果返回类型是指针,则内部“->”则可用于该指针,编译器则对该指针解引用并从结果对象中获取指定成员,如果没有这个指定成员,则报错。
- 如果还会类型是类类型的其他对象(或者是这个对象的引用),则将递归调用该对象应用该“->”运算符,直到返回一个带有指定成员的对象的指针,不然报错。
例如我们编程中可以这样使用:
(5)特殊运算符:
比如“[]”,“<<”,“>>”,“数据类型”,没错甚至数据类型也可以重载,我们在下面将看一看如何重载这些特殊的运算符:
例如“[]”小括号运算符重载的代码:
#include <iostream>
class CInt
{
public:
CInt(int a) :ma(a){}
bool operator<(int lhs)
{
return ma < lhs;
}
const CInt operator++(int)
{
const CInt tmp(ma);
ma = ma + 1;
return tmp;
}
CInt& operator++()
{
++ma;
return *this;
}
int& operator[](int* parr)
{
return parr[ma];
}
operator int()
{
return ma;
}
private:
int ma;
};
int main()
{
int arr[] = { 21, 3, 124, 32, 534, 5 };
int len = sizeof(arr) / sizeof(arr[0]);
for (CInt i = 0;i < len; i++)//for(CInt i =0; i.operator<(len); i.operator++())
{
std::cout << arr[i] << " ";//arr[i.operator int()]
}
std::cout << std::endl;
return 0;
}
我们可以这样调用并查看运行结果:
我们可以看到,对象i会调用自身的int类型的重载函数,返回ma后,再调用[]运算符的重载函数,与预想结果一致。
再例如“>>”,“<<”,输入输出流运算符重载的代码,如下图:
这里>>输入运算符有一点需要注意:
- friend std::ostream& operator<<(std::ostream& out, const Ccomplex& rhs);//输出运算符的重载
- friend std::istream& operator>>(std::istream& in, Ccomplex& rhs);//输入运算符的重载
- 注意:<<输出运算符的重载,第一个参数是输出流对象的引用,第二个参数是需要输出的类对象的引用,因为不需要修改对象,一般前面加上const,返回值是ostream&
- 但是:>>输入运算符的重载,第一个参数是输入流对象的引用,第二个参数是需要输入的类对象的引用,但是需要对对象进行赋值,所以不能加上cosnt,返回值是istream&
运算符重载总结:
- 不能重载的运算符:“.”,“.*”,“::”,“?:”,“sizeof”
- 类型转换函数只能定义为类的成员函数,不能定义为类的友元函数。C++提供了4中类型转换方法:1.const_cast(去除常性) 2.static_cast(安全性更高) 3.reinterpret_cast(类似于C的转换,一般用于指针) 4.dynamic_cast(用于RTTI信息的转换)
- 运算符重载实际上是函数重载,因此编译器对于运算符重载的选择,遵循函数重载的选择原则。
- 重载之后的运算符不能修改运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构
- 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。
二:经典案例:复数的重载complex
代码如下:
#include <iostream>
class Ccomplex
{
public:
Ccomplex(int real = 0, int image = 0):mreal(real), mimage(image){}
const Ccomplex operator+(int rhs)// +运算符的重载
{
return Ccomplex(mreal + rhs, mimage);
}
bool operator!=(const Ccomplex& rhs)// !=运算符的重载
{
return (mreal != rhs.mreal) || (mimage != rhs.mimage);
}
bool operator<(const Ccomplex& rhs)// <运算符的重载
{
return (mreal < rhs.mreal) || ((mreal == rhs.mreal) && (mimage < rhs.mimage));
}
bool operator==(const Ccomplex& rhs)
{
return !operator!=(rhs);
}
void show()
{
std::cout << mreal << ":" << mimage <<std::endl;
}
private:
int mreal;
int mimage;
friend const Ccomplex operator+(int lhs, const Ccomplex& rhs);// 操作数在左的+运算符重载,使用友元函数
friend std::ostream& operator<<(std::ostream& out, const Ccomplex& rhs);//输出运算符的重载
friend std::istream& operator>>(std::istream& in, Ccomplex& rhs);//输入运算符的重载
//注意:<<输出运算符的重载,第一个参数是输出流对象的引用,第二个参数是需要输出的类对象的引用,因为不需要修改对象,一般前面加上const,返回值是ostream&
//但是:>>输入运算符的重载,第一个参数是输入流对象的引用,第二个参数是需要输入的类对象的引用,但是需要对对象进行赋值,所以不能加上cosnt,返回值是istream&
};
const Ccomplex operator+(int lhs, const Ccomplex& rhs)// 操作数在左的+运算符重载,使用友元函数
{
return Ccomplex(lhs + rhs.mreal, rhs.mimage);
}
std::ostream& operator<<(std::ostream& out, const Ccomplex& rhs)//输出运算符的重载
{
out << rhs.mreal << ":" << rhs.mimage;
return out;
}
std::istream& operator>>(std::istream& in, Ccomplex& rhs)//输入运算符的重载
{
std::cout << "mreal : ";
in >> rhs.mreal;
std::cout << "mimage : ";
in >> rhs.mimage;
return in;
}
int main()
{
int a = 10;
int b = 20;
int rt = a + b;
std::cout << rt << std::endl;
//std::cout << typeid(std::cout).name() << std::endl;
Ccomplex com1(10, 20);
Ccomplex com2(30, 40);
com1.show();
com2.show();
com1 = com2 + 10;
com1.show();
com1 = 10 + com2;
com1.show();
if(com1 != com2)
{
std::cout << com1 << std::endl;
}
if(com1 < com2)
{
std::cout << com2 << std::endl;
}
std::cin>>com1;
com1.show();
return 0;
}
我们可以运行一下,看一看测试结果:
我们可以看到,测试结果与预想一致。
三:模板形参中传递函数指针
我们先看一下函数类型的表现形式:
代码如下:
#include <iostream>
void Show()
{
std::cout << "hello" << std::endl;
}
void Print()
{
std::cout << "world" << std::endl;
}
int* Func()
{
return new int;
}
template<typename T>//打印函数类型
void Call(T func)
{
std::cout << typeid(T).name() << std::endl;
(*func)();
}
template<typename R>//打印函数返回值类型
R Call1(R(*func)())
{
std::cout << typeid(R).name() << std::endl;
return (*func)();
}
class Test
{
public:
Test(int a = 0) :ma(a){}
void Show()
{
std::cout << "ma : " << ma << std::endl;
}
int getMa()
{
return ma;
}
private:
int ma;
};
class Sum
{
public:
Sum(int a = 0, int b = 0) :ma(a), mb(b){}
int add()
{
return ma + mb;
}
private:
int ma;
int mb;
};
template<typename T>
void Call2(T cppfunc)
{
std::cout << typeid(T).name() << std::endl;
Test test(10);
(test.*cppfunc)();
}
template<typename R>
R Call3(R(Test:: *cppfunc)())
{
Test test(20);
return (test.*cppfunc)();
}
template<typename R,
typename C>
R Call4(R(C::*cppfunc)())
{
std::cout << typeid(R).name() << " " << typeid(C).name() << std::endl;
C c;
return (c.*cppfunc)();
}
template<typename R,
typename C>
R Call5(C& c, R(C::*cppfunc)())
{
return (c.*cppfunc)();
}
int main()
{
//int *p = Call1(&Func);
//Call1(&Show);
//Call(&Print);
//Call(&Func);
Test test(20);
Sum sum(20, 40);
Call5(test,&Test::Show);
Call5(test,&Test::getMa);
Call5(sum,&Sum::add);
return 0;
}
我们运行一下,看一看测试结果:
我们可以看到,Func函数的返回值是int*,而Show函数返回值是void,而Print函数的类型信息为返回值为void,遵守cdecl调用约定,而Func函数类似。
我们再看一下,函数模板形参传递函数指针进去后,是如何调用的: