果然进入Part III了之后,难度也不一样了。
copy control实际上没怎么看懂,过段时间还得回头加深理解。
Anyway,现在继续往前学习,进入重载操作与转换式:
Overloaded operations and Conversions
14.1 Basic Concepts
----------------------------------------------------------------
重载的操作符有特殊的名称:关键字operator,跟随操作符的symbol。
重载操作符的函数,和所以其他函数一样,具备返回值、参数列表和函数体。
另外,函数调用的操作符“()”,也可以被重载。
如果重载操作符的函数是类的成员函数的话,左操作数隐式的绑定在this指针上。
大多数的操作符都可以被重载,除了以下几种:
:: .* . ?:
我们不能重载build-in类型的操作,也不能发明新的操作符。
重载过的操作符的优先级和结合性,与重载之前相同。
比如下面的代码:
class Foo {
public:
Foo() = default;
Foo &operator+(const Foo&);
int i;
private:
string *ps;
};
Foo &Foo::operator+(const Foo &rhs)
{
i += rhs.i;
return *this;
}
int main(int argc, char *argv[])
{
Foo f1, f2;
Foo f3 = f1 + f2;
Foo f4;
f4.operator+(f3);
Foo f5 += f1;
return 0;
}
由于成员函数的左操作符绑定在this指针上,这里的重载“+”只有一个操作数。
另外注意,重载的操作符实际上是个函数,因此它不能保证原始操作符的evaluated顺序,
尤其是,“&&”和“||”没有短路功能,因此所有的操作数都会被evaluated,因此最好不要reload它们。
最好不要reload的操作符:
, & && ||
什么时候应该重载操作符呢:
* 类中有IO操作,则重载shift操作符。
* 类中有比较操作,则重载==,而且应该重载!=。
* 类中有单一的自然排序,则重载<,而且应该重载所有的关系操作符。
* 重载操作符的返回值,应该和build-in保持兼容。
什么时候应该将操作符重载为成员函数呢:
* =,[],(),->应该被重载为成员函数。
* 混合赋值的函数,一般应该重载为成员函数。
* 和对象的state密切相关的,或者和类type绑定(++,--,*)的操作符应该重载为成员函数。
* sysmmetric操作符,比如算术、相等、关系、bitwise操作符,应该重载为普通函数。
string类就将“+”重载成了非成员函数,因此下面两个表达式都合法:
string u1 = "Hi" + s;
string u2 = s + "Hi";
14.2 Input and Output Operators
----------------------------------------------------------------
包含IO操作的类,一般都会定义input和output操作。
output操作符的第一个参数通常是ostream对象的nonconst引用(因为需要更改ostream状态);
output操作符的第二个参数通常是类的const引用(因为要避免copy操作)。
input和output操作符必须重载为非成员函数;
由于它们对私有成员的访问,因此通常将它们声明为friends。
input操作符通常得处理出错的情况,output操作符则不担心这个问题。
什么时候input操作会出错:
* stream的数据类型出错。
* 读取到了end-of-file,或者其他istream错误。
14.3 Arithmetic and Relational Operators
----------------------------------------------------------------
一般来说,如果一个类定义了算术操作和related compound assignment操作,
那么通常应该使用后者来实现前者。
一般来说,如果定义了“==”,那么“<”最好和它的物理含义兼容,
因此其实有些类,我们并不需要为它定义“<”操作。
14.4 Assignment Operators
----------------------------------------------------------------
它可以被重载,它必须被定义为成员函数,它应该是copy-assigning、move-assigning兼容。
14.5 Subcript Operators
----------------------------------------------------------------
下标操作符必须重载为成员函数。
14.6 Increment and Decrement Operators
----------------------------------------------------------------
这两个操作符,对于迭代器来说非常常见。
一般来说,建议将它们实现为成员函数。
一般来说,prefix和postfix应该一起实现。
比如下面的代码:
Foo &operator++();
Foo &operator--();
Foo operator++(int);
Foo operator--(int);
14.7 Member Access Operators
----------------------------------------------------------------
“->”,必须被重载为成员函数;
“*”,一般需要被重载为成员函数。
( * 这一节很有难度,需要以后再深入的学习!!!)
14.8 Function-Call Operators
----------------------------------------------------------------
函数调用的操作符“()”,也可以被重载。
比如下面的代码:
struct absInt {
int operator()(int val) const {
return (val < 0)? -val : val;
}
};
int main(int argc, char *argv[])
{
absInt absObj;
int ui = absObj(-10);
cout << ui << endl;
return 0;
}
当使用类重载“()”替代函数的功能时,由于类里面可以存储state,它比函数更加灵活。
重载“()”的函数必须是类的成员函数。
这样的类,我们称之为函数对象:function objects.
函数对象通常和算法结合起来使用。
比如下面的代码:
class PrintString {
public:
PrintString(ostream &o = cout, char c = ' ') : os(o), sep(c) {}
void operator()(const string &s) const {os << s << sep;}
private:
ostream &os;
char sep;
};
int main(int argc, char *argv[])
{
vector<string> vs = {"123", "456", "789", "abc", "defg"};
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
return 0;
}
也就是说,作为算法的函数参数,可以是lambda表达式、bind函数,以及函数对象类。
实际上,lambda表达式就是没有命名的函数对象。
库里面定义了一些操作的模板,比如plus:
plus<int> intAdd;
int sum = intAdd(10, 20);
cout << sum << endl;
输出结果是:
[marianna@localhost Debug]$ ./hello2
30
库函数对象,也可以和算法结合起来使用。
比如下面的代码:
vector<string> vs = {"123", "456", "789", "abc", "defg"};
for_each(vs.begin(), vs.end(), PrintString(cout, ' '));
cout << endl;
sort(vs.begin(), vs.end(), greater<string>());
for_each(vs.begin(), vs.end(), PrintString(cout, ' '));
cout << endl;
输出结果是:
[marianna@localhost Debug]$ ./hello2
123 456 789 abc defg
defg abc 789 456 123
库函数对象可以直接作用于指针,这样我们可以比较地址的高低(直接比较的话,某些情况下无意义)。
另外,function模板可以用来将函数指针、函数对象、lambda表达式都转换成指定的函数类型:
function<int(int, int)> f1 = add; //函数指针
function<int(int, int)> f2 = dev(); //函数对象
function<int(int, int)> f3 = [](int i, int j) {return i * j} //lambda表达式
这样,可以方便的用函数名称key定义函数类型的map:
map<string, int(*)(int, int)> binops;
或者:
map<string, function<int(int, int)>> binops;
14.9 Overloading, Conversions, and Operators
----------------------------------------------------------------
转换操作符(Conversion Operators):
operator type() const;
比如:
operator int() const { return val; }
它必须是类的成员函数,不能有返回值,参数表必须为空,并且总是const的。
转换操作符总是隐式的发生,因此无法为它传递参数。
转换操作符虽然没有返回值,但是它必须返回一个type类型的值。
慎用类的类型转换!
文章评论(0条评论)
登录后参与讨论