第14章 重载运算与类型转换
介绍
内置类型运算都有基本的运算符来支持,而我们想要实现类类型的一些运算,就需要自己重载运算符。
基本概念:重载的运算符是具有特殊名字的函数,他们的名字由关键字operator和后面要定义的运算符号共同组成。
和其他函数一样,也有返回类型,参数列表和函数体。
注意:
当一个重载的运算符是成员函数时,this绑定到左侧的对象,成员运算符函数的参数比运算对象的参数少一个.
比如+重载成成员函数,那么它的参数只能有一个,默认类对象this本身是一个参数。
不能重载的运算符 : :: .* . ?:
不应该被重载的运算符: 逻辑运算符,逗号运算符,取地址运算符
逻辑运算符不支持短路求值和求值顺序的属性,逗号和取地址本身有内置含义
我们重载运算符时最好和内置的形成映射最好,也就是类似程度。比如IO运算符,==运算符(若定义也该定义!=)等等
!选择作为成员函数还是非成员函数
成员函数:
=,[ ],( ), ->,
符合赋值+=,-=.. ,
++,--,*(解引用) 改变对象的状态或与给定类型密切相关的运算符
非成员函数:
算数+,-,*,/...
相等性== ,!=
关系 >,<,>=,<=..
位运算符 ^, |, &
方便记忆:可以看一个运算符左右两侧的对象是否可以互换位置,不能互换位置则一般为成员函数,可以互换位置则一般为非成员函数
比如=赋值运算符,右边的值给左边的值赋值,若互换位置则完全改变了意思,所以为成员函数
+加号运算符,左右位置无所谓3+5和 5+3都相同,所以为非成员函数。
1.输入输出运算符
<1.重载输出运算符<<
输出运算符应定义为非成员函数,因为要读写类的非共有数据,所以要定义为友元。
且返回值为std::ostream&, 参数为std::ostream&和类对象的引用。
注意:通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符
我们应减少对输出格式的控制。
<2.重载输入运算符>>
输入运算符和输出运算符格式上类似,也是非成员函数,返回输入流引用(流不能拷贝),参数是输入流引用和类对象
和输出运算符不同的是,输入运算符必须处理输入可能失败的情况,而输出运算符不需要。
当流含有错误类型的数据时读取操作可能失败,读取数据到文件末尾或遇到其他流错误也会失败。
不需要逐个检查,只在末尾检查即可,当读取操作发生错误时,输入运算符应该负责从错误中恢复。
一些输入运算符可能需要更多的数据验证工作。
2.算数和关系运算符
我们把算数和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换,一般不需要改变参数状态,所以都是常量引用。
如果定义了算数运算符,则他一般也会定义一个对应的复合赋值运算符,最有效的方式是使用复合赋值来定义算数运算符。
如果类同时定义了算数运算符和相关的赋值运算符,则通常情况下应该使用复合赋值来实现算术运算符
3.相等运算符
依次比较每个成员是否相等。
注意:
如果类定义了operator==,那么类也应该定义operator!=。
相等运算符和不相等运算符中的一个应该把工作委托给另外一个。
如果某个类逻辑上有相等性的含义,则该类应该定义operator==,这样做可以使得用户更加容易的使用标准库算法(部分标准库算法必须要有==支持)。
4.关系运算符
定义了相等运算符的类通常也应该定义关系运算符,因为关联容器和一些算法要用到小于运算符。所以operator<会比较有用。
注意:
在定义关系运算符我们应当考虑一些问题,类的成员一般不是仅有一个,我们定义相等运算符时,比较的是全部的成员,但是在关系运算符可能不需要比较这么多。
primer上说:
如果存在唯一一种逻辑可靠的<定义,则应该为这个类定义<运算符,如果类同时还包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符。
意味这不要轻易定义<运算符,如果<和==比较的逻辑相同(也就是比较的成员相同)才定义<运算符。
注意:一些情况我们必须定义<运算符,比如类对象需要存在map或set中的时候等等
5.赋值运算符
类还可以定义其他赋值运算符以使用别的类型作为右侧运算对象。
和拷贝赋值运算符及移动赋值运算符一样,其他重载的赋值运算符也必须先释放当前内存空间,不同之处是无需检查自赋值。
我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。
复合赋值运算符
复合赋值运算符不非得是类的成员,不过我们还是倾向于把包括复合赋值在内的所有赋值运算符都定义在类的内部。
为了与内置类型的复合赋值保持一直,类中的复合赋值运算符也要返回其左侧运算对象的引用。
赋值运算符必须定义成类的成员,复合赋值运算符通常也应该这样做,这两类运算符都应该返回对象的引用。
6.下标运算符
表示容器的类通常可以通过元素在容器中的位置访问元素,这些类一般会定义下标运算符operator[ ]。
下标运算符必须是成员函数。
下标运算符通常以所访问元素的引用作为返回值。可以作为左值或右值
我们最好同时定义下标运算符的常量版本和非常量版本,当作用于一个常量对象时,下标运算符返回常量引用以确保我们不会修改返回值。
如果一个类包含下标运算符,则它通常会有两个版本:一个返回普通引用,另一个是类的常量成员并返回常量引用。
7.递增递减运算符
定义递增和递减运算符的类应该同时定义前置版本和后置版本。这些运算符应该被定义为类的成员。
为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。
区分前置运算符和后置运算符:后置版本提供一个额外的不被使用的int类型的参数,使用后置运算符时,编译器为这个形参提供一个值为0的实参。
这个形参的唯一作用就是区分前置版本和后置版本。
为了与内置版本保持一致,后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非一个引用。对于后置版本来说,在
递增或递减之前首先需要记录对象的状态。
注意:
后置版本里可以调用前置版本。
前置版本在递增之前要判断是否到达末尾,前置版本递减要在递减之后判断是否出界。
如果我们要通过函数调用的方式调用后置版本,则必须为他整型参数传递一个值,尽管我们不使用这个值。
8.成员访问运算符
解引用运算符检查是否在范围内,然后返回所指元素的一个引用,箭头运算符不执行任何自己的操作,而是调用解引用运算符并返回解引用结果的地址。
箭头运算符必须是类的成员,解引用运算符通常也是类的成员,尽管并非必须如此。
重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
1.关于set
C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生。
关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
关于set有下面几个问题:
(1)为何map和set的插入删除效率比用其他序列容器高?
大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:
A
/ \
B C
/ \ / \
D E F G
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
(2)为何每次insert之后,以前保存的iterator不会失效?
iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。
(3)当数据元素增多时,set的插入和搜索速度变化如何?
如果你知道log2的关系你应该就彻底了解这个答案。在set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。
2.set中常用的方法
begin() ,返回set容器的第一个元素
end() ,返回set容器的最后一个元素
clear() ,删除set容器中的所有的元素
empty() ,判断set容器是否为空
max_size() ,返回set容器可能包含的元素最大个数
size() ,返回当前set容器中的元素个数
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int>a;
a.insert(1);
a.insert(2);
a.insert(6);
a.insert(100);
a.insert(54);
set<int>::iterator i=a.begin();
set<int>::iterator x;
set<int>::iterator y;
x=--a.end();
cout<<"最后一个元素是"<<*x<<endl;
for(;i!=a.end();i++)
{
cout<<*i<<endl;
}
cout<<endl;
int m=a.count(100);
cout<<m<<endl;
a.erase(1);
cout<<"now there are "<<a.size()<<" in this set"<<endl;
x=a.find(2);
cout<<*x<<endl;
if(x!=a.end())
cout<<"2 found"<<endl;
y=a.find(3);
cout<<*y<<endl;
cout<<*a.end()<<endl;
if(y!=a.end())
cout<<"3 not found"<<endl;
return 0;
}
一些函数看输出结果就可知用处,
需要说一下的就是,a.find()这个函数,返回给定值,若没有找到就返回a.end()
erase()函数:
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int>a;
for(int i=1;i<=10;i++)
a.insert(i);
set<int>::iterator first;
set<int>::iterator second;
set<int>::iterator x;
a.erase(8);
//a.erase(a.begin());
first=a.begin();
second=a.begin();
second++;
second++;
a.erase(first,second);
for(x=a.begin();x!=a.end();x++)
cout<<*x<<endl;
return 0;
}
lower_bound(key_value) ,返回第一个大于等于key_value的定位器
upper_bound(key_value),返回最后一个大于等于key_value的定位器
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s;
s.insert(1);
s.insert(3);
s.insert(4);
cout<<*s.lower_bound(2)<<endl;
cout<<*s.lower_bound(3)<<endl;
cout<<*s.upper_bound(3)<<endl;
return 0;
}
自定义比较函数
(1)元素不是结构体:
struct ty
{
int data;
};
struct mycomp
{
bool operator()(const ty&a,const ty&b)
{
return a.data>b.data;
}
};
set<int,mycomp>s;
set<int,mycomp>::iterator it;
(2)如果元素是结构体,可以直接将比较函数写在结构体内。
struct Info
{
string name;
float score;
//重载“<”操作符,自定义排序规则
bool operator < (const Info &a) const
{
//按score从大到小排列
return a.score<score;
}
}
set<Info> s;
......
set<Info>::iterator it;
//string1.h
#ifndef _STRING_H_
#define _STRING_H_
#include <memory>
#include <iostream>
#include <cstring>
using namespace std;
class String
{
friend std::ostream& operator<<(std::ostream &os, const String &s);
friend bool operator<(const String &lhs, const String &rhs);
friend bool operator==(const String &lhs, const String &rhs);
public:
String() :st(nullptr) { }
String(const char *s);
String(const String &s);
String& operator=(const String &s);
char& operator[](std::size_t n);
const char& operator[](std::size_t n)const;
~String();
private:
char* alloc_n_copy(const char *s);
void free();
static std::allocator<char>alloc;
char *st;
};
#endif
/////////////////////////////////
//string1.cpp
#include "string1.h"
//allocator<T> a a可以为类型为T的对象分配内存
//a.allocate(n) 分配一段原始的未构造的内存,可以保存n个T类型的对象
//a.deallocate(p, n) 释放T*指针p中地址开始的内存,保存了n个类型为T的对象;p必须是allocate返回的指针,n必须是p创建时所要求的大小,必须保证这些指针调用过destroy
//a.construct(p, args) p必须是T*指针,指向原始内存,args传递给构造函数
//a.destroy(p) p位T*指针,对p执行析构函数(销毁)
char* String::alloc_n_copy(const char *s)
{
st = alloc.allocate(strlen(s)+1);//a.allocate(n) 分配一段原始的未构造的内存,可以保存n个T类型的对象
char *p = st;
int i = 0;
while (s[i] != '\0')
{
alloc.construct(p++,s[i++]);//a.construct(p, args) p必须是T*指针,指向原始内存,args传递给构造函数
}
alloc.construct(p,s[i]);//把‘\0’赋值给字符p 以免打印出来乱码
return st;
}
void String::free()
{
char *p = st;
size_t num = strlen(p) + 1;
while (*p != '\0')
{
alloc.destroy(p++);
}
//a.destroy(p) p位T*指针,对p执行析构函数(销毁)
alloc.destroy(p);
//a.deallocate(p, n) 释放T*指针p中地址开始的内存,保存了n个类型为T的对象;p必须是allocate返回的指针,n必须是p创建时所要求的大小,必须保证这些指针调用过destroy
alloc.deallocate(st,num);
}
String::String(const char *s)
{
st = alloc_n_copy(s);//alloc_n_copy 分配内存,拷贝元素
}
String::String(const String &s)
{
st = alloc_n_copy(s.st);
}
String& String::operator=(const String &s)
{
if (st!=s.st)
{
if (st)
{
free();
}
st = alloc_n_copy(s.st);
}
return *this;
}
//如果一个类包含下标运算符,则它通常会有两个版本:一个返回普通引用,另一个是类的常量成员并返回常量引用
char& String::operator[](std::size_t n)
{
return st[n];
}
const char& String::operator[](std::size_t n)const
{
return st[n];
}
String::~String()
{
if (st!=NULL)
{
free();
st = NULL;
}
}
//重载<<
ostream& operator<<(std::ostream &os, const String &s)
{
os << s.st;
return os;
}
//重载<操作符
bool operator<(const String &lhs, const String &rhs)
{
if (strcmp(lhs.st,rhs.st) < 0)
{
return true;
}
else{
return false;
}
}
//重载双等号操作符
bool operator==(const String &lhs, const String &rhs)
{
if (strcmp(lhs.st,rhs.st) == 0)
{
return true;
}
else{
return false;
}
}
///////////////////////////////////
#include "string1.h"
#include <set>
std::allocator<char> String::alloc;
void main()
{
String s1("123");
String s2("456");
String s3("789");
String s4("123");
set<String> st;
st.insert(s1);
st.insert(s2);
st.insert(s3);
st.insert(s4);
for(const String &s:st)
{
cout << s << endl;
}
cout << s1[1] << endl;
}
///////////////////////////////////
函数调用运算符必须是成函数。一个类可以员定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。如果类定义了调用运算符,则该类被称为函数对象,因为可以调用这种对象,所以我们说这些对象的行为像函数一样。
#include <iostream>
using namespace std;
struct absInt
{
int operator()(int val)const
{
return val < 0 ? -val : val;
}
};
void main()
{
int val = -42;
absInt t;
cout << t(val) << endl; //t是一个对象而非函数
system("pause");
}
//函数对象类除了operator()之外也可以包含其他成员
//通常函数对象类包含一些数据成员
//这些成员用来定制调用运算符中的操作
#include <iostream>
#include <string>
class PrintString
{
public:
PrintString(std::ostream &o = std::cout, char t = ' ') :
os(o), c(t) { }
void operator()(const std::string &s)const //借用辅助工具来完成函数的操作
{
os << s << c;
}
private: //private成员可以用来保存“辅助”工具
std::ostream &os;
char c;
};
//函数对象比一般函数灵活就是它可以让另完成函数所需要的辅助成员成为自己的类成员。和lambda类似 ^ _^
//函数对象常常作为泛型算法的实参
int main()
{
PrintString ps;
std::string s = "abc";
ps(s);
system("pause");
return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
class cmp
{
public:
cmp(int i = 0) :
t(i) { }
bool operator()(int a)
{
return a == t;
}
private:
int t;
};
int main()
{
std::vector<int>ivec = { 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 };
cmp cp(2);
//最好不要在for_each里面使用类成员函数!
std::for_each(ivec.begin(), ivec.end(), [&](int &i){ if (cp(i)) i = 100; });
for (const int i : ivec)
std::cout << i << std::endl;
system("pause");
return 0;
}
<1.lambda是函数对象
回忆一下泛型算法里面解释的lambda:当定义一个lambda时,编译器生成一个与lambda对应的新的类类型。当向一个函数传递一个lambda时,同时定义了一个新类型和
该类型的一个对象
其实当我们编写一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象,在lambda表达式产生的类中含有一个重载的函数调用运算符。
也就是[](const string &lhs, const string &rhs)
{ return lhs.size() < rhs.size(); }
编译器翻译成也就是等价于
class shrotstring
{
public:
bool operator()(const string &lhs, const string &rhs)const
{ return lhs.size() < rhs.size(); }
};
表示lambda及相应捕获行为的类当一个lambda通过引用捕获变量时,将由程序负责确保lambda执行时引用所引用的对象的确存在,因此,编译器可以直接使用该引用而无需
在lambda产生的类中将其存储为数据成员
当一个lambad通过值将变量拷贝到lambda时,lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用
捕获的变量来初始化数据成员。
lambda产生的类不含默认构造函数,赋值运算符及默认析构函数:它是否含有默认的拷贝/移动构造函数则通常视捕获的数据成员类型而定。
<2.标准库定义的函数对象
头文件 #include <functional>
标准库定义了一组算术、关系与逻辑函数对象类,表14-3列出了这些类。标准库还定义了一组函数适配器,使我们能够特化或者扩展标准库所定义的以及自定义的函数对象类。这些标准库函数对象类型是在functional头文件中定义的。
表14-3 标准库函数对象
类型 函数对象 所应用的操作符
算术函数对象类型 plus<Type> +
minus<Type> -
multiplies<Type> *
divides<Type> /
modulus<Type> %
negate<Type> -
关系函数对象类型 equal_to<Type> ==
not_equal_to<Type> !=
greater<Type> >
greater_equal<Type> >=
less<Type> <
less_equal<Type> <=
逻辑函数对象类型 logical_and<Type> &&
logical_or<Type> |
logical_not<Type> !
1.每个类表示一个给定操作符
每个标准库函数对象类表示一个操作符,即,每个类都定义了应用命名操作的调用操作符。例如,plus是表示加法操作符的模板类型。plus模板中的调用操作符对一对操作数应用+运算。
不同的函数对象定义了执行不同操作的调用操作符。正如plus定义了执行+操作符的调用操作符,modulus类定义了应用二元操作符%的调用操作符,equal_to类应用==,等等。
有两个一元函数对象(unary function-object)类:一元减(negate<Type>)和逻辑非(logical_not<Type>)。其余的标准库函数对象都是表示二元操作符的二元函数对象(binary function-object)类。为二元操作符定义的调用操作符需要两个给定类型的形参,而一元函数对象类型定义了接受一个实参的调用操作符。
2.表示操作数类型的模板类型
每个函数对象类都是一个类模板,我们需要为该模板提供一个类型。正如从诸如vector的顺序容器所了解的,类模板是可以用于不同类型的类。函数对象类的模板类型指定调用操作符的形参类型。
例如,plus<string>将string加法操作符应用于string对象,对于plus<int>,操作数是int值,plus<Sales_item>将+应用于Sales_item对象,依次类推:
plus<int> intAdd; //function object that can add two int values
negate<int> intNegate; //function object that can negate an int value
//uses intAdd::operator(int,int)
int sum=intAdd(10,20);
sum=intAddd(10,intNegate(10));
3.在算法中使用标准库函数对象
函数对象常用于覆盖算法使用的默认操作符。例如,sort默认使用operator<按升序对容器进行排序。为了按降序对容器进行排序,可以传递函数对象greater。该类将产生一个调用操作符,调用基础对象的大于操作符。如果svec是一个vector<string>对象,以下代码
sort(svec.begin(),svec.end(),greater<string>());
将按降序对vector进行排序。像通常那样,传递一对迭代器以指明被排序序列。实参是greater<string>类型的临时对象,是一个将>操作符应用与两个string操作数的函数对象。
//14.8.2标准库定义的函数对象
#include <functional>
#include <iostream>
using namespace std;
void main()
{
//算数
cout << "算数" << endl;
int a = 10, b = 20;
int ret = 0;
//plus类定义一个函数调用运算符用于对运算对象执行+的操作p509
plus<int> intAdd;
ret = intAdd(a,b);
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
minus<int> intmp;//减法
ret = intmp(a,b);
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
multiplies<int> intMul;//乘法
ret = intMul(a,b);
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
divides<int> divi;//除法
ret = divi(a,b);
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
modulus<int> intMode;//类定义一个调用运算符执行二元的%操作 取余
ret = intMode(a,b);
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
negate<int> neg;
ret = neg(a);//取相反数
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
cout << "关系" << endl;
int a2 = 10, b2 = 20;
int ret2 = 0;
equal_to<int> et;//是否相等
ret = et(a,b);
cout << ret << endl;
cout << "\n==============我是分界线==========\n";
not_equal_to<int> net; //不等
ret2 = net(a2, b2);//1
cout << ret2 << std::endl;
cout << "\n==============我是分界线==========\n";
greater<int> gt; //大于
ret2 = gt(a2, b2);//0
cout << ret2 << std::endl;
cout << "\n==============我是分界线==========\n";
greater_equal<int> gte; //大于等于
ret2 = gte(a2, b2);//0
cout << ret2 << std::endl;
cout << "\n==============我是分界线==========\n";
std::less<int> ls; //小于
ret2 = ls(a2, b2);//1
std::cout << ret2 << std::endl;
cout << "\n==============我是分界线==========\n";
std::less_equal<int> lel; //小于等于
ret2 = lel(a2, b2);//1
std::cout << ret2 << std::endl;
//逻辑
int a3 = 10, b3 = 20;
int ret3 = 0;
std::cout << "逻辑" << std::endl;
std::logical_and<int> la; //and
ret3 = la(a3, b3);//1
std::cout << ret3 << std::endl;
cout << "\n==============我是分界线==========\n";
std::logical_or<int> lo; //or
ret3 = lo(a3, b3);
std::cout << ret3 << std::endl;
cout << "\n==============我是分界线==========\n";
std::logical_not<int> ln; //not
ret3 = ln(a3);
std::cout << ret3 << std::endl;
cout << "\n==============我是分界线==========\n";
system("pause");
return;
}
重载,类型转换与运算符
我们能定义类类型之间的转换,转换构造函数和类型转换运算符共同定义了类类型转换。
<1.类型转换运算符
是类的一种特殊的成员函数,负责将一个类类型的值转换成其他类型。
operator type( )const;
type表示某种类型。但是该类型必须能作为返回类型。类型转换运算符既没有显示的返回类型,也没有形参,而且必须定义成类的成员函数,类型转换通常不应该改变待
转换的对象。
一个类型转换函数必须是类的成员函数,它不能声明返回类型,也没有形参,类型转换函数通常为const
注意:应该避免过度的使用类型转换函数。
类型转换函数可能产生意外结果。
类通常很少定义类型转换运算符,但是定义像bool类型转换还是比较常见
c++11 显示类型转换运算符
explicit operator type( ) const { };
static_cast<type>(name);
当类型转换运算符是显式的我们才能只能类型转换。不过必须通过显式的强制类型转换才行。
但是存在一个例外:既当如果表达式被用作 条件,则编译器会将显示的类型转换自动应用于它。
包括while, if, do, for语句头条件表达式,(!, ||, &&)的运算对象, (? :)条件表达式
注意: 流对象转换bool也是因为标准库定义了流向bool显式类型转化
向bool的类型转换通常用在条件部分,因此operator bool 一般定义为explicit 的。
<2.避免有二义性的类型转换。
如果一个类中包含一个或多个类型转换,则必须确保在类类型和目标类型转换之间只存在唯一一种转换方式。否则我们的代码可能会存在二义性。
通常情况下,不要为类定义相同的类型转换,也不要在类中定义两个及两个以上转换源或转换目标是算数类型的转换。
我们无法用强制类型转换来解决二义性,因为强制类型转换也面临着二义性。
最好不要创建两个转换源都是算数类型的转换。
operator int( )const
operator double( )const
当我们使用两个用户定义的类型转换时,如果转换函数之前或之后存在标准类型转换,则标准类型转换将决定最佳匹配到底是哪个。
注意:
不要让两个类执行相同的类型转换,比如A转换B的同时B也转换为A。
避免转换目标是内置算数类型的类型转换。
如果我们对一个类既提供了转换目标是算数类型的类型转换,也提供了重载的运算符,则会遇到重载运算符与内置运算符二义性的问题。