首页IT科技字符串处理系统设计(冷知识:预处理字符串操作符)

字符串处理系统设计(冷知识:预处理字符串操作符)

时间2025-07-09 00:09:09分类IT科技浏览4819
导读:以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「englyf」https://mp.weixin.qq.com/s/Xr2pFCJ4j0DZYo2PO6-KQg...

以下内容为本人的学习笔记             ,如需要转载                     ,请声明原文链接 微信公众号「englyf」https://mp.weixin.qq.com/s/Xr2pFCJ4j0DZYo2PO6-KQg

当年学习C语言的第一门课就提到过标记(Token)的概念       ,不过             ,相信在多年之后你再次听到这个术语时会一脸懵逼                     ,比如我             。

因此特地翻了翻资料       ,整理下来这些笔记                     。

在C语言中什么是标记?

标记是编程语言处理的基本单元      ,也叫最小划分元素                     ,比如关键字              、操作符                    、变量名       、函数名       、字符串                    、数值等等       。

下面举例说明一下: printf("hello world!");

对上面的语句进行标记划分              ,可分为5个标记      ,如下:

printf // 函数名 ( // 左小括号操作符 "hello world!" // 字符串 ) // 右小括号操作符 ; // 分号

预处理字符串操作符

在C语言中                    ,预处理字符串操作符有两个              ,#和##             。

# 字符串化操作符

用途是,将标记(Token)转成字符串                     。

Syntax:

#define TOKEN_NAME(param) #param

Basic Usage:

#include <stdio.h> #define MACRO_NAME(param) #param int main() { printf(MACRO_NAME(hello world)); return 0; }

Output:

hello world

在项目实践中                    ,用宏定义的值的同时也需要将宏名转成字符串使用                     ,对日志的输出尤其管用       。

Best Practice:

#include <stdio.h> #define NAME(param) #param #define LEN_MAX 10 int main() { int array[LEN_MAX] = {0}; int index = 10; if (index >= LEN_MAX) { printf("error: %s:%d is over %s:%d\n", NAME(index), index, NAME(LEN_MAX), LEN_MAX); } else { printf("read %s[%d]=%d\n", NAME(array), index, array[index]); } return 0; }

Output:

error: index:15 is over LEN_MAX:10

如果修改如下:

int index = 9;

Output:

read array[9]=0

## 标记(Token)连接操作符

用途是,将##前后的标记(Token)串接成新的单一标记      。

syntax:

#define TOKEN_CONCATENATE(param1, param2) param1##param

Basic Usage:

#include <stdio.h> #define TOKEN_CONCATENATE(param1, param2) param1##param2 int main() { printf("%d\n", TOKEN_CONCATENATE(12, 34)); return 0; }

Output:

1234

通常             ,编码实践中                     ,代码中会出现一些书写看上去雷同的片段       ,极其啰嗦冗余                     。为了压缩源码篇幅             ,可以参考代码生成器的思想                     ,在预编译阶段用宏定义代码片段展开替换       ,同时根据输入的参数用##组合各种标记              。

假设有个需求是声明定义一组同一类型的结构体的变量      ,并初始化其内部成员      。既然声明定义的这些变量属于同一类型的结构体                     ,那么按照直接编码的方式              ,就会有多次重复的代码片段出现      ,里边包括了声明定义语句                    ,以及初始化各个成员的语句              ,不同的只是变量名或者参数而已                    。

举个栗子,下面基于同一类型的结构体                    ,声明定义两个变量                     ,并初始化,看代码

#include <stdio.h> #include <string.h> #define NAME(param) #param typedef struct { char *data; int data_size; /* number of byte real */ int max_size; /* maximnm data size.*/ } my_type; #define my_type_create(name, size) \ char name ## _ ## data[size] = {0}; \ my_type name; \ memset(&name, 0x00, sizeof(name)); \ name.data = name ## _ ## data; \ name.max_size = size; \ printf("variable name=%s\nmember data=%s, data_size=%d, max_size=%d\n", \ NAME(name), NAME(name ## _ ## data), name.data_size, name.max_size); \ int main() { my_type_create(var1, 10) my_type_create(var2, 20) }

上面的代码中             ,定义了宏my_type_create                     ,内部实现了结构体变量的声明定义       ,以及内部成员的初始化              。如果按照直接编码的方式             ,代码量相对于上面的代码量会虚增n-1倍                     ,n=变量的个数。

在main函数中       ,调用宏的时候输入参数var和10      ,那么在编译预处理阶段                     ,根据输入的参数              ,宏my_type_create会展开为以下的代码段                    。

char var_data[10] = {0}; \ my_type var; \ memset(&var, 0x00, sizeof(var)); \ var.data = var_data; \ var.max_size = 10; \ printf("variable name=%s\nmember data=%s, data_size=%d, max_size=%d", \ “var             ”, var_data, var.data_size, var.max_size); \

Output:

variable name=var1 member data=var1_data, data_size=0, max_size=10 variable name=var2 member data=var2_data, data_size=0, max_size=20

## 还有个特殊的用途

在宏定义中      ,也支持用...代表可变参数                     。

#define MY_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)

由于可变参数数目不确定                    ,所以没有具体的标记。于是为了引用可变参数              ,语言层面提供了可变宏(Variadic macros)__VA_ARGS__来引用它             。

但是,在宏定义时                    ,如果直接使用__VA_ARGS__来引用可变参数                     ,一旦可变参数为空就会引起编译器报错,看看下面的例子

#include <stdio.h> #define LOG_INFO(fmt, ...) printf("[I]" fmt "\n", __VA_ARGS__) int main() { LOG_INFO("info..."); LOG_INFO("%s, %s", "Hello", "world"); }

Output:

main.c: In functionmain’: main.c:3:62: error: expected expression before ‘)’ token 3 | #define LOG_INFO(fmt, ...) printf("[I]" fmt "\n", __VA_ARGS__) | ^ main.c:6:3: note: in expansion of macroLOG_INFO6 | LOG_INFO("info..."); | ^~~~~~~~

为了解决上面的问题             ,在__VA_ARGS__前面添加上##                     ,这样的目的是告诉预处理器       ,如果可变参数为空             ,那么前面紧跟者的逗号,在宏定义展开时会被清理掉                     。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
ubuntu 云盘(ubuntu云存储服务器有哪些优势)