第十四章

14.1

        用于内置类型的操作符,其含义不能改变。例如,内置的整型加号操作符不能重定义:

// error: cannot redefine built-in operator for ints
int operator+(int, int);

        重载操作符必须具有至少一个类类型或枚举类型(第 2.7 节)的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。

        一般将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员

// member binary operator: left-hand operand bound to implicit thispointer
Sales_item& Sales_item::operator+=(const Sales_item&);
// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);

        加返回一个右值,而复合赋值返回对左操作数的引用,赋值操作符与复合赋值操作符应返回操作符的引用

选择成员或非成员实现

        赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

        像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做, 如果定义非成员复合赋值操作符, 不会出现编译错误。

        改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员

        对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

        我们不能将该操作符定义为类的成员, 否则, 左操作数将只能是该类类型的对象:

// if operator<< is a member of Sales_item
Sales_item item;
item << cout;

        这个用法与为其他类型定义的输出操作符的正常使用方式相反。

14.5

        类定义下标操作符时,一般需要定义两个版本:一个为非 const 成员并返回引用,另一个为 const 成员并返回 const 引用。

14.6

        重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象

ScreenPtr p(&myScreen); // copies the underlying Screen
p->display(cout);

        因为 p 是一个 ScreenPtr 对象,p->display 的含义与对(p.operator->())->display 求值相同。对 p.operator->() 求值将调用ScreenPtr 类的 operator->,它返回指向 Screen 对象的指针,该指针用于获取并运行 ScreenPtr 所指对象的 display 成员。

14.7

        自增自减的后缀式:

CheckedPtr CheckedPtr::operator++(int)
{
// no check needed here, the call to prefix increment will do the check
CheckedPtr ret(*this);         // save current value
++*this;                       // advance one element, checking
the increment
return ret;                    // return saved state
}
CheckedPtr CheckedPtr::operator--(int)
{
// no check needed here, the call to prefix decrement will do the check
CheckedPtr ret(*this);         // save current value
--*this;                       // move backward one element and check
return ret;                    // return saved state
}
         假定不抛出异常,后自增操作符函数以返回存储在 ret 的副本而结束。因此,返回之后,对象本身加了 1,但返回的是尚

未自增的原值

        为了与内置操作符一致, 后缀式操作符应返回旧值(即,尚未自增或自减的值),并且,应作为值返回,而不是返回引用。返回的是临时对象,所以不应该是引用(前自增是引用)。同赋值和复合赋值与加操作

14.8
struct absInt {
    int operator() (int val) {
        return val < 0 ? -val : val;
    }
};

        通过为类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用:

int i = -42;
absInt absObj; // object that defines function call operator
unsigned int ui = absObj(i); // calls absInt::operator(int)

        定义了调用操作符的类, 其对象常称为函数对象, 即它们是行为类似函数的对象

函数对象可以比函数更灵活

        在第 11.2.3 节 计算有多少个单词长度在 6 字符以上通过将 GT6 定义为带函数调用成员类,可以获得所需的灵活性。将这个类命名为 GT_cls 以区别于函数:

// determine whether a length of a given word is longer than a stored bound
class GT_cls {
public:
GT_cls(size_t val = 0): bound(val) { }
bool operator()(const string &s)
{ return s.size() >= bound; }
private:
std::string::size_type bound;
};
cout << count_if(words.begin(), words.end(), GT_cls(6))<< " words 6 characters or longer" << endl;
         这个 count_if 调用传递一个 GT_cls 类型的临时对象而不再是名为 GT6的函数。用整型值 6 来初始化那个临时对象,构造函数将这个值存储在 bound成员中。现在,count_if 每次调用它的函数形参时,它都使用 GT_cls 的调用操作符,该调用操作符根据 bound 的值测试其 string 实参的长度

14.8.2. 标准库函数对象

        标准库函数对象类型是在 functional 头文件中定义的有两个一元函数对象类:一元减(negate<Type>))和逻辑非(logical_not<Type>))。其余的标准库函数对象都是表示二元操作符的二元函数对象类。为二元操作符定义的调用操作符需要两个给定类型的形参,而一元函数对象类型定义了接受一个实参的调用操作符。

plus<int> intAdd;         // function object that can add two int values
negate<int> intNegate;    // function object that can negate an int value
// uses intAdd::operator(int, int) to add 10 and 20
int sum = intAdd(10, 20); // sum = 30
// uses intNegate::operator(int) to generate -10 as second parameter
// to intAdd::operator(int, int)
sum = intAdd(10, intNegate(10)); // sum = 0

        函数对象常用于覆盖算法使用的默认操作符。例如,sort 默认使用operator< 按升序对容器进行排序。为了按降序对容器进行排序,可以传递函数对象 greater :

// passes temporary function object that applies > operator to two strings
sort(svec.begin(), svec.end(), greater<string>());
14.8.3. 函数对象的函数适配器 

        标准库提供了一组函数适配器,用于特化和扩展一元和二元函数对象。函数适配器分为如下两类:

  • 绑定器,是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
  • 求反器,是一种函数适配器,它将谓词函数对象的真值求反。 

        标准库定义了两个绑定器适配器:bind1st 和 bind2nd。每个绑定器接受一个函数对象和一个值。正如你可能想到的,bind1st 将给定值绑定到二元函数对象的第一个实参,bind2nd 将给定值绑定到二元函数对象的第二个实参。例如,为了计算一个容器中所有小于或等于 10 的元素的个数,可以这样给 count_if传递值:

count_if(vec.begin(), vec.end(),bind2nd(less_equal<int>(), 10));

        标准库还定义了两个求反器:not1 和 not2。你可能已经想到的,not1 将一元函数对象的真值求反,not2 将二元函数对象的真值求反。

count_if(vec.begin(), vec.end(),not1(bind2nd(less_equal<int>(), 10)));

        这里,首先将 less_equal 对象的第二个操作数绑定到 10,实际上是将该二元操作转换为一元操作。再用 not1 对操作的返回值求反,效果是测试每个元素是否 <=。然后, 对结果真值求反。这个 count_if 调用的效果是对不 <= 10 的那些元素进行计数。

14.9.2

        转换函数采用如下通用形式:operator type();转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空,转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为 const 成员

         使用转换函数时,被转换的类型不必与所需要的类型完全匹配。必要时可在类类型转换之后跟上标准转换以获得想要的类型。例如,在一个 SmallInt 对象与一个 double 值的比较中:

SmallInt si;
double dval;
si >= dval // si converted to int and then convert to double

        类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错: 

// class to hold unsigned integral values
class Integral {
public:
Integral(int i = 0): val(i) { }
operator SmallInt() const { return val; }
private:
std::size_t val;
};

        可以在需要 SmallInt 的地方使用 Integral,但不能在需要 int 的地方使用Integeral:

int calc(int);
Integral intVal;
SmallInt si(intVal); // ok: convert intVal to SmallInt and copy to si
int i = calc(si); // ok: convert si to int and call calc
int j = calc(intVal); // error: no conversion to int from Integral
14.9.3
class SmallInt {
public:
// conversions to SmallInt from int and double
SmallInt(int = 0);
SmallInt(double);
// Conversions to int or double from SmallInt
// Usually it is unwise to define conversions to multiple arithmetic types
operator int() const { return val; }
operator double() const { return val; }
// ...
private:
std::size_t val;
};

        正如可能存在两个转换操作符,也可能存在两个构造函数可以用来将一个值转换为目标类型 :

void manip(const SmallInt &);
double d; int i; long l;
manip(d); // ok: use SmallInt(double) to convert the argument
manip(i); // ok: use SmallInt(int) to convert the argument
manip(l); // error: ambiguous

        第三个调用具有二义性。没有构造函数完全匹配于 long。使用每一个构造函数之前都需要对实参进行转换:

  •  标准转换(从 long 到 double)后跟 SmallInt(double)
  • 标准转换(从 long 到 int)后跟 SmallInt(int)
14.9.5. 重载、转换和操作符 
ClassX sc;
int iobj = sc + 3;
        有四种可能性:
  • 有一个重载的加操作符ClassX int 相匹配
  • 存在转换,将 sc 和/或 int 值转换为定义了 + 的类型。如果是这样,该表达式将先使用转换,接着应用适当的加操作符
  • 因为既定义了转换操作符又定义了 + 的重载版本,该表达式具有二义性。
  • 因为既没有转换又没有重载的 + 可以使用,该表达式非法。
class SmallInt {
public:
SmallInt(int = 0); // convert from int to SmallInt
// conversion to int from SmallInt691
operator int() const { return val; }
// arithmetic operators
friend SmallInt
operator+(const SmallInt&, const SmallInt&);
private:
std::size_t val;
};

        现在,可以用这个类将两个 SmallInts 对象相加,但是,如果试图进行混合模式运算,将会遇到二义性问题:

SmallInt s1, s2;
SmallInt s3 = s1 + s2; // ok: uses overloaded operator+
int i = s3 + 0; // error: ambiguous
         第一个加使用接受两个 SmallInt 值的 + 的重载版本。第二个加有二义性,问题在于,可以将 0 转换为 SmallInt 并使用 + 的 SmallInt 版本,也可以将s3 转换为 int 值并使用 int 值上的内置加操作符。既为算术类型提供转换函数,又为同一类类型提供重载操作符,可能会导致重载操作符和内置操作符之间的二义性 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值