网站建设包含哪些费用,企业服务器,aspnet网站开发书,网络公司是干什么的文章目录基本概念调用选择作为成员还是非成员输入和输出运算符算术和关系运算符相等和不等运算符赋值运算符下标运算符递增和递减运算符成员访问运算符函数调用运算符lambda是函数对象标准库定义的函数对象可调用对象与function重载、类型转换与运算符类型转换运算符避免有二义…
文章目录基本概念调用选择作为成员还是非成员输入和输出运算符算术和关系运算符相等和不等运算符赋值运算符下标运算符递增和递减运算符成员访问运算符函数调用运算符lambda是函数对象标准库定义的函数对象可调用对象与function重载、类型转换与运算符类型转换运算符避免有二义性的类型转换基本概念
重载的运算符是具有特殊名字的函数它们的名字由关键字operator和其后要定义的运算符号共同组成。其参数数量和该运算符的运算数量一样多。除了重载的函数调用运算符operator()之外其他重载运算符不能含有默认参数。
如果一个运算符函数是成员函数则它的第一个运算对象绑定到this指针。对于一个运算符函数来说它或者是类的成员或者至少含有一个类类型的参数
int operator(int, int); //err我们可以重载大多数已有的运算符而无权发明新的运算符号。重载后运算符和内置运算符的优先级和结合律保持一致。 一般不建议重载逗号、取地址、逻辑与和逻辑或运算符。
if (a b) { ... } 如果 a 为 false则 b 根本不会计算。 但一旦重载了 operator, operator||这个短路规则 完全丢失编译器会强制先算两个操作数再传给重载函数。
int x (f1(), f2()); // 保证先执行 f1再执行 f2 如果重载了 operator,它就变成了你自定义的逻辑而失去了“顺序保证”这个语言特性。
取地址则是对于类类型已经有了内置的含义。
调用
//直接调用
data1 data2;//间接调用
operator(data1, data2); //普通运算符
data1.operator(data2); //成员运算符选择作为成员还是非成员
赋值()下表([])调用(())成员访问符(-)等必须是成员复合运算符一般是成员改变对象状态的运算符或者给定类型密切相关的运算符如递增、递减和解引用运算符通常是成员具有对称性的运算符例如算数、相等性、关系和位运算符等通常是非成员输入输出运算符必须是非成员如果是成员的话不符合第一个操作对象是输入输出。
输入和输出运算符
//输出
ostream operator(ostream os, const Sales_data item);
Sales_data data;
cout data;//输入
istream operator(istream is, Sales_data item)
{double price;is item.bookNo item.units_sold price;if (is)item.revenue item.units_sold * price;elseitem Sales_data();return is;
}
cin data;输入运算符通常会在还会进行错误处理。
算术和关系运算符
如果定义了算术运算符一般也会定义一个对应的复合运算符
Sales_data operator(const Sales_data lhs, const Sales_data rhs)
{Sales_data sum lhs;sum rhs;return sum;
}相等和不等运算符
相等运算符和不等运算符中的一个应该把工作委托给另外一个
bool operator(const Sales_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);
}赋值运算符
拷贝赋值和移动赋值可以把类的一个对象赋值给该类的另外一个对象。此外类还可以定义其他赋值运算符以使得别的类型作为右侧运算对象
//标准库vector类还定义了第三种赋值运算符该运算符接受花括号元素列表作为参数
vectorstring v;
v {a, an, the};下标运算符
如果一个类包含下标运算符则通常会定义两个版本一个返回普通的引用另一个则是类的常量成员返回常量引用
class StrVec
{
public:std::string operator[](std::size_t n){return element[n];}const std::string operator[](std::size_t n) const{ return element[n];}
private:std::string *element;
}递增和递减运算符
定义递增和递减运算符的类应该同时定义前置和后置版本。这些运算符通常被定义为成员。
后置版本接受一个额外的int形参该形参一般不被使用
class StrBlobPtr
{
public://前置StrBlobPtr operator();StrBlobPtr operator--();//后置StrBlobPtr operator(int);StrBlobPtr operator--(int);
}//显示调用
StrBlobPtr p(a1);
p.operator(0); //后置
p.operator(); //前置成员访问运算符
解引用运算符首先检查curr是否在合理范围内如果是则返回curr所指对象的一个引用箭头运算符调用解引用并返回其结果地址
class StrBlobPtr
{std::string operator*() const{auto p check(curr, dereference past end);return (*p)[curr];}std::string* operator-() const{return this-operator*();}
}值得注意的是这两个运算符都定义成了const成员这是因为获取一个元素并不会改变该对象的状态。
StrBlob a1 {a, an, the};
StrBlobPtr p(a1);
*p okay; //给a1的首元素赋值
cout p-size() endl; //打印4okay的大小
cout (*p).size() endl; //等价和大多数其他运算符一样我们能令operator*完成任何指定的操作。箭头运算符则不是这样它只能拥有成员访问这个最基本的含义。形如point-mem的表达式来说point必须是指向对象的指针或者是一个重载了operator-的类的对象
(*point).mem; //point为指针
(point.operator-())-mem; //point为重载了operator-的对象函数调用运算符
如果类重载了函数调用运算符则我们可以像使用函数一样使用该类的对象。一个类可以定义多个不同版本的调用运算符相互之间在参数数量或类型上有所区别。
如果定义了函数调用运算符则该类的对象称作函数对象。
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;
}函数对象常常作为泛型算法的实参
for_each(vs.begin(), vs.end(), PrintString(cerr, \n));vs中的对象打印到cerr中并换行分割。
lambda是函数对象
当我们编写一个lambda后编译器会将该表达式翻译成一个未命名类的未命名对象。该类中含有一个重载函数调用运算符
stable_sort(words.begin(), words.end(), [](const string a, const b){return a.size() b.size();});//该lambda等价于该类的一个未命名对象
class ShorterString
{
public://lambda默认值捕获因此生成的唯一成员函数是const//如果是引用捕获则不是constbool operator()(const string a, const b) const{return a.size() b.size();}
};//等价lambda表达式写法
stable_sort(words.begin(), words.end(), ShorterString());值捕获产生的类必须为每个值捕获的变量建立对应的数据成员同时构建构造函数
auto wc find_if(words.begin(), words.end(), [sz](const string a){ return a.size sz;});class SizeComp
{
public:SizeComp(size_t n) : sz(n) {}bool operator()(const string s) const{return s.size() sz;}
private:size_t sz;
};auto wc find_if(words.begin(), words.end(), SizeComp(sz));引用捕获不用生成成员变量和构造函数。
标准库定义的函数对象
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类每个类分别定义了一个执行命名操作的调用运算符。
sort(vec.begin(), vec.end(), greaterstring());可调用对象与function
C中有几种可调用的对象函数、函数指针、lambda表示式、bind创建的对象以及重载了函数调用运算符的类。两种不同类型的可调用对象可以同享一种调用形式例如int(int, int)。
可以通过function表示不同类型的可调用对象
int add(int i, int j) {return i j;}
auto mod [](int i ,int j) {return i % j;};
struct divide
{int operater()(int i, int j) {return i / j;}
};mapstring, functionint(int, int) binops
{{, add},{-, std::minusint()},{/, divide()},{*, [](int i, int j){return i * j;}},{%, mod}
};值得注意的是同名可调用对象有重载时不能存入function类型的对象中。可通过函数指针或者lambda表示式消除二义性。
重载、类型转换与运算符
转换构造函数和类型转换运算符共同定义了类类型转换。
类型转换运算符
类型转换运算符时类的一种特殊成员函数它负责将一个类类型的值转换成其它类型operator type() const;满足下列条件
一个类型转换函数必须是类的成员函数不能声明返回类型形参列表必须为空函数通常是const的。
class SmallInt
{
public:SmallInt(int i 0) : val(i) {}//虽然没有指定返回类型但实际会返回type类型operator int() const {return val;}
private:int val;
}其中构造函数能够将算术类型转化为SmallInt而类型转换运算符能够将SmallInt类型转化为算术类型
SmallInt si;
si 4; //int-SmallInt
si 3; //SmallInt-int有时隐式的类型转换看上去会觉得困扰例如当istream含有bool的类型转换时下面的代码可以正常编译
int i 42;
cin i; //cin - bool - int为了防止这样的情况可以定义显示的类型转换运算符
class SmallInt
{
public:explicit SmallInt(int i 0) : val(i) {}explicit operator int() const {return val;}
};SmallInt si 3;
si 3; //err
static_castint(si) 3; //ok值得注意的是如果表达式被用作条件则编译器会将显示的类型转换自动应用于它即显示的类型转换将被隐式的执行
if、while、do的条件语句部分for 语句头的条件表达式逻辑非、或、与的运算对象条件运算符的条件表达式。
大部分类都定义了向bool转化的显示类型转换运算符例如IO类型
while (std::cin value)避免有二义性的类型转换
如果定义了一组类型转换它们的转换源或者转换目标可以通过其他类型转换联系在一起则会产生二义性问题
最好只创建一个算术类型的转换
//最好只创建一个算术类型的转换
struct A
{A(int 0);A(double);operator int() const;operator double() const;
};void f2(long double);
A a;
f2(a); //err二义性不知道用哪一个进行类型转换long lg;
A a2(lg); //err不知道用哪一个构造函数最好不要在两个类之间构建相同的类型转换
struct B;
struct A
{A() default;A(const B);
};struct B
{operator A() const;
};A f(const A);
B b;
A a f(b); //err//可以显示指定
A a1 f(b.operator A());
A a2 f(A(b));