首页 > 其他分享 >冷知识:预处理字符串操作符

冷知识:预处理字符串操作符

时间:2023-04-08 12:00:41浏览次数:48  
标签:__ name ## var 操作符 字符串 data 预处理 size

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「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:10 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 function ‘main’:
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 macro ‘LOG_INFO’
    6 |   LOG_INFO("info...");
      |   ^~~~~~~~

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

标签:__,name,##,var,操作符,字符串,data,预处理,size
From: https://blog.51cto.com/englyf/6177501

相关文章

  • vs调试“字符串中字符无效”处理办法
    在使用VS2019调试代码时,查看变量值时,utf8格式字符串不能正常显示,需要在变量名后手动添加",s8",就能正常查看字符啦。总结如下:,s8:将字符串转成unicode展示,数字将变量拆分为数组显示,数字是要显示多少位,此法对constchar*这类原始字符串非常有用,x16进制查看,hr查看Win......
  • C#判断字符串是否是有效的XML格式数据
    说明在try-catch语句块中,创建XmlDocument对象,并使用LoadXml方法加载xml字符串。如果没有异常,则说明xml字符串是有效的,返回true,反之为false。代码实现///<summary>///Xml字符串格式验证///</summary>///<paramname="xmlString">Xm......
  • KMP 字符串
    KMP题目描述给定一个字符串\(S\),以及一个模式串\(P\),所有字符串中只包含大小写英文字母以及阿拉伯数字。模式串\(P\)在字符串\(S\)中多次作为子串出现。求出模式串\(P\)在字符串\(S\)中所有出现的位置的起始下标。输入第一行输入整数\(N\),表示字符串\(P\)的长度......
  • 1234. 替换子串得到平衡字符串
    题目链接:1234.替换子串得到平衡字符串方法:同向双指针解题思路若可以通过「替换一个子串」的方式,使原字符串s变成一个「平衡字符串」,则说明子串外任意字符的数量\(s≤n/4\),否则一旦有一个字符的数量大于\(n/4\),那么不论如何替换,必定有另一个字符的数量小于\(n/4......
  • JS 字符串特殊字符全部替换空
    1、方法constformatStr=(str)=>{constvalue=str.replace(/[`:_~!@#$%^&*()\+=<>?"{}|,\/;'\\[\]·~!@#¥%……&*()——\+={}|《》?:“”【】、;‘’,。、-]/g,'',)returnvalue}2、实例......
  • 题目 1031: [编程入门]自定义函数之字符串反转
    在主函数中输入一个字符串(不包含空格),写一个新函数将字符串按反序存放,并在主函数中输出反序后的字符串gets()能把字符串写入数组里,我只需要再写一个新数组,把array数组的最后一个元素赋值给新数组的第一个元素,把array的倒数第二个赋值给新数组的第二个……这样一个一个赋值,万一阿......
  • 关于一些OJ上的\r以及\n以及字符串行输入的一些警示
    \r,\n,\r\n的区别-小天-博客园(cnblogs.com)这篇文章详细的解释了在Windows系统和Linux系统下的换行的区别概括的说,就是Windows系统下的“\r\n”等于Linux系统下的’\n‘因此在一些搭建在Linux终端上的Oj,我们输入时的回车是在WIndows系统中的输入,OJ在评判输出的时候会在L......
  • UVA - 10905 Children's Game 字符串的排序
    题目大意:给出N个数字串,要求拼出有数字最大的串解题思路:用string就很好解决#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>usingnamespacestd;constintmaxn=60;stringstr[maxn];intcmp(stringa,stringb){ returna+b>b+a;}......
  • 如何使用awk进行字符串大小写的转换?
    1、问题:如何将下面的这个字符串,全部转换为  大写  ?DOMaiN,verify,reference,offset,LIMIT,TYPE,ref,context,LOGIN,CONTEXT,sa  使用awk的toupper()函数来实现[root@yks01~]#echo"DOMAIN,verify,reference,offset,LIMIT,TYPE,ref,context,LOGIN,CONTEXT,sa"......
  • 字符串学习笔记(一)
    一些定义:1.Border:如果一个字符串的某个前缀同与它长度相同的后缀完全相同,就称这个前缀(后缀)是这个字符串的一个Border.2.周期:如果一个字符串s满足对于任意的p<i\(\leqslant\)|s|,s[i]=s[i-p],则称p是字符串s的周期,一个字符串可能有很多个周期。3.循环节:在周期的......