===15.1.1 类成员与非成员===
1、怎样决定是把一个操作符声明为类成员还是名字空间成员
a、如果一个重载操作符是类成员那么只有当跟它一起被使用的左操作数是该类的对象时,它才会被调用,如果该操作符的左操作数必须是其他的类型,那么重载操作符必须是名字空间成员
b.C++要求赋值= 下标[] 调用() 和成员访问箭头-> 操作符必须被定义为类成员操作符,任何把这些操作符定义为名字空间成员的定义都会被标记为编译时刻错误
例如
// 错误: 必须是类成员
char& operator[]( String & ,int ix );
===15.1.2 重载操作符的名字===
只有在C++预定义操作符集中的操作符才可以被重载,程序员只能为类类型或枚举类型的操作数定义重载操作符
1、// 不能被重载的操作符
:: .*. ?:
2、对于内置类型的操作符,它的预定义意义不能被改变
// 错误: 不能为 int 重新定义内置的操作符
int operator+( int, int );
3、也不能为内置数据类型定义其他的操作符,例如有两个数组类型操作数的operator+不能被加入到内置操作集中
4、预定义的操作符优先级不能被改变,如:
x == y + z;
总是在operator==之前执行operator+
5、除了对operator()外,对其他重载操作符提供缺省实参都是非法的
6、操作符预定义的操作数个数arity 必须被保留
// 非法: ! 是一元操作符
bool operator!( const String &s1, const String &s2 )
{
return( strcmp( s1.c_str(), s2.c_str() ) != 0 );
}
对于内置类型,四个预定义的操作符+ - * 和& 既可被用作一元操作符,也可被用作二元操作符,操作符的这两种版本都可以被重载
===15.2 友元===
友元声明以关键字friend 开始,它只能出现在类定义中
因为友元不是授权类的成员,所以它不受其所在类的声明区域public private 和protected 的影响
只有当一个类的定义已经被看到时,它的成员函数才能被声明为另一个类的友元,这并不总是能够做到的
例如如果Screen 类必须把Window 类的成员函数声明为友元而Window类必须把Screen 类的成员函数声明为友元,该怎么办呢?
在这种情况下,可以把整个Window类声明为Screen 类的友元。
例如
class Window;
class Screen {
friend class Window;//如果定义window成员函数为Screen为友元的话,会由于没有看到window成员函数定义而失败
// ...
};
===15.4 操作符[]===
下标操作符必须能够出现在一个赋值操作符的左右两边,为了能在左边出现,它的返回值必须是一个左值,这可以通过把返问类型指定为一个引用来实现
#include <cassert>
inline char&
String::operator[]( int elem ) const
{
assert( elem >= 0 && elem < _size );
return _string[ elem ];
}
下标操作符的返回值是被索引的元素的左值这是它能够出现在赋值的左边的原因
===15.7 操作符++和--===
两种递增和递减操作符:前置版本和后置版本
为区分后置操作符与前置操作符的声明,重载的递增和递减后置操作符的声明有一个额外的int 类型的参数
Screen& operator++();//前置
Screen& operator++(int);//后置
对于后置操作符的显式调用要求为第二个整型实参指定一个实际的值
parr.operator++(1024); // 调用后置操作符++
===15.8 操作符new 和delete===
1、类成员操作符new()的返回类型必须是void*型,并且有一个size_t 类型的参数,这里的size_t 是一个在系统头文件<cstddef>中被定义的typedef
class Screen {
public:
void *operator new( size_t );
// ...
};
Screen *ps = new Screen;//操作符的size_t 参数自动被初始化为Screen 类的大小(按字节计数)
2、类成员操作符delete()的返回类型必须是void,并且第一个参数的类型是void*
class Screen {
public:
void operator delete( void* );
};
delete ps; //操作符的void*参数自动被初始化为ps 值
3、操作符new()和delete()都是类的静态static 成员,它们遵从静态成员函数的一般限制
这些操作符被自动做成静态成员函数而无需程序员显式地把它们声明为静态的
4、delete的参数
为一个类类型而定义的delete()操作符,如果它是被delete 表达式调用的,则它可以有两个参数而不是一个,第一个参数仍然必须是void*型而第二个参数必须是预定义类型size_t记住它被包含在库头文件<cstddef>中
例如
class Screen {
public:
// replaces:
// void operator delete( void* );
void operator delete( void *, size_t );
};
4、执行顺序
用操作符new()的分配动作如
Screen *ptr = new Screen( 10, 20 );
与下列双语句序列等价
// C++伪码
ptr = Screen::operator new( sizeof( Screen ) );
Screen::Screen( ptr, 10, 20 );
即new 表达式先调用该类的操作符new()来分配存贮区,然后再调用构造函数初始化该对象,如果操作符new()失败则抛出bad_alloc 类型的异常并且不会调用构造函数
用操作符delete()释放存贮区的动作如
delete ptr;
与下列双语句序列等价
// C++伪玛
Screen::~Screen( ptr );
Screen::operator delete( ptr, sizeof( *ptr ) );
即delete 表达式首先调用该对象的析构函数,然后再调用该类的操作符delete()释放存贮区,如果ptr 的值是0 则不会调用析构函数和操作符delete()
===15.8.1 数组操作符new[]和delete[]===
类成员操作符new[]()的返回类型必须是void* 并且第一个参数的类型是size_t
成员操作符delete[]()的返回类型必须是void 它的第一个参数必须是void*类型,一个类的操作符delete[]()也可以有两个参数,第二个参数的类型是预定义类型size_t
===15.8.2 定位操作符new()和delete()==
根据操作符new()是否分配内存或者是否重新使用已分配的内存,类设计者可以决定是否
提供与特定操作符new()相匹配的操作符delete() ,如果说操作符new()分配了内存,则应该提
供定位操作符delete() 以便“当new 表达式调用的构造函数抛出异常”时可以正确地释放内存(隐式调用delete),
如果定位操作符new()没有分配内存,则无需提供相匹配的操作符delete()来释放内存
上述提到的隐式调用delete必须与new的声明匹配(参数匹配)
如:
Screen *ps = new ( start ) Screen;
调用 void *operator new( size_t, Screen* );
异常调用void operator delete( void*, Screen* );
===15.9.1 转换函数===
1、在类体中通过指定关键字operator并在其后加上转换的目标类型后,我们就可以声明转换函数
在转换函数的声明中,关键字operator 后面的名字不一定必须是内置类型的名字
typedef char *tName;
class Token {
public:
Token( char*, int );
//转换函数
operator SmallInt() { return val; }
operator tName() { return name; }
operator int() { return val; }
private:
SmallInt val;
char *name;
};
2、转换函数采用如下的一般形式
operator type();
这里的type 可用内置类型类类型或typedef 名取代,但是不允许type 表示数组或函数
类型,转换函数必须是成员函数,它的声明不能指定返回类型和参数表
operator int( SmallInt & ); // 错误: 不是成员
class SmallInt {
public:
int operator int(); // 错误: 返回类型
operator int( int = 0 ); // 错误参数表
// ...
};
3、显式调用
#include "Token.h"
Token tok( "function", 78 );
// 函数型的表示法: 调用 Token::operator SmallInt()
SmallInt tokVal = SmallInt( tok );
// static_cast: 调用 Token::operator tName()
char *tokName = static_cast< char * >( tok );
4、在用户定义的转换之后只允许标准转换序列
如果为了到达目标类型,必须应用第二个用户定义的转换则编译器不会隐式应用任何转换
例如,如果Token 没有定义operator int()
则下列调用是非法的
extern void calc( int );
Token tok( "pointer", 37 );
// 没有定义 Token::operator Int()
// 这个调用会产生编译时刻错误
calc( tok );
如果没有定义Token::operator int() tok 向int 型的转换,就会要求调用两个用户定义的转换函数,
实参tok 将首先需要从Token 转换到SmallInt 使用转换函数Token::operator SmallInt()
然后还需要用转换函数SmallInt::operator int()把用户定义的转换的结果转换成int 型
===15.10 选择一个转换===
1、用户定义的转换序列:
标准转换序列——>用户定义的转换——>标准转换序列
这里,用户定义的转换,或者调用转换函数,或者调用构造函数
2、二义性
编译器并不是总能选择出一个用户定义的转换序列作为最佳序列来执行转换
也许所有可能的转换都一样好,在这种情况下我们称转换是二义的
例如
a、使用Number 类定义的两个转换函数
class Number {
public:
operator float();
operator int();
// ...
};
就不可能把Number 型的对象隐式地转换成long 型的对象
// 错误: 两个操作符 float() 和 int() 都可以应用
long lval = num;
// ok: 显式强制转换
long lval = static_cast< int >( num ); //operator int()
b、
class SmallInt {
public:
SmallInt( const Number & );
// ...
};
class Number {
public:
operator SmallInt();
// ...
};
extern void compute( SmallInt );
extern Number num;
compute( num ); // 错误: 两个可能的转换
实参num 可以用两种不同的方式被转换成SmallInt 型
可以使用构造函数SmallInt::SmallInt(const Number&)或转换函数Number::operator SmallInt()
因为这两个函数一样好所以这个调用是错的
// ok: 显式调用以便解决二义性
compute( num.operator SmallInt());
compute( SmallInt( num ) ); // 错误: 仍然是二义的
===15.10.2 侯选函数===
namespace NS {
class SmallInt {
friend SmallInt add( SmallInt, int ) { /* ... */ }
};
class String { /* ... */ };
String add( const String &, const String & );
}
const matrix& add( const matrix &, int );
double add( double, double );
int main() {
// si 的类型是 SmallInt 类
// 该类在名字空间 NS 中被声明
NS::SmallInt si(15);
add( si, 566 ); // 调用友元 function
return 0;
}
则候选函数是
1 全局函数
add( const matrix &, int )
add( double, double )
2 名字空间函数
NS::add( const String &, const String & )
3 友元函数
NS::add( SmallInt, int )
===15.10.3 类域中的函数所调用的候选函数===
namespace NS {
struct myClass {
void k( int );
static void k( char* );
void mf();
};
int k( double );
};
void h(char);
void NS::myClass::mf() {
h('a'); // 调用全局 h( char )
k(4); // 调用 myClass::k( int )
}
编译器以相反的顺序搜寻限定修饰符NS::myClass::
即先在myClass 中,然后再在名字空间NS 中,针对成员函数mf()的定义中用到的名字,查找可见的声明
在这个查找过程中,一旦找到了一个函数声明,则查找在调用点可见的候选函数的过程马上结束
===15.10.4 对用户定义的转换序列划分等级===
1、标准转换序列优于用户定义的转换序列
2、如果两个用户定义的转换序列使用不同的转换函数或不同的构造函数,则两个转换序列被认为程度一样好
用户定义的转换序列中只能有一个用户定义的转换,所以在第一个用户定义的转换之后只能考虑标准转换
===15.11 重载解析和成员函数===
成员函数的重载解析三步骤
1 选择候选函数
2 选择可行函数
3 选择最佳匹配函数
===15.11.3 可行函数===
1、静态成员函数
class myClass {
public:
static void mf( int );
char mf( char );
};
int main() {
char cobj;
myClass::mf( cobj ); // 哪个成员函数?
}
char 型的实参cobj 是mf(char)的参数的精确匹配,但是,被选择的成员函数是非静态成员函数不能被直接调用,被编译器标记为错误
2、const或volatile属性
class myClass {
public:
static void mf( int* );
void mf( double );
void mf( int ) const;
// ...
};
int main() {
const myClass mc;
double dobj;
mc.mf( dobj ); // 哪个成员函数 mf()?
}
mc 是const 对象,对于const 对象只有const 非静态成员函数才可以被调用
void mf( int ) const;被选为该调用的最佳可行函数
3、静态函数
class myClass {
public:
static void mf( int );
char mf( char );
};
int main() {
const myClass mc;
int iobj;
mc.mf( iobj ); // 可以调用静态成员函数吗?static void mf( int );为最佳
}
注:静态成员函数不能被声明为const 或volatile
静态成员函数只能直接访问类的静态成员
===15.12.1 候选的操作符函数===
候选的操作符函数集是前面列出的五个候选函数集的并集,例如
namespace NS {
class myFloat {
myFloat( double );
};
class SmallInt {
friend SmallInt operator+(const SmallInt &, int ) { /* ... */ }
public:
SmallInt( int );
operator int();
SmallInt operator+( const myFloat & );
// ...
};
SmallInt operator+( const SmallInt &, double );
}
int main() {
// si 的类型是类 SmallInt:
// 该类在名字空间 NS 中声明
NS::SmallInt si(15);
int res = si + 5.66; // which operator+ ?
return 0;
}
对于main()中使用的operator+() 这五个候选函数集合给出了七个候选操作符函数
1 第一个候选函数集合是空的,
在main()中operator+()是在全局域中被使用的全局域中没有可见的重载操作符operator+()的声明
2 第二个候选函数集合包含了定义SmallInt 类的名字空间NS 中声明的操作符,下面的操作符在名字空间NS 中被定义
NS::SmallInt NS::operator+( const SmallInt&, double );
3 第三个候选函数集合包含了被声明为SmallInt 类的友元的操作符
下面的操作符是SmallInt 类的友元
NS::SmallInt NS::operator+( const SmallInt &, int );
4 第四个候选函数集合包含了被声明为SmallInt 类的成员的操作符
下面的操作符是SmallInt 类的成员
NS::SmallInt NS::SmallInt::operator+( const myFloat & );
5 第五个候选函数集合包含了内置的二元操作符
int operator+( int, int )
double operator+( double, double )
T* operator+( T*, I )
T* operator+( I, T* )