“#”的迷雾

本文深入探讨了 NSDictionaryOfVariableBindings 宏的实现原理,解析了预编译指令 # 在宏定义中的作用,包括如何实现参数的字符串化及命名串联。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:http://blog.xcodev.com/blog/2013/12/16/mists-of-the-sharp/


在代码中使用Autolayout时,大家都会使用NSDictionaryOfVariableBindings这个宏,这个宏可以生成一个变量名到变量值映射的Dictionary。比如NSDictionaryOfVariableBindings(button1, button2)将会生成一个{ @"button1" = button1, @"button2 = button2 }的Dictionary。它是怎么做到的呢?我们来看看这个宏的定义:

#define NSDictionaryOfVariableBindings(...) _NSDictionaryOfVariableBindings(@"" # __VA_ARGS__, __VA_ARGS__, nil)

这个宏定义中有3个参数,后两个参数不难理解,但第一个参数中间有个#符号,语法上看起来比较怪异,这个是什么呢?以前在做越狱的mobilesubstrate开发时,其中定义的一堆宏频繁使用了这个符号,下面就来揭开#这个符号在宏定义中的迷雾。

预编译的一些知识

我们的代码在build时并不是直接进行编译的,在编译之前还进行了预编译处理。预编译会把include或import的文件导入到文件中,同时会将代码中用到的宏进行替换。注意宏是直接在代码中替换成宏的定义的,如果有嵌套也会逐层替换。

“#”指示一些预编译命令

预编译命令一般都是以#开头的,比如#include#import#if等,在这里就不一一说明了,本文主要说明一下#在宏定义里面的一些作用。

宏参数字符串化

在一个参数前加一个#,预处理时将会变成这个参数名的字符串常量,即字符串化(stringify)。比如:

#define GET_NAME(X) #X
int a = 0;
NSLog(@"%s",GET_NAME(a));      //output: "a"
NSLog(@"%s",GET_NAME(a+3));    //output: "a+3"

将会得到以下输出:

a
a+3

可以看出#,将参数原样转换成字符串常量,如果参数是一个表达式,那么输出这个表达式的原样字符串常量。

回头再看看NSDictionaryOfVariableBindings的定义:

#define NSDictionaryOfVariableBindings(...) _NSDictionaryOfVariableBindings(@"" # __VA_ARGS__, __VA_ARGS__, nil)

如果这样生成两个button的映射:

NSDictionaryOfVariableBindings(button1, button2);

那么预编译时就会转换成:

_NSDictionaryOfVariableBindings(@"""button1, button2", button1, button2, nil);

由于两个常量字符串放在一起就是字符串常量串联,将变成两个字符串常量组合在一起的字符串常量,也就是上面是一个空字符串"""button1, button2"串联,所以上面的代码等价于:

_NSDictionaryOfVariableBindings(@"button1, button2", button1, button2, nil);

那么_NSDictionaryOfVariableBindings函数就可以将它的第一个参数按逗号,分割开作为key,后面就是各个key对应的值了。因此这段代码就创建了一个内容为{ @"button1" = button1, @"button2 = button2 }的Dictionary。

命名的串联

#在宏定义中的另一个作用就是用于命名的串联,用##就可以串联它左右两边的命名,比如以下代码:

#define CONCAT(X, Y) X ## Y
NSString *helloworld = @"Hello, world!";
NSLog(@"%@",CONCAT(hello, world)); //output: "Hello, world"

CONCAT(hello, world)实际被转换成helloworld。注意一下,因为宏是预编译阶段进行展开的,就是说在编译之前,因此代码中的helloworld即使没有定义其实也是没问题的,预编译处理后,这两个命名是不存在的。

可选可变参数

##在宏定义中可以放在__VA_ARGS__之前表示可变参数可以为空,否则的话可变参数至少为一个了。

#define MYLOG(format, ...) NSLog(format, ##__VA_ARGS__)
MYLOG(@"Don't make an error!");

上面代码中MLOG中只有一个参数,如果不加##,则MLOG至少需要两个参数,在Xcode里将会出现编译错误。


### 关于迷雾大陆挂机脚本的开发与获取 #### 迷雾大陆游戏概述 迷雾大陆是一款基于策略和冒险的游戏,玩家通常需要花费大量时间来完成任务、升级角色以及探索地图。为了减少重复操作的时间成本,许多玩家会选择使用挂机脚本来自动化这些过程[^1]。 #### 挂机脚本的功能需求分析 挂机脚本的核心功能在于模拟人类行为以实现自动化的游戏操作。对于迷雾大陆而言,常见的功能模块可能包括但不限于: - 自动拾取物品并分类存储。 - 定期执行战斗指令或技能释放。 - 路径规划与导航支持,用于高效移动至目标地点。 - 实时监控资源状态(如血量、魔法值),并在必要时触发恢复机制。 #### 技术选型建议 针对此类应用的技术栈选择至关重要。以下是几种常用技术方向及其适用场景说明: ##### Python语言方案 Python因其语法简洁明了而成为初学者友好型编程工具之一,在编写小型实用程序方面表现优异。可以利用`pyautogui`库来进行屏幕点击、键盘输入等基本交互动作;借助`selenium webdriver`控制浏览器加载网页版客户端从而实施更复杂的逻辑处理[^2]。 ```python import pyautogui import time def auto_collect(): while True: pyautogui.press('e') # 假设按键E代表收集附近掉落物 time.sleep(5) if __name__ == "__main__": auto_collect() ``` 请注意以上仅为简单演示片段,实际项目需考虑更多边界条件及异常情况处理。 ##### JavaScript/AutoHotkey组合方式 如果目标平台允许通过注入自定义JS代码或者运行AHK宏文件,则这种方法能够提供极高的灵活性同时保持较低的学习门槛。特别是当面对原生桌面应用程序而非Web端口时尤为有效[^3]。 然而值得注意的是无论采用何种手段构建自己的辅助软件都应当遵循相应法律法规以及尊重官方运营方制定的服务条款以免造成不必要的麻烦。 #### 获取现成解决方案途径提示 虽然网络上有不少分享开源项目的社区网站比如GitHub, GitLab等等可供查找是否有他人已经完成了类似的工程可以直接借鉴学习甚至直接部署到本地环境中测试效果如何。但是下载不明来源第三方制作好的exe安装包存在较大安全隐患因此强烈推荐优先尝试自己动手实践从零搭建属于个人定制版本的作品[^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值