xml
hello,这里是AuroraWanderll。
兴趣方向:C++,算法,Linux系统,游戏客户端开发
欢迎关注,我将更新更多相关内容!
这是类和对象系列的第六篇文章,上篇指引:类和对象五:初始化列表和static
类和对象(六)--友元、内部类与再次理解类和对象
简易目录
- 友元详解
- 内部类详解
- 再次理解类和对象
3. 友元
友元确实突破了封装性,提供了便利但增加了耦合度(破坏了软件工程高内聚低耦合的核心思想),应该谨慎使用(部分条件下必须用)。
3.1 友元函数
问题背景:重载 operator<< 的困境
问题: 如果将 operator<< 重载为成员函数,调用方式会变得很奇怪。
class Date {
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day)
{}
// 尝试作为成员函数重载 <<
// 调用方式:d1 << cout 而不是正常的 cout << d1
ostream& operator<<(ostream& _cout) {
_cout << _year << "-" << _month << "-" << _day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d(2024, 5, 20);
d << cout; // Error错误: 奇怪的调用方式,不符合习惯
// 我们希望的是:cout << d;
}
原因: 成员函数的第一个参数是隐藏的 this 指针,所以对象必须在 << 的左侧。
cout也需要是第一个参数,所以二者产生冲突,非常尴尬,难以解决
因此,我们引出了友元
解决方案:使用友元函数
class Date {
// 声明友元函数 - 可以访问私有成员,一般习惯在最前面声明,但是其实无论放在哪个访问限定符号下都没影响
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year), _month(month), _day(day)
{}
private:
int _year;
int _month;
int _day;
};
// 全局函数定义 - 注意:不再是成员函数!
ostream& operator<<(ostream& _cout, const Date& d) {
_cout << d._year << "-" << d._month << "-" << d._day; // 可以访问私有成员
return _cout;
}
istream& operator>>(istream& _cin, Date& d) {
_cin >> d._year >> d._month >> d._day; // 可以访问私有成员
return _cin;
}
int main() {
Date d;
cin >> d; // 正确success: 正常调用:operator>>(cin, d)
cout << d << endl; // 正确success: 正常调用:operator<<(cout, d)
return 0;
}
友元函数的特点
-
不是成员函数:是定义在类外部的普通函数,注意:不再是作为成员函数重载,而是直接定义在类外面
-
可以访问私有成员 :在类内部声明为
friend -
不受访问限定符限制 :可以在
public、private、protected任何区域声明 -
不能用
const修饰 :因为没有this指针 -
一个函数可以是多个类的友元
class A; // 前向声明
class B {
private:
int _b_data = 10;
friend void PrintBoth(const A& a, const B& b); // 声明友元
};class A {
private:
int _a_data = 20;
friend void PrintBoth(const A& a, const B& b); // 声明友元
};// 一个函数是两个类的友元
void PrintBoth(const A& a, const B& b) {
cout << "A data: " << a._a_data << endl; // 访问A的私有成员
cout << "B data: " << b._b_data << endl; // 访问B的私有成员
}int main() {
A a;
B b;
PrintBoth(a, b); // 输出:A data: 20, B data: 10
}
3.2 友元类
概念: 一个类可以是另一个类的友元,友元类的所有成员函数都可以访问另一个类的私有成员。
基本用法
class Time {
// 声明Date类为Time类的友元类
friend class Date;
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)
{}
// Date类的成员函数可以访问Time类的私有成员
void SetTime(int hour, int minute, int second) {
_t._hour = hour; // 直接访问Time的私有成员
_t._minute = minute; // 直接访问Time的私有成员
_t._second = second; // 直接访问Time的私有成员
}
void Display() {
cout << _year << "/" << _month << "/" << _day << " ";
cout << _t._hour << ":" << _t._minute << ":" << _t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // Time类对象作为Date的成员
};
int main() {
Date d(2024, 5, 20);
d.SetTime(10, 30, 45);
d.Display(); // 输出:2024/5/20 10:30:45
}
友元类的重要特性
1. 单向性(没有交换性)
class Time {
friend class Date; // Date是Time的友元
private:
int _hour;
};
class Date {
// Time不是Date的友元,所以Time不能访问Date的私有成员
private:
int _year;
};
void Test() {
Time t;
Date d;
// Date可以访问Time的私有成员
// d._year = 2024; // 在Time类中不能这样写,因为Time不是Date的友元
}
2. 不能传递
class A {
friend class B; // B是A的友元
private:
int _a_data;
};
class B {
friend class C; // C是B的友元
private:
int _b_data;
};
class C {
public:
void Test(A& a, B& b) {
// b._b_data = 10; // 正确:C是B的友元,所以C可以访问B的私有变量
// a._a_data = 20; // 错误!C不是A的友元(友元关系不能传递)
}
};
3. 不能继承
class Base {
friend class FriendClass;
private:
int _base_data;
};
class Derived : public Base {
private:
int _derived_data;
};
class FriendClass {
public:
void Test(Base& b, Derived& d) {
b._base_data = 10; // 正确:FriendClass是Base的友元
// d._derived_data = 20; // 错误!友元关系不能继承到派生类
// d._base_data = 30; // 正确:但只能访问基类部分(通过基类引用)
}
};
实际应用场景
1. 紧密协作的类
// 树节点和树类 - 紧密协作
class TreeNode {
friend class Tree; // 树类需要频繁操作节点
private:
int _data;
TreeNode* _left;
TreeNode* _right;
TreeNode* _parent;
};
class Tree {
public:
void Insert(int value) {
// 可以直接操作TreeNode的所有私有成员
// 简化了树的实现
}
void Remove(int value) {
// 同样可以直接访问节点私有成员
}
private:
TreeNode* _root;
};
2. 工厂模式
class Product {
friend class Factory; // 只有工厂能创建Product
private:
Product() {} // 构造函数私有化
int _id;
string _name;
};
class Factory {
public:
static Product* CreateProduct(int id, const string& name) {
Product* p = new Product();
p->_id = id; // 访问私有成员
p->_name = name; // 访问私有成员
return p;
}
};
使用友元的注意事项
- 破坏封装:友元可以直接访问私有成员,破坏了面向对象的封装原则
- 增加耦合:友元类之间紧密耦合,修改一个类可能影响另一个类
- 谨慎使用:只在真正必要时使用,比如运算符重载(cout,cin等等)、紧密协作的类
- 文档说明:使用友元时应该添加注释说明原因
总结
- 友元函数:主要用于运算符重载,让全局函数能访问类的私有成员
- 友元类:让一个类能访问另一个类的私有成员,用于紧密协作的类
- 特性:单向性、不能传递、不能继承
- 原则:谨慎使用,避免过度破坏封装性
4. 内部类
概念
内部类是指定义在另一个类内部的类。内部类是一个独立的类,它不属于外部类。
重要特性:
- 内部类是外部类的友元类
- 外部类不是内部类的友元
- 不能通过外部类对象直接访问内部类成员
基本用法
class Outer {
private:
static int outer_static;
int outer_data = 100;
public:
// 内部类定义在public区域
class InnerPublic {
public:
void AccessOuter(const Outer& outer) {
cout << "访问外部类静态成员: " << outer_static << endl; // 直接访问静态成员
cout << "访问外部类实例成员: " << outer.outer_data << endl; // 通过对象访问实例成员
}
};
// 内部类定义在private区域
class InnerPrivate {
public:
void Show() {
cout << "私有内部类" << endl;
}
};
void UseInner() {
InnerPrivate inner; // 外部类可以创建内部类对象
inner.Show();
}
};
int Outer::outer_static = 50; // 静态成员初始化
int main() {
Outer outer;
// 访问public内部类
Outer::InnerPublic inner_pub;
inner_pub.AccessOuter(outer); // 可以访问外部类的私有成员
// Outer::InnerPrivate inner_priv; // 错误!私有内部类不能在外部访问
outer.UseInner(); // 正确:通过外部类方法间接使用私有内部类
return 0;
}
内部类的特性
1. 访问权限控制
内部类可以定义在外部类的任何访问区域:
class Container {
public:
// public内部类 - 外部可以访问
class PublicInner {
public:
void PublicMethod() {
cout << "Public Inner Class" << endl;
}
};
protected:
// protected内部类 - 只有派生类可以访问
class ProtectedInner {
public:
void ProtectedMethod() {
cout << "Protected Inner Class" << endl;
}
};
private:
// private内部类 - 只有外部类内部可以访问
class PrivateInner {
public:
void PrivateMethod() {
cout << "Private Inner Class" << endl;
}
};
public:
void Test() {
PublicInner pub;
ProtectedInner prot;
PrivateInner priv;
pub.PublicMethod();
prot.ProtectedMethod();
priv.PrivateMethod(); // 外部类内部可以访问所有内部类
}
};
int main() {
Container c;
Container::PublicInner pub; // 正确
pub.PublicMethod();
// Container::ProtectedInner prot; // 错误
// Container::PrivateInner priv; // 错误
c.Test();
}
2. 直接访问外部类静态成员
内部类可以直接访问外部类的静态成员,不需要对象或类名;
并且可以通过创建对象的方式访问外部类普通成员
class Outer {
private:
static int static_var;
int instance_var = 10;
public:
class Inner {
public:
void AccessStatic() {
cout << "直接访问静态成员: " << static_var << endl; // 不需要Outer::static_var
// cout << instance_var << endl; // 错误!不能直接访问实例成员(重要)
}
void AccessInstance(const Outer& outer) {
cout << "通过对象访问实例成员: " << outer.instance_var << endl; // 正确,可以通过创建外部类对象访问外部类实例成员
}
};
static void SetStatic(int val) {
static_var = val;
}
};
int Outer::static_var = 100;
int main() {
Outer::Inner inner;
inner.AccessStatic(); // 输出: 直接访问静态成员: 100
Outer outer;
inner.AccessInstance(outer); // 输出: 通过对象访问实例成员: 10
Outer::SetStatic(200);
inner.AccessStatic(); // 输出: 直接访问静态成员: 200
}
3. 大小与外部类无关
sizeof(外部类) 只包含外部类的成员,不包含内部类:
准确的说:
核心要点:
sizeof(外部类)只计算外部类自己的成员变量sizeof(内部类)只计算内部类自己的成员变量- 内部类的定义不会增加外部类的大小
- 内部类和外部类是独立的类,只是作用域嵌套
cpp
class Outer {
private:
int data1 = 10;
int data2 = 20;
public:
class Inner {
private:
double inner_data1 = 1.1;
double inner_data2 = 2.2;
double inner_data3 = 3.3;
public:
void Show() {
cout << "Inner class method" << endl;
}
};
void UseInner() {
Inner inner;
inner.Show();
}
};
int main() {
cout << "sizeof(Outer): " << sizeof(Outer) << endl; // 输出: 8 (两个int)
cout << "sizeof(Outer::Inner): " << sizeof(Outer::Inner) << endl; // 输出: 24 (三个double)
Outer outer;
Outer::Inner inner;
outer.UseInner(); // 正常使用
}
实际应用场景
1. 迭代器模式
内部类最经典的用途是实现迭代器:
class List {
private:
struct Node {
int data;
Node* next;
Node(int val) : data(val), next(nullptr) {}
};
Node* head = nullptr;
public:
// 内部类:迭代器
class Iterator {
private:
Node* current;
public:
Iterator(Node* node) : current(node) {}
int& operator*() {
return current->data;
}
Iterator& operator++() {
current = current->next;
return *this;
}
bool operator!=(const Iterator& other) {
return current != other.current;
}
};
void Add(int value) {
Node* newNode = new Node(value);
newNode->next = head;
head = newNode;
}
Iterator begin() {
return Iterator(head);
}
Iterator end() {
return Iterator(nullptr);
}
};
int main() {
List list;
list.Add(3);
list.Add(2);
list.Add(1);
// 使用内部类迭代器
for (List::Iterator it = list.begin(); it != list.end(); ++it) {
cout << *it << " "; // 输出: 1 2 3
}
cout << endl;
}
2. 实现细节隐藏
用私有内部类隐藏实现细节:
class Database {
private:
// 私有内部类,对外隐藏连接细节
class Connection {
private:
string connection_string;
bool connected = false;
public:
Connection(const string& conn_str) : connection_string(conn_str) {}
bool Connect() {
// 实际的连接逻辑
connected = true;
cout << "连接到: " << connection_string << endl;
return true;
}
void Disconnect() {
connected = false;
cout << "断开连接" << endl;
}
};
Connection* conn = nullptr;
public:
Database(const string& conn_str) {
conn = new Connection(conn_str); // 外部类创建内部类对象
}
~Database() {
if (conn) {
conn->Disconnect();
delete conn;
}
}
bool Open() {
return conn->Connect();
}
// 外部无法直接创建Connection对象,实现了信息隐藏
};
int main() {
Database db("server=localhost;user=admin");
db.Open();
// Database::Connection conn; // 错误!Connection是私有内部类
}
5. 再次理解类和对象
从现实世界到计算机世界的转换过程
步骤1:现实世界抽象(人脑认知)
例子:洗衣机
- 属性:品牌、容量、颜色、当前状态
- 行为:洗衣、脱水、烘干、设置模式
步骤2:计算机语言描述(编写类)
// 对洗衣机的抽象描述
class WashingMachine {
private:
string brand; // 品牌
double capacity; // 容量
string color; // 颜色
string state; // 当前状态
public:
WashingMachine(string b, double c, string clr)
: brand(b), capacity(c), color(clr), state("待机") {}
// 行为方法
void Wash() {
state = "洗衣中";
cout << brand << "洗衣机开始洗衣..." << endl;
}
void Rinse() {
state = "脱水中";
cout << "进行脱水操作" << endl;
}
void SetMode(string mode) {
cout << "设置模式: " << mode << endl;
}
void DisplayStatus() {
cout << brand << " - 状态: " << state << endl;
}
};
步骤3:实例化对象(计算机认知)
int main() {
// 计算机现在"认识"洗衣机了
WashingMachine myWasher("海尔", 8.5, "白色"); // 创建具体的洗衣机对象
WashingMachine officeWasher("美的", 10.0, "灰色"); // 另一个洗衣机对象
myWasher.DisplayStatus(); // 输出: 海尔 - 状态: 待机
myWasher.Wash(); // 输出: 海尔洗衣机开始洗衣...
myWasher.Rinse(); // 输出: 进行脱水操作
}
步骤4:模拟现实实体
// 更完整的洗衣机模拟
class AdvancedWashingMachine {
private:
string brand;
double capacity;
int waterLevel; // 水位
int temperature; // 温度
int speed; // 转速
bool isPoweredOn;
public:
AdvancedWashingMachine(string b, double c)
: brand(b), capacity(c), waterLevel(0), temperature(0),
speed(0), isPoweredOn(false) {}
void PowerOn() {
isPoweredOn = true;
cout << brand << "洗衣机已启动" << endl;
}
void SetWaterLevel(int level) {
if (isPoweredOn) {
waterLevel = level;
cout << "水位设置为: " << level << endl;
}
}
void SetTemperature(int temp) {
if (isPoweredOn) {
temperature = temp;
cout << "温度设置为: " << temp << "°C" << endl;
}
}
void StartWash() {
if (isPoweredOn && waterLevel > 0) {
cout << "开始洗衣程序..." << endl;
cout << "容量: " << capacity << "kg, 温度: " << temperature
<< "°C, 水位: " << waterLevel << endl;
}
}
};
int main() {
// 模拟现实中的洗衣机使用
AdvancedWashingMachine washer("小天鹅", 7.5);
washer.PowerOn(); // 开机
washer.SetWaterLevel(3); // 设置水位
washer.SetTemperature(40); // 设置温度
washer.StartWash(); // 开始洗衣
}
类和对象的本质理解
类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性, 那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。
总结
内部类要点:
- 内部类是外部类的友元,可以访问外部类所有成员
- 内部类的访问权限受其在外部类中位置的控制
- 内部类可以直接访问外部类的静态成员
- 内部类不影响外部类的大小
类和对象要点:
- 类:对现实实体的抽象描述,是自定义的数据类型
- 对象:类的具体实例,占用实际内存空间
- 过程:现实抽象 → 类描述(用计算机语言,让计算机认识到这个对象) → 实例化 → 借助对象模拟现实
通过类和对象,我们能够在计算机世界中建立与现实世界的映射关系,实现更加直观和易于维护的程序设计。
xml
感谢你能够阅读到这里,如果本篇文章对你有帮助,欢迎点赞收藏支持,关注我,
我将更新更多有关C++,Linux系统·网络部分的知识。