VB这题要怎么做,最好有证明题的基本步骤是,好的直接采纳!不废话!

刚刚学习java的感觉呢就是学习中發现自己学习的小小的项目里面的代码不能完全理解,有点死记硬背的感觉

不知道该从哪里学习起,我自己的基础很差

麻烦各位老鸟,提提意见哟

我喜欢上面这句话它能说明JavaScript 和 Haskell被人抱怨的原因。通过上面的分类方法我们会发现预处理也是一类被人们经常使用的语言(因为人们经常抱怨它)。人们从来没有考虑將它从C或者C++中分离出来如果这样做的话,我敢说它将会是在榜上排名第一的语言预处理器非常有用,并且无处不在事实上,如果不使用预处理器编写一些正式的跨平台的C++应用将会非常困难。

乙:我知道对的,预处理器是最糟糕的嘿,你能合并我的提交吗我添加了一些有用的宏。

我想很多人对上面的谈话都会很熟悉如果不认真对待,20年后我们可能还会听到这样的对话不幸的是,这种长久存茬也许是预处理器唯一可认为的优点(redeeming quality)换句话说,我的问题很简单既不是什么理论的,哲学的也不是什么理想的不着边的问题,是非常实际的问题

我不在意有人用预处理器不做任何检查就进行标识符,关键字替换(有人认为这是非法的)也不在意预处理器尝试做箌而实际上却不能合适的处理逗号的问题。我甚至不在意includes和includes guards我对#pragma也没意见。多数时候你必须务实(pragmatic)冷静的对待现在的一切。

下面我將列举一种预处理器的使用场景你可能会认为这种做法是我瞎编乱造的,但是请耐心些。现在假设你正在重构某个跨平台的应用的玳码,你决定做一些不寻常的事情比如,重命名某个函数

那是不可能的。 从来没有可能永远不会。

本质上说编译器或者其它必须鼡到的成熟的编译器前端工具都不会分析全部代码。那些未被启用的部分从没有被编译解析,被分词或者其它的任何形式的分析

那些未被启用的部分可能不是有效的C++代码,下面的是可以编译通过的:

因此如果编译器将未启用的部分考虑进来,它有可能都不能生产一个匼法的抽象语法树(AST)更糟糕的是,预处理就像它的名字所说的那样,是作为一个独立的状态进行的预处理指令可能会插入任何两個C++的标记中间(表达式或者语句),像下面所示:

其它让人同样关注的问题是编译器不可能知道哪些#ifdef 和#defines的组合可以形成一个有效的程序

提供了一系列的宏定义用来在编译期启用或者禁用某个特性。假如你不需要某个日历控件你可以通过定义#QT_NO_CALENDAR_WIDGET,这可以生成一个相对小些的②进制文件这不会起作用,事实上我怀疑这从来就没有起作用过假设,在某些时候Qt有大约100个类似的编译期配置选项考虑到程序可能采用的的配置选项的组合数,它相对于配置选项的个数以指数型的方式爆炸式的增长如果有100个编译选项,程序可能采用的的配置选项的組合为 2100也就是说可能会有 2100个程序需要测试,即使采用云端自动化测试也是非常困难的。

所有未经测试的代码是坏代码

你大概应该知噵这个著名的断言。那么那些没有编译好的代码呢

既然认为预处理是有害的,那么我们该怎么对待它呢

顺便说一下,不仅仅是预处理器有这个缺点所有的现代的处理器都有这个问题。也许我们应该避免做任何类似的上面的预处理

好吧,我们今天只谈谈该如何对待预處理指令:

  1. 优先使用常量而不是 #define

    这一条非常简单然而我还是看到很多用宏定义的常量。总是使用static const 或者constexpr而不是用define。如果你的程序的构建過程涉及到设置一系列的变量比如版本号或者git的hash值,最好考虑生成一个源文件而不是用define来定义这些构建参数

  2. 上面的片段来自Win32的API,即使對于那些非常简单的只有一行代码的功能你最好也用函数。
    如果你需要对函数的参数进行惰性运算( lazy evaluation)用lambda。下面是一个用宏解决问题嘚的反例但是宏只是最初考虑的方案之一。

  3. 妥善的将平台相关的部分放到不同的文件、类或者函数将会减少那些#indef出现在你的代码中。泹是这并没有解决我上面提出的问题当你在Linux平台工作时,你可能不太想重命名一个在windows上的符号或改变一个平台相关的符号

  4. 限制构建程序的可选配置项的数目

    如果你有可选的依赖项,这些依赖项包含软件中的某些功能最好使用插件系统或者将你的工程分成几个不同的,楿互独立的构建组件和程序而不是在缺少某些依赖时使用#ifdef来禁用这些代码路径。确保测试覆盖了包含或不包含这些依赖的所有程序为叻避免这些麻烦,可以考虑从不将依赖设置为可选的

    某些代码真的只运行在release版本吗?

    避免Debug/Release版本有太多的代码路径记住,未经编译测试嘚代码是坏代码

    某个特性真的应该禁用吗?

    比起依赖特性或功能(feature)在使用预处理方面应该更严格一些,特性从不应该通过预编译选项设置为可选的应该通过运行时的标志或者插件系统来实现。

  5. 目前很少有编译器不支持#pragma once,使用#pragma once更容易少犯错误更简单,程序编译起来也哽快和include guard告别吧!

  6. 尽量多写些代码,而不是宏

    这一条应视情况而变多数情况下不值得因为有少数的几个C++标志重复了就将它们用宏来替换。在C++语言规则内写代码不要试图过于聪明,容忍一点点重复这样的代码可能是更可读的,更易于维护的你的IDE会感谢你这样做:)

  7. 宏用过後尽可能快的用 #undef来取消。头文件中的每个宏都应该有文档说明
    宏是没有范围的,用你的工程名大写作为宏名的前缀
    如果你在使用一个苐三方框架,比如Qt它既有较长的宏名也有较短的( signal and QT_SIGNAL ),应该尽量使用较长的特别是当它们可能作为你的API的一部分向外暴露时。不要提供短嘚宏名宏名应该能够不和其它代码冲突,比如boost::signal or std::min等

  8. 不要将ifdef块放在C++语句中间

    上面的代码有几个问题:难阅读,难维护clang-format等工具也不好处理咜,另外有时它也可能是坏代码。
    可以像下面这样写成两个不同的语句:

    在某些情况下这样做可能比较困难这也表明在这样的情况下伱应该将你的代码切分为更多的函数或者最好抽出这些打算条件编译的代码为函数。

上面讲的那些建议是对任意版本的C++都适用的如果你使用的是一个较新的编译器,有更多的方法来帮助你减少日常宏的使用

  1. 使用modules可以节省编译时间,也可以为宏划定范围这样各个modules内部的宏就不会互相影响。在2018年初除了GCC, MSVC和clang实现了module或者正在实现,在生产环境中还没有可以用的编译器支持modules尽管大家都没有相关的经验,我们還是可以期望使用module可以让工具开发更容易更方便的启用一些新特性,比如在符号缺失时自动包含相应modules自动清除不需要的modules等。

  2. 当未启用嘚代码路径组织良好时(没有引用未知符号时)if constexpr 相对于 #ifdef是个更好的选择,因为这样做的话未启用的代码路径将依然是AST的一部分,可以被编译器或者其它静态分析工具或者代码重构工具检查

一些编译器提供其它相对于宏更好的选择,但是现实点你可能最终仍然不得不訴诸于预处理器。但是幸运的是我们仍然有许多可以改善的地方:

  1. 将 -D 用编译器定义的变量替换
    -D 是最常用的方法来查询系统的编译环境:Debug/Release,或系统架构或优化选项等。
    我们可以想象下利用一些列的常量通过std::compiler来获取这些信息,像下面这样:

    用同样的方式我们可以想象有┅些编译器外部的,声明在源码中的常量但是它们在编译器中被定义或者已经定义的值被覆写的。这将比像constexpr x = SOME_DEFINE这样定义要好如果有一种方式能够限制这些变量的取值,像下面这样:

    我希望能够给编译器更多的信息让它能够知道这些各种各样的配置变量的作用,甚至编译器能够判断这些变量的哪些组合是合法的这样我们可以更好的对源码建模,因此可以提供更好的工具和静态分析

  2. Rust社区只要有机会都会鈈遗余力的强力推销Rust语言中的优点。确实Rust在不少事情做得非常好。编译器可配置是其中的一项:

    通过使用一个 attribute 系统在编译单元中条件包含某个符号是一个非常有意思的想法
    首先,它非常可读并且也是自文档化()的,其次即使某个符号没有包含在构建单元中,我们也鈳以尝试着去解析它更重要的是,唯一的声明为编译器提供了相关实体足够的信息以实现强大的工具静态分析和重构。

    它有一个非常恏的特性:它的组织非常好由于编译器知道

    上面编译器将只能够解析等号左侧的,因为静态分析或工具不需要其它部分

    静态分析只需偠索引类名和公共成员。

    当然在当前生效的代码路径下引用一个废弃的声明是非法的,但是如果配置正确编译器将会确保这从不会发苼。当然这需要耗费一些计算资源,但这是值得的你将得到强有力的保证:你的代码是合法的。这样你在Linux上写的代码也将很难把你嘚windows build搞挂。

    然而这也不是看上去那么简单。如果废弃的实体内部包含当前编译器不知道的语法可能是某个编译器厂商提供的扩展或者一些新的C++特性,会发生什么呢我认为解析基于尽力而为的策略,当某个解析失败时编译器能够跳过当前的语句并且给出那块源码信息不能通过编译的警告信息,这是合理的“我不能够重命名110行到130行间的Foo函数”这样的提示信息比“我重命名了一些Foo函数的使用,但可能不是铨部你最好手动浏览整个工程,真的别烦编译器了,用grep解决吧”这样的提示信息要好得多

  3. 通过反射生存代码和符号

    元数据类(metaclasses)的提案是自 modules和concepts后最好的提案。特别的提案 是一篇在许多方面都非常好的文章

    它引入了非常多的基础设施,其中一个是 declname 关键字它可以通过任意字母和数字的组合构建标识符。

    int declname(“foo”, 42) = 0; 创建变量 foo42 考虑到使用宏最多的情况是通过字符串的拼接来生成新的标识符是, 这个特性确实非常囿意思。希望编译器在这种情况下对于生成的符号有足够的信息来恰当的索引这些符号

    不受好评的 在未来应该称为历史。

  4. 要摆脱对宏的依赖我们需要一种新的类型的宏

    因为宏的本质是文本替换,它们的参数采用的是惰性求值尽管我们可以使用lambda来模拟这种行为,但是这昰非常麻烦的(cumbersome)那么,我们还可以利用函数的惰性求值吗

    我的想法是使用代码注入这个工具来创建某种新的“宏”,由于想不到更恏的名字我称之为“语法宏”。本质上如果你为你的一段代码(一段你可以在某个时候在程序的某个地方注入的代码)起一个名字,尣许这段代码有几个参数这样你就为自己准备了一个宏。但是这个宏是在语义方面做的检查而不是像预处理器那样在源码的token方面做的检查

    那这怎么工作呢,相关 如下 :

    好吧我们看看上面发生了什么:

    我们首先用 constexpr { } 创建了一个 constexpr 块。这是元数据类(metaclasses)的一部分一个 constexpr 块是一個混合的语句块,在这个语句块里面所有的变量都必须是 constexpr并且没有其它副作用。这个块的唯一作用是创建注入的片段在编译时修改块實体的属性。(元数据类就是基于 constexpr 块的语法糖其实我想说我们事实上不需要元数据类)

    在 constexpr 块内我们定义了一个宏log。注意到这个宏不是函數它们将会展开为代码,它们不返回任何东西也不在栈上存在log就是一个可以被限定的标志符,在同一个块内不能是任何其它实体的名芓“语法宏”和其它标志符一样遵循同样的名字查找规则。

    这里使用 -> 作为注入符 -> 用来描述所有代码注入相关的操作,和 -> 本来的使用方法不冲突在上面的例子中,log就是一个实现了代码注入的“语法宏”我们将其定义为log->(){…} 。

    “语法宏”的内部本身是一个可以包含任意C++表達式的 constexpr 块这个 constexpr 块可以在一个 constexpr 的语境中进行运算。

    “语法宏”可能包含0个1个或多个用 -> {}表示的注入语句。一个注入语句创建一个代码片段在宏被调用时立刻注入,也就是说当“语法宏”展开的地方,相关的注入语句立刻注入

    一个“语法宏”可能注入一个表达式或者0个戓者多个语句。一个注入表达式的“语法宏”只能在需要这个表达式的地方展开

    尽管“语法宏”没有任何类型,它有有编译器决定的某種属性

    你可以传递任何参数到一个“语法宏”,就像你往一般的函数传递参数那样参数在展开前就运算了,是一种强数据类型

    然而,你也可以在表达式中传递反射这假设我们能够对任意表达式取反射。作用于表达式e的反射的类型是 decltype(e)

    宏的最后的一个魔法是宏展开前表达式将会隐式的转换为它们的反射。

    从根本上说我们是在移动AST的各个节点,这和当前反射和代码注入是一致的

    从上面的例子,log->(“Hello %”, “World”)看上去像一个常规的返回类型为空的函数调用不同的是这里的 -> 表示这里有宏展开。

    最后在计算之前标志符能够作为参数传递可能會减少对关键字的依赖,例如:

“语法宏”不可以完全替代预处理器上的宏也不打算这么做。最主要的是因为“语法宏”必须是合法嘚C++代码,它们将会在多个时候被审查(在定义时展开前,展开时展开后),它们也禁止 token soup它们必须是合法的C++:注入合法的C++同时也要用匼法的C++作为参数。

这意味着我们不能注入部分语句操纵部分语句或者将任意语句作为参数。

它们确实解决了惰性求值和条件编译的问题例如,你不能利用它们实现foreach是因为 for(; ; )不是一个完整的语句,for(; ; );和for(; ; ) {}是但是这没有什么作用。

有很多关于名字查找的问题一个宏应该看箌它展开的上下文吗? 参数应该感知它自己在一个宏内部吗

我认为有局限是好事。如果你真的需要创造一些新的C++基础设施,也许目前的语訁正好缺少这部分在这种情况下,我建议你写一个提案或者你需要一个代码生成器,或者只是一些更多的抽象或者更多实际的代码洏已。

“语法宏”非常奇妙并且它也绝对没有包含在目前已有的C++提案中,我确信这可能会是代码注入特性的一个有逻辑的演变

它看起来有点像,但是它又不支持任意语句它看起来就是C++的一部分,而不是有单独语法的另一门语言

预处理器看起来当然是容易犯错的。但是你可以做很多事来使你更少的依赖它C++社区也可以通过提供更好的宏的替代品来使宏逐渐变得不那么有用。

这可能要花数十年的時间但是这将是值得的。不仅仅是因为宏从根本上来说是糟糕的更因为工具目前是,将来也会更多的依赖语言本身而不是糟糕的文夲替换。

因为我们真的需要更好的工具我们需要尽力减少我们对预处理器的依赖。


// 为onmessage事件绑定监听器接收消息
// 接收、并显示消息

// 当客户端连接进来时自动激发该方法
//控制对尖括号等特殊字符进行转义
两个代码都没提示错误但是为什么功能不能正常运荇的?求大神帮忙

我要回帖

更多关于 VB编程的一般步骤 的文章

 

随机推荐