3.1简介
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元的分类:友元函数和友元类
3.2友元函数
在类和对象(中)我们讲到了运算符重载的概念,那么现在我们来尝试重载一下流插入(>>)和流提取(<<)。在重载之前,我们需要明白C++中是怎么实现cin和cout流插入、流提取并且能够自动识别类型的。
答案是通过运算符重载和函数重载,cin 和 cout 分别是 istream 和 ostream 类的两个全局对象,而 istream 类中对流提取运算符 >> 进行了运算符重载,osteam 中对流插入运算符 << 进行了运算符重载,所以 cin 和 cout 对象能够完成数据的输入输出;同时,istream 和 ostream 在进行运算符重载时还进行了函数重载,所以其能够自动识别数据类型。
下面以Date类为例:
class Date { public: Date(int year = 1970, int month = 1, int day = 1) :_year(year) , _month(month) , _day(day) {} //流插入 ostream& operator<<(ostream& out) const { cout << _year << "/" << _month << "/" << _day; return out; } //流提取 istream& operator>>(istream& in) { in >> _year; in >> _month; in >> _day; return in; } private: int _year; int _month; int _day; }; int main() { Date d; cin >> d; cout << d; return 0; }
这里我们发现一个问题,运算符<<和>>的左操作数必须是本类的对象,所以如果按照这种实现方式的话,我们在使用的时候应该是这样的:d >> cin; d << cout,但是这样显然违背了我们写运算符重载的初衷:提高代码的可读性。所以为了将右操作数变成本类的对象,我们只能重载为全局函数,然后将ostream和istream的参数放在函数首位,但是如果重载为全局函数我们又会面对一个新问题,那就是无法访问类内部的私有成员。所以出现了友元函数的概念。
友元函数的特征
- 可以直接访问类的私有成员;
- 是定义在类外部的普通函数,不属于任何类;
- 可以在类定义的任何地方声明,不受类访问限定符限制;
- 需要在类的内部声明,声明时需要加friend关键字;
- 一个函数可以是多个类的友元函数;
- 友元函数的调用与普通函数的调用原理相同。
现在我们使用友元修改一下我们重载的对于Date类的<<和>>:
class Date { friend ostream& operator<<(ostream& out, const Date& d); friend istream& operator>>(istream& in, Date& d); public: Date(int year = 1970, int month = 1, int day = 1) :_year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; }; //流插入 ostream& operator<<(ostream& out, const Date& d) { cout << d._year << "/" << d._month << "/" << d._day; return out; } //流提取 istream& operator>>(istream& in, Date& d) { in >> d._year; in >> d._month; in >> d._day; return in; }
3.3友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性
比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承
class Time { friend class Date; //声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量 public: Time(int hour = 0, int minute = 0, int second = 0) : _hour(hour) , _minute(minute) , _second(second) {} private: int _hour; int _minute; int _second; }; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} void SetTimeOfDate(int hour, int minute, int second) { // 直接访问时间类私有的成员变量 _t._hour = hour; _t._minute = minute; _t._second = second; } private: int _year; int _month; int _day; Time _t; };
4.内部类
4.1概念
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元,所以内部类可以通过外部类的对象参数来访问外部类的所有成员,但是外部类不是内部类的友元。
4.2特性
- 内部类可以定义在外部类的public、protected、private都是可以的
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
sizeof(外部类)=外部类
,和内部类没有任何关系
class A { private: static int k; int h; public: class B // B天生就是A的友元 { public: void foo(const A& a) { cout << k << endl;//OK cout << a.h << endl;//OK } }; }; int A::k = 1; int main() { A::B b; b.foo(A()); return 0; }
5.匿名对象
class A { public: A(int a = 10) :_a(a) { cout << "A(int a)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { //创建有名对象的方式 A a1; A a2(15); A a3 = 5; //创建匿名对象,不用取名字 A(); return 0; }
上述代码的运行结果:
匿名对象的特点:
- 不用取名字;
- 生命周期只有这一行,下一行就会自动调用析构函数
使用场景:
class Solution { public: int Sun_Solution(int n) { //... return n; } }; int main() { Solution().Sun_Solution(10);//在这种情况下匿名对象就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说 return 0; }