您的位置:首页 > 其它

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

2016-01-06 16:12 330 查看
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
;
}
const std::string& operator[](std::size_t n) const
{
return elements
;
}


14.6自增自减运算符

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

对于迭代器自增自减的时候,需要检查边界。

前置返回值为引用,后置返回值为值。

后置多了一个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).”。

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

operator*可以当做一个一般的函数进行调用。

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;

转换成可以作为函数返回值的类型(除了void、当然不包括数组、函数类型)

没有显式返回值(通常返回值是类型转换运算符自己),没有形参

一般不应该改变待转换对象的内容,所以定义成const

class SmallInt
{
public:
operator int()const { return val; }
private:
std::size_t val;
};


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

explicit operator type() const;

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

if、else if、while、do、for的条件部分

逻辑!、||、&&的运算对象

?:的条件表达式

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避免二义性的类型转换

A有转换构造函数,将B转换成A。同时,B定义了转换运算符,可以将B转换成A类型。则在用到B转换为A的语句时,不能确定适用的函数。

所有算术类型的转换级别都是相同的,若A由转换构造函数,将int/double转换为A,则给定long long类型转换成A就会错误(long类型可能直接使用int转换,因为某些编译器中long和int是同一类型,如visual C++);同理,转换运算符也会有这个问题。

对于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.”

若一组重载的函数接收A、B类型的参数,A、B类型都有接收int类型的转换构造函数,则int类型做参数调用这个函数的时候,会出现二义性。(这通常意味着A、B类设计存在不足)

如果重载函数接收C类型参数,C类型有接收double类型的转换构造函数,则仍会出现二义性,因为抵用额外标准类型转换后再调用自定义转换级别是相同的。

14.9.3函数匹配与重载运算符

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

A定义了参数为int的转换构造函数和转换为int的类型转换函数,并且有重载的运算符+,则A的对象a进行运算a+10就会产生二义性,无法确定是对a转换成int进行的内置加法,还是10转换成A的重载加法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: