【C语言进阶剖析】25、# 和 ## 操作符使用分析

1 #运算符

  • # 运算符用于在预处理期将宏参数转换为字符串
  • # 的转换作用是在预处理期完成的(编译器不知道),因此只在宏定义中有效

用法如下:
在这里插入图片描述

用法的意思是:给宏STRING(x) 传一个参数 x,就会被替换为 #x,然后 # 运算符将 x 转换为字符串。也就是说没有双引号的 hello world! 将被转换为 “hello world!”

看段简单的代码,对 # 运算符有个直观的了解,了解 # 运算符的基本用法

// 25-1.c
#include<stdio.h>
#define STRING(x) #x
int main()
{
    printf("%s\n", STRING(hello world!));
    printf("%s\n", STRING(100));
    printf("%s\n", STRING(while));
    printf("%s\n", STRING(return));
    return 0;
}

编译运行结果如下:

$ gcc 25-1.c -o 25-1
$ ./25-1
hello world!
100
while
return

可以看到在宏定义 #define STRING(x) #x 中,直接将宏参数转换为字符串,也就是将没有双引号的 hello world!,100,while,return 变为字符串 “hello world!”,“100”,“while” 和 “return”

为了更加深入的了解这个过程,我们单步编译一下,只进行预处理,为了避免不必要的信息,将上面代码的第二行 #include<stdio.h> 注释

$ gcc -E 25-1.c -o 25-1.i

打开文件 25-1.i,如下所示:

# 1 "25-1.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "25-1.c"



int main()
{
    printf("%s\n", "hello world!");
    printf("%s\n", "100");
    printf("%s\n", "while");
    printf("%s\n", "return");
    return 0;
}

从预编译过后的文件 25-1.i 可以看出,# 运算符在预处理期将宏参数转换为字符串

2 # 运算符的妙用

看到上面的例子,不禁有个疑问,知道了# 运算符可以在预处理期将宏参数转换为字符串,这有什么用呢?下面通过一个例子来说明 # 运算符的妙用,如果我们想知道调用了什么函数,结果是什么,将函数名和结果打印出来,这该怎么操作呢,下面就看看 # 运算符的妙用吧

// 25-2.c
#include<stdio.h>
#define CALL(f, p)(printf("Call function %s\n", #f), f(p))
int square(int n)
{
    return n * n;
}
int func(int x)
{
    return x;
}
int main()
{
    int result = 0;
    result = CALL(square, 4);
    printf("result = %d\n", result);
    result = CALL(func, 10);
    printf("result = %d\n", result);
    return 0;
}

定义的宏是个逗号表达式,返回值等于最后一个表达式的值,利用 # 操作符将函数名转换为字符串,再调用这个函数,计算结果。

编译运行结果如下:

$ gcc 25-2.c -o 25-2
$ ./25-2
Call function square
result = 16
Call function func
result = 10

为了更详细的了解这个过程,同样注释第二行 #include<stdio.h>,单步编译,只进行预处理

$ gcc -E 25-2.c -o 25-2.i

打开文件 25-2.i,如下所示:

# 1 "25-2.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "25-2.c"



int square(int n)
{
    return n * n;
}
int func(int x)
{
    return x;
}
int main()
{
    int result = 0;
    result = (printf("Call function %s\n", "square"), square(4));
    printf("result = %d\n", result);
    result = (printf("Call function %s\n", "func"), func(10));
    printf("result = %d\n", result);
    return 0;
}

3 ## 运算符

  • ## 运算符用于在预处理期粘连两个标示符(两个标示符变成一个标示符)
  • ## 的连接作用是在预处理期完成的,因此只有在宏定义中有效
  • 编译器不知道 ## 的连接作用

用法如下:
在这里插入图片描述

## 运算符将两个标识符连接成一个标识符,a##b 就变成了 ab,所以 a##1 就相当于 a1

下面通过一个实例了解 ## 运算符的基本用法,直接看代码

