第14章 操作重载与类型转换
1、可以直接调用一个重载的运算符函数
如果我们对于一个运算符比如+号,既有重载的成员函数,又有重载的非成员函数,那么此时直接使用+号,那么就会出现错误。因为编译器不知道要使用哪个运算符。
class A{
public:
A(int v):v(v){}
int v;
A operator + (A b){
return A(v+b.v+1);
}
};
A operator + (A a1, A a2){
return A(a1.v + a2.v);
}
int main()
{
A a(1),b(2);
//cout << (a+b).v;//error:ambiguous overload for 'operator +';
cout << (a.operator+(b)).v << endl;//4
cout << operator+(a,b).v << endl;//3
return 0;
}
2、逗号, 逻辑与 && 逻辑或 || 运算符建议不要重载
因为三种运算符本身有求值顺序和短路求值特性(&& 和 || 有短路特性)的,但是重载后的运算符本质上是一次函数调用,所以求值顺序和短路求值特性都会消失。
另外逗号运算符和取地址运算符,C++语言已经定义了这两种运算符用于类类型的特殊含义。因为内置了特殊的含义,所以这两种运算符不应该被重载。
class A{
public:
A(int v):v(v){}
int v;
};
bool operator &&(A a1,int v){
cout << v << endl;
return false;
}
int main()
{
A a(0);
int v = 1;
a && (v--);//直接调用函数 打印1
a && (--v);//打印 -1
return 0;
}
3、定义成非成员还是成员?
赋值= 下标[] 调用() 和成员访问箭头->运算符必须是成员。
改变类的状态的运算符或者与给定类型密切相关的运算符,如递增,递减,和解引用运算符,通常应该是成员。
具有对称性的运算符可能转换为任意一端的运算对象,例如 算术,相等性,关系,和位运算等,通常都是普通的非成员函数。
4、重载的运算符的不同类型的顺不可改变。
class A{
public:
A(int v):v(v){}
int v;
};
int operator + (A a,int v){
return a.v + v;
}
int main()
{
A a(0);
int v = 1;
cout << a + v << endl;//1
// cout << v + a << endl;//未定义该函数
string s("aaa");
s = s+"111";
s = "111" + s;
cout << s << endl;//111aaa111
return 0;
}
因为定义了A+int,所以A+v可以,而v+A不可以。
string将 + 定义成普通的非成员函数 operator + (string ,string)所以不管怎么加,都可以执行。(const char* 可以隐式地转化成string)。
5、重载输入输出运算符 >> 和 <<
首先两个运算符的os is参数 和 返回值都必须是非const的引用,因为流对象不可拷贝且会改变状态。
其次对于运算符输入运算符>>,类的对象必须是非const引用,对于输出运算符<<,类的对象建议为const引用。
//输出运算符
ostream &operator << (ostream &os,const A&a){
os << a.v;
return os;
}
//输入运算符
istream &operator >>(istream &is,A &a){
is >> a.v;
return is;
}
int main()
{
A a;
cin >> a;
cout << a << endl;
return 0;
}
6、相等运算符== 与 不等运算符 !=
一般一个类定义了==运算符,那么一般也要定义一个!=运算符。
且!=运算符只是调用==运算符来工作,即只有一个运算符真正负责比较工作。
class A{
public:
A(int v):v(v){}
int v;
};
bool operator == (const A &a1,const A &a2){
return a1.v == a2.v;
}
bool operator !=(const A &a1,const A &a2){
return !(a1 == a2);
}
int main()
{
int a(1),b(1),c(2);
cout << (a==b) << endl;//1
cout << (a==c) << endl;//0
}
7、可以给一个类定义多个重载的运算符。
class A{
public:
A &operator = (A a){
v = a.v;
return *this;
}
A &operator =(int v1){
v = v1;
return *this;
}
int v;
};
int main()
{
A a,b,c;
a = (c = 2);
cout << a.v << endl;
a = 10;
cout << a.v << endl;
return 0;
}
8、重载下标运算符[]
重载下标运算符[]首先应返回引用,其次一般定义const版本和非const版本两个重载函数。
class A{
public:
A():v(new int[5]){}
int &operator[](int i){
return v[i];
}
const int& operator[](int i)const{
return v[i];
}
int *v;
};
int main()
{
A a;
a[2] = 10;
cout << a[2] << endl;
const A b;
// b[2] = 100;// 错误。const
return 0;
}
因为b是常量,所以不能给b[2]赋值。
9、重载 递增++ 递减–运算符。
首先,每种运算符重载前置和后置两个版本。
其次,前置版本返回引用,后置版本返回值。
class A{
public:
A(int v):v(v){}
//前置运算符
A &operator++(){++v;return*this;}
A &operator--(){--v;return *this;}
A operator++(int ){int tmp = v;v++;return A(tmp);}
A operator--(int ){int tmp = v;--v;return tmp;}
int v;
};
int main()
{
A a(10);
++a;
cout << a.v << endl;//11
--a;
cout << a.v << endl;//10
int tmp = (a--).v;//10
cout << tmp << endl;//10
cout << a.v << endl;//9
tmp = (++a).v;
cout << tmp << endl;//10;
cout << a.v << endl;//10
//显示调用
tmp = (a.operator--()).v;
cout << tmp << endl;//9
cout << a.v << endl;//9
tmp = (a.operator--(0)).v;
cout << tmp << endl;//9
cout << a.v << endl;//8
return 0;
}
10、重载*解引用和->成员访问运算符
注意: *解引用返回对象的引用,-> 成员运算符返回成员的地址。
-> 成员运算符获取成员这一事实永不变。
class A{
public:
A(int v):v(v){}
//前置运算符
A &operator++(){++v;return*this;}
A &operator--(){--v;return *this;}
//后置运算符
A operator++(int ){int tmp = v;v++;return A(tmp);}
A operator--(int ){int tmp = v;--v;return tmp;}
int v;
};
class A_ptr{
public:
A_ptr(A &a):a_ptr(&a){}
A& operator*()const{
return *a_ptr;
}
A* operator->()const{
return a_ptr;
}
A *a_ptr;
};
int main()
{
A a(10);
A_ptr ap(a);
cout << ap->v << endl;
ap->operator++();
cout << ap->v << endl;
cout << (*ap).v << endl;
}
11、类类型转换
类类型转化:
隐式转换构造函数(单参数构造函数)和类型转换运算符共同定义。
隐式转换构造函数:别的类型转化成当前类的类型。
类型转换运算符:当前类对象转换成其他类型。
类型转换符无显示返回类型,无形参,必须定义成类的成员函数。
class A{
public:
int val;
A(int v):val(v){}//隐式转换构造函数 int->A
//隐式类型转换运算符,A->int
//编译器会在合适的地方自动执行A->int 的转换
operator int () const{return val;}
//显示类型转换运算符,A->bool
//C++11支持,只有显示请求转换,才能A->bool
//但是当表达式被用作条件时,该转换会被隐式执行。
explicit operator bool() const {return val;}
};
int main()
{
A a(10);
int b = a + 5;//a转换成10,然后与5相加
cout << b << endl;//15
bool c = static_cast<bool>(a);
cout << c << endl;//1
if(a)cout << "YES"<<endl;//YES
else cout << "NO" << endl;
return 0;
}
12、有二义性的类型转换
情况1: 假设现在要把B类型对象转换成A类型对象。且如果A中有参数为B的构造函数 并且 B中有目标对象为A的类型转换符,那么从B->A时,既可以调用A的转换构造函数,又可以调用B的转换运算符,产生二义性。
情况2: 如果类A存在与多个内置算数类型之间的转换,那么要小心。
情况3:假设func(Dd)和func(Cc)是两个重载函数。且D类和C类对象可以用int转换得来,那么func(10)调用将产生二义性。
**结论:除了显示的explict 向 bool 类型的转换除外,应尽量避免定义类型转换运算符和非显示的转换构造函数。
class A{
public:
int val;
A(int v):val(v){}//1
A(double v):val(v){}//2
operator int()const{return val;}//3
operator double()const{return val;}//4
};
int main()
{
A a = 11;
long long b = 10;
// A c(b)//错误,1呢还是2呢?
// b+a;//错误,3呢还是4呢?
return 0;
}