类设计--友元+静态成员+对象之间的关系
一.友元
1. 核心定义
友元是 C++ 提供的打破封装的机制:允许外部函数 / 类访问一个类的私有 (private) / 保护 (protected)成员。
2. 三大友元类型(必考)
(1)全局函数作为友元
在类内部声明friend+ 全局函数原型,该全局函数就能访问类私有成员。
class Person { // 声明全局函数为友元(位置无要求,习惯写顶部) friend void showPerson(Person&); friend ostream& operator<<(ostream&, Person&); private: int pid; string name; public: Person(int pid, string name) : pid(pid), name(name) {} }; // 友元全局函数:可以直接访问私有成员 void showPerson(Person& p) { cout << p.pid << " " << p.name << endl; } // 运算符重载<< 常用友元写法(必须全局/友元) ostream& operator<<(ostream& cout, Person& p) { cout << p.pid << " " << p.name; return cout; }(2)类的成员函数作为友元
让另一个类的某个成员函数可以访问当前类的私有成员。
class Point; class Line { public: void draw(Point& p); // 仅声明 }; class Point { // 声明 Line::draw 为友元 friend void Line::draw(Point&); private: int x, y; }; void Line::draw(Point& p) { cout << p.x << p.y; // 可访问私有 }(3)类作为友元(友元类)
整个类都可以访问当前类的私有成员(最常用)。
class Point { friend class Line; // Line 是 Point 的友元类 private: int x, y; }; class Line { public: void test(Point& p) { p.x = 10; // 直接访问私有 } };3. 友元高频考点 / 面试题
- 友元是单向的:A 是 B 的友元 ≠ B 是 A 的友元
- 友元不传递:A 是 B 友元,B 是 C 友元 ≠ A 是 C 友元
- 友元不能继承:子类无法继承父类的友元关系
- 友元不是成员函数:没有 this 指针
- 作用:运算符重载(
<</>>)、跨类访问、提高效率 - 缺点:破坏封装性,慎用
二.类的静态成员
1. 核心概念
- 静态成员:被
static修饰的成员,属于类,不属于任何对象 - 所有对象共享同一份静态成员
- 静态成员存放在全局数据区,不占用对象内存空间
2. 静态成员变量
语法规则
- 类内声明,类外定义 / 初始化
- 定义时不能加 static
- 访问方式:
类名::成员或对象.成员
class Math { private: // 1. 类内声明静态变量 static float PI; public: // 静态常量:C++11 可直接类内初始化(必须 constexpr/const) static constexpr int R = 2; }; // 2. 类外定义初始化(无static) float Math::PI = 3.1415; // 访问 Math::R; // 推荐 Math().getPI();3. 静态成员函数
- 属于类,无 this 指针
- 只能访问静态成员(静态变量 / 静态函数)
- 不能访问非静态成员(非静态变量 / 函数)
- 调用:
类名::函数()或对象.函数()
class Tool { private: int x; // 非静态 static int cnt; // 静态 public: static void show() { // x++; 错误!无this,不能访问非静态 cnt++; // 正确 } };4. 静态成员高频考点
- 静态成员不占用对象大小,
sizeof不计入 - 静态变量生命周期:程序整个运行期
- 静态函数无 this 指针,不能用
const修饰 - 静态常量:
static const / static constexpr可类内初始化
三.静态的应用:单例模式(singleton)
单例模式确保一个类在程序中只有一个实例,并提供全局访问点。字节面试常考 “线程安全的单例实现”。
1. 实现核心要点
- 构造函数私有化(禁止外部创建对象)。
- 拷贝构造函数和赋值运算符
delete(禁止拷贝)。 - 提供静态成员函数获取唯一实例。
2. 两种经典实现
饥汉模式(饿汉模式):程序启动时就创建实例(静态初始化)。
class Singleton { private: Singleton() = default; // 私有构造 Singleton(const Singleton&) = delete; // 禁止拷贝 Singleton& operator=(const Singleton&) = delete; // 禁止赋值 static Singleton instance; // 静态实例(程序启动时初始化) public: static Singleton& getInstance() { return instance; } }; Singleton Singleton::instance; // 类外定义静态实例- 优点:线程安全(静态初始化由编译器保证,在
main函数前完成)。 - 缺点:如果实例很大且未被使用,会浪费资源;程序启动速度变慢。
- 优点:线程安全(静态初始化由编译器保证,在
懒汉模式:第一次调用
getInstance时才创建实例(延迟加载)。class Singleton { private: Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton* instance; // 静态指针 public: static Singleton* getInstance() { if (instance == nullptr) { // 第一次检查(避免不必要的加锁) instance = new Singleton(); // 延迟创建 } return instance; } }; Singleton* Singleton::instance = nullptr; // 初始化为空- 优点:延迟加载,节省资源。
- 缺点:线程不安全(多线程下可能创建多个实例)。
3. 面试高频考点:线程安全的懒汉模式
- 双重检查锁定(DCL, Double-Checked Locking):需配合
volatile和内存屏障(C++11 起推荐用std::call_once或静态局部变量)。#include <mutex> class Singleton { private: Singleton() = default; static Singleton* volatile instance; // volatile防止指令重排 static mutex mtx; public: static Singleton* getInstance() { if (instance == nullptr) { // 第一次检查(无锁) lock_guard<mutex> lock(mtx); // 加锁 if (instance == nullptr) { // 第二次检查(有锁) instance = new Singleton(); } } return instance; } }; - C++11 静态局部变量(推荐):静态局部变量的初始化在 C++11 中是线程安全的(编译器保证)。
class Singleton { private: Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; public: static Singleton& getInstance() { static Singleton instance; // 静态局部变量(第一次调用时初始化) return instance; } }; - 内存泄漏问题:懒汉模式中
new的实例未被delete。解决方法:使用std::unique_ptr或嵌套类的静态对象析构时回收。
四.类对象之间的关系
1. 依赖关系(Dependency):“使用 a”
- 定义:一个类的成员函数临时使用另一个类的对象(最弱的关系)。
- 代码表现:成员函数的参数是另一个类的对象 / 引用 / 指针,或函数内部使用另一个类的对象。
- 例子:
Account的pushMoney函数依赖BankCard。class BankCard { private: int balance; public: BankCard(int v) : balance(v) {} }; class Account { public: // 依赖:BankCard作为参数 void pushMoney(BankCard& card, int m) { card.balance += m; // 需友元支持访问私有成员 } }; - UML:虚线箭头,从使用者指向被使用者。
2. 关联关系(Association):“有 a”
- 定义:一个类将另一个类作为成员属性(结构关系)。
- 分类:单向关联(A 有 B 的成员)、双向关联(A 和 B 互相有对方的成员)。
- 例子:
Account有PCard指针作为成员。class PCard; // 前向声明 class Account { private: PCard* pcard; // 单向关联:Account有PCard的指针 }; - UML:实线箭头(单向)或实线(双向)。
3. 聚合关系(Aggregation):“整体 - 部分,部分可独立”
- 定义:特殊的关联,体现 “整体与部分” 的关系,但部分可以独立于整体存在(整体不拥有部分的生命周期)。
- 代码表现:整体类的构造函数接收部分类的引用 / 指针,且析构时不销毁部分类。
- 例子:
Line聚合Point(Point可单独存在,Line不负责销毁Point)。class Point { private: int x, y; public: Point(int x, int y) : x(x), y(y) {} }; class Line { private: Point& p1; // 引用:Point是外部创建的 Point& p2; public: Line(Point& p1, Point& p2) : p1(p1), p2(p2) {} // 接收外部Point // 析构函数不delete p1/p2(Point生命周期独立) }; - UML:空心菱形 + 实线箭头(菱形指向整体)。
4. 组合关系(Composition):“强整体 - 部分,部分不可独立”
- 定义:强聚合,“整体与部分” 关系更紧密,部分不能独立于整体存在(整体拥有部分的生命周期)。
- 代码表现:整体类的构造函数中
new部分类,析构函数中delete部分类。 - 例子:
Line组合Point(Point由Line创建和销毁)。class Point { private: int x, y; public: Point(int x, int y) : x(x), y(y) {} }; class Line { private: Point* p1; // 指针:Line负责创建和销毁 Point* p2; public: Line(int x1, int y1, int x2, int y2) { p1 = new Point(x1, y1); // 整体创建部分 p2 = new Point(x2, y2); } ~Line() { delete p1; // 整体销毁部分 delete p2; } }; - UML:实心菱形 + 实线箭头(菱形指向整体)。
5. 面试高频考点:四种关系的区别
| 关系 | 语义 | 代码表现 | 生命周期关联 |
|---|---|---|---|
| 依赖 | 使用 a | 函数参数 / 局部变量 | 无 |
| 关联 | 有 a | 成员变量 | 无 |
| 聚合 | 整体 - 部分(独立) | 成员变量(引用 / 外部指针) | 部分独立于整体 |
| 组合 | 整体 - 部分(不独立) | 成员变量(内部 new/delete) | 部分由整体管理 |
