1.C++中重载操作符时什么时候定义成友元,什么时候定义为成员方法
在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。而在某种情况下,程序员没得选择,只能重载为类成员。
但成员函数运算符与友元函数运算符也具有各自的一些特点:
(
1
) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(
2
) 以下一些双目运算符不能重载为类的友元函数:
=
、()、[]、
->
。必须将他们定义为成员,定义为非成员函数将在编译的时候出现错误。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(
3
) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(
4
) 若一个运算符的操作需要修改对象的状态或者与给定类型紧密联系的一些操作符,选择重载为成员函数较好。如自增,自减,解引用。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(
5
) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(
6
) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一 个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部 类型的对象,该运算符函数必须作为一个友元函数来实现。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(
7
) 当需要重载运算符具有可交换性时,即对称的操作符,如关系运算符,相等操作符,算数运算符,位操作符,则选择重载为友元函数。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
1.<< 和 >>操作符的重载。
输入>> 和输出<<是有区别的,在输出的情况下 不许要判别任何情况,但是在输入的时候,必须处理输入错误和文件结束的可能。类似你需要一个char类型,但是输入了一个int 类型。
输出<< 的重载
输出<< 模板:
ostream& operator << (ostream & +名字,const +类型名字 & + 对象名字)
{
//函数体
}
返回类型是ostream的引用。因为ostream 对象是不可以复制的,必须返回引用。第二个参数是const 引用 对象,因为不许要改变对象的内容。例子:
因为这里 输出要访问到类中的私有成员,需要将输出函数声明为友元函数。
class MyString
{
friend ostream & operator << (ostream &out,const MyString &s);
private:
char *m_data;
int data;
public:
//普通构造函数
MyString(const char * str = NULL,const int s = 0);
//拷贝构造函数
MyString(const MyString & other);
//重载操作符号
MyString& operator = (const MyString& my);
//析构函数
~MyString();
//赋值函数
//MyString & operator = (const MyString &other);
};
//MyString 输出函数
ostream & operator << (ostream &out,const MyString &s)
{
cout << s.m_data << " ";
cout << s.data ;
return out;
}
输出中,应该尽量减少操作符所做的格式化操作,让用户自己控制输出细节。
如果定义为类的成员,则左操作数必须是该类类型的对象,第二个参数是ostream 对象。类似:
//MyString 输出函数
ostream & MyString::operator << (ostream &out)
{
cout << m_data << " ";
cout << data << endl;
return out;
}
那么你使用的时候只能这么使用: MyString s; s << cout ; 这种输出方法 肯定是不好的,与我们平常使用相差比较大,看着不爽。为了支持正常用法,所以第一个参数也就是左操作数必须是ostream对象,所以 输入和输出一般定义为非成员函数,并且声明为类的友元函数。
输入>> 操作符的重载。
//输入 模板
istream & operator >> (istream &+名字 ,类型名字 & + 对象名字)
{
//函数体
}
返回类型是ostream的引用。因为ostream 对象是不可以复制的,必须返回引用。第二个参数不是是const 引用 对象,因为要改变对象的内容。例子:
因为这里 输出要访问到类中的私有成员,需要将输入函数声明为友元函数。
class MyString
{
friend ostream & operator << (ostream &out,const MyString &s);
//MyString 输入函数
friend istream & operator >> (istream &in ,MyString &s);
private:
char *m_data;
int data;
public:
//普通构造函数
MyString(const char * str = NULL,const int s = 0);
//拷贝构造函数
MyString(const MyString & other);
//重载操作符号
MyString& operator = (const MyString& my);
//析构函数
~MyString();
//赋值函数
//MyString & operator = (const MyString &other);
};
//MyString 输入函数
istream & operator >> (istream &in ,MyString &s)
{
cout << "input data of MyString \n";
in >> s.data >> s.m_data;
if(in)
cout << " gouzao by opreator >> ";
else
{
cout << " shuru cuowu \n";
s= MyString();
}
//s= MyString();
return in;
}
输入期间会检查是否发生错误,可能发生的错误种类包括:
(1)任何读操作都可能因为提供不正确的值发生失败,如果期望读入的类型和实际读入的类型数据不一样,则会发生失败。失败了这个流对象的后续使用都会失败。
(2)任何读入都可能遇到输入流中的文件错误或者其他一些错误。
无需检查每次输入,只需要在使用数据前检查一下。如果失败了,将整个对象复位,就好像一个空的 类型对象,具体做法采用默认构造函数重新生成一个对象并赋值给参数对象。
2.算数操作符和关系运算符
一般将算数运算符和关系运算符定义为非成员函数。
加法操作符+
加法模板
类型名字(不是引用) operator+ (const 类型名字&+对象名字,const 类型名字&+对象名字)
{
//函数体
}
加法模板
类型名字(不是引用) operator+ (const 类型名字&+对象名字,const 类型名字&+对象名字)
{
//函数体
}
因为参数对象的内容不会发生改变,这两个相加会产生一个新的对象,为了与内置操作符保持一致,加法返回一个右值,所以要返回对象,而不是对象的引用,(这个类中必须存在复制构造函数/拷贝构造函数)
例子:
//加法操作符+
MyString operator + (const MyString& s1, const MyString& s2)
{
MyString ret;//生成一个新的对象作为返回。
int len = strlen(s1.m_data)+ strlen(s2.m_data) + 1;
delete []ret.m_data;
ret.m_data = new char(len);
strncpy(ret.m_data,s1.m_data,strlen(s1.m_data));
strcpy(ret.m_data + strlen(s1.m_data) ,s2.m_data);
ret.data = s1.data + s2.data;
return ret;
}
操作符+=(需要声明为成员函数)
// +=操作符重载
MyString& operator += (MyString& s1, const MyString& s2)
{
int len = strlen(s1.m_data) + strlen(s2.m_data) + 1;
char *sp = new char(len);
strncpy(sp,s1.m_data,strlen(s1.m_data));
strncpy(sp + strlen(s1.m_data), s2.m_data,strlen(s2.m_data));
sp[len - 1] = '\0';
delete s1.m_data;
s1.m_data = sp;
s1.data += s2.data;
return s1;
}
对于 +操作符,我们可以调用+= 来实现:
MyString operator + (const MyString& s1, const MyString& s2)
{
MyString ret(s1);//将s1赋值给另外一个新的对象
ret += s2;
return ret;
}
3.赋值操作符
可以为一个类定义多个附加的赋值操作符,赋值操作符可以重载,但是无论形参为何种类型,赋值操作符必须为成员函数。这一点与复合赋值操作符不同
赋值操作符和复合赋值操作符都应返回左操作数的引用。
4.[ ]操作符
可定义下标操作符的时候,一定要定义两个版本,一个对于const对象操作,一个对非const对象操作。