作为成员函数的操作符
● operator+=
需要一个左值。 需要通过现有对象(左值)来调用类似+=这样的操作符,这些对象将修改左值。
●如果操作符 “+=” 必须通过现有对象来调用,那么便可将该操作符实现为成员函数。 通过非对象不能调用操作符“+=”,因为它需要修改调用它的对象。
● 注意: 一般而言,如果重载操作符需要一个左值(它将修改第1个操作数),那么, 可将其作为成员函数来实现。
这表明,只能通过现有对象调用这样的操作符(作为成员函数实现的操作符)。 所有具有赋值操作符特色的其他操作符(+= -= *= /=)都可以作为成员函数来实现。
● 实现重载操作符有两个十分重要的规则:
(1) 任何不需要左值和互换的操作符,最好作为非成员函数(+ - 等)实现。 这允许编译器在参数不匹配的情况下转换第1个参数
(2) 任何需要左值的操作符最好作为成员函数实现。 这清楚地表明只能通过现有的、可修改的对象调用它。
作为非成员函数实现的操作符
● 能以单个参数调用的构造函数称为转换构造函数。 它规定了从它的(第一个)参数类型转换为它的类(构造函数所属于的类)类型。 如果转换构造函数没有用explicit 限定符声明, 那么,在需要时将进行隐式转换(使用转换构造函数)
class X
{
public:
explicit X(int i); //只在显式要求时才进行转换
};
void f(X a){}
int main()
{
int i = 0;
f(i); //不会运行,因为X(int) 不是一个转换构造函数
f(X(i));// 运行,要求显式转换
}
● 只有在被调用的函数是非成员函数时,才会转换(由用户单独明确地定义)表达式的第一个参数。被调用的函数是成员函数时,不转换它的第一个参数。
● 注意: 对每个值只能进行一次转换。 只有不存在二义性才能进行转换。如果应用转换引起了多个重载函数匹配,则不进行转换,因为调用不够明确。
● 如果未找到直接匹配的函数,无论是成员函数还是非成员函数,都将转换(由用户单独明确地定义)它们的第二个参数和后续的参数。
● 任何不需要左值和互换的操作符,最好作为非成员函数(+ - 等)实现。 这允许编译器在参数不匹配的情况下转换第1个参数
● 需要左值却不需要互换的操作(如+=、-= 等), 不用转换第一个参数。对于这样的操作符,应该作为成员函数实现。
转换构造函数
● 转换构造函数的作用是将一个其他类型的数据转换成一个类的对象。注意:转换构造函数只能有一个参数。如果有多个参数,就不是转换构造函数。(当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。)
● 转换构造函数的作用是将某种类型的数据转换为类的对象。
● 下面的转换构造函数,将int类型的r转换为Student类型的对象,对象的age为r,num为1004.
Student(int r)
{
int num=1004;
int age= r;
}
注意 :转换构造函数也是构造函数重载的一种,但是转换构造函数只能有一个参数,如果有多个参数就不是转换构造函数。原因:如果有多个参数,无法确认把哪一个参数
注意: 如果不想让转换构造函数生效,也就是拒绝其它类型通过转换构造函数转换为本类型,可以在转换构造函数前面加上explicit,用来抑制隐式转换
#include <iostream>
using namespace std;
class Complex
{
public:
Complex() //默认构造函数
{
real = 0;
imag = 0;
cout << "调用默认构造函数" << endl;
}
Complex(double r,double i):real(r),imag(i) //有参数的初始化构造函数,并用初始化表进行初始化
{
cout << "调用有参数的初始化构造函数" << endl;
}
Complex(double i) //转换构造函数,对传入的参数进行转换成对象
{
real = i;
imag = 0;
cout << "调用转换构造函数" << endl;
}
void Display()
{
cout << real << "+" << imag << "i" << endl;
}
private:
double real;
double imag;
};
int main()
{
Complex C1;
C1.Display();
Complex C2(2,2);
C2.Display();
Complex C3(1.2);
C3.Display();
return 0;
}
● 那么 s1+19 呢(类对象与int直接相加)?
因为我们定义了 转换构造函数,那么 s1+19,执行如果过程:
(1)首选调用+号运算符,发现19不是Student类的对象,而是int类型
(2)然后调用转换构造函数,将19变为Student(19)
(3)现在便可以进行加法运算。
转换函数
● 用转换构造函数可以将一个指定类型的数据转换为类的对象,但是不能反过来将一个类的对象转换为一个其他类型的数据。 类型转换函数的作用是将一个类的对象转换成另一个类型的数据。
● 类型转换函数的语法格式为:
operator 类型名()
{
return object;
}
注意 : 类型转换函数在函数名前不能指定函数类型,也没有参数。返回类型是类型名决定的,只能作为成员函数,因为转换的类型是本类的对象,不能作为友元函数或者普通函数。 它还可以是虚函数,还可以被继承。对一个值最多只能进行一次用户定义的转换(转换构造函数或转换函数)。
#include<iostream>
using namespace std;
class Student
{
private:
float score;
int age;
public:
Student(void)
{
cout << "默认构造函数被调用!" << endl;
age = 18;
score = 0;
}
Student(int a, float s)
{
cout << "带参数的构造函数被调用!" << endl;
age = a;
score = s;
}
operator float()
{
cout << "转换函数被调用!" << endl;
return score;
}
};
int main()
{
Student stu1(18, 86), stu2(18, 97.5);
float f = 6.75 + stu2; //调用转换函数,加法的时候不需要重载“+”,但是如果把6.75 转换为“Student”对象,然后执行加法,需要重载“+”;
cout << "输出f的值:" << f << endl;
system("pause");
return 0;
}
● 转换函数不仅限于内置类型,它还可以从一个类转换为另一个类
● 在类中随意添加转换构造函数和转换函数不是个好习惯,因为它们同时存在可能导致二义性。
消除对临时对象的需求
● 还有一个需要说明的问题。通过构造函数和转换函数进行的转换,都创建了临时对象。 在使用转换构造函数时,创建了临时对象。
对于每个创建的对象, 都需要调用构造函数和析构函数(在删除临时对象时)。 这极大地增加了程序的执行时间。
对于大多数从类转换为内置类型的转换函数,会创建一个临时变量(不是对象)。 创建一个内置类型变量(如 int) 比创建一个对象的开销要小的多。
应该考虑尽量避免创建临时对象的方案。 注意: 在许多情况下, 避免临时对象都是一个好的编程习惯, 而不仅限于重载操作符。
从操作符函数返回结果
●左值操作符(通过现有对象调用的操作符) 可以返回对操作符左边对象(操作符调用的目标)的引用。 这是实现左值操作符安全、高效、正确的方式
● 根据操作数计算出结果的可互换操作符(+、 - 等 ),不能返回引用或指针。 这样的操作符要按值返回。