第14章 重载运算与类型转换

  • l  通常情况下,不应该重载逗号、取地址、逻辑或与运算符,这样可能会改变求值顺序、短路特性。对于逗号、取地址运算符,重载之后还会改变内置的含义。
  • l  运算符重载可以定义在类,是内成员函数,也可以是非成员函数。当做为成员函数定义时,this会默认成为第一个参数,绑定到左侧运算对象。
  • l  需要改变左侧运算对象状态、访问左侧运算对象内容,必须定义成成员函数。
  • l  具有对称性的运算、可能发生转换任意一端的运算,一般应该定义成非成员

14.2输入输出运算符

  • l  输入输出运算符必须是非成员函数,这样第一个参数才会是运算符左侧的stream
  • l  输入输出运算符的第一个参数应该是stream的非const引用(写入或读取会改变stream状态),在函数最后返回(实现链式调用)。
  • l  通常需要读写类的private成员,所以一般声明为友元函数
  • l  输入运算符需要处理可能的错误,而输出运算符一般不需要

 

class ClassName
{
    friend ostream& operator<<(ostream&, ClassName&);
    friend istream& operator>>(istream&, ClassName&);
private:
    string name;
};
ostream& operator<<(ostream& os, ClassName& obj)
{
    os << obj.name;
    return os;
}
istream& operator>>(istream& is, ClassName& obj)
{
    is >> obj.name;
    //处理输入错误
    //设置foilbit、eofbit、badbit
    return is;
}

 

14.3算术关系运算符

  • l  算术运算符一般不需要改变运算对象内容,并且要允许左侧运算对象进行类型转换,所以一般都是非成员函数,形参为const引用
  • l  算术运算一般会得到新值,新值是局部变量,所以返回其副本作为结果(不能返回引用)
  • l  一般来讲会定义对应的复合赋值运算,可以使用之进行算术运算(用其中一个对象构造副本,对副本使用复合赋值运算符,返回副本)
  • l  相等运算符和不等运算符应该成对出现,并且使用其中一个进行比较就可以了。而且,如果定义了相等/不等运算符,标准库容器和算法也可以使用了。
  • l  关系运算符定义了顺序关系,应该与关系容器中对关键字的要求一致(参见有序容器的要求:严格弱化的”<”操作)
  • l  如果有==运算符,则关系运算应该与之保持一致,特别是当对象!=时,必定有less than的一个。
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs;  // copy data members from lhs into sum
    sum += rhs;  // add rhs into sum
    return sum;
}
bool operator==(cons tSales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() == rhs.isbn() &&
        lhs.units_sold == rhs.units_sold &&
        lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
    return!(lhs == rhs);
}

 

14.4赋值运算符

在拷贝控制中讲解了赋值运算符的拷贝赋值和移动赋值,还有一种是使用花括号列表进行赋值。

vector<string> vec;
vec = { "123" ,"456","789" };

对于之前编写的动态内存管理类,我们可以添加这个赋值特性

