从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(上):https://developer.aliyun.com/article/1513652
3.3 static成员使用场景
如果有这么一个要求:设计一个只能在栈上定义对象的类。
class StackOnly { public: StackOnly(int x = 0, int y = 0) :_x(x) , _y(0) { } private: int _x = 0; int _y = 0; }; int main() { StackOnly so1; // 栈 static StackOnly so2; // 静态区 return 0; }
怎么设计一个只能在栈上定义对象的类?
应该不让类外面的人随便调用构造函数,所以我们把构造函数设置成私有,
那就要再设计一个类内的成员函数获取在栈上定义对象的函数:
class StackOnly { public: StackOnly CreateObj() { StackOnly so; return so; } private: StackOnly(int x = 0, int y = 0) :_x(x) , _y(0) { } int _x = 0; int _y = 0; }; int main() { //StackOnly so1; // 栈 //static StackOnly so2; // 静态区 CreateObj(); return 0; }
现在这里的代码是过不了的,CreateObj(); 需要对象调,创造对象又要调用CreateObj();
这就是一个先有鸡还是先有蛋的问题了。
这时我们的静态成员函数就能上场了:(因为静态成员用类域也能调)
class StackOnly { public: static StackOnly CreateObj() { StackOnly so; return so; } private: StackOnly(int x = 0, int y = 0) :_x(x) , _y(0) { } int _x = 0; int _y = 0; }; int main() { //StackOnly so1; // 栈 //static StackOnly so2; // 静态区 StackOnly so3 = StackOnly::CreateObj(); return 0; }
(类和对象后面的OJ题还会有使用静态成员的场景)
这里有两个问题:
1. 静态成员函数可以调用非静态成员函数吗?
2. 非静态成员函数可以调用类的静态成员函数吗?
问题1是不可以的因为静态成员函数没有this指针。
问题2是可以的,因为静态的属于整个类。
4. 友元(friend)
4.1 引入:日期类的流提取
下面这个日期类,我们是调用 Print 成员函数来打印的:
#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() const { cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 7); d1.Print(); return 0; }
我们此时思考一个问题,我们能不能用 cout 输出一下 d1 呢? cout << d1;
这样当然是不行的,主要的原因还是这个是一个操作符。
是C++里面的 流插入 ,这里的意思就是要像流里面插入一个 d1。
我们说过,内置类型是支持运算符的,而自定义类型是不支持的,
它是不知道该怎么输出的,输入也是一样的道理,也是不知道该怎么去输入。
那怎样才能向我们内置类型一样去用 流插入 和 流提取 呢?
依然可以使用重载这个运算符的方法来解决!
cout 其实是一个全局类型的对象,这个对象的类型是 ostream :
内置类型之所以能直接支持你用,是因为 ostream 已经帮你写好了。
所谓的 "自动识别类型" ,不过只是函数重载而已……
你是 int 它就匹配 int ,你是 char 它就匹配 char 。
我们现在知道了, cout 是一个 ostream 类型的对象了,我们来重载一下:
第一想法是这样吗?:
#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() const { cout << _year << "年" << _month << "月" << _day << "日" << endl; } void operator<<(ostream& out) { out << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 7); //d1.Print(); cout << d1; return 0; }
这时我们发现 cout << d1 还是识别不了,调不动。
这里不识别的原因是因为它是按参数走的,第一个参数是左操作数,第二个参数是右操作数。
双操作数的运算符重载时,规定第一个参数是左操作数,第二个参数是右操作数。
我们这里是成员函数,那第一个参数是隐含的this
所以,我们在调用这个流插入重载时就需要:
d1.operator<<(cout);
我们要直接写就会成这样:
#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() const { cout << _year << "年" << _month << "月" << _day << "日" << endl; } void operator<<(ostream& out) { out << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 7); //d1.Print(); //cout << d1; d1 << cout; return 0; }
可以打印出来了,但是这样看起来就变扭了:
这不符合我们对 "流" 的理解,我们正常理解流插入,是对象流到 cout 里面去。
因为被隐含的 this 指针参数给占据了,所以就一定会是左操作数,
这时如果写成成员函数,双操作数的左操作数一定是对象。
基于这样的原因,我们如果还是想让 cout 到左边去,就不能把他重载成成员函数了。
可以直接把它重载成全局的,在类外面,不是成员函数了就没有这些隐含的东西了!
这样的话就可以让第一个参数变为左操作数,即 out 在第一个位置,Date& d 在第二个位置:
void operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; }
这个时候调用是肯定能调的动了,调的是全局函数。
但我们现在面临的问题是,不能访问私有的问题。
能访问私有的问题改如何解决?把 private 改为 public ?
这种方式肯定是不好的,当然我们可以写个 getYear getMonth getDay 去获取它们。
这样也可以,但是输入的时候怎么办?我们再实现 cin 流体去的时候是要 "写" 的。
这时候就麻烦了,你还得写一个 set,属实是麻烦,有没有更好地办法可以解决这种问题呢?
铺垫了这么久,终于来辣:C++ 引入了一个东西叫做 —— 友元。
4.2 友元的概念
一个全局函数想用对象去访问对象的 private 或者 protected,就可以用友元来解决。
友元分为 友元函数 和 友元类 。
比如刚才我们想访问 Date 类,就可以把它定义为 友元函数 ,友元的声明要放到类里面。
需要注意的是:友元破坏了封装,能不用就不用。
从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(下):https://developer.aliyun.com/article/1513654?spm=a2c6h.13148508.setting.27.5e0d4f0eimuY68