【C++ 从基础到项目实战】C++(九):友元与设计模式初探——打破封装的艺术

📌 阅读时长: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 篇。关注我,不错过后续更新。

相关推荐
minji...1 小时前
Linux高级IO(六)基于ET模式、单reactor反应堆的epoll版本的TCP计算服务器
linux·服务器·网络·c++·epoll·socket套接字·reactor反应堆模式
hhb_6181 小时前
Bash变量不加引号:空格文件名致命陷阱
开发语言·chrome·bash
宸津-代码粉碎机1 小时前
Spring AI企业级RAG进阶|文档智能分片调优、ES深度整合、接口限流熔断监控生产实战
java·开发语言·人工智能·后端·spring·elasticsearch·oracle
两年半的个人练习生^_^1 小时前
JVM进阶系列:彻底理解 Java 内存模型(JMM)
java·开发语言
cpp_25011 小时前
P10377 [GESP202403 六级] 好斗的牛
数据结构·c++·算法·题解·洛谷·gesp六级
邪修king1 小时前
C++ 红黑树自平衡核心:旋转变色、规则详解与 STL 选型逻辑
数据结构·c++·b树·算法
一个博客2 小时前
pdf-viewer 实现预览pdf文件
开发语言·javascript·pdf
在繁华处9 小时前
Java从零到熟练(四):面向对象基础
java·开发语言
Unbelievabletobe9 小时前
解决了股票api接口盘后数据更新慢的问题
大数据·开发语言·python