Effective C++ 2e Item28

博客主要讲述了C++全局名字空间存在的问题,如易导致名字冲突。介绍了划分全局名字空间的方法,包括加前缀和使用namespace,还说明了访问名字空间符号的三种方式。同时提到在编译器不支持时可用struct近似实现,不过struct存在诸多限制,编译器支持时应尽早用真正的名字空间。

条款28: 划分全局名字空间

全局空间最大的问题在于它本身仅有一个。在大的软件项目中,经常会有不少人把他们定义的名字都放在这个单一的空间中,从而不可避免地导致名字冲突。例如,假设library1.h定义了一些常量,其中包括:

const double LIB_VERSION = 1.204;

类似的,library2.h也定义了:

const int LIB_VERSION = 3;

很显然,如果某个程序想同时包含library1.h和library2.h就会有问题。对于这类问题,你除了嘴里骂几句,或给作者发报复性邮件,或自己编辑头文件来消除名字冲突外,也没其它什么办法。

但是,作为程序员,你可以尽力使自己写的程序库不给别人带来这些问题。例如,可以预先想一些不大可能造成冲突的某种前缀,加在每个全局符号前。当然得承认,这样组合起来的标识符看起来不是那么令人舒服。

另一个比较好的方法是使用C++ namespace。namespace本质上和使用前缀的方法一样,只不过避免了别人总是看到前缀而已。所以,不要这么做:

const double sdmBOOK_VERSION = 2.0;      // 在这个程序库中,
                                         // 每个符号以"sdm"开头
class sdmHandle { ... };                

sdmHandle& sdmGetHandle();             // 为什么函数要这样声明?
                                       // 参见条款47

而要这么做:

namespace sdm {
  const double BOOK_VERSION = 2.0;
  class Handle { ... };
  Handle& getHandle();
}

用户于是可以通过三种方法来访问这一名字空间里的符号:将名字空间中的所有符号全部引入到某一用户空间;将部分符号引入到某一用户空间;或通过修饰符显式地一次性使用某个符号:

void f1()
{
  using namespace sdm;           // 使得sdm中的所有符号不用加
                                 // 修饰符就可以使用

  cout << BOOK_VERSION;          // 解释为sdm::BOOK_VERSION
  ...

  Handle h = getHandle();        // Handle解释为sdm::Handle,
                                 // getHandle解释为sdm::getHandle
  ...                           

}

void f2()
{
  using sdm::BOOK_VERSION;        // 使得仅BOOK_VERSION不用加
                                 // 修饰符就可以使用

  cout << BOOK_VERSION;           // 解释为
                                  // sdm::BOOK_VERSION
  ...

  Handle h = getHandle();         // 错误! Handle和getHandle
                                  // 都没有引入到本空间
  ...                            

}

void f3()
{
  cout << sdm::BOOK_VERSION;      // 使得BOOK_VERSION
                                  // 在本语句有效
  ...                            

  double d = BOOK_VERSION;        // 错误! BOOK_VERSION
                                  // 不在本空间

  Handle h = getHandle();         // 错误! Handle和getHandle
                                  // 都没有引入到本空间
  ...                           

}

(有些名字空间没有名字。这种没命名的名字空间一般用于限制名字空间内部元素的可见性。详见条款M31。)

名字空间带来的最大的好处之一在于:潜在的二义不会造成错误(参见条款26)。所以,从多个不同的名字空间引入同一个符号名不会造成冲突(假如确实真的从不使用这个符号的话)。例如,除了名字空间sdm外,假如还要用到下面这个名字空间:

namespace AcmeWindowSystem {

  ...

  typedef int Handle;

  ...

}

只要不引用符号Handle,使用sdm和AcmeWindowSystem时就不会有冲突。假如真的要引用,可以明确地指明是哪个名字空间的Handle:

void f()
{
  using namespace sdm;                 // 引入sdm里的所有符号
  using namespace AcmeWindowSystem;    // 引入Acme里的所有符号

  ...                                  // 自由地引用sdm
                                       // 和Acme里除Handle之外
                                       // 的其它符号

  Handle h;                            // 错误! 哪个Handle?

  sdm::Handle h1;                      // 正确, 没有二义

  AcmeWindowSystem::Handle h2;         // 也没有二义

  ...

}

假如用常规的基于头文件的方法来做,只是简单地包含sdm.h和acme.h,这样的话,由于Handle有多个定义,编译将不能通过。

