焦点放在“重载操作符”被调用的时机,被调用的方法,它们的行为,它们应该如何与其他操作符发生关系以及如何夺取“重载操作符”的控制权。
一 对定制的“类型转换函数”保持警惕
1 隐式类型转换的两种方式:
单自变量constructors: 指能够以单一自变量成功调用的constructors(可能声明具有单一参数,也可能声明具有多个参数,数,并且除了第一参数之外都有默认值);
class Name{
public:
Name(const string& s); //可以将string转化为Name
...
};
隐式类型转换:一个拥有奇怪类型的member function:关键词operator之后加上一个类型名称,不能为此函数指定返回值类型,因为返回值类型基本已经表现于函数名称上;
class Rational{
public:
...
operator double() const; //将Rational转换为double类型
};
//在以下情况下隐式类型转化函数会被默认调用
Rational r(1,2);
double d = 0.5 * r; //将r转化为double类型然后进行乘法运算
以上为隐式类型转换的两种方式,通常在没有任何外在踪迹的条件下被调用。(注意可能会遗失信息的隐式转换,如double->char)
2 消除隐式类型转换函数
原因:可能会导致错误的(非预期的)函数被调用
解决办法:以另外一个功能对等的函数取代类型转换操作符。
class Rational{
public:
...
double asDouble() const; //将Rational转换为double类型
};
//在以下情况下隐式类型转化函数会被默认调用
Rational r(1,2);
cout << r << endl; //使用之前的方法将会把r隐式转换为double之后输出
// 这种情况下,此语句错误,Rational没有operator<<函数
cout << r.asDouble() <<endl; //以double的形式输出r
调用类型转换函数虽然不如隐式类型转换方便,但却有效地避免了默认调用错误的函数;例:string object -> C-style char*: c_str();
3 消除单自变量constructor提供的隐式转换
简易法:C++11 explicit 只要将构造函数声明为explicit,编译器便不能因隐式转换的需要调用constructor,不过仍然允许显示转换
编译器不支持简易法时:没有一个转换程序可以内含一个以上的用户定制转换行为(即单自变量constructor或饮食类型转换操作符)
例:
template<class T>
class Array{
public:
class ArraySize{
public:
ArraySize(int num):theSize(num){}
int size() const {return theSize;}
private:
int theSize;
};
Array(ArraySize size);
};
ArraySize是一个proxy classes,它的每一个对象都是为了其他对象而存在的。ArraySize对象作为int的替身完成了一次基于单自变量constructor的int的隐式转换,由于不允许第二次用户定制转换行为,避免了单自变量constructor提供的隐式转换
二 区别increment和decrement操作符的前置(prefix),后置(postfix)形式
后置式有一个int自变量作为参数,并且在它被调用的时候,编译器默默地为该int指定一个0值
前置式返回一个reference ,后置式返回一个const对象
const作用:
1:维持与内建类型行为一致:i++++错误 但是 ++++i合法;
2:i++++第二个operator++改变的对象是第一个operator++对象的返回值,而不是原对象;
//前置式:累加后取出
UPInt& UPInt::operator++()
{
*this += 1;
return *this;
}
//后置式:取出后累加
const UPInt::operator++(int) //参数的目的只是区分前置式和后置式
//为避免没有使用函数命名参数的警告,可以略去不打算使用的参数名称
{
UPInt oldValue = *this;
++(*this);
return oldValue;
}
三 千万不要重载&&,||和,操作符
若对&&,||进行重载,左右表达式都会作为操作符函数的表达式进行评估,且评估顺序未明确定义,这会破坏&&,||的“骤死性”;
逗号表达式:逗号左侧的值先被评估,之后右侧的值被评估,整个表达式的结果以逗号右侧的值为代表,若进行重载,左右两侧的值均被作为参数,无法确定其评估顺序
其他 不能重载的运算符:
. | .* | :: | ?: |
new | delete | sizeof | typeid |
static_cast | dynamic_cast | const_cast | reinterpret_cast |
四 了解各种不同意义的new和delete
new: 1 分配足够的内存,用来放置某类型的对象 ; 2 调用constructor,为分配内存中的对象设置初值; (不能改变new的行为)
operator new: 被new调用,执行必要的内存分配动作(可以被重载);
void* operator new(size_t size);
返回值类型为void*,指向一块原始的,未设初值的内存;
placement new:(头文件 : #include<new>)
class Widget{
public:
Widget(int widgetSize);
...
};
Widget* construtWidgetInBuffer(void* buffer,int widgetSize)
{
return new (buffer) Widget(widgetSize);
}
删除和内存释放:
为了避免资源泄露,每一个分配行为都必须匹配一个相应但相反的释放动作
delete:调用析构函数,调用operator delete释放该对象占用的内存
数组:
new[]:调用operater new[] 分配足够的内存空间,对数组中的每一个对象调用一个constructor;
delete[]:针对数组中的每一个对象调用其destructor(与构造函数的调用顺序相反),然后再调用operator delete[]释放内存。