前言
本文章主要介绍预处理和#define定义标识符和定义宏、#和##的作用、宏和函数对比和条件编译。所以代码均在vs2022上运行,希望本文章能帮助到各位同学。如有错误,还望指正。
文章目录
预处理符号
| 预处理符号 | 作用 |
|---|---|
| __ FILE __ | 正在被编译的源文件文件名 |
| __ LINE __ | 当前行号 |
| __ DATE __ | 文件编译日期 |
| __ TIME __ | 文件编译时间 |
| __ STDC __ | 1(如果编译器遵循ANSI C) |
#include <stdio.h>
int main()
{
printf("FIEL: %s\n", __FILE__);
printf("LINE: %d\n", __LINE__);
printf("DATE: %s\n", __DATE__);
printf("TIMR: %s\n", __TIME__);
return 0;
}

调试窗口显示时间为文件被编译时间,下方的系统时间为文件运行时间。因为vs2022并不严格遵循ANSI C所以这里无法演示__STDC__。
#define
语法: #define name stuff
#define 定义标识符
在定义标识符中stuff可以是常量表达式、字符串常量、数据类型甚至是一段代码
上代码
#include <stdio.h>
#define MAX 10
#define STR "string"
#define CH char
#define CLEAR getchar()
int main()
{
CH n = CLEAR;
printf("%d\n", MAX);
printf("%s\n", STR);
printf("%c\n", n);
return 0;
}
运行结果

编译器进行预处理后,#define定义的标识符将被替换。替换的值可以是数字10、字符串、关键字以及代码。替换后的代码如下:
int main()
{
char n = getchar();
printf("%d\n", 10);
printf("%s\n", "string");
printf("%c\n", n);
return 0;
}
注意:在#define定义标识符后加';',在预处理进行define替换时也会将';'替换进去,容易造成语法错误。例如:
//定义后加';'
#define MAX;
int main()
{
//替换前
printf("%d", MAX);
//替换后
printf("%d", MAX;);
return 0;
}
#define 定义宏
什么是宏?
答:#define的机制中,存在一个允许将参数替换到文本中的规定。这种实现通常称为宏(macor)或定义宏(define macor)。
宏的声明
宏的声明: #define name(parament-list) stuff
parament-list(参数列表) 是一个由逗号隔开得符号表,可能出现在stuff中
上代码:
#include <stdio.h>
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
int main()
{
int a = 8;
int b = 5;
int m = MAX(a, b);
printf("%d", m);
return 0;
}
运行结果:

预处理替换后代码:
int main()
{
int a = 8;
int b = 5;
int m = ((a) > (b) ? (a) : (b));
printf("%d", m);
return 0;
}
注意:
1.参数列表在左括号必须于name紧邻。
2.如果两者之间存在空白,则会被解释为stuff的一部分。
3.建议stuff整体和表达式带括号
为什么要sutff整体和表达式带括号呢?
我们先假设不带括号的情况,比如:
#include <stdio.h>
#define MUT(X,Y) X * Y
int main()
{
int a = 8;
int b = 2;
int m = MUT(a+b,b+a);
printf("%d", m);
return 0;
}
//预处理替换后
#define MUT(X,Y) X * Y
int main()
{
int a = 8;
int b = 2;
int m = a + b * a + b;
printf("%d", m);
return 0;
}
对于这段代码我们想要得到的理想结果是:(8+2) x (8+2) = 100 ,然后将100赋值给变量m。但是由于’*'的优先级高于‘+’,所以实际的运算是:8+2x8+2 = 8+16+2 = 26,最终将26赋值给m。因此为了防止运算符优先级造成不符合理想值的结果,在定义宏时建议stuff整体和表达式带括号。
#define的替换规则:
1.调用宏时,首先对参数进行检查,将#define定义的符号替换。
2.将被替换的文本插入程序中原来文本的位置,对于宏,参数名被他们的值所替换。
3.最后再对结果文件进行扫描,如果还存在#define定义的符号,则重复上述过程。
注意:
1.宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归。
- [x]
#define MUT(X) ( (X) * (MUT(X-1)))
- [√]
#define MAX 10
#define MUT(X,Y) ((X) * (Y) + (MAX))
2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索和替换。
#define MAX 10
int main()
{
printf("MAX");
return 0;
}
运行结果:

