目录
[1. 友元关系是单向的](#1. 友元关系是单向的)
[2. 友元关系不能传递](#2. 友元关系不能传递)
一、友元类是什么?
前面学习了友元函数:
cpp
friend void PrintDate(const Date& d);
友元函数可以让一个普通函数访问类的私有成员。
而友元类是让一个类可以访问另一个类的私有成员。
写法是:
cpp
friend class 类名;
例如:
cpp
class Date {
friend class Time;
private:
int _year;
int _month;
int _day;
};
这表示:
Time 类是 Date 类的友元类,Time 类中的成员函数可以访问 Date 类中的私有成员。
二、友元类的简单例子
cpp
#include <iostream>
using namespace std;
class Date {
friend class Time;
public:
Date(int year = 2024, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
class Time {
public:
void PrintDate(const Date& d) {
cout << d._year << "-" << d._month << "-" << d._day << endl;
}
};
int main() {
Date d(2024, 5, 1);
Time t;
t.PrintDate(d);
return 0;
}
运行结果:
cpp
2024-5-1
这个例子中,_year、_month、_day 都是 Date 的私有成员。
正常情况下,time 类是不能直接访问它们的。
但是因为 Date 中声明了:
cpp
friend class Time;
所以 Time 类就可以访问 Date 的私有成员。
三、友元类的特点
友元类有以下几个特点:
cpp
1. 友元类可以访问另一个类的滴油成员;
2. 友元关系是单向的;
3. 友元关系不能传递;
4. 友元会破坏封装,不能滥用。
1. 友元关系是单向的
如果写:
cpp
class Date {
friend class Time;
};
表示 Time 类可以访问 Date 的私有成员。
但是不代表 Date 类可以访问 Time 的私有成员。
如果要 Date 访问 Time 类的私有成员,需要在 Time 类中声明:
cpp
class Time {
friend class Date;
};
2. 友元关系不能传递
如果:
cpp
A 是 B 的友元
B 是 C 的友元
不能推出:
cpp
A 是 C 的友元
友元关系只能对当前声明的类有效。
四、什么是内部类?
内部类就是在一个类的内部再定义一个类。
例如:
cpp
class A {
public:
class B {
public:
void Print() {
cout << "B class" << endl;
}
};
};
这里 B 就是 A 的内部类。
使用内部类时,需要通过类域访问:
cpp
A::B b;
b.Print();
完整代码:
cpp
#include <iostream>
using namespace std;
class A {
public:
class B {
public:
void Print() {
cout << "B class" << endl;
}
};
};
int main() {
A::B b;
b.Print();
return 0;
}
运行结果:
cpp
B class
五、内部类的特点
内部类有以下几个特点:
cpp
1. 内部类定义在另一个类的内部
2. 使用是要通过 外部类::内部类 的方式
3. 内部类受外部类的类域限制
4. 内部类天然具有外部类的友元权限
也就是说内部类可以访问外部类的私有成员。
例如:
cpp
#include <iostream>
using namespace std;
class A {
public:
class B {
public:
void Print(const A& a) {
cout << a._a << endl;
}
};
private:
int _a = 10;
};
int main() {
A a;
A::B b;
b.Print(a);
return 0;
}
运行结果:
cpp
10
这里 _a 是 A 的私有成员,但是内部类 B 可以访问它。
不过内部类在 C++ 中用的没有 Java 那么多,基本了解即可。
六、封装是什么?
封装是面向对象的三大特性之一。
简单说就,封装就是:
把数据和操作数据的方法放到一起,并通过访问限定符控制外部能不能直接访问。
例如:
cpp
class Date {
public:
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
这里 _year、_month、_day 是私有成员,外部不能直接访问。
外部想使用日期,只能通过公有接口,比如:
cpp
Print();
这就是封装。
七、为什么要封装?
封装的意义主要有两个:
cpp
1. 保护数据
2. 降低代码之间的耦合
如果成员变量全部写成 public,外部可以随意修改:
cpp
d._month = 100;
d._day = -1;
这样对象很容易进入错误状态。
如果把数据放到 private,再通过成员函数控制访问,就可以在函数中加入检查逻辑。
例如:
cpp
void SetDate(int year, int month, int day) {
if (month >= 1 && month <= 12) {
_month = month;
}
}
这样可以减少错误。
八、友元和封装的关系
友元可以让类外的函数或其它类访问私有成员。
这确实很方便,但是会破坏封装性。
所以友元要谨慎使用。
一般原则是:
cpp
能用成员函数解决,就优先用成员函数
确实不适合写成成员函数时,再考虑友元
比如输入输出运算符重载:
cpp
cout << d;
cin >> d;
这种场景下,为了保证正常输入习惯,operator<< 和 operator>> 通常携程全局函数,然后声明为友元。
这属于比较合理的友元使用场景。
九、小结
本篇主要学习了友元类、内部类和封装。
需要记住:
- 友元类使用 friend class 类名; 声明;
- 友元类可以访问另一个类的私有成员;
- 友元关系是单向的;
- 友元关系不能传递的;
- 内部类在是一个类的内部定义的类;
- 内部类使用时需要写成 外部类::内部类 ;
- 内部类天然具有外部类的友元权限;
- 封装是把数据和操作数据的方法放在一起;
- 封装可以保护数据,降低耦合;
- 友元会破坏封装,所以不能滥用。
友元和内部类本质上都是C++中突破访问限制的机制。学到他们不是为了经常使用,而是为了理解C++类和对象访问权限、类域以及封装之间的关系。