运算符重载
看完这一章再结合前面看的拷贝构造函数方才好好的了解了对象中=复制与拷贝之间的区别。主要介绍的有几点:1、介绍可重载的运算符,2、重载中的参数的修饰以及返回值、使用成员函数还是全局3、自动类型转换
一、运算符与普通函数调用的不同之处:1、调用运算符的时候要将运算符放在函数之间,2、是由编译器决定调用哪一个函数的。这个怎么说呢。编译器会对参数进行转换之类的。比如很可能根据函数,而将我们传入的Int参数转为浮点型的。
一、可重载的运算符
1、我们较为常见的一元、二元运算符。在一元中的i++与++i其实调用的是不同的函数。
2、Operator =
1). 在这里我主要关心的是operator =,因为在类中对象会经常使用到这个重载的运算符。首先它是作为内部成员函数的,它是密切的与左侧的对象相联系的。为什么一定要使用成员函数呢,若为全局则会我们可以定下下面的函数: int opertor=(int,MyType),将其它的值转换为Int型,显然是不可取的,因此编译器通过强制operator=为成员函数而避开这个问题。类似的有()、[]、->。都必须为成员函数的形式。
2). 这个函数负责将右侧对象中的内容拷贝到当前的对象中。其函数定义一般如下:
Integer& operator =(const Integer& intVa)
{
cout<<"operator ="<<endl;
m_nData=intVa.m_nData;
return* this;
}
a.首先我们观察到在参数中使用的是常引用。那么表示传入的参数不允许改变其值。在任何的函数参数中,仅需要从参数中读取不改变它,我们都应该使用的是常引用。
b.返回值为引用类型,我们还记得之前的拷贝构造函数中使用的是返回新对象的形式,其实若运算符的结果产生的是一个新值就应该产生一个作为返回值的新对象。如+.在我们的拷贝构造函数中,是需要将传入的参数的值拷贝到新的对象中来。我们再来分析下这两种的情况。
const Integer operator + (const Integer& intVa)const//因为在返回的时候很可能是一个临时变量,作为常量的方式出现。
{
cout<<"operator+"<<endl;
cout<<&intVa<<endl;
//return Integer(m_nData+intVa.m_nData);//返回值中将会产生一个临时变量,再调用第一个构造函数。将值赋于其中
//在这种方式中会直接的将这个对象创建在外部返回值的内存单元中,因为不是真正
//的创建一个局部对象,所以只需要创建一个构造函数,且不会调用析构函数
Integer tmp=Integer(m_nData+intVa.m_nData);//在返回前必须调用拷贝构造函数将值拷贝到外面的对象的存储单元中,
return tmp; //在返回之前将调用析构函数释放在内部创建对象的
}
在第一中情况下我们创建了一个新对象,然后将返回对象,我们知道在返回之前会将tmp中的值全部的拷贝到函数框架外面的对象中,然后调用析构函数释放tmp所占的内存。然而第二种形式中,会将值直接的拷贝到外部的返回值内存单元中。可见这种临时变量的效率会更低,于是下面的这种方式常称为返回值优化,但是可读性就会差一些。
c.那么使用全局有没有什么优势呢?1、在成员版本中必须保证左侧操作数已经处于正确的形式,什么意思呢?就是正确的类型,不能寄希望于左值可以像右值一样可以进行类型转换,我们知道在派生的时候经常会使用到类型的转换。还有一种情况是左值为别的类的时候,比如输入输出流,这个时候或许我们就应该使用的是在函数前面加上friend表示全局。
3)与拷贝构造函数
Integer(const Integer& Data)
{
this->m_nData=Data.m_nData;
}
Integer temp1(1),temp2,temp3=temp1;
temp2=temp1;
那么上面的代码显示了拷贝构造函数与赋值重载运算符之间。我们关注于第3个对象。它调用的并不是我们自定义的=,因为temp3之前不存在,在对象被定义的地方构造函数必须被调用,现在这个对象是从其他对象中创建的,于是只有一个选择:拷贝构造函数.只用temp2才是真正的调用的是operator=。
4)若用户没有创建这个函数,那么系统会帮我们默认的创建一个,如同默认的拷贝构造函数一样,但是同样的我们不能保证可以正确的将值赋于这个要创建的对象,尤其是含有指针的情况下。
二、自动类型转换
1、类型转换?将当前的对象转为我们需要的类型对象。这个就有两种方式:1)构造函数和重载的运算符
class Orange{};
class Apple
{
public:
Apple(int weigh){};
Apple(const Orange& orange){}
Apple(const Apple& apple){}
};
class Banana
{
int i;
public:
Banana(int x=0):i(x){}
operator Apple()const
{
return Apple(i);
}
};
void f(Apple){}
int main()
{
Banana ba;
Apple app=ba;//首先将Ba通过重载的运算符获得一个Apple的对象的实例,2、调用默认的拷贝构造函数。
Orange or;
f(or);
system("pause");
return 1;
}
1)首先在f中我们要传入的参数为Apple类型的,但是在Apple的构造函数中,我们传入一个Orange的类型的参数,于是,此时编译器会检查是否有从对象Orange到Apple的方法,然后构造函数帮我们生成一个Apple对象。
2)在Banana的类中使用运算符重载,创建一个成员函数,通过在后面跟随要获得的类型。因为没有指定一个返回类型,于是编译器就将正在重载的运算符作为返回的类型。
三、小结:
1、运算符的返回值要根据是否创建一个新的对象而定。
2、全局变量会有其灵活性,但是不安全性也会增加
3、个人觉得这种的类型转换还是少用为好,尽管很方便,何不使用更好的多态与继承之类的方法。