重载与类型转换
来源:互联网 发布:大数据质量管理 编辑:程序博客网 时间:2024/05/22 13:18
重载:定义类与类之间运算符所要完成的具体工作。
类型转换:定义类与类、类与内置类型之间的转化规则。
一、重载运算概述
1、格式
重载运算符本质上是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成。
2、形参数目
重载函数的形参数与其作用的运算对象数量一样多。但是若一个运算符函数是成员函数,则其第一个运算对象绑定到this指针上,因此其形参数比运算对象数少一个。
3、调用形式
我们可以像调用普通函数一样直接调用运算符函数,实现与直接使用运算符相同的功能:
data1+data2;//普通的表达式operator+(data1, data2);//等价的函数调用
如果一个运算符是类的成员函数:
data1+=data2;//基于“调用”的表达式data1.operator+=(data2);//对成员运算符函数的等价调用
4、含义
设计重载函数时,应该使用与内置类型一致的含义。
比如operator+一般将其定义成类的加法操作,而不要将其定义成减法。
5、成员还是非成员
在设计重载运算符之间,需要选择其作为类的成员函数还是普通函数。
a)、赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员。
b)、复合赋值运算符(+=,-=)一般来说应该是成员,但不是必须的。
c)、改变对象状态的运算符通常应该是成员,如递增(++)、递减(--)和解引用(*)。
d)、具有对称性质的运算符通常应该是普通的非成员函数,如算术、相等性、关系和位运算符。
二、输入输出运算符
输入输出运算符必须是非成员函数,但由于IO运算符通常需要读写类的私有成员,所以一般声明为友元。
1、输出运算符重载函数operator<<
输出运算符第一个形参是一个非常量ostream对象的引用,第二个形参是一个常量的引用,返回值的是它ostream形参。
Sales_data的输出运算符:
ostream &operator<< (ostream &os, const Sales_data &item){os<< item.isbn() << " " <<item.units_sold<< " " <<item.revenue<< " " << item.avg_price();return os; }
2、输入运算符重载函数operator>>
输入运算符的第一次形参是运算符将要读取的流的引用,第二形参是将要读入到的对象的引用,返回值是某个给定流的引用。
Sales_data的输入运算符:
istream &operator>>(istream &is, Sales_data &item){double price;is >> item.bookNo >> item.units_sold >>price;if (is)//检查输入是否成功item.revenue = item.unites_sold * price;elseitem = Sales_data();//输入失败,对象被赋予默认的状态return is;}
三、算术、关系运算符
算术和关系运算符通常定义为非成员函数,从而可以对左侧或右侧的运算对象进行转换。
算术、关系运算符一般不会改变输入变量的值,因此其形参是常量的引用。
运算结果一般位于一个局部变量之内,操作完成后返回该局部变量的副本作为其结果。
1、相等运算符operator==
通常来说,我们使用operator==来检验两个对象是否相等,我们通过比较对象中的每个元素的值来得到结果。
如果类定义了operator==,则这个类也应该定义operator!=。
2、关系运算符operator<
因为关联容器和一些算法要用到小于运算符,所以定义operator<会比较有用。
定义的关系运算符必须满足两个条件:
a)、定义顺序关系,令其与关联容器中对关键字的要求一致。
b)、如果类同时也含有==运算符的话,则定义一种关系令其与==保持一致。特别是,如果两个对象是!=的,那么一个对象应该<另外一个。
四、赋值运算符operator=
通过重载赋值号=,我们可以用其他类型对象向特定类的对象赋值。
赋值运算符必须为类的成员函数。
与内置类型的赋值运算符一致,新的赋值运算符也将返回其左侧运算对象的引用(*this)。
StrVec &StrVec::operator=(initializer_list<string> il){// alloc_n_copy分配内存空间并从给定范围内拷贝元素auto data = alloc_n_copy(il.begin(), il.end());free();//销毁对象中的元素并释放内存空间elements = data.first;//更新数据成员使其指向新空间first_free = cap = data.second;return *this;}
五、下标运算符operator[ ]
与下标的原始定义兼容,下标运算符以所访问元素的引用作为返回值。
下标运算符一般定义一个常量版本和一个非常量版本,其中常量版本的返回值不能修改。
class StrVec{public:std::string& operator[ ](std::size_t n){ return elements[n]; }const std::string& operator[ ] (std::size_t n) const{ return elements[n]; }private:std::string *element;//指向数组首元素的指针};
六、递增、递减运算符operator++,operator--
递增、递减运算符改变的是所操作对象的状态,一般将其设定为成员函数。
注意:递增和递减既有前置版本,也有后置版本。
1、前置递增、递减运算符
前置递增、递减运算符返回当前对象的引用。
StrBlobPtr& StrBlobPtr::operator++(){check(curr, "increment past end of StrBlobPtr")++curr;return *this;}
2、后置递增、递减运算符
后置版本的递增、递减运算符接受一个额外的int类型实参。这个形参的唯一作用是区分前置版本和后置版本的函数。
StrBlobPtr StrBlobPtr::operator++(int){StrBlobPtr ret = *this;//记录当前的值++*this;//向前移动一个元素return ret;//返回之前记录的状态}
七、成员访问运算符operator*,operator->
成员访问运算符包括解引用运算符(*)和箭头运算符(->)。
解引用运算符返回所指对象的一个引用。箭头运算符不执行自己的操作,而是调用解引用运算符并返回解引用结果元素的地址。
八、函数调用运算符operator()
1、基本用法
如果一个类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。
struct absInt{int operator() (int val) const{return val < 0 ? -val ; val;}} ;
absInt类定义了函数调用运算符,我们可以将其对象向函数一样调用:
int i = -42;absInt absObj;//含有函数调用运算符的对象int ui = absObj(i);//将i传递给absObj.operator()
2、含有状态的函数对象类
函数对象类除了operator()还可以包含其他成员,这些成员可以自定义函数调用运算符的功能。
class PrintString{public:PrintString (ostream &o cou, char c = ' ' ):os(o), sep(c) { }void operator() {const string &s} const { os<<s<<sep; }//函数调用运算符会用到类的私有成员private:ostream &os;//用于写入的目的流char sep;//用于将不同输出隔开的字符};
当定义PrintString的对象时,对于分隔符及输出流既可以使用默认值,也可以提供我们自己的值:
PrintString printer;//使用默认值,打印到coutprinter(s);//在cout中打印s,后跟一个空格PrintString errors(cerr, '\n');//使用提供的值进行PrintString初始化errors(s);//在cerr(而不是cout)中打印s,后跟一个换行符,而不是空格
函数对象常常作为泛型算法的实参,我们可以在for_each算法中使用PrintString 对象:
for_each(vs.begin(), vs.end(),PrintString(cerr, '\n'));
第三个参数是PrintString的临时变量,调用for_each时,会将vs中的每个元素依次打印到cerr中,并以换行符分隔。
3、lambda是函数对象
a)、普通的lambda表达式与不含状态的函数对象等价
stable_sort(words.begin(), words.end(),[ ](const string &a, const string &b) {return a.size() < b.size(); });
其中的lambda表达式的行为类似于下面这个类的一个未命名对象:
class ShorterString{public:bool operator() (const string &s1, const string &s2) const { return s1.size() < s2.size();}};
b)、带捕获参数列表的lambda与含有状态的函数对象类等价
auto wc = find_if(word.begin(),words.end(),[sz] (const string &a){return a.size() >= sz;});
该lambda产生的类将形如:
class SizeComp{SizeComp(size_t n): sz(n) {}//构造函数形参对应捕获的变量//该调用运算符的返回类型、形参和函数体都与lambda一致bool operator () (const string &s) const{ return s.size() >= sz; }private:size_t sz;//该数据成员对应通过值捕获的变量}
使用函数对象:
auto wc = find_if (words.begin(), words.end(), SizeComp(sz) );
4、标准库定义的函数对象
头文件functional中定义了一组表示算术运算符、关系运算符和逻辑运算符的类。
我们可以在算法中使用函数对象:
sort(svec.begin(), svec.end(), greater<string>());
将svec按降序排列。其中greater<string>是标准库函数对象,用于在两个string间执行>比较运算。
5、可调用对象与function
C++中共有5种可调用对象:函数、函数指针、lamdba表达式、bind创建的对象以及重载了函数调用运算符的类。
对于不同类型的调用对象可能具有相同的调用形式:
int add(int i, int j) {return i+j;}//普通函数auto mod = [ ](int i, int j) { return i%j;};//lambda表达式struct divide//函数对象类{int operator()(int de, int di){return de/di;}};
以上3个可调用对象都具有int(int,int)的调用形式,C++中,我们可以使用标准function类型来存储可调用对象类:
function<int (int, int)> f1 = add;//函数指针function<int (int, int)> f2 = divide(); //函数对象类的对象function<int (int, int)> f3 = [ ] (int i, int j)//lambda表达式{return i*j;};cout<<f1(4,2)<<endl;//打印6cout<<f2(4,2)<<endl;//打印2cout<<f3(4,2)<<endl;//打印8
九、类型转换运算符
1、概述
类型转换运算符负责将一个类类型转换成其他类型。一般形式:
operator type() const;
其中type表示该对象所要转换成的类型。
类型转换运算符特点:
a)、没有显示的返回类型
b)、没有形参
c)、必须定义成类的成员函数
d)、一般定义成const成员
2、例子
class SmallInt{public:SmallInt(int i = 0): val(i){if(i < 0 || i>255)throw std::out_of_range("Bad SmallInt value");}operator int( ) const { return val; }private:std::size_t val;};
SmallInt类的构造函数定义了从算术类型像类类型的转换,而类型转换函数定义了从类类型向算术类型的转换。
使用:
SmallInt si;si = 4;//拷贝赋值,现将4隐式转换为SmallInt类型,然后调用SmallInt::operator=si + 3;//先将si隐式转换成int,然后执行整数的加法
3、抑制隐式类型转换
上述类型转换是隐式完成的,它可能会带来我们所不期望的结果,在C++中,我们可以通过explicit关键字来抑制隐式类型转换:
class SmallInt{public://编译器不会自动执行这一类型转换explicit operator int() const { return val; }//其他与之前版本一致};
与显示构造函数类似,编译器也不会将一个显式的类型转换运算符用于隐式类型转换:
SmallInt si = 3;//正确:SmallInt的构造函数不是显式的si + 3;//错误:此处需要隐式的将si转换成int型,但类的运算符是显式的static_cast<int>(si) + 3;//正确:显式地请求类型转换
4、避免二义性的类型转换
定义类型转换时,要确保在类类型和目标类型之间只存在唯一一种转换方式。
两种情况可以产生多重转换路径:
第一种情况是两个类提供相同的类型转换。
例子:
struct B;struct A{A( ) = default;A(const B&);//A类的构造函数接受B类型对象,把一个B转换成A//其他数据成员};struct B{operator A( ) const;//B类中定义的类型转换,也是把一个B转换成A//其他数据成员};A f(const A&);B b;A a = f(b);//二义性错误:含义是f(B::operator A())//还是f(A::A(const B&))?
这里我们调用f(b)时,要将B类型实参b转换成A类型,但是同时有两种方法可以实现:是调用A类的构造函数,还是调用B类的类型转换函数?
在编译器看来,这两种方法效果相同,不分伯仲,因此会产生二义性错误。
第二种情况是类定义了多个转换规则,而这些类型本身又可以通过其他类型转换联系在一起。
例子:
struct A{A(int = 0);//最好不要创建两个转换源都是算术类型的类型转换A(double);operator int() const;//最好不要创建两个转换对象都是算术类型的类型转换operator double() const;//其他成员};void f2(long double);A a;f2(a);//二义性错误:含义是f(A::operator int())//还是f(A::operator double())?long lg;A a2(lg);//二义性错误:含义是A::A(int)还是A::A(double)?
- 重载与类型转换
- (十四)重载与类型转换
- 重载运算与类型转换
- 重载、类型转换与运算符
- 第十四章 重载运算与类型转换
- c++ 重载运算与类型转换
- C++重载运算与类型转换
- 第十四章 重载运算与类型转换
- S14操作重载与类型转换
- 转换构造函数与类型转换运算符重载
- C++重载之转换构造函数与类型转换函数
- 重载操作符operator()与用户自定义类型转换
- Operator运算符重载与Implicit隐式类型转换
- C++11(13):重载运算与类型转换
- 第14章-重载运算符与类型转换
- c++primer要点-重载运算与类型转换
- 第14章重载运算与类型转换
- Operator运算符重载与Implicit隐式类型转换
- excel批量隐藏多行/列为空值的单元格
- hibernate中集合映射关联映射小记
- android之ConnectivityManager简介,网络连接状态
- 恋你。我爱≠你爱
- mysql事务和锁InnoDB
- 重载与类型转换
- Java压缩图片util,可等比例宽高不失真压缩,也可直接指定压缩后的宽高
- linux 实时操作系统简介
- 某公司“技术文档编写与评审案例教学”圆满结束!
- Error retrieving parent for item: No resource found that matches the given name.....
- 猫猫学iOS 之微博项目实战(1)微博主框架-子控制器的添加
- Lua学习笔记二 数据类型及字符串操作
- 未能找到类型或命名空间名称"xxxxxx"的真正原因
- Longest Palindromic Substring