【C语言】 —— 预处理详解(下)

2024-07-13 1341阅读

【C语言】 —— 预处理详解(下)

  • 前言
  • 七、# 和 \##
    • 7.1 # 运算符
    • 7.2 ## 运算符
    • 八、命名约定
    • 九、# u n d e f undef undef
    • 十、命令行定义
    • 十一、条件编译
      • 11.1、单分支的条件编译
      • 11.2、多分支的条件编译
      • 11.3、判断是否被定义
      • 11.4、嵌套指令
      • 十二、头文件的包含
        • 12.1 头文件被包含的方式
          • (1) 本地文件被包含的方式
          • (2)库文件包含
          • 12.2 嵌套文件包含
          • 十三、 其他预处理指令

            前言

              在上期【C语言】 —— 预处理详解(下)的学习中,我们详细介绍了预处理中宏的相关知识,相信大家都收获不少。别急还有,本期让我们继续学习预处理方面的其他知识吧。

            七、# 和 ##

            7.1 # 运算符

            • # 运算符将宏的一个参数转换成字符串字面量。它进允许出现在带参数的宏的替换列表中
            • # 运算符所执行的操作可理解为“字符串化”

                什么意思呢?

                我们先来做一个铺垫:

              int mian()
              {
              	printf("hello"   "world\n");
              	printf("helloworld\n");
              	return 0;
              }
              

                上述两句代码有什么区别呢?我们一起来看看:

              【C语言】 —— 预处理详解(下)

                可以看到,两个字符串和一个字符串的效果是一样的。C语言会把两个字符串天然连成一个字符串,中间加空格也没用。

                现在有这么一个场景:

              int main()
              {
              	int a = 1;
              	printf("The value of a is %d\n", a);
              	int b = 20;
              	printf("The value of b is %d\n", b);
              	float f = 8.5f;
              	printf("The value of f is %f\n", f);
              	return 0;
              }
              

                我们发现三句代码的逻辑都是非常相像的,但又有些许不同。

                

                那我们想既然他们这么相像,能不能把他们封装成一个函数,以方便使用呢?

                但是函数是做不到的这个功能的

                那怎么办呢?

                我们可以尝试用宏来解决呢

              #define Print(n, format) printf("The value of n is " format "\n", n)
              int main()
              {
              	int a = 1;
              	Print(a, "%d");
              	//printf("The value of a is %d\n", a);
              	int b = 20;
              	Print(b, "%d");
              	//printf("The value of b is %d\n", b);
              	float f = 8.5f;
              	Print(f, "%f");
              	//printf("The value of f is %f\n", f);
              	return 0;
              }
              

                

              运行结果:

              【C语言】 —— 预处理详解(下)

                我们发现 n n n 一直没变,那应该怎么修改呢?

                这时就应该用到我们的 # 运算符了:# 将宏的一个参数转换成字符串字面量,即 n n n 变成 “ n ” “n” “n”

                这时,我们再运用拼接大法就成了

              #define Print(n, format) printf("The value of " #n " is " format "\n", n)
              

                

                不懂?看下面的解释就懂啦

              【C语言】 —— 预处理详解(下)

                

              7.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;
              }
              

                

              【C语言】 —— 预处理详解(下)

                这样难免太过繁琐,而且两函数之间有很多相似的地方。

                那有没有办法能快速地创建出这样的函数呢?就像一个函数的模具一样,套一下一个函数就出来了

                

                我们可以这样来写一个宏:

              #define GENERIC_MAX(type) \
              		type type##_max(type x, type 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
              GENERIC_MAX(float);  //相当于定义了一个函数float_max
              int main()
              {
              	int r1 = int_max(3, 5);
              	printf("%d\n", r1);
              	float r2 = float_max(2.3f, 7.6f);
              	printf("%f\n", r2);
              	return 0;
              }
              

                

              运行结果:

              【C语言】 —— 预处理详解(下)

                

                我们也可以在 g c c gcc gcc 环境下观察预处理后的 .i 文件,有个更直观地了解

              【C语言】 —— 预处理详解(下)

                当然,这样生成的函数也是不方便调试的

                那这里 ## 起到什么作用呢?

                加了 ##,编译器才会认为他们是一个符号

                让我们来看看不加 ## 的效果:

              【C语言】 —— 预处理详解(下)

                

              八、命名约定

                一般来讲,函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者

                

              我们平时的一个习惯是:

              • 把宏名全部大写
              • 函数名不要全部大写

                  当然,这些命名规则并不是绝对的

                  比如 o f f s e t offset offset 这个宏就写成了全小写

                  

                注: o f f s e t offset offset 是用来计算结构体成员相对于结构体起始位置的偏移量的

                  

                九、# u n d e f undef undef

                  # u n d e f undef undef 指令用来移出一个宏定义

                【C语言】 —— 预处理详解(下)

                  上述代码,在 169 行使用 # u n d e f undef undef 移除了宏 MAX。在移除之前的 168 行调用时没问题的,但移除之后的 170 行调用就会报错

                  

                十、命令行定义

                  许多 C 的编译器(不包括VS)提供了一种能力,允许在命令行中定义符号。用于启动编译过程

                  

                  例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性就有用处。(假定某个程序中声明了一个某长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要的数组能够大些)

                【C语言】 —— 预处理详解(下)

                【C语言】 —— 预处理详解(下)

                  命令行定义式在预处理阶段处理的,在预处理阶段时,上述代码中 s z sz sz 的值已经确定了

                  

                十一、条件编译

                  在编译一个程序的时候如果将一条(一组语句)编译或者放弃是很方便的。因为我们可以用条件编译指令

                  

                  条件编译指令就是这段代码我想让你编译就编译,不想让你编译你就不要编译了。我们可以给他设定一个条件,条件为真,这段代码就参与编译,条件为假,这段代码就不要编译了。

                  

                比如说:

                  一些调试性的代码,删除可惜,保留又碍事,所以我们可以选择性编译

                  

                【C语言】 —— 预处理详解(下)

                  

                常用的条件编译指令:

                11.1、单分支的条件编译

                #if 常量表达式
                    //···
                #endif
                

                【C语言】 —— 预处理详解(下)

                  

                11.2、多分支的条件编译

                #if  常量表达式
                	//···
                #elif  常量表达式
                	//···
                #else
                	//···
                #endif
                

                  哪条语句为真,就执行哪条语句

                #define M 1
                int main()
                {
                #if M == 0
                	printf("hello\n");
                #elif M == 1
                	printf("world\n");
                #elif M == 2
                	printf("csdn\n");
                #endif
                	printf("886\n");
                	return 0;
                }
                

                  

                11.3、判断是否被定义

                #if defined(symbol)
                #ifdef symbol
                //上面两个的反面
                if !defined(symbol)
                #ifndef symbol
                

                【C语言】 —— 预处理详解(下)

                  

                11.4、嵌套指令

                #if defined(OS_UNIX)
                	#ifdef OPTION1
                		unix_version_option1();
                	#endif
                	
                	#ifdef OPTION2
                		unix_version_option2();
                	#endif
                	
                #elif defined(OS_MSDOS)
                	#ifdef OPTION2
                		msdos_version_option2();
                	#endif
                	
                #endif
                

                  

                十二、头文件的包含

                12.1 头文件被包含的方式

                (1) 本地文件被包含的方式

                # include "filename"
                

                  查找策略:先在源文件所在的工程目录下查找,如果头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件

                  如果再找不到就编译错误

                  

                L i n u x Linux Linux 环境的标准头文件路径(头文件放在哪):

                 /usr/include
                

                VS 环境的标准头文件路径:

                C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
                //这是VS2013的默认路径
                

                  

                (2)库文件包含

                #include 
                

                  查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

                  

                  这样是不是可以说,对于库文件也可以使用 “ ” 的形式包含

                  答案是肯定的,但是这样做查找的效率比较低,当然这样也不容易区分是库文件还是本地文件

                  

                12.2 嵌套文件包含

                  学习了前面的(编译和链接),我们知道头文件的包含在预处理阶段就是直接将该文件的代码拷贝到包含头文件的地方

                  

                  如果一个头文件被包含了 10 次,那就实际被编译了 10 次,如果重复包含,对编译的压力就比较大

                t e s t . c test.c test.c

                #include "test.h"
                #include "test.h"
                #include "test.h"
                #include "test.h"
                #include "test.h"
                int main()
                {
                return 0;
                }
                

                t e s t . h test.h test.h

                void test();
                struct Stu
                {
                int id;
                char name[20];
                };
                

                  但在一个工程中,一个文件难免被包含多次,那么如何解决这个问题呢?

                  答案:条件编译

                #ifndef __TEST_H__
                #define __TEST_H__
                		//头文件的内容
                #endif
                

                  

                怎么理解呢?

                • 当第一次包含头文件时,要不要编译呢?先进行判断
                • __TEST_H__这个符号并没有被定义,要进行编译
                • 紧接着定义__TEST_H__符号
                • 之后再次包含该头文件,发现__TEST_H__已被定义,不再对之后包含的该头文件进行编译

                  不过上面这种写法比较麻烦,还有另外一种写法:

                  #pragma once
                  

                    效果与上面的方式是一样的

                    这样就可以避免头文件的重复引入

                    

                  十三、 其他预处理指令

                  #error
                  #pragma
                  #line
                  ···
                  #pragma pack()//在结构体部分介绍
                  

                  有兴趣的小伙伴可以阅读 《C语言深度解剖》

                    

                    

                    

                    

                    


                    好啦,本期关于预处理的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]