第十四章(重载运算和类型转换)
1).自定义运算符号以及类型之间的转换规则。
{
print(cout,add(item1,item2));
cout << item1 + item2;
}
/1.基本概念
1).几点说明。
- 运算符函数名字由
operator 关键字,和它要定义的运算符号一起组成。由返回类型,函数体,参数列表。 - 参数数量和运算对象的关系。
是否成员函数 | 参数数量和运算对象关系 |
---|
是 | 比运算对象少一,this 作为隐式的参数绑定第一个参数。 | 否 | 相等 |
- 传入参数时,需要注意,左侧运算对象依次向右,为第一个,第二个……参数。
- 对于既可以是二元也可以是一元的运算符号,通过传入的参数个数来确定是几元的版本。
- 除了,重载调用运算符的运算符函数外,其他的运算符函数都不允许有默认实参。
- 重载运算符号的作用对象,至少有一个是类类型(可以是成员的函数)。即,我们不能修改内置的运算符号的含义。
{
int operator+(int,int);
}
- 只能定义已经存在的运算符号,不可以自己创造符号。
- 重载的运算符的优先级和结合律是不变的。
2).如何使用重载运算符号。
{
data1 + data2;
operator(data1,data2);
data1 += data2;
data1.operator+=(data2);
}
3).并不是所有的运算符号都可以重载。
::``.* . ,? :
- 原本有明确的求值顺序,例如
&& ,|| ,, - 或者在类中有明确的含义,
& 就是取地址. - 对于
&& ,|| 还有短路原则。
4).该怎么重载运算符号
- 当类的操作和运算符的逻辑是相关的,才考虑重载运算符。
- 尽可能地保持和内置的版本是一样的。
- 例如逻辑运算符和关系运算符应该返回的是
bool ;算术运算返回一个类类型的值; - 赋值运算和复合赋值运算符应该返回左侧运算对象的一个引用。
- 执行顺序应该一致。例如,
operator+= ,应该先执行+ 再执行= ;
- 如果一个类定义了,
operator== 通常地它也应该有operator!= 的定义。 - 如果一个类定义了
operator< ,通常他也应该定义其他的关系操作。 - 如果一个类定义了,算术运算,或者运算,最好也将它的复合运算一起定义。
- **无二义性原则,**重载的运算符号,不应该是令人迷惑的。
5).重载成成员函数还是非成员函数?
- 如果是成员,那么它的实参就会比原来运算符的运算对象数量减一。第一个实参就是
this 。 - 如果不是成员,那么实参要和运算对象数量一致,且顺序也一致。
- 和调用对象相关的,改变对象状态的,一般是成员。例如,
=,[],(),-> ,与this 息息相关,必须是成员。复合赋值运算一般是成员。递增,递减,解引用,和this 关系密切,应该是成员。 - 有对称,可以进行类型转换运算的,算术,相等性,关系,位运算一般是非成员函数。
- 下面这个说明,混合表达式时必须是非成员的。
{
string s = "hello,world";
string s1 = s + "nihao";
string s2 = "nihao" + s;
}
练习,
{
class Sales_data {
friend istream& operator>>(istream &,Sales_data &);
friend ostream& operator<<(ostream &,Sales_data &);
public:
Sales_data& operator+=(const Sales_data&);
};
Sales_data operator+(const Sales_data &,Sales_data &);
}
{
"couble" == "stone";
vec[1] == vec[2];
vec == vec;
vec[1] == "stone";
}
- 14.4,
->,() 必须为成员。否则会报错。 - 14.5
{
class Date {
friend ostream& operator<<(ostream &,const Date &);
private:
int year, month, day;
};
ostream& operator<<(ostream &os,const Data &D) {
const char sep = '\t';
os << D.year << sep << D.month
<< sep << D.day << endl;
return os;
}
}
/2.输入和输出运算符
//1.重载输出运算符<<
1).为什么第一个形参是非常量的ostream 引用。
- 非常量,因为向流写入内容会流的状态。
- 引用,
ostream 无法拷贝。
2).第二个形参一般是const 引用。
const ,因为打印,不会改变值- 引用,避免不必要的拷贝。
3).返回什么?
4).应用例子。如上练习。
- 注意,输出不应该控制太多细节,例如换行,这也是和内置类型保持一致;冲在输出时输出内容即可,而关于格式由用户自己决定。
5).一般是友元。
- 如果是成员。
{
Sales_data data;
data << cout;
}
- 是友元,需要访问私有的数据成员。
//2.重载输入运算符>>
1).第一个形参是运算符号要读取流的引用。第二形参是要读入到的非常量的对象的引用。返回的是给定流的引用。 2).应用例子。
{
istream& operator>>(istream &is,Sales_data &d) {
double price;
cin >> d.bookNo >> d.units_sold >> price;
if (is)
d.revenue = d.units_sold * price;
else
d = Sales_data();
return is;
}
}
3).输入时,可能遇到的错误。
- 流中含有错误的数据类型时,读取操作可能会失败。例如,当读取完
bookNO 时,假设后面要输入的应该是两个数字,当后面的数据不是两个数字时,则读取操作会失败,后续的流的其他使用都将会失败。 - 读取操作到达文件流的末尾或者输入流遇到其他的错误时,读取操作也会失败。
- 程序中没有逐一检查,而是后续一起检查。
- 重载的输入操作,应该确保即使输入错误,也保证对象是处于一种合理的状态,如上例,将对象进行重置。
- 更进一步,输入运算符也应该设置,流的条件状态来标示失败的信息。最好的方式就是使用现成的
failbit,badbit,eofbit
/3.算术和关系运算
1).几点说明。
- 一般是定义为非成员函数。
- 一般不需要改变运算对象的类型,所以我们把形参设置为常量的引用。
- 返回的是比较结果的副本即可。
- 如果定义了算术运算,一般也会定义一个对应的复合赋值运算符号。此时,最有效的是用复合赋值运算符来定义算术运算符号(再内部使用)。
{
Sales_data
operator+(const Sales_data &l;const Sales_data &r) {
Sales_data sum = l;
sum += r;
return sum;
}
}
练习,
{
class Sales_data {
friend Sales_data operator-(const Sales_data &,const Sales_data &);
public:
Sales_data& operator-=(const Sales_data &);
};
Sales_data operator-(const Sales_data &l,const Sales_data &r) {
Sales_data sub = l;
sub -= r;
return sub;
}
Sales_data& Sales_data::operator-=(cosnt Sales_data &r) {
units_sold -= r.units_sold;
revenue -= r.revenue;
return *this;
}
}
- 14.14,使用复合来实现,减少重复的代码。方便。
//1.相等运算符
1).每一个成员都相等才相等。
{
bool operator==(const Sales_data &l,const Sales_data &r) {
return l.isbn() == r.isbn() &&
l.units == r.units &&
l.revenue == r.revenue;
}
bool operator!=(const...) {
return !(l == r);
}
}
2).重载运算符号的好处。
- 类就像是内置类型一样,可以使用
== 来判断是否相等,没有记忆压力,符合习惯。 - 绑定定义。有了
== ,应该有!= 。 - 我们可以进行“委托”,减少代码量。
!= 的实现实际就是依靠== 实现的。
//2.关系运算符
1).由于关联容器和一些算法经常使用到< 。所以定义operator= 会比较有用。 2).关系运算的要求。
- 定义一个明确的顺序。保证有明确的大小关系,传递性质。
- 如果不是相等,那么一定有一个对象小于另一个对象。特别是当定义了
== 时,需要定义一种关系是与== 一致的。
- 例如,对于
Sales_data ,虽然我们可以只定义isbn 的比较满足了1。可是这与我们的== 含义是不一样的。 - 或者
== ,不满足,但是isbn 是相等的。总而言之就是定义不统一。
3).什么时候定义< 。
- 要满足以上的两个条件。
- 否则还有可能一些一些小的复杂情况。
/4.赋值运算符
1).除了同类对象之间的赋值(移动赋值,和拷贝赋值),我们的类还应该定义其他的赋值运算以使用别的类型的作为右侧对象。
- 例如,
vector 就定义了第三种的赋值运算符。接受{} 里面的元素作为参数。 - 对自己的
StrVec 类进行扩展。
- 和
vector 一样,我们应该返回该对象的引用。 - 并且它应该作为类的成员。
- 赋值运算,不管形参是什么,他都应该是类的成员。
{
v = {"a","b"};
class StrVec {
public:
StrVec& operator=(initializer_list<string>);
};
StrVec& StrVec::operator=(initialized_list<string> il) {
auto data = alloc_n_copy(il.begin(),il.end());
free();
elements = data.first;
first_free = data.second;
return *this;
}
}
2).复合赋值运算符
{
Sales_data& Sales_data::operator+=(const Sales_data &r) {
units_sold += r.units_sold;
...
return *this;
}
}
练习,
- 14.21,复合运算就对每一个成员都是用这个复合运算;在加法或者减法中,需要拷贝,再进行复合的运算,这样更加复合规范。
- 14.22,可以进行隐式类型转换的。
{
class Sales_data {
public:
Sales_data& operator=(const string &);
};
Sales_data& Sales_data::operator=(const string &isbn) {
bookNo = isbn;
return *this;
}
}
- 14.24,浅拷贝就可以满足需求,因此不需要额外定义拷贝赋值,和移动赋值。即可以使用默认的版本。
- 14.25,按照需求进行构造,例如是否需要只接受一个
string 的赋值运算符函数。
/5.下标运算符
1).几点注意,
- 返回值,访问元素的引用(与内置版本一致),既可以作为左值,也可以作为右值;
- 设置两个版本,一个是
const 一个是非const 。例如,当对一个常量进行下标运算时,返回常量引用保证不会修改内容。另一个返回的是普通的引用。
{
string& StrVec::operator[](size_t n) {
return elements[n];
}
const string& StrVec::operator[](size_t n) const {
return elements[n];
}
const StrVec cvec = vec;
svec[0] = "zero";
cvec[0] = "zip";
}
/6.递增递减运算符
1).几点说明。
- 同时设置前置和后置,(绑定定义)
- 是对对象的迭代器进行改变,所以一般是成员函数
- 前置的,返回的是对象的引用。
2).定义前置版本。
{
class StrBlobPtr {
public:
StrBobPtr& operator++();
StrBobPtr& operator--();
};
}
- 先检查对象(指针)的有效性,再检查递增或者递减是否合法
- 如果不是抛出异常,反之返回对象的引用。
{
StrBobPtr& StrBobPtr::operator++() {
check(curr,"increment past end of StrBobPtr;");
++curr;
return *this;
}
--curr;
check(curr,"decrement past the begin of StrBobPtr;");
return *this;
}
3).重载前置和后置。
- 由于函数的名字,参数个数,都一样。怎么重载?
- 方法,后置版本添加一个不被使用的
int 类型的形参。这个形参不命名,编译器会为这个形参提供一个值为0的实参。 - 一般来说,这个额外的形参就是器区别的作用。
- 返回值类型是不重载的。
4).定义后置版本的。
{
class...
StrBlobPtr operator++(int);
StrBlobPtr operator--(int);
Str... StrBlo...::operator++(int) {
StrBlobPtr ret = *this;
++*this;
return ret;
}
Str... ret = *this;
--*this;
return ret;
}
5).使用
{
p.operator++(0);
p.operator++();
}
/7.成员访问运算符
1).定义以及说明。
- 定义为
const 成员,不会改变对象的状态。 - 返回值是否是常量,根据指针所指向的内容决定。
- 构造时候的形参,是否是
const 。
{
class...
string& operator*() const {
auto p = check(curr,"dereference past end");
return (*p)[curr];
}
string* operator->() const {
return &(*this->operator*());
}
}
2).应用。
{
*p = "hi";
p->size();
(*p).size();
}
3).-> 和* 的不一样,
- 理论上,对于
* 的重载我们可以是返回一个固定值,或者打印。 - 而对于
-> ,我们永远不要丢失访问成员的基本含义。改变的是从哪一个对象获取成员,但是获取成员的含义是不变的。
{
point->mem;
(*point).mem;
point.operator->mem;
}
- 指针,
(*point).mem ;先解引用,再从对象中获取成员。 - 对于对象。使用
point.operator() 的结果来获取mem ;如果是一个指针,按照1.方式进行。如果结果本身含有重载的operator-> ,重复调用当前的步骤。直到过程结束返回所需内容,或者程序报错。
- 重载了箭头的运算函数必须返回类的指针或者自定义了箭头运算符的某个类的对象。
练习,
- 14.31,对于默认可以完成的拷贝控制,我们不需要特别定义。但是注意三五法则。
- 14.32,
{
class...
string* operator->() {
return ptr->operator->();
}
StrBlobPtr *ptr;
}
/8.函数调用运算符
1).优势。
- 既可以像函数一样使用该类的对象,
- 也可以保存状态。
2).简单的例子。
{
struct absInt {
int operator()(int val) const {
return val < 0? -val : val;
}
};
}
{
int i = -42;
absInt = absObj;
int ui = absObj(i);
}
3).几点说明。
- 函数调用运算符必须是成员函数,可以定义多个调用运算符,只要符合重载的定义即可。
- **函数对象。**定义了
() 的类的对象。对象的行为像函数一样。
4).应用。
{
class PrintString...
PrintString(ostream &o = cout,char c = ' ') : os(cout),sep(c) {}
void operator()(const string &s) const {os << s << sep;}
ostream &os;
char sep;
PrintString printer;
printer(s);
PrintString errors(cerr,'\n');
errors(s);
}
{
for_each(vs.begin(),vs.end(),PrintString(cerr,'\n'));
}
//1.lambda是函数对象
1).lambda 也曾经在for_each 中使用,编译器将该表达式翻译成一个未命名类的未命名对象。并且在lambda 表达式产生的类中含有一个重载的函数调用运算符。
- 函数体,形参列表完全一致。
- 由于一般情况下,
lambda 是值捕获,不会改变变量的值。所以在它等价的类中的operator= 加了const 。如果是引用捕获,那么就不能是const 。
{
[](const Sales_data &a,const Sales_data &b) {return a.size() > b.size();}
class ...
bool operator()(const Sales_data &a,const Sales_data &b) const {return a.size() > b.size();}
}
- 假定上面的类的名字是
ShorterString 。泛型算法的第三个实参是可调用对象。
{
stable_sorted(word.begin(),word.end(),ShorterString());
}
2).lambda 的捕获类型和数据成员的关系。
- 引用捕获,则无需存储为数据成员
- 值捕获,是拷贝到
lambda 类中去,因此需要建立数据成员,同时创建构造函数,用捕获到的值进行初始化。
{
[sz](const string &s) {return s.size() >= sz;}
class c {
c(size_t n) : sz(n) {}
bool operator()(const string &s) {return s.size() >= sz;}
size_t sz;
};
}
- 等价的类没有默认构造函数,因此想要构造对象必须有一个实参。
- 调用时,
find_if(w.begin(),w.end(),c(sz)); 注意c(sz) 是一个函数对象。 lambda 是否需要移动,拷贝构造视情况而定。而赋值一般不需要。而析构一般是默认的。
练习
- 14.41,
lambda 是函数对象的简化;如果需要多次地使用,并且保存装填,使用函数对象。
//2.标准库定义的函数对象
1).性质
- 称为表示运算符的函数对象。
- 这些类都定义了调用运算符(和名称相互匹配的)。
- 都是模板,我们可以指定具体的类型,也就是指定调用运算符的形参类型,从而实例化一个对象。这些对象就是函数对象。
{
plus<int> intAdd;
negate<int> intNegate;
int sum = intAdd(12,12);
sum intNegate(inAdd(10,12));
sum = intAdd(10,intNegate(10));
}
2).以下是标准库函数对象。均定义在头文件functional 中
算术 | 关系 | 逻辑 |
---|
plus<T> | equal_to<T> | logical_and<T> | minus<T> | not_equal_to<T> | logical_or<T> | multiplies<T> | greater<T> | logical_not<T> | divides<T> | grater_equal<T> | | modulus<T> | less<> | | negate<T> | less_equal<T> | |
3).在泛型算法中使用。
- 例如在排序算法中,默认就是使用
operator< 进行排序。如果要进行升序排序。
{
sort(svec.begin(),svec.end(),greater<string>());
}
- 注意对于我们的类,只要类中定义了
<> ,就可以使用相似的。例如在Sales_data 定义了<> ,就可以使用模板类生成对应的对象。 - 直接比较两个无关的指针是未定义的。问题,如何排序
vector 中的指针呢?
{
vector<string *> pvec;
sort(pvec.begin(),pvec.end(),[](const string *a,const string *b) {reurn a < b;})
sort(pvec.begin(),pvec.end(),less<string *>());
}
- **由于关联容器是使用
less<key_type> 对元素进行排序的。**因此我们可以定义一个指针的set 或者map ,而不需要声明是less 。
练习,
{
count_if(vec.begin(),vec.end(),greater<int>(1024));
find_if(vec.begin(),vec.end(),not_equal_to<string>("pooh"));
transform(vec.begin(),vec.end(),vec.end(),multiplies<int>(2));
}
//3.可调用对象和function
1).几种可调用对象。
- 函数,函数指针
lambda ,定义了调用运算符号的类的对象bind 创建的对象
2).可调用对象的类型。
lambda ,未命名的类类型- 函数以及函数指针,由返回值类型以及它的参数类型决定。
3).不同类型的可调用对象可能共享一种调用形式。
- 调用形式指明了调用返回的类型以及传递给调用的实参类型。一种调用形式对应一个函数类型。
int (int,int); 表明了一个接受两个int 返回一个int 的函数类型。
{
int add(int i,int j) {return i+ j;}
auto mod = [](int i,int j) {return i % j;};
struct divide {
int operator()(int i,int j) {return i / j;}
};
int (int,int)
}
3).函数表,用于存储可调用对象的指针。
- 构建一个简单的桌面计算器。
- 利用
map 来实现。利用表示运算符符号的string 作为关键字。函数指针作为值。 map<string,int(*)(int,int)> binops; - 添加,
binops.insert({"+",add});``{"+",add} 是一个pair 。 - 问题,
binops.insert({"%",mod}); //错误,这不是函数指针。mod 是一个类类型,而divide 也是一个类类型。
4).解决。标准库function 类型
- 定义在头文件
functional 中。 - 它是一个模板,创建时需要给出额外的信息。
function<int(int,int)>; 这里声明的是一个function 类型,它可以表示,接受两个int ,返回一个int 的调用形式的,可调用对象。
{
function<int(int,int)> f1 = add;
function<int(int,int)> f2 = divide();
function<int(int,int)> f3 = [](int i,int j) {return i * j;}
cout << f1(3,2) << f2(1,2) << f3(2,3);
}
5).重新定义。
map<string,function<int(int,int)>> binops; 传入的参数是可调用对象即可。而不是之前的只能是函数指针。
{
map<string,function<int(int,int)>> binops = {
{"+",add},
{"-"minus<int>()},
{"/",divide()},
{"*",[](int i,int j){return i * j;}},
{"&",mod}
};
}
6).使用。
- 注意到,我们传入的可调用对象不需要命名。通过关键字就可以返回可调用对象,从而进行调用即可。
- 下标运算返回的是引用。
function 也重载了调用运算符号,它接受实参,然后传递给存好的可调用对象。
{
binops["+"](10,5);
binops["-"](10.5);
...
}
7).重载函数和function
{
int add(int i,int j) {return i + j;}
double add(double i,double j) {return i + j;}
map<string,function<int(int,int)>> binops;
binops.insert({"+",add});
}
{
int (*p)(int,int) = add;
binops.insert({"+",p});
}
{
binops.insert({"+",[](int a,int b){return add(a,b);}});
}
- 旧版本中的
unary_function,binary-function 和这里的function 没有关联。他们已经被更加通用的bind 代替。
8).function 的操作
操作名称 | 相关描述 |
---|
function<t> f | f是可以存储t类型的可调用对象的空function 。 | function<t> f(nullptr) | 显式地指出为空 | function<t> f(obj) | 在f中存储可调用对象obj | f | 作为一个条件,如果f含有可调用对象为真,反之为假 | f(args) | 调用f中的可调用对象,args 为传递的参数 | 定义为function<t>的成员类型 | | result_type | 该function 类型的可调用对象的返回值类型 | argument_type | 当只有一个或者两个实参时定义的类型。一个实参时才有 | first_argument | 两个实参时才有 | second_argument | 两个实参时才有 |
/9.重载,类型转换和运算符
1).类类型转换由以下共同定义。(也被称为用户自定义的类型转换)
- 转换构造函数
- 类型转换运算符
//1.类类型转换运算符
1).简介。
- 成员函数,将一个类转换为另一个类。不能声明返回类型;形参列表为空;不应该改变转换对象的内容,是一个
const 成员。
{
class SmallInt {
public:
operator int(int = 0)const;
};
}
- 形式,
operator type() const {} - 可以转换为任意类型(函数指针,数组指针,引用等),除了
void ,还有数组,函数等不能作为函数返回类型的类型。
2).为什么类型转换运算符没有形参,以及返回类型的和转换类型的对应关系。
- 类型转换运算符是隐式执行的,所以无法给他们传递实参,也就不可以定义形参。
- 虽然不指定返回类型,但是实际上每一个类型转换函数都会返回一个对应类型的值
operator int*() const {return 42;}//错误,返回的类型和转换的类型不对应
3).例子。
{
class SmallInt {
public:
SmallInt(int i = 0) : val(i) {
if (i < 0 || i > 255) {
throw out_of_range("Bad SmallInt value");
}
}
operator int() const {return val;}
private:
size_t val;
};
}
- 以上的例子,既定义了从算术类型转换到类类型的转换,也定义了从类类型到
int 的转换。
{
SmallInt si;
si = 4;
si + 3;
}
- 内置的类型转换和用户自定义的隐式类型转换可以一起使用。谁前谁后没有关系。
{
SmallInt si = 3.14;
si + 3.14;
}
4).定义的转换,不应该由二义性。例如Date ,转换为int 是迷惑的。
- 表示时间的数字,19890712
- 表示从某一个时间点开始的天数。
5).实际中,很少会定义类的类型转换。
- 类型转换是自动发生的,更多的是意外,而不是方便
- 但是转换为
bool 还是比较普遍的。
6).隐式转换的坏处。
- 由于
bool 是一种算术类型,它就可以应用在任何需要算术类型的上下文中。 - 因此转换成
bool 也会由意想不到的后果。这一点在istream 中表现明显。
{
int i = 32;
cin << i;
}
7).解决,显式的类型转换运算符
- 形式,加上关键字
explicit 。 - 有了声明为显式,编译器就不会自动执行这一个类型转换。
{
SmallInt si = 3;
si + 3;
static_cast<int>(si) + 3;
}
- 例外,如果表达式被用于条件,那么编译器会将显式类型转换自动应用,此时相当于是隐式。
if,while,do 的条件for 的条件!,||,&& 的运算对象?: 的条件表达式
练习,
operator const int() {} 转换为const int
//2.避免有二义性的类型转换
1).确保类类型和目标类型之间只有唯一的一种转换方式。以下容易发生二义性。
- 两个类提供类相同的类型转换。例如,A定义了接受B对象的转换构造函数;B定义了向A转换的类型转换运算符。
{
A(const B&){}
operator A(){}
A f(const A&);
B b;
A a = f(b);
A a1 = f(b.operator A());
A a2 = f(A(b));
}
- 类定义了多个转化规则。尤其是定义了多个接受参数是算术类型的构造函数,或者转换目标都是算术类型的转换函数。因为算术运算自身就有很多的转换规则。所以类最好只定义一个和算术类型有关的转换规则。
{
struct A {
A(int = 0);
A(double);
operator int() const;
operator double() const;
};
void f2(long double);
A a;
f2(a);
long lg;
A a2(lg);
}
2).根本原因其实就是转换的等级是一致的。 3).注意,一旦定义了算术转换运算符
- 不再定义向其他算术类型转换的运算符
- 不再定义接受算术类型的重载运算符函数。
- 其实,除了显式转为
bool ,尽可能避免使用。因为意想不到。
4).重载函数的参数,含有转换构造函数的问题
{
struct C {
C(int);
};
Struct D {
D(int);
};
void f(const C&);
void f(const D&);
f(12);
f(C(12));
}
5).不会考虑标准类型转换的情况。
- 当有两个或者多个用户自定义的类型转换都提供了可行的匹配时,标准类型转换会被忽略(可行函数请求的转换函数不唯一。)。因为重载时,是不同类型直接和形参进行匹配。或者,所有可行函数都请求同一用户自定义的类型转换函数进行的匹配。
{
E...
E(double);
void f(const E&);
f(10);
}
练习
//3.函数匹配和重载运算符
1).重载运算符也是重载的函数。通过给定的表达式,判定到底是使用内置的还是重载的运算符。
- 当运算符函数出现在表达式中时(
c + c1 ),候选函数会比调用运算符 调用函数时更大。例如,a sym b 可能是
a.operatorsym(b); //a时类,且有该成员函数operator(a,b); //该函数是一个普通函数- 内置的版本。
- 当我们直接调用时,由于调用的形式不一样,含义是较明确的;
- 使用对象或者对象的指针,引用,那么考虑成员版本
- 使用的是普通的调用。考虑的是非成员的版本。
2).重载运算符,算术类型转换运算符和二义性
- 既提供了,转换目标是算术类型的转换,又提供了重载的运算符。这样会使得,重载的运算符号和内置的运算符号二义性。
{
friend operator+(const SmallInt&,const Small...);
SmallInt(int = 0);
operator int() const {}
SmallInt s1,s2;
SmallInt s3 = s1 + s2;
int i = s3 + 0;
}
- 结论,不要随意地定义向除了显式的
bool 转换外的算术类型转换。
练习,
- 14.52,注意,是否意味着,
+ 的左侧运算对象可以转换成另一个对象,然后使用转换后对象的成员运算符号? - 14.53,使用显式地转换一个运算对象,可以避免二义性。例如,
SmallInt(3.13);//显式地构造一个对象。 或者,static_cast<int> si;//强转为int类型
/10.小节
- 在类中可以定义,转换为源为自身(拷贝构造/赋值运算符?),或者转换目的为自身的类型转换,这样的类型转换将会自动执行。
|