所谓重载,就是赋予新的含义。函数重载可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载也是一个道理,同一个运算符可以有不同的功能。本关我们就一起来学习运算符重载的使用。
运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。也就是说,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载。
重载运算符声明方式如普通成员函数一样,只不过他的名字包含关键字 operator,以及紧跟其后的一个 C++ 预定义的操作符。格式如下:
函数类型 operator 运算符名称 (形参表列)
{
// 对运算符的重载处理
}
例如:
class Test{ /* 类声明 */ };
Test operator+(Test t1,Test t2); // 重载 + 运算符,这个运算符要求两个操作数
并不是所有 C++ 中的运算符都可以支持重载,我们也不能创建一个新的运算符出来并且不能改变运算符操作对象的个数。有的运算符只能作为类成员函数被重载,而有的运算符则只能当作普通函数来使用:
不能被重载的运算符有:.
、.*
、::
、?:
、sizeof
只能作为类成员函数重载的运算符有:()
、[]
、->
、=
运算符重载有两种方式:
使用外部函数进行运算符重载;
使用成员函数进行运算符重载。
运算符重载之外部函数
要调用运算符重载函数,有两种方法,一种是通过函数名调用,即operator+(t1,t2)
,另一种是在使用运算符的时候自动调用,这里介绍第二种。
就如同函数重载的最佳匹配规则,使用一个运算符时,会寻找名为operator<运算符>
,且与当前操作数最佳匹配的那个重载版本进行调用。
例如:
class Test{/* 类声明 */};
class D:public Test {}; //创建一个 Test 类的子类
//外部函数
Test operator+(Test t1,Test t2){/* 一些操作 */}
int main()
{
Test t1,t2;
Test t3 = t1 + t2; //最佳匹配是operator+(Test,Test)
D d1,d2;
D d3 = d1 + d2; //最佳匹配也是operator+(Test,Test)
}
至于运算符重载函数内部怎么实现(定义),那就可以根据需求来了,例如:
class Test
{
public:
int a;
};
Test operator+(Test &t1,Test &t2)
{
//重载加法运算符,实际对Test类中的a成员变量进行加法运算
Test t;
t.a = t1.a + t2.a;
return t;
}
int main()
{
Test t1,t2;
t1.a = 10; t2.a = 20;
cout << (t1 + t2).a <
输出结果为:
30
注意:在运算符重载函数中也是要考虑类成员访问性的问题的。
运算符重载之成员函数
运算符重载的函数也可以写到某个类中成为一个成员函数,格式与写在外面没有区别,但在参数列表上有一些差异。
成为成员函数的运算符重载函数的参数需要少写一个最左边的参数,而少的这个参数就由当前的对象代替。
例如
class Test
{
public:
int a;
Test operator+(Test& t2); //少了左边的一个参数
};
Test Test::operator+(Test &t2)
{
Test t;
t.a = a + t2.a; //当前对象代替了原本的第一个参数
return t;
}
int main()
{
Test t1,t2;
t1.a = 10; t2.a = 20;
cout << (t1 + t2).a << endl; //调用的是 Test 类成员函数的那个重载版本
}
输出结果为:
30
注意:作为成员函数的运算符重载函数也会受访问性影响。
有时候我们希望某个函数能访问一个类的非公有成员,但又不想把它做成这个类的成员函数,这个时候就可以使用友元。
如果要将一个函数变成一个类的友元,只需要在类中函数前加一个 friend 关键字来声明函数即可,并且访问性不受限制。即表现形式为:
friend <返回类型> <函数名> (<参数列表>);
但这个友元函数他不属于该类的成员函数,他是定义在类外的普通函数,只是在类中声明该函数可以直接访问类中的 private 或者 protected 成员。
例如:
class Test{
private:
int a;
protected:
int b;
public:
friend void fun(Test t);//友元函数
};
void fun(Test t)
{
t.a=100;
t.b=2000;
cout<<"a = "<class TestFriend
{
private:
int a;
}; // TestFriend 类的声明在 Test 类之前
class Test
{
private:
int a;
friend class TestFriend; // 声明友元类
};
有时候作为友元的类的声明不便放在友元声明之前,这个时候就可以使用前置声明。不过前置声明的类只是一个名字(或者说不完全类型),不能访问它内部的内容。
例如:
class TestFriend; // 前置声明 TestFriend 类,只是一个名字
class Test
{
private:
int a;
friend class TestFriend; // 声明友元类
friend void TestFriend::Func(); // 尝试将 TestFriend 类的成员函数 Func 作为友元函数,但由于 TestFriend 类目前只有前置声明,所以会出错。
};
class TestFriend // TestFriend 类的声明在 Test 类之后
{
private:
int a;
public:
void Func();
};
最后,友元声明还有如下限制:
友元关系不能被继承。
友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元。
友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类 A 的友元。
一个构造函数接收一个不同于其类类型的形参,可以视为将其形参转换成类的一个对象,像这样的构造函数称为转换构造函数。因此转换构造函数的作用就是将一个其他类型的数据转换成一个类的对象。
除了创建类对象之外,转换构造函数还为编译器提供了执行隐式类型转换的方法。只要在需要类的类型值的地方,给定构造函数的形参类型的值,就将由编译器执行这种类型的转换。
转换构造函数是构造函数的一个特例,当一个构造函数的参数只有一个,而且是一个其他类型的 const 引用时,它就是一个转换构造函数。
例如
class T1{};
class T2
{
public:
T2(const T1 &t); // 从 T1 转换到 T2 的转换构造函数
};
有了转换构造函数,就可以实现不同类型之间的类型转换了,比如
/* 类定义同上 */
int main()
{
T1 t1;
T2 t2= (T2)t1; // 用类型转换语法,从 T1 转换到 T2
T2 t3(t1); // 或者直接调用转换构造函数
}
注意:转换构造函数只能有一个参数。如果有多个参数,就不是转换构造函数。