C++ Primer学习总结 第14章 操作重载与类型转换

版权声明: https://blog.csdn.net/sgh666666/article/details/88951057

第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;
}

猜你喜欢

转载自blog.csdn.net/sgh666666/article/details/88951057