名字空间的概念加入到C++标准的时间相对较晚,所以有些人会认为它不太重要,可有可无。但这种想法是错误的,因为C++标准库(参见条款49)里几乎所有的东西都存在于名字空间std之中。这可能令你不以为然,但它却以一种直接的方式影响到你:这就是为什么C++提供了那些看起来很有趣的、没有扩展名的头文件,如<iostream>, <string>等。详细介绍参见条款49。

由于名字空间的概念引入的时间相对较晚,有些编译器可能不支持。就算是这样,那也没理由污染全局名字空间,因为可以用struct来近似实现namespace。可以这样做:先创建一个结构用以保存全局符号名,然后将这些全局符号名作为静态成员放入结构中:

// 用于模拟名字空间的一个结构的定义
struct sdm {
  static const double BOOK_VERSION;
  class Handle { ... };
  static Handle& getHandle();
};

const double sdm::BOOK_VERSION = 2.0;      // 静态成员的定义

现在,如果有人想访问这些全局符号名,只用简单地在它们前面加上结构名作为前缀:

void f()
{
  cout << sdm::BOOK_VERSION;

  ...

  sdm::Handle h = sdm::getHandle();

  ...
}

但是,如果全局范围内实际上没有名字冲突,用户就会觉得加修饰符麻烦而多余。幸运的是,还是有办法来让用户选择使用它们或忽略它们。

对于类型名,可以用类型定义(typedef)来显式地去掉空间引用。例如,假设结构s(模拟的名字空间)内有个类型名T,可以这样用typedef来使得T成为S::T的同义词:

typedef sdm::Handle Handle;

对于结构中的每个(静态)对象X,可以提供一个(全局)引用X,并初始化为S::X:

const double& BOOK_VERSION = sdm::BOOK_VERSION;

老实说,如果读了条款47,你就会不喜欢定义一个象BOOK_VERSION这样的非局部静态对象。(你就会用条款47中所介绍的函数来取代这样的对象)

处理函数的方法和处理对象一样,但要注意,即使定义函数的引用是合法的,但代码的维护者会更喜欢你使用函数指针:

sdm::Handle& (* const getHandle)() =      // getHandle是指向sdm::getHandle
  sdm::getHandle;                         // 的const 指针 (见条款21)

注意getHandle是一个常指针。因为你当然不想让你的用户将它指向别的什么东西,而不是sdm::getHandle,对不对?

(如果真想知道怎么定义一个函数的引用,看看下面:

sdm::Handle& (&getHandle)() =      // getHandle是指向
  sdm::getHandle;                  // sdm::getHandle的引用

我个人认为这样的做法也很好,但你可能以前从没见到过。除了初始化的方式外,函数的引用和函数的常指针在行为上完全相同,只是函数指针更易于理解。)

有了上面的类型定义和引用,那些不会遭遇全局名字冲突的用户就会使用没有修饰符的类型和对象名;相反,那些有全局名字冲突的用户就会忽略类型和引用的定义,代之以带修饰符的符号名。还要注意的是,不是所有用户都想使用这种简写名,所以要把类型定义和引用放在一个单独的头文件中,不要把它和(模拟namespace的)结构的定义混在一起。

struct是namespace的很好的近似,但实际上还是相差很远。它在很多方面很欠缺,其中很明显的一点是对运算符的处理。如果运算符被定义为结构的静态成员,它就只能通过函数调用来使用,而不能象常规的运算符所设计的那样,可以通过自然的中缀语法来使用:

// 定义一个模拟名字空间的结构,结构内部包含Widgets的类型
// 和函数。Widgets对象支持operator+进行加法运算
struct widgets {
  class Widget { ... };


  // 参见条款21:为什么返回const
  static const Widget operator+(const Widget& lhs,
                                const Widget& rhs);

  ...

};

// 为上面所述的Widge和operator+
// 建立全局(无修饰符的)名称

typedef widgets::Widget Widget;


const Widget (* const operator+)(const Widget&,        // 错误!
                                 const Widget&);       // operator+不能是指针名
 

Widget w1, w2, sum;

sum = w1 + w2;                           // 错误! 本空间没有声明
                                         // 参数为Widgets 的operator+

sum = widgets::operator+(w1, w2);        // 合法, 但不是
                                         // "自然"的语法

正因为这些限制,所以一旦编译器支持,就要尽早使用真正的名字空间。