#和##
c语言中的字符串存在这样的一种特性:
”helloworld" == “hello” “world”
那么现在有这样一个问题:
printf(“the student’s name is %s”, name);
printf(“the student’s age is %d”, age);
printf(“the student’s score is %f”, score);
如何通过传参实现上面代码的功能。
对于上面的问题我们可以看出两类参数:
1.name、age、score;
2.%d、%s、%f;
两个选择:函数和宏。
然而对于函数来说实现这一功能几乎不可能。首先函数的参数无法直接插入字符串,必须借助格式化输出诸如“%d”。但是"%d"又该如何作为函数的参数插入字符串呢?在这方面宏就可以做到。不过我们需要了解一下#的作用。
#:
把一个宏的参数变成对应的字符串(只能是宏的参数)
上代码
#include <stdio.h>
#define PRT(val) printf(#val)
int main()
{
int a = 95;
PRT(a);
return 0;
}
运行结果

从运行结果可以看出#val直接将参数名变成了字符串“a”,而不是“95”。到这里运用字符串的特性和#的作用就可以很好的解决上述问题。
上代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define PRT(val,format) printf("the student's "#val" is "#format, val)
int main()
{
int age = 30;
PRT(age,%d);
return 0;
}
//替换后
int main()
{
int age = 30;
printf("the student's ""a"" is ""%d", val);
return 0;
}
运行结果

##的作用:
可以把位于它两端的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。
上代码
#include <stdio.h>
#define TEG(X,Y) X##Y
int main()
{
int studentAge = 30;
printf("%d", TEG(student, Age));
return 0;
}
//替换后
int main()
{
int studentAge = 30;
printf("%d", studentAge);
return 0;
}
运行结果

宏和函数对比:
| 宏的优点 | 函数的缺点 |
|---|---|
| 宏再处理小规模运算时空间和时间上更优于函数,因为宏只需将代码替换,不需要额外的调用和返回。 | 函数再处理一些例如比大小这类的小规模运算时,用于调用函数和从函数返回的代码可能比实际执行这类小规模运算所需时间更多 |
| 每一次使用宏时除非插入的代码较短,否则可能将大幅增加程序的长度 | 无论运行多少次,函数仅保存一份 |
| 宏无关类型,无论参数为何种类型都适用,甚至宏可以将数据类型作为参数,以及将参数插入字符串。因为与类型无关,所以也不够严谨 | 函数的参数必须声明为特定的类型,所以函数只能在合适类型的表达式上使用。因此在调用上更为严谨。 |
| 宏是没法调试的 | 函数是可以调试的 |
| 宏可能会带来运算符优先级的问题,导致程序容易出错 | 函数是可以预知的 |
| 宏不可以递归 | 函数可以递归 |
命名约定:
宏名全部大写
函数名不要全大写
伪装为函数的宏:
offsetof -宏
代码如下:
#define offsetof(s,m) ((size_t)&(((s*)0)->m))
getchar -宏
#undef name :
移除一个宏定义

条件编译:
满足条件参与编译.不满足则放弃编译。它与if与else较为相似,但是if与else是满足条件则执行否则不执行,无论执行与否都会参与编译。
#ifdef和#endif
#ifdef和#endif成对出现。如果标识符已被定义则编译#ifdef和#endif之间的代码。反之则放弃编译。
#include <stdio.h>
#define MIN 100
int main()
{
int m = 1;
int n = 2;
#ifdef MAX
printf("%d", m);
#endif
#ifdef MIN
printf("%d", n);
#endif
return 0;
}
运行结果

#if 和#endif
#if 常量表达式
#endif
表达式为真则编译,反之放弃编译
int main()
{
#if 2 < 3
printf("A\n");
#endif
#if 3 < 2
printf("B\n");
#endif
return 0;
}
运行结果

多个分支嵌套条件编译
#if
#elif
#else
#endif
int main()
{
#if 3 < 2
printf("A\n");
#elif 2 > 3
printf("B\n");
#else
printf("C\n");
#endif
return 0;
}
运行结果

判断是否被定义
#if defined(标识符) == #ifdef 标识符
如果标识符被定义则编译,反之放弃编译
#if !defined(标识符) == #ifndef 标识符
如果标识符未定义则编译,如已定义则放弃编译
#include <stdio.h>
int main()
{
#if defined(MAX)
printf("A\n");
#endif
#ifdef MAX
printf("A\n");
#endif
#if !defined(MAX)
printf("B\n");
#endif
#ifndef MAX
printf("B\n");
#endif
return 0;
}
运行结果

结尾
关于预处理与#define等相关知识都以介绍完毕。最后感谢您阅读本文章,希望您有所收获。