// 25-3.c
#include<stdio.h>
#define NAME(n) name##n
int main()
{
    int NAME(1);
    int NAME(2);
    NAME(1) = 1;
    NAME(2) = 2;
    printf("%d\n", NAME(1));
    printf("%d\n", NAME(2));
    return 0;
}

宏定义 #define NAME(n) name##n,表示连接 name 和 n,使之变为一个标示符 namen,所以 int NAME(1) 被宏替换为 name1,int NAME(2) 被宏替换为 name2

真的是和我们分析的一样吗,我们编译运行一下:

$ gcc 25-3.c -o 25-3
$ ./25-3
1
2

我们再单步编译一下,只进行预编译,查看预编译之后的代码,同样为了避免不必要的信息,将程序第二行注释。

$ gcc -E 25-3.c -o 25-3.i

打开文件 25-3.i,如下所示,和我们分析的完全一致

# 1 "25-3.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "25-3.c"



int main()
{
    int name1;
    int name2;
    name1 = 1;
    name2 = 2;
    printf("%d\n", name1);
    printf("%d\n", name2);
    return 0;
}

4 ##运算符的工程应用

下面我们看一个 ## 运算符在工程中的应用

如果我们要定义一个结构体,因该怎么定义呢,很简单,定义如下:

#include<stdio.h>
struct Student 
{
    char* name;
    int id;
};
int main()
{
    struct Student s;
    s.name = "s1";
    s.id = 0;
    return 0;
}

我们可能会定义很多变量,每次定义一个变量前面都要加 struct,这很麻烦,能不能不写呢,当然可以,使用 typedef 就可以了,代码如下:

#include<stdio.h>
typedef struct _tag_Student Student;
struct _tag_Student
{
    char* name;
    int id;
};
int main()
{
    Student s;
    s.name = "s1";
    s.id = 0;
    return 0;
}

这样在定义变量的时候直接使用结构体名字 Student 就可以了,不用再写 struct,但是还不够完美,因为在定义结构体的时候还是需要写 struct,如果定义很多结构体,那岂不是要重复写很多遍,有没有办法不写呢,## 运算符的作用就来了。

// 25-4.c
#include<stdio.h>
#define STRUCT(type) typedef struct _tag_##type type;\
    struct _tag_##type
STRUCT(Student) 
{
    char* name;
    int id;
};
int main()
{
    Student s1;
    Student s2;
    s1.name = "s1";
    s1.id = 0;
    s2.name = "s2";
    s2.id = 1;
    printf("s1.name = %s\n", s1.name);
    printf("s1.id = %d\n", s1.id);
    printf("s2.name = %s\n", s2.name);
    printf("s2.id = %d\n", s2.id);
    return 0;
}

上面的代码中,type 是结构体的名字,## 运算符连接 _tag_ 和 type 变为 _tag_type,这不就是我们前面定义的结构体吗,struct _tag_##type 可以看作 struct _tag_type

编译运行一下:

$ gcc 25-4.c -o 25-4
$ ./25-4
s1.name = s1
s1.id = 0
s2.name = s2
s2.id = 1

看了上面的过程可能还是有点蒙,下面将第二行 #include<stdio.h> 注释,单步编译,只进行预处理,看一下预处理之后的代码

$ gcc -E 25-4.c -o 25-4.i

打开文件 25-4.i,如下所示:

# 1 "25-4.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "25-4.c"




typedef struct _tag_Student Student; struct _tag_Student
{
    char* name;
    int id;
};
int main()
{
    Student s1;
    Student s2;
    s1.name = "s1";
    s1.id = 0;
    s2.name = "s2";
    s2.id = 1;
    printf("s1.name = %s\n", s1.name);
    printf("s1.id = %d\n", s1.id);
    printf("s2.name = %s\n", s2.name);
    printf("s2.id = %d\n", s2.id);
    return 0;
}

这不就是我们之前写的代码吗,只不过通过 ## 标识符,使我们写起来更加简单了。

5 小结

1、# 运算符用于预处理期将宏参数转换为字符串
2、## 运算符用于预处理期粘连两个标识符
3、编译器不知道 # 和 ## 运算符的存在
4、# 和 ## 运算符只在宏定义中有效

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页