一、运算符重载的关键字和注意点
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字
operator
后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
需要注意的点:
(1)不能通过连接其他符号来创建新的操作符:比如
operator@
。(2)重载操作符必须有一个类类型参数。
(3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,尽量不要改变其含义。
(4)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
。
(5).* :: sizeof ?: .
注意以上5个运算符不能重载。
(6)重载的操作符的函数的参数个数必须与原来运算符需要的操作个数相同(在类内会有一个隐含的this指针参数)。
(7)可以写在类内作为成员函数也可以在全局函数,有一个例外就是赋值运算符只能作为成员函数。
(8)如果类的成员变量是私有的,那么全局重载函数就无法访问该成员变量了,有两种办法1、使用友元、提供一些访问成员变量的接口。
(9)重载函数可以显示调用 ,如:p1.operator=(p2)
,使用隐式:p1 = p2
(实际上编译器也会转换成显示调用)。
(10)当有重载运算符的位置在全局和类内都有的情况下,优先调用类内的,所以当类内实现了,就没有必要在全局上再实现了。
二、重载=
运算符
赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
返回类型 :
const 类名 &
返回类的话就可以实现连续赋值了。
函数名:operator=
。
参数类型:(const & 类名)
,因为编译器自动会传一个隐含的this
,所以我们传一个参数就够了。
实现:
class Kind
{
public:
//构造函数
Kind(int a = 10,int b = 10)
{
_a = a;
_b = b;
}
//重载 = //只能作为成员函数 const 防止被修改
const Kind & operator=(const Kind& p)
{
this->_a = p._a;
this->_b = p._b;
//返回 *this 使其可以连续赋值
return *this;
}
private:
int _a;
int _b;
};
int main()
{
Kind p1(20, 20);
Kind p2;
Kind p3;
//连续赋值
p3 = p2 = p1;
cout << "p1: " << p1._a << " " << p1._b << endl;
cout << "p2: " << p2._a << " " << p2._b << endl;
cout << "p3: " << p3._a << " " << p3._b << endl;
return 0;
}
其实默认的赋值运算符重载函数就是像上写的那样进行赋值,我们对于这样的拷贝叫做浅拷贝,这样做有一个弊端就是如果遇到动态申请的空间的话就有可能发生程序崩溃,这是因为共用一块空间当另一个对象将这块空间释放之后被赋值的那个对象再使用这块空间时就会发生崩溃。
如:
class Kind
{
public:
//构造函数
Kind(int a = 10,int b = 10)
{
_a = a;
_b = b;
arr = new int;
*arr = a;
}
//重载 = //只能作为成员函数 const 防止被修改
const Kind & operator=(const Kind& p)
{
this->_a = p._a;
this->_b = p._b;
this->arr = p.arr;
//返回 *this 使其可以连续赋值
return *this;
}
private:
int _a;
int _b;
int* arr;
};
int main()
{
Kind p1(20, 20);
Kind p2 = p1;
return 0;
}
当上述的p1将arr释放了,p2再使用arr就会发生崩溃。
当我们遇到这种情况时我们使用深拷贝。
//深拷贝
const Kind& operator=(const Kind& p)
{
this->_a = p._a;
this->_b = p._b;
int* tmp = new int;
if (tmp == nullptr)
exit(-1);
*tmp = *p.arr;
this->arr = tmp;
//返回 *this 使其可以连续赋值
return *this;
}
总结:
当遇到动态申请的空间时需要重写赋值运算符,如果没有用编译器自动生成的即可。
三、重载+
运算符
1、作为成员函数
返回值: 类型 (类) ,用传值的方式即可,因为返回值是存在栈中当函数销毁时该返回值也会被系统回收。
函数名:operator+
参数:(const & 类名)
,因为编译器自动会传一个隐含的this
,所以我们传一个参数就够了。
实现:
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//重载 + //作为成员函数
Kind operator+(const Kind & p)
{
Kind tmp;
tmp._a = p._a + this->_a;
tmp._b = p._b + this->_b;
return tmp;
}
private:
int _a;
int _b;
};
int main()
{
Kind p1(20, 20);
Kind p2(20, 20);
Kind p3 = p1 + p2;
cout << "p1: " << p1._a << " " << p1._b << endl;
cout << "p2: " << p2._a << " " << p2._b << endl;
cout << "p3: " << p3._a << " " << p3._b << endl;
return 0;
}
2、全局函数:
与成员函数不同的是需要多传一个参数,其他的与成员函数一样。
实现:
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//作为友元函数,可以访问私有成员。
friend Kind operator+(const Kind& p1 ,const Kind &p2 );
private:
int _a;
int _b;
};
//重载 + //全局函数
Kind operator+(const Kind& p1 ,const Kind &p2 )
{
Kind tmp;
tmp._a = p1._a + p2._a;
tmp._b = p1._b + p2._b;
return tmp;
}
3、总结:
可以写全局的函数也可以写成员函数,实现 - * /
运算符重载时的形式与上述的一样。
四、重载 ==
运算符
1、作为成员函数
返回值:
bool
。
函数名:operator==
。
参数:(const &类名)
,因为编译器自动会传一个隐含的this
,所以我们传一个参数就够了。
实现:
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//重载 == //作为成员函数
bool operator==(const Kind& p)
{
//成员都相等就相等
if (this->_a == p._a && this->_b == p._b)
return true;
else
return false;
}
private:
int _a;
int _b;
};
int main()
{
Kind p1(20, 20);
Kind p2(20, 20);
if (p1 == p2)
{
cout << "p1==p2" << endl;
}
else
{
cout << "p1!=p2" << endl;
}
return 0;
}
2、作为全局函数
与成员函数不同的是需要多传一个参数,其他的与成员函数一样。
实现:
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//作为友元函数,可以访问私有成员。
friend Kind operator==(const Kind& p1 ,const Kind &p2 );
private:
int _a;
int _b;
};
//重载 == //作为全局函数
bool operator==(const Kind& p1, const Kind& p2)
{
if (p1._a == p2._a && p1._b == p2._b)
return true;
else
return false;
}
3、总结
可以写全局的函数也可以写成员函数,实现 != >= <= > <
运算符重载时的形式与上述的一样。
五、重载前置++
和 后置 ++
运算符
1、前置++
(1)作为成员函数
返回值:返回用引用的方式返回
*this
,这样即可进行计算和Kind p2 = ++p1;
和提高效率。
函数名:operator++
。
参数:无(实际上有一个隐含的this指针)。
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//重载 前++ //作为成员函数
Kind & operator++()
{
//对成员变量++
this->_a++;
this->_b++;
return *this;
}
private:
int _a;
int _b;
};
int main()
{
Kind p1(20, 20);
cout << p1._a << " " << p1._b << endl;
++p1;
cout << p1._a << " " << p1._b << endl;
return 0;
}
(2)作为全局函数
多了个参数,其余的一样。
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//作为友元函数,可以访问私有成员。
friend Kind& operator++(Kind& p);
private:
int _a;
int _b;
};
//重载 前置++ //作为全局函数
Kind& operator++(Kind& p)
{
//对成员变量++
p._a++;
p._b++;
return p;
}
2、后置++
后置++与前置++的不同是我们在加之前要保存原来的作为返回值,这样才符合后置++运算。
后置++与前置++如何区别捏?
后置++比前置++的参数多一个 int ,这个参数我们不用传值,主要用于区分前置和后置。
(1)作为成员函数
返回值:返回
临时的原来的类
。
函数名:operator++
。
参数:(int)(实际上还有一个隐含的this指针)。
实现:
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//重载 后置++ //作为成员函数
Kind operator++(int)
{
//对成员变量++
Kind p = *this;
this->_a++;
this->_b++;
return p;
}
private:
int _a;
int _b;
};
int main()
{
Kind p1(20, 20);
cout << p1._a << " " << p1._b << endl;
Kind p2 = p1++;
cout << p2._a << " " << p2._b << endl;
cout << p1._a << " " << p1._b << endl;
return 0;
}
(2)作为全局函数
多了个参数,其余的一样。
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//作为友元函数,可以访问私有成员。
friend Kind operator++(Kind & tmp,int);
private:
int _a;
int _b;
};
//重载 后置++ //作为全局函数
Kind operator++(Kind & tmp,int)
{
//对成员变量++
Kind p = tmp;
tmp._a++;
tmp._b++;
return p;
}
3、总结
前置和后置的区别在于返回值,他们也可实现全局和成员函数两种,前置 – 和 后置 – 也是类似这样写。
六、重载 <<
>>
运算符
cout
是 ostream
类的一个对象,而 <<
符在这个类已经进行了一些重载
如:
ostream& operator<<(const int &a)
{
//输出代码
return *this;
}
重载<<
作为类成员函数
class A
{
public:
ostream& operator<<(ostream& ost)
{
//输出
return ost;
}
}
int main()
{
A a;
//下面会报错吗?
cout<<a;
return 0;
}
答案是:会,因为如果在类内重载 <<
操作符 ,这样使用 cout<< 类对象
是会报错的,因为在参数列表中会隐含着一个 ostream *this
指针,这样使用的话第一个参数就是ostream *this
,但是我们在其他类将其作为成员函数去重载时应该也会传一个隐含的 this
指针作为第一参数吧,这样的话第一个参数就会与 cout
冲突,除非输出时写成 类对象<<cout
,第一个参数就是 类的this指针、第二个参数就是 ostream
,这样可以正常输出,但是这样使用是不自然的,因此推荐使用全局函数重载 <<
操作符,并且以 ostream
作为第一个参数,其他类作为第二参数,这样就可以实现 cout<<类对象
了
重载 >>
返回类型 :
ostream&
使其能够连续的输出,&
提高效率。
函数名:operator<<
。
参数:ostream & ost, const Kind& p
。
//输出
ostream & operator<<( ostream& ost, const Kind& p)
{
ost << p._a << " " << p._b << endl;
return ost;
}
一样的 cin
是 istream
类 的对象,>>
也在 istream
类里进行了一些重载。
所以建议在全局里进行重载,理由同上。
重载 >>
返回类型 :
istream&
使其能够连续的输出。
函数名:istream<<
。
参数:istream & ost, const Kind& p
。
//输入
istream& operator>>(istream& ist, Kind& p)
{
ist >> p._a >> p._b;
return ist;
}
测试:
//重载 >> 和 <<
class Kind
{
public:
//构造函数
Kind(int a = 10, int b = 10)
{
_a = a;
_b = b;
}
//作为友元函数,可以访问私有成员。
friend istream& operator>>(istream& ist, const Kind& tmp);
friend ostream& operator<<(ostream& ost, const Kind& p);
private:
int _a;
int _b;
};
//输出
ostream & operator<<( ostream& ost, const Kind& p)
{
ost << p._a << " " << p._b << endl;
return ost;
}
//输入
istream& operator>>(istream& ist, Kind& p)
{
ist >> p._a >> p._b;
return ist;
}
int main()
{
Kind p1;
//输入
cin >> p1;
//输出
cout << p1;
return 0;
}