【C++ 从基础到项目实战】C++(九):友元与设计模式初探——打破封装的艺术
阅读时长22分钟 | 关键词C、友元函数、友元类、friend、单例模式、设计模式引言前面文章中我们反复强调封装——用private把数据藏起来。但有时我们确实需要给某些外部的函数或类开一扇后门让它们能访问私有成员。这扇门就叫友元friend。文章最后我们还会用静态成员 友元的知识实现第一个设计模式——单例模式。一、友元函数类的特许通行证1.1 什么是友元函数在类中用friend声明的普通函数可以访问该类的私有成员classBox{private:doublewidth;public:Box(doublew):width(w){}friendvoidprintWidth(Boxb);// 声明友元函数};// 定义友元函数在类外部voidprintWidth(Boxb){// 可以直接访问私有成员std::cout宽度b.widthstd::endl;}intmain(){Boxbox(10.0);printWidth(box);// 输出宽度10}1.2 友元函数可以修改私有成员classCuboid{private:doublelength,width,height;public:Cuboid(doublel,doublew,doubleh):length(l),width(w),height(h){}friendvoidupdateDimensions(Cuboidc,doublel,doublew,doubleh);frienddoublecalculateVolume(constCuboidc);};voidupdateDimensions(Cuboidc,doublel,doublew,doubleh){c.lengthl;c.widthw;c.heighth;// 直接修改私有成员}doublecalculateVolume(constCuboidc){returnc.length*c.width*c.height;// 直接读取私有成员}1.3 友元函数的要点特性说明不是成员函数没有this指针通过参数传递对象声明位置类内任意位置public/protected/private 都行访问权限可访问该类的所有成员public protected private集中声明建议将友元声明集中在类的开头或结尾便于代码维护1.4 友元的优缺点优点缺点实现运算符重载 (,) 的自然语法破坏封装性多类协作时访问对方私有成员增加类之间的耦合度简化某些特殊操作的代码滥用后代码难以维护二、友元类整班都是 VIP声明一个类为另一个类的友元则该类的所有成员函数都能访问对方的私有成员classCircle{private:doubleradius;public:Circle(doubler):radius(r){}friendclassGeometry;// Geometry 是 Circle 的友元类};classGeometry{public:doublecalcArea(constCirclec){return3.14159*c.radius*c.radius;// 访问私有成员}voidsetRadius(Circlec,doubler){c.radiusr;// 修改私有成员}};intmain(){Circlec(5.0);Geometry g;std::coutg.calcArea(c)std::endl;// 78.5397g.setRadius(c,8.0);std::coutg.calcArea(c)std::endl;// 201.062}友元关系的三大特性// 1. 单向性A 是 B 的友元 ≠ B 是 A 的友元classA{friendclassB;};// B 能访问 A 的私有// A 不能访问 B 的私有 ← 除非 B 也 friend class A// 2. 非传递性A→B 是友元B→C 是友元 ≠ A→C 是友元// 3. 不能被继承父类的友元不能自动访问子类的新增私有成员三、友元 运算符重载友元最经典的用法是重载流运算符和classCuboid{private:doublelength,width,height;public:Cuboid(doublel,doublew,doubleh):length(l),width(w),height(h){}// 友元重载 运算符friendCuboidoperator(constCuboida,constCuboidb);// 友元重载 运算符friendstd::ostreamoperator(std::ostreamos,constCuboidc);};Cuboidoperator(constCuboida,constCuboidb){returnCuboid(a.lengthb.length,std::max(a.width,b.width),std::max(a.height,b.height));}std::ostreamoperator(std::ostreamos,constCuboidc){osCuboid(c.length, c.width, c.height);returnos;}intmain(){Cuboidc1(3,2,1),c2(4,1,5);std::coutc1c2std::endl;// Cuboid(7, 2, 5)}四、设计模式初探单例模式Singleton学完了静态成员 私有构造函数 友元我们已经具备了实现单例模式的能力——一个全局只能存在一个实例的类。4.1 为什么需要单例日志记录器全局共用一个数据库连接池避免重复创建连接配置管理器全局一份配置4.2 基本实现classSingleton{private:staticSingleton*instance;// 静态指针保存唯一实例Singleton(){}// ① 构造函数是私有的外部不能 newSingleton(constSingleton)delete;// ② 禁止拷贝Singletonoperator(constSingleton)delete;// ③ 禁止赋值public:staticSingleton*getInstance(){// ④ 静态方法获取唯一实例if(instancenullptr)instancenewSingleton();returninstance;}voiddoSomething(){std::cout单例模式工作中...std::endl;}};Singleton*Singleton::instancenullptr;// 静态成员定义intmain(){// Singleton s; ❌ 构造函数是私有的Singleton*s1Singleton::getInstance();Singleton*s2Singleton::getInstance();std::cout(s1s2)std::endl;// 1 — 同一个对象s1-doSomething();}4.3 C11 线程安全版Meyer’s SingletonclassSingleton{public:// C11 保证局部静态变量的初始化是线程安全的staticSingletongetInstance(){staticSingleton instance;returninstance;}voiddoSomething(){std::cout线程安全单例std::endl;}private:Singleton()default;Singleton(constSingleton)delete;Singletonoperator(constSingleton)delete;};// 使用Singleton::getInstance().doSomething();实现方式线程安全内存释放代码量原始指针 new❌不自动多Meyer’s Singleton✅ (C11)自动极少 日常开发中直接用Meyer’s Singleton简单安全无需手动 delete。4.4 设计模式思维模式核心思想C 实现关键单例 (Singleton)全局唯一实例私有构造函数 静态变量工厂 (Factory)集中创建对象静态方法 返回指针/智能指针观察者 (Observer)一对多通知虚函数 指针列表设计模式不是银弹但了解它们能让你在面对常见问题时不再重新发明轮子。小结序号知识点一句话总结1友元函数friend 声明的外部函数可访问类私有成员常用于运算符重载2友元类整个类都能访问对方的私有成员3友元三大特性单向、非传递、不能被继承4友元利弊方便但破封装只在确实需要时用5单例模式私有构造 静态变量 → 全局唯一实例6Meyer’s SingletonC11 线程安全局部静态变量自动清理推荐首选至此面向对象核心模块全部完成下一篇文章我们将进入模板编程——用泛型编程写出类型无关的高复用代码。本文是「C 从基础到项目实战」系列的第 9 篇。关注我不错过后续更新。