内容概要:本文提出了一种基于非合作博弈理论的居民负荷分层调度模型,并结合双层鲸鱼优化算法(Two-level Whale Optimization Algorithm)进行高效求解,模型算法均通过Matlab代码实现。研究针对电力系统中居民侧用电负荷的复杂调度问题,引入非合作博弈机制刻画各用户之间的利益竞争关系,实现负荷的分层优化分配;同时设计双层优化架构,上层优化资源配置,下层模拟用户自主决策行为,提升了模型的实用性合理性。通过智能优化算法求解多层级、非凸非线性的博弈模型,有效提高了调度方案的收敛性全局寻优能力,适用于现代智能电网中的需求侧管理能源优化场景。; 适合人群:具备电力系统基础理论知识和Matlab编程能力,从事智能电网、能源优化调度、需求侧管理、博弈论应用等方向的科研人员、高校研究生及工程技术人员。; 使用场景及目标:①应用于居民区电力负荷的分层优化调度系统设计仿真分析;②为非合作博弈在多主体能源系统建模中的应用提供方法论支持;③利用双层鲸鱼算法解决具有嵌套结构的复杂双层优化问题,提升求解效率调度方案的可行性。; 阅读建议:建议读者结合提供的Matlab代码深入理解模型构建逻辑算法实现流程,重点关注博弈模型的效用函数设计、纳什均衡求解思路以及双层优化结构的迭代机制,宜配合实际用电数据开展复现实验以验证模型有效性鲁棒性。
内容概要:本文围绕基于自适应神经模糊推理系统(ANFIS)智能控制器的可再生能源微电网功率管理系统展开研究,结合Simulink仿真实现,深入探讨了微电网中功率的智能调控经济机组组合调度问题。通过引入ANFIS控制器,有效应对风能、光伏等可再生能源出力的波动性不确定性,提升系统运行的稳定性电能质量。研究内容涵盖微电网多源协调控制策略、功率平衡管理、优化调度模型构建及仿真验证,实现了对分布式电源、储能系统和负荷的协同优化,兼顾经济性可靠性目标,并通过仿真平台验证了所提方法的有效性优越性。; 适合人群:具备电力系统、自动化或新能源相关专业背景,熟悉Matlab/Simulink仿真环境,从事微电网能量管理、智能控制、能源优化等领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于高比例可再生能源接入场景下的微电网能量管理系统研发教学实践;②为实现微电网功率稳定控制经济高效运行提供先进的智能控制解决方案;③支撑高水平学术论文复现、科研课题攻关及实际工程项目的仿真验证方案优化。; 阅读建议:建议结合提供的Simulink模型相关代码进行动手实践,重点关注ANFIS控制器的设计流程、规则库构建参数调优方法,并通过传统PID或MPC控制策略的对比实验,深入理解其在动态响应鲁棒性方面的优势。同时可进一步拓展文中提出的优化调度逻辑,应用于多目标、多约束的复杂实际应用场景中。
内容概要:本文档聚焦于“直流电机双闭环控制Matlab仿真”,系统阐述了基于Matlab/Simulink平台实现直流电机双闭环控制系统(主要包括速度环电流环)的设计仿真全过程。通过构建直流电机的数学模型,结合PI控制器进行调控,实现对电机转速和电枢电流的高精度动态控制,验证控制策略的稳定性响应性能。文档详细介绍了仿真模型的搭建流程、关键参数的整定方法、系统动态波形的分析手段以及仿真结果的有效性验证,体现了经典自动控制理论在实际电机系统中的工程应用,是电机控制电力电子技术相结合的典型研究案例。; 适合人群:具备自动控制原理、电机拖动基础、电力电子技术和Matlab/Simulink仿真能力的电气工程、自动化、机电一体化等专业的本科生、研究生及从事电机驱动系统研发的工程技术人员。; 使用场景及目标:①作为高校课程设计或实验教学材料,帮助学生深入理解双闭环调速系统的工作机理工程实现;②服务于科研项目,为新型电机控制算法(如滑模、模糊PID等)的开发性能对比提供基础仿真验证平台;③作为工业界产品前期设计的仿真工具,用于评估不同控制策略在动态响应、抗干扰能力和稳态精度方面的可行性。; 阅读建议:建议读者在学习过程中紧密结合自动控制理论知识,亲手在Simulink环境中搭建完整的双闭环仿真模型,通过反复调整PI控制器的比例积分参数,观察并分析转速、电流的阶跃响应曲线,从而深刻理解反馈控制的本质、系统稳定性条件以及参数整定对动态性能的影响,进而掌握电机控制系统的设计精髓。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值