每日复习笔记5.13
C++复习笔记
c++ primer笔记
重载操作符与转换
重载操作符的定义
重载操作符是具有特殊名称的函数:保留字operator后接需要定义的操作符符号。像其他函数一样,重载操作符也需要返回值类型和形参列表。
除了函数调用操作符之外,重载操作符的形参数目(包括成员函数隐含的this指针)与原操作符的操作数数目要相同。
- 重载操作符必须具有一个类类型的操作数
用于内置类型的操作符,其含义是不能改变的。也不能为任何内置类型定义额外的新的操作符。
重载操作符必须具有至少一个类类型或者枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类对象的操作符的含义。
- 优先级和结合性是固定的
操作符的优先级和结合星星以及操作数的数目不能改变。
- 不再具有短路求值的特性
重载操作符并不保证操作数的求值顺序。尤其是对于逻辑运算符的求值。在&&和||的重载版本里,前后两个操作数都需要求值,而且对于操作数的求值顺序不做要求。但是内置类型的这两个运算是存在短路求值现象的。所以重载这类操作符不是一个好的选择。
- 类成员和非类成员
大多数重载操作符可以被定义为普通的非类成员函数或者是类的成员函数。
一般来说,将算数和关系操作符定义为非成员函数,而将赋值操作符定义为成员函数。
- 操作符重载和友元关系
操作符定义为非成员函数时,通常必须将他们设置为所操作类的友元。
重载操作符的设计
- 不要重载具有内置含义的操作符
重载逗号、取地址、逻辑与、逻辑或等操作符通常不是好的做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用那些内置含义。
- 大多数操作符对于类对象是没有意义的
除非提供了重载定义,赋值、取地址和逗号操作符对于类类型操作数没有意义。设计类的时候,应该确定 呀哦支持哪些操作符。
个人理解,很多操作符在具有类类型操作数时,都需要重载该操作符,定义新的操作以便于支持类类型对象的其他操作。
当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常必用操作符更好。如果不是普通操作,没必要为了简介而使用操作符。
- 选择成员或非成员实现
一般遵循以下原则:
a)赋值()、下标()、调用()和成员访问箭头()等操作符时必须定义为成员函数,将这些操作符定义为非成员函数时,会发生编译错误。
b)像赋值一样,复合赋值操作符通常也会被定义为成员。但不同的是,不一定非得这样做。如果定义为非成员函数也不会出现编译错误。
c)改变对象状态或者与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常定义为成员。
d)对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。
输入和输出操作符
输出操作符<<的重载
为了和IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对ostream形参的引用。
重载输出操作符一般的简单定义如下:
ostream& operator <<(ostream& os, const Classtype &object)
{
//对于object的合法操作
//准备实际要输出的成员
os << //...
//返回对ostream形参的引用
return os;
}
- Slaes_item输出操作符
对于之前定义的Sales_item类
可以如下重载输出操作符:
ostream& operator << (ostream& out, const Sales_item& s)
{
out << s.isbn << "\t" << s.unit_sold << "\t"
<< s.revenue << "\t" << s.avg_price();
return out;
}
- 输出操作符通常所做格式化应当尽量少
一般而言,输出操作符赢输出对象的内容,进行最小限度的格式化,不应该输出换行符
尽量少的格式化可以让用户自己控制输出细节。
- IO操作符必须时非成员函数
不能将操作符定义为类的成员,否则,左操作数只能是该类类型的对象
Sales_item item;
item << cout;
这种用法与为其他类型定义的输出操作符用法正好相反。正常用法一般都是左操作数是ostream类型。
通常类将IO操作符设为友元。
输入操作符>>的重载
更重要的是,输入和输出操作符有如下不同:输入操作符必须处理错误和文件结束的可能性。
- Slaes_item输入操作符
进行如下的定义:
istream& operator >> (istream& in, Sales_item& s)
{
double price;
in >> s.isbn >> s.units_sold >> price;
if(in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); //这里就属于处理错误
return in;
}
设计输入操作符时,如果可能,要确定错误恢复措施。
算术操作符和关系操作符
一般而言,将算术操作符和关系操作符定义为非成员函数,例如:
Sales_item& operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs);
ret += rhs;
return ret;
}
需要注意的是,为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。
不返回引用的原因:算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中运算,返回对这个变量的引用是一个运行时错误(因为该变量是函数作用域,返回之后,该变量就超出了作用域,造成错误)。
相等操作符
Sales_item的相等操作符可以进行如下定义:
inline bool operator==(const Sales_item &lhs, const Sales_item &rhs)
{
//必须将此重载操作符定义为Sales_item的友元
bool equal = lhs.units_sold == lhs.units_sold &&
lhs.revenue == rhs.revenue &&
lhs.same_isbn(rhs);
return equal;
}
inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
//利用已经重载的==,重载!=
return !(lhs == rhs);
}
这里包含了以下的设计原则:
a)如果类定义了相等操作符,该操作符的含义是两个对象包含相同的数据。
b)如果类具有一个操作,能确定该类型的两个对象是否相等,同城将该函数定义为operatpr==而不是创建命名函数。用户将习惯于使用相等操作符来比较对象,而且这样做比记住新名字更容易。
c)如果类定义了operator=,它也应该定义operator!=。
d)相等和不等操作符一般应该相互联系起来定义,让一个操作符完成比较对象的实际工作,另一个操作符调用前者。
一般来说,定义了重载相等操作符的类更容易与标准库一起使用。有些算法,例如find,默认使用了相等操作符,如果类定义了==,则这些算法无需特殊处理,而直接用于该类类型。
关系操作符
在关联容器和某些算法中会使用到小于操作符,所以定义operator<会是很有用的。
一般而言,关系操作符,诸如相等操作符,应定义为非成员函数。
赋值操作符
之前提到过合成赋值操作符,可以接受类类型的形参。通常该形参是对类类型的const引用,但也可以是类类型或者类类型的非const引用。如果没有定义这个操作符,编译器将合成它。类的赋值操作符必须是类的成员,以便于让编译器得知是否需要合成一个赋值操作符。
可以为一个类定义许多附加的赋值操作符,这些赋值操作符会因为右操作数类型的不同而不同。例如string类型定义了三种赋值操作符,分别是
a)接受const string&作为右操作数
b)接受C风格字符串作为右操作数
c)接受char类型作为右操作数
string car("Volks");
car = "BMW"; //接受C风格字符串
string model;
model = 'T'; //接受char类型数据
//这些定义为
string& operator=(const string &);
string& operator=(const char*);
string& operator=(const char);
赋值操作必须返回对*this的引用
string赋值操作符返回了string的引用,这与内置类型的赋值一致。而且。因为复制返回一个引用,就不需要创建和撤销结果的临时副本。返回值通常是左操作数的引用。
下标操作符
下标操作符必须是类的成员函数。
- 提供读写访问
定义下标操作符的难点在于,它在作用复制的做右操作数时都应该表现正常。所以我们将下标操作的返回值定义为引用,这样就可以用作赋值的任何一方。
需要注意的是,可以对const和非cosnt对象使用下标操作。但是用于const对象时,返回值为const引用,不能作为赋值的目标。
类定义下标操作符时,一般需要定义两个版本,一个为非const成员返回引用;另一个为const成员返回const引用
- 原型下标操作符
简单定义如下的下标操作符:
class Foo{
public:
int &operator[] (const size_t);
cosnt int &operator[] (const size_t) const;
private:
vector<int> data;
};
int &Foo::operator[] (const size_t index)
{
return data[index];
}
const int &Foo::operator[] (const size_t index) const
{
return data[index];
}