//列表赋值运算符
StrVec &operator= (std::initializer_list<std::string> ls)
{
    auto data = alloc_n_copy(ls.begin(), ls.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

 

14.5下标运算符

下标运算符通常定义两个版本,一个是const版本,另一个是nonconst版本。

下标运算符必须是成员函数。

//下标运算符
std::string&operator[](std::size_t n)
{
    return elements[n];
}
const std::string& operator[](std::size_t n) const
{
    return elements[n];
}

 

14.6自增自减运算符

There are both prefix and postfix versions. These operators usually should be defined as members.

  1. 对于迭代器自增自减的时候,需要检查边界。
  2. 前置返回值为引用,后置返回值为值。
  3. 后置多了一个int参数,但是没有使用,在执行的时候使用了前置的函数。
class StrBlobPtr {
public:
    // increment and decrement
    StrBlobPtr&operator++();  // prefix operators
    StrBlobPtr&operator--();

    StrBlobPtr operator++(int);  // postfix operators
    StrBlobPtr operator--(int);
    // other members as before
};
// prefix:return a reference to the incremented/decremented object
StrBlobPtr& StrBlobPtr::operator++()
{
    // if curr already points past the end of the container, can't increment it
    check(curr, "increment past end of StrBlobPtr");
    ++curr;  // advance the current state
    return*this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
    // if curr is zero, decrementing it will yield an invalid subscript
    --curr;  // move the current state back one element
    check(-1, "decrement past begin of StrBlobPtr");
    return*this;
}
// postfix: increment/decrementthe object but return the unchanged value
StrBlobPtr StrBlobPtr::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check
    StrBlobPtrret = *this;  // save the current value
    ++*this;  // advance one element; prefix ++ checks the increment
    returnret;  // return the saved state
}
StrBlobPtr StrBlobPtr::operator--(int)
{
    // no check needed here; the call to prefix decrement will do the check
    StrBlobPtrret = *this;  // save the current value
    --*this;    // movebackward one element; prefix -- checks the
    decrement
        returnret;  // return the saved state
}

 

14.7成员访问运算符

迭代器类以及智能指针类中,拥有成员访问运算符,包括解引用运算符、箭头运算符。

对于内置的指针类型,访问其成员我们使用(*ptr).mem,使用ptr->men与之等价,“ptr->”实际上就成为了“(*ptr).”。

对于定义了->运算符的对象,我们使用*和->都将使用自定义的版本。

  1. operator*可以当做一个一般的函数进行调用。
  2. operator->在函数调用完成后有附加动作。如果他返回的结果是一个内置指针,则在函数调用完成之后,对返回的内置指针执行->操作;如果返回的结果本身含有重载的operator->,则继续执行返回对象的operator->调用过程。(所以,其返回值必须是一个内置指针或者重载了operator->操作的对象
class Ptr
{
    std::string& operator*() const
    {
        auto p = check(curr, "dereference past end");
        return(*p)[curr];  // (*p) is the vector to which this object points
    }
    std::string*operator->() const
    {// delegate the real work to the dereference operator
        return&this->operator*();
    }
};

 

14.8函数调用运算符

Objects of classes that define the call operator are referred to as function objects. Such objects “act like functions” because we can call them.

class absInt
{
public:
    int operator()(int integer)
    {
        return integer > 0 ? integer : -integer;
    }
};
int main()
{
    absInt funObj;
    int b = funObj(-155);
}

 

14.8.1函数调用与lambda表达式

lambda表达式实际上就是一个函数对象,捕获列表中的数据将会成为对象的成员。

14.8.2标准库的函数对象

Arithmetic算术

plus<Type>

minus<Type>

multiplies<Type>

divides<Type>

modulus<Type>

negate<Type>

Relational关系

equal_to<Type>

note_equal_to<Type>

greater<Type>

greater_equal<Type>

less<Type>

less_equal<Type>

Logical逻辑

logical_amd<Type>

logical_or<Type>

logical_not<Type>

默认情况下sort使用<进行排序,为降序。可以如下使用升序。

#include<functional>
sort(svec.begin(), svec.end(), greater<string>());

 

14.8.3可调用对象与function

调用形式(call signature):int(int,int)

不同的类型,具有相同的调用形式

//普通函数
int add(int i, int j) { return i + j; }
//lambda,其产生一个未命名的函数对象类
auto add = [](int i, int j) { return i + j; };
//函数对象类
class add
{
    int operator()(int i, int j) { return i + j; }
};

 

这种调用形式,可以用一种模板类型代替

#include<functional>
function<int(int, int)> fun;

 

function不能识别重载函数

//普通函数
int add(int i, int j) { return i + j; }
double add(int i, double j) { return i + j; }
//这句话会出错
function<int(int, int)> fun = add;

 

函数指针可以识别重载

//函数指针可以识别重载
int(*f)(int, int) = add;
function<int(int, int)> fun = f;

 

14.9重载、类型转换与运算符

转换构造函数(将其他类型转换成自己)

构造函数只接受一个实参,实际上定义了此类型的隐式转换机制。当然,可以使用explicit声明阻止这个转换。

隐式类型转换运算符(将自己转换成其他类型)

operator type() const;

  1. 转换成可以作为函数返回值的类型(除了void、当然不包括数组、函数类型)
  2. 没有显式返回值(通常返回值是类型转换运算符自己),没有形参
  3. 一般不应该改变待转换对象的内容,所以定义成const
class SmallInt
{
public:
    operator int()const { return val; }
private:
    std::size_t val;
};

 

显式类型转换运算符(将自己转换成其他类型)

explicit operator type() const;

如果表达式用在条件语句,则会隐式执行

  1. if、else if、while、do、for的条件部分
  2. 逻辑!、||、&&的运算对象
  3. ?:的条件表达式
class SmallInt
{
public:
    explicit operator int()const { return val; }
private:
    std::size_t val;
} 
int main()
{
    SmallInt si;
    int a = static_cast<int>(si);
}

 

转换为bool

while(std::cin>>value)

cin的>>运算符返回cin本身,因为在条件语句,隐式转换成bool类型。

非条件语句中,如cin<<2,因为cin没有定义<<、如果cin可以隐式转换成bool,则可以将<<用作左移符号。但是非条件语句,不能隐式转换,所以这句话是错误的。

14.9.2避免二义性的类型转换

  1. A有转换构造函数,将B转换成A。同时,B定义了转换运算符,可以将B转换成A类型。则在用到B转换为A的语句时,不能确定适用的函数。
  2. 所有算术类型的转换级别都是相同的,若A由转换构造函数,将int/double转换为A,则给定long long类型转换成A就会错误(long类型可能直接使用int转换,因为某些编译器中long和int是同一类型,如visual C++);同理,转换运算符也会有这个问题。
  3. 对于2,可以只定义一个算术类型的转换,如果使用时需要转换成其他算术类型,编译器会自动使用内置的转换。

The easiest rule of all: With the exception of an explicitconversion to bool, avoid defining conversion functions and limit nonexplicit constructors to those that are “obviously right.”

  1. 若一组重载的函数接收A、B类型的参数,A、B类型都有接收int类型的转换构造函数,则int类型做参数调用这个函数的时候,会出现二义性。(这通常意味着A、B类设计存在不足)
  2. 如果重载函数接收C类型参数,C类型有接收double类型的转换构造函数,则仍会出现二义性,因为抵用额外标准类型转换后再调用自定义转换级别是相同的。

14.9.3函数匹配与重载运算符

当我们使用内置类型与类类型进行运算的时候,若有运算符重载和类定义的类型转换,则可能会发生二义性。

A定义了参数为int的转换构造函数和转换为int的类型转换函数,并且有重载的运算符+,则A的对象a进行运算a+10就会产生二义性,无法确定是对a转换成int进行的内置加法,还是10转换成A的重载加法。

posted on 2016-01-06 16:12  峰入云  阅读(371)  评论(0编辑  收藏  举报

导航