目录
五.#和##运算符
5.1--#运算符
5.2--##运算符
六.命名约定,#undef,命令行定义
6.1--命名约定
6.2--#undef
6.3--命名行定义
七.条件编译
常见的条件编译指令:
1.普通的条件编译:
2.多个分支的条件编译(可以利用条件语句来辅助理解):
3.判断是否被定义:
4.嵌套指令:
八.头文件的包含
8.1--头文件被包含的方式:
8.1.1--本地文件包含
8.1.2--库文件包含
8.2--嵌套文件包含
九.其他预处理指令
🔥个人主页:@草莓熊Lotso的个人主页
🎬作者简介:C++研发方向学习者
📖个人专栏:《C语言》
⭐️人生格言:生活是默默的坚持,毅力是永久的享受。
五.#和##运算符
5.1--#运算符
#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执⾏的操作可以理解为”字符串化“。
当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 .
# define PRINT(n) printf( "the value of " #n " is " format "\n" , n);
具体代码演示:
#include<stdio.h>
#define PRINT(n, format) printf("the value of "#n" is "format"\n", n)
int main()
{
int a = 10;
//printf("the value of a is %d\n", a);
PRINT(a, "%d");
float f = 3.14f;
//printf("the value of f is %f\n", f);
PRINT(f, "%f");
return 0;
}

我们在这里主要分析一下变量a,我们定义好宏后,用上面的方式去调用它,我们把a替换到了宏的体内,就出现了#a,而#a转换为"a"这一字符串,然后format也被"%d"替换。
我们还是看看PRINT(a, "%d");这一代码预处理之后的形式:
printf ( "the value of " "a" " is " "%d" "\n" , a);
运行代码就得出来我们想要的结果。
5.2--##运算符
int int_max ( int x, int y){return x > y ? x : y;}float float_max ( float x, float y){return x > y ? x : y;}
反复这样写形式差不多的函数太繁琐了,于是我们定义了如下所示的一个宏:
// 宏定义# define GENERIC_MAX(type) \type type##_max(type x, type y)\{ \return (x>y?x:y); \}
我们来使用这个宏,定义不同函数试试:
GENERIC_MAX( int ) // 替换到宏体内后 int##_max ⽣成了新的符号 int_max 做函数名GENERIC_MAX( float ) // 替换到宏体内后 float##_max ⽣成了新的符号 float_max 做函数名int main (){// 调⽤函数int m = int_max( 8 , 9 );printf ( "%d\n" , m);float fm = float_max( 6 .5f , 8 .5f );printf ( "%f\n" , fm);return 0 ;}
最终代码实现:
#include<stdio.h>
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
//调⽤函数
int m = int_max(8, 9);
printf("%d\n", m);
float fm = float_max(6.5f, 8.5f);
printf("%f\n", fm);
return 0;
}
这里简单的举个例子帮助大家理解一下##运算符的使用,还想要更深入了解的可以去查阅一下相关资料来学习。
六.命名约定,#undef,命令行定义
6.1--命名约定
- 把宏名全部⼤写
- 函数名不要全部⼤写
6.2--#undef
--我们通常用这条指令去移除一个宏定义
# undef NAME// 如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。
我们来看两个实际例子直观的了解一下它的使用:
例一:
#include <stdio.h>
#define M 100
int main()
{
printf("%d\n", M);
#undef M
#define M 1000
printf("%d\n", M);
return 0;
}
例二:
#include<stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
int a = 10;
int ret=SQUARE(a);
printf("%d\n", ret);
#undef SQUARE(x)
#define SQUARE(x) ( ( x ) + ( x ) )
int ret2 = SQUARE(a);
printf("%d\n", ret2);
}
6.3--命名行定义
#include <stdio.h>
int main()
{
int array[ARRAY_SIZE];
int i = 0;
for (i = 0; i < ARRAY_SIZE; i++)
{
array[i] = i;
}
for (i = 0; i < ARRAY_SIZE; i++)
{
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
编译指令(linux环境演示):
//linux 环境演⽰gcc -D ARRAY_SIZE=10 programe.c
七.条件编译
#include <stdio.h>
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef __DEBUG__//如果定义了就可以打印数组
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}
常见的条件编译指令:
1.普通的条件编译:
# if 常量表达式//...# endif// 常量表达式由预处理器求值。如:# define __DEBUG__ 1# if __DEBUG__//..# endif
2.多个分支的条件编译(可以利用条件语句来辅助理解):
#if 常量表达式
//...# elif 常量表达式//...# else//...# endif
3.判断是否被定义:
//如果被定义了就继续# if defined(symbol)# ifdef symbol//如果没被定义就继续# if !defined(symbol)# ifndef symbol
4.嵌套指令:
# if defined(OS_UNIX)# ifdef OPTION1unix_version_option1();# endif# ifdef OPTION2unix_version_option2();# endif# elif defined(OS_MSDOS)# ifdef OPTION2msdos_version_option2();# endif# endif
八.头文件的包含
8.1--头文件被包含的方式:
8.1.1--本地文件包含
1. #include "filename"
- 先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件。
- 如果找不到就提示编译错误。
1. /usr/include
VS环境的标准头⽂件的路径:
1. C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
2. // 这是 VS2013 的默认路径
我们这里注意一下,要按照自己的安装路径去找。
8.1.2--库文件包含
1. #include <filename.h>
8.2--嵌套文件包含
我们已经知道, #include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。
我们来看看实际例子吧~
test.c:
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
return 0;
}
test.h:
void test();
struct Stu
{
char name[20];
int age;
float score;
};
这里给大家分享两种方法:
方法一:在每个文件的开头写如下内容
# ifndef __TEST_H__# define __TEST_H__// 头⽂件的内容# endif
这个方式首先判断test.h有没有被定义,如果没有就定义一次,这样之后下次再重复出现test.h也没有用了,不符合这里的条件判断,所有可以有效的避免头文件的重复引入。
方法二:新式的现代写法
# pragma once
利用这种方式同样也可以有效的避免头文件的重复引入,更简洁一点。
注意:这里给大家推荐一下《⾼质量C/C++编程指南》中附录的考试试卷的两个笔试题
- 头⽂件中的 ifndef/define/endif是⼲什么⽤的?
- #include <filename.h> 和 #include "filename.h" 有什么区别?
如果大家有兴趣的话可以在评论区留言后私信我,我把这本书分享给大家,这其中附录的考试试卷很有参考价值,比较重要。
九.其他预处理指令
#error
#pragma
# line...这里就不做介绍了,感兴趣的可以⾃⼰去了解一下,建议参考《C语⾔深度解剖》学习。# pragma pack() //在结构体部分介绍过,这个指令可以改变编译器的默认对齐数
往期回顾:
【C语言预处理详解(上)】--预定义符号,#define定义常量,#define定义宏,带有副作用的宏参数,宏替换的规则,宏和函数的对比
【C语言编译与链接】--翻译环境和运行环境,预处理,编译,汇编,链接
【通关文件操作(上)】--文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写 【通关文件操作(下)】--文件的顺序读写(续),sprintf和sscanf函数,文件的随机读写,文件缓冲区,更新文件
结语:本篇文章就到此结束了,继前面一篇文章后,在此篇文章中给大家分享了预处理详解中的剩余知识点,如#和##运算符,命名约定,命令行定义 ,#undef,条件编译,头文件的包含,嵌套文件包含,其他预处理指令等,到此C语言专栏的知识点分享也差不多结束了,后续笔者还会继续分享其它的内容,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家长久以来的支持。