友元函数
什么是友元函数
私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行。这固然能够带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书写的麻烦。
在面向对象编程中,友元函数(friend function)是一个指定类(class)的“朋友”,该函数被允许访问该类中private、protected、public的数据成员。普通的函数并不能访问这些数据,然而宣告一个函数成为一个类的友元函数则被允许访问这些数据。
友元函数的宣告可以放在类声明的任何地方,不受访问限定关键字private、protected、public的限制。一个相似的概念是友谊类。
友谊关键字应该谨慎使用。如果一个拥有private或者protected成员的类,宣告过多的友元函数,可能会降低封装性的价值,也可能对整个设计框架产生影响。
友元函数的存在形式有?
- 友元函数之全局函数
现在我们有一个全局函数 distance ,通过它计算两个点之间的距离,如果直接访问肯定是不行的,编译会报错。当我们在类Point中将其声明为友元之后,就可以了。
如图:
- 友元函数之成员函数
假设类A有一个成员函数,该成员函数想去访问另一个类B类中的私有成员变量。这时候则可以在第二个类B中,声明第一个类A的那个成员函数为类B的友元函数,这样第一个类A的某个成员函数就可以访问第二个类B的私有成员变量了。同样还是求取两个点之间的距离,现在我们再定义一个类 Line ,由Line 中的成员函数 distance 完成:
- 友元之友元类
如上的例子,假设类 Line 中不止有一个 distance 成员函数,还有其他成员函数,它们都需要访问Point 的私有成员,如果还像上面的方式一个一个设置友元,就比较繁琐了,可以直接将 Line 类设置为 Point 的友元。
友元函数的封装性
不可否认,友元在一定程度上将类的私有成员暴露出来,破坏了信息隐藏机制,似乎是种“副作用很大的药”,但俗话说“良药苦口”,好工具总是要付出点代价的,拿把锋利的刀砍瓜切菜,总是要注意不要割到手指的。
友元的存在,使得类的接口扩展更为灵活,使用友元进行运算符重载从概念上也更容易理解一些,而且, C++ 规则已经极力地将友元的使用限制在了一定范围内,它是单向的、不具备传递性、不能被继承,所以,应尽力合理使用友元。
注意:友元的声明是不受 public/protected/private 关键字限制的。
运算符重载
运算符重载的原则
1.不能无中生有(不能定义新的运算符),也不要改变运算符的原有意义。
2.重载不能改变运算符运算对象(即操作数)的个数。
3.重载不能改变运算符的优先级与结合性。
4.重载运算符的函数不能指定默认的参数值。
5.重载运算符函数的参数应至少有一个是类对象(或类对象的引用),不能都是基本类型。
不能重载的运算符
作用域操作符:::
条件操作符:?:
点操作符:.
指向成员操作的指针操作符:->*,.*
还有 sizeof运算符
.、.*运算符不能重载是为了保证访问成员的功能不能被改变,域运算和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。
函数调用运算符
使用静态变量计数
使用私有成员变量
私有成员体现了封装性的特点,而静态全局变量只是起到了一个计数的作用,是共享的。除此之外,func函数只是在逻辑顺序上的执行,而fo可以无限的创建,函数对象可以体现出状态。
一般地,把这种带有状态的函数对象成为闭包—>匿名函数—>lambda表达式
下标访问运算符
在C++对下标访问运算符重载的好处
1.增加 安全性,对下标进行判断,相比C里面数组安全
2.重载【】之后,可以放在等号的左边
在什么情况下,需要加引用符号
1.防止返回值是对象的时候调用拷贝构造函数
2.允许连续赋值的时候,cout << “hello” << endl;
运算符重载—自增
1.运算符重载之普通函数形式进行重载
2.运算符重载之成员函数
3.运算符重载之友元函数的形式进行重载(可以直接对类的私有成员进行操作,非常方便,并且符合加法的习惯,两个操作数,没有采用隐藏this指针的方式,只有一个操作数出现,不符合加法习惯,推荐使用以友元函数的形式进行重载)
特别地,如果以自身状态为改变目标的重载,可以以成员函数的形式进行重载,比较方便。比如这里+=, -=, /=都可以以这种成员函数方式进行重载。
特殊运算符的重载
前置和后置++的运算符重载
非常清晰,后置++首先要把表达式的值保存下来,之后对成员变量进行++,然后返回保存下来的表达式的值,注意顺序。其中后置++中参数列表中有一个int,这只表示着,后置++的一个标示,不代表传参。这是实现c++的大佬写的,大佬就是规则的制定者,记住吧少年
这里com是一个局部变量,所以要注意千万不要返回引用,这里要把引用去掉。
这里也可以看得出,前置++和后置++是有区别的,前置++的效率都说效率很高,因为前置++返回的是一个引用,是对象本身。后置++返回的是一个局部对象,包含一个执行拷贝构造函数的过程,所以相比下来,效率就比前置++的 执行效率低。
看一个有意思的左值和右值
前置++返回的是对象本身,所以可以加取地址符号,是左值。
而后置++返回的是一个局部变量,是右值。
运算符重载代码:
#include <iostream>
#include <string.h>
#include <vector>
using std::cin;
using std::endl;
using std::cout;
using std::cerr;
using std::vector;
class String {
public:
String()
: _pstr(nullptr)
{
cout << "String()" << endl;
}
String(const char *pstr)
{
cout << "String(const char*)" << endl;
_pstr = new char[strlen(pstr) + 1];
strcpy(_pstr, pstr);
}
String(const String &pstr){
cout << "String(const String&)" << endl;
_pstr = new char[strlen(pstr._pstr) + 1];
strcpy(_pstr, pstr._pstr);
}
~String(){
cout << "~String()" << endl;
if(_pstr != nullptr){
delete _pstr;
_pstr = nullptr;
}
}
void display() const
{
cout << _pstr << endl;
}
String &operator=(const String &);
String &operator=(const char *);
String &operator+=(const String &