📌 阅读时长:22分钟 | 关键词:C++、友元函数、友元类、friend、单例模式、设计模式
引言
前面文章中我们反复强调"封装"------用 private 把数据藏起来。但有时,我们确实需要给某些外部的函数或类开一扇"后门",让它们能访问私有成员。这扇门就叫友元(friend) 。文章最后,我们还会用静态成员 + 友元的知识,实现第一个设计模式------单例模式。
一、友元函数:类的"特许通行证"
1.1 什么是友元函数?
在类中用 friend 声明的普通函数,可以访问该类的私有成员:
cpp
class Box {
private:
double width;
public:
Box(double w) : width(w) {}
friend void printWidth(Box &b); // 声明友元函数
};
// 定义友元函数(在类外部)
void printWidth(Box &b) {
// 可以直接访问私有成员!
std::cout << "宽度:" << b.width << std::endl;
}
int main() {
Box box(10.0);
printWidth(box); // 输出:宽度:10
}
1.2 友元函数可以修改私有成员
cpp
class Cuboid {
private:
double length, width, height;
public:
Cuboid(double l, double w, double h) : length(l), width(w), height(h) {}
friend void updateDimensions(Cuboid &c, double l, double w, double h);
friend double calculateVolume(const Cuboid &c);
};
void updateDimensions(Cuboid &c, double l, double w, double h) {
c.length = l; c.width = w; c.height = h; // 直接修改私有成员
}
double calculateVolume(const Cuboid &c) {
return c.length * c.width * c.height; // 直接读取私有成员
}
1.3 友元函数的要点
| 特性 | 说明 |
|---|---|
| 不是成员函数 | 没有 this 指针,通过参数传递对象 |
| 声明位置 | 类内任意位置(public/protected/private 都行) |
| 访问权限 | 可访问该类的所有成员(public + protected + private) |
| 集中声明 | 建议将友元声明集中在类的开头或结尾,便于代码维护 |
1.4 友元的优缺点
| 优点 | 缺点 |
|---|---|
实现运算符重载 (<<, >>) 的自然语法 |
破坏封装性 |
| 多类协作时访问对方私有成员 | 增加类之间的耦合度 |
| 简化某些特殊操作的代码 | 滥用后代码难以维护 |
二、友元类:整班都是 VIP
声明一个类为另一个类的友元,则该类的所有成员函数都能访问对方的私有成员:
cpp
class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
friend class Geometry; // Geometry 是 Circle 的友元类
};
class Geometry {
public:
double calcArea(const Circle &c) {
return 3.14159 * c.radius * c.radius; // 访问私有成员
}
void setRadius(Circle &c, double r) {
c.radius = r; // 修改私有成员
}
};
int main() {
Circle c(5.0);
Geometry g;
std::cout << g.calcArea(c) << std::endl; // 78.5397
g.setRadius(c, 8.0);
std::cout << g.calcArea(c) << std::endl; // 201.062
}
友元关系的三大特性
cpp
// 1. 单向性:A 是 B 的友元 ≠ B 是 A 的友元
class A { friend class B; }; // B 能访问 A 的私有
// A 不能访问 B 的私有 ← 除非 B 也 friend class A
// 2. 非传递性:A→B 是友元,B→C 是友元 ≠ A→C 是友元
// 3. 不能被继承:父类的友元不能自动访问子类的新增私有成员
三、友元 + 运算符重载
友元最经典的用法是重载流运算符 << 和 >>:
cpp
class Cuboid {
private:
double length, width, height;
public:
Cuboid(double l, double w, double h) : length(l), width(w), height(h) {}
// 友元重载 + 运算符
friend Cuboid operator+(const Cuboid &a, const Cuboid &b);
// 友元重载 << 运算符
friend std::ostream &operator<<(std::ostream &os, const Cuboid &c);
};
Cuboid operator+(const Cuboid &a, const Cuboid &b) {
return Cuboid(a.length + b.length,
std::max(a.width, b.width),
std::max(a.height, b.height));
}
std::ostream &operator<<(std::ostream &os, const Cuboid &c) {
os << "Cuboid(" << c.length << ", " << c.width << ", " << c.height << ")";
return os;
}
int main() {
Cuboid c1(3, 2, 1), c2(4, 1, 5);
std::cout << c1 + c2 << std::endl; // Cuboid(7, 2, 5)
}
四、设计模式初探:单例模式(Singleton)
学完了静态成员 + 私有构造函数 + 友元,我们已经具备了实现单例模式 的能力------一个全局只能存在一个实例的类。
4.1 为什么需要单例?
- 日志记录器:全局共用一个
- 数据库连接池:避免重复创建连接
- 配置管理器:全局一份配置
4.2 基本实现
cpp
class Singleton {
private:
static Singleton *instance; // 静态指针,保存唯一实例
Singleton() {} // ① 构造函数是私有的!外部不能 new
Singleton(const Singleton &) = delete; // ② 禁止拷贝
Singleton &operator=(const Singleton &) = delete; // ③ 禁止赋值
public:
static Singleton *getInstance() { // ④ 静态方法获取唯一实例
if (instance == nullptr)
instance = new Singleton();
return instance;
}
void doSomething() {
std::cout << "单例模式工作中..." << std::endl;
}
};
Singleton *Singleton::instance = nullptr; // 静态成员定义
int main() {
// Singleton s; ❌ 构造函数是私有的
Singleton *s1 = Singleton::getInstance();
Singleton *s2 = Singleton::getInstance();
std::cout << (s1 == s2) << std::endl; // 1 --- 同一个对象!
s1->doSomething();
}
4.3 C++11 线程安全版(Meyer's Singleton)
cpp
class Singleton {
public:
// C++11 保证局部静态变量的初始化是线程安全的!
static Singleton &getInstance() {
static Singleton instance;
return instance;
}
void doSomething() {
std::cout << "线程安全单例" << std::endl;
}
private:
Singleton() = default;
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
};
// 使用:
Singleton::getInstance().doSomething();
| 实现方式 | 线程安全 | 内存释放 | 代码量 |
|---|---|---|---|
| 原始指针 + new | ❌ | 不自动 | 多 |
| Meyer's Singleton | ✅ (C++11) | 自动 | 极少 |
💡 日常开发中直接用 Meyer's Singleton,简单安全,无需手动 delete。
4.4 设计模式思维
| 模式 | 核心思想 | C++ 实现关键 |
|---|---|---|
| 单例 (Singleton) | 全局唯一实例 | 私有构造函数 + 静态变量 |
| 工厂 (Factory) | 集中创建对象 | 静态方法 + 返回指针/智能指针 |
| 观察者 (Observer) | 一对多通知 | 虚函数 + 指针列表 |
设计模式不是银弹,但了解它们能让你在面对常见问题时不再"重新发明轮子"。
小结
| 序号 | 知识点 | 一句话总结 |
|---|---|---|
| 1 | 友元函数 | friend 声明的外部函数可访问类私有成员,常用于运算符重载 |
| 2 | 友元类 | 整个类都能访问对方的私有成员 |
| 3 | 友元三大特性 | 单向、非传递、不能被继承 |
| 4 | 友元利弊 | 方便但破封装,只在确实需要时用 |
| 5 | 单例模式 | 私有构造 + 静态变量 → 全局唯一实例 |
| 6 | Meyer's Singleton | C++11 线程安全,局部静态变量自动清理,推荐首选 |
至此,面向对象核心模块全部完成!下一篇文章,我们将进入模板编程------用泛型编程写出类型无关的高复用代码。
本文是「C++ 从基础到项目实战」系列的第 9 篇。关注我,不错过后续更新。