小工具知识网

当前位置: 首页 > 行业知识 > C语言:预处理

行业知识

C语言:预处理

2026-11-08 10:07:09 来源:互联网转载

C语言:预处理

    • 预定义符号
    • #define
      • 定义常量
      • 定义宏
      • 宏与函数对比
      • #操作符
      • ##操作符
      • 条件编译
      • 头文件包含
        • 库文件包含
        • 本地文件包含
        • 嵌套���件包含

          预定义符号

          C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。

          __FILE__ //进⾏编译的源⽂件__LINE__ //⽂件当前的⾏号__DATE__ //⽂件被编译的⽇期__TIME__ //⽂件被编译的时间__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

          示例:

          printf("file:%s line:%d\n", __FILE__, __LINE__);

          输出结果:

          file:test.c line:1

          #define

          在C语言中,#define是一个预处理器指令,用于定义宏。

          宏是一个被预处理器替换的标识符或一个标识符和参数的组合。宏定义可以用来简化代码、提高代码的可读性和可维护性。

          使用#define可以定义常量、函数宏等。

          定义常量

          • 定义常量:
             #define PI 3.14

            这样就可以在代码中使用PI来代表3.14。

            • 定义关键字:
              #define reg register

              将 reg 定义为关键字 register,可以将reg这个简写代替register关键字使用。

              • 定义代码段
                #define CASE break;case

                正常的switch语句每一个case都要加上break,通过这个写法,我们可以在写CASE时自动补齐break。


                定义宏

                #define 机制允许把参数替换到文本中,这种功能叫做 宏(macro) / 定义宏(define macro)

                语法:

                #define name(parament-list) stuff

                其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。

                示例:

                #define SQUARE(x) x * x

                当我们在代码中输入以下代码:

                int main(){int a = 5;int b = SQUARE(a);return 0;}

                在编译后就会转化为:

                int main(){int a = 5;int b = a * a;return 0;}

                也就是直接发生了文本替换,这种宏的形式非常像函数,因此也可以称为宏函数。但是其也有很多需要注意的地方。

                比如以下代码:

                int a = 5;int b = SQUARE(a + 1);

                我们希望先执行a + 1,然后再传入SQUARE中,但是其不会这样做因为其会将上述代码直接替换为:

                int a = 5;int b = a + 1 * a + 1;

                由于操作符优先级的问题,我们不会得到想要的结果。为了处理这个情况,我们需要把参数用小括号括起来:

                #define SQUARE(x) (x) * (x)

                代码就变成:

                int a = 5;int b = (a + 1) * (a + 1);

                这样我们就可以行使预期的功能了。

                那么我们再看到一串代码:

                #define DOUBLE(x) (x) + (x)int a = 5;int b = 10 * DOUBLE(a);

                代码编译后为:

                int a = 5;int b = 10 * 5 + 5;  

                又出现了一样的问题,我们的 10 * DOUBLE(a)并没有先执行DOUBLE(a),而展开后,又出现了操作符优先级问题,所以我们的宏还要再优化:

                #define DOUBLE(x) ((x) + (x))

                在宏的最外侧再加一层括号,就可以独立运行,不受外界操作符影响了。

                通过以上推断,我们可以发现,宏虽然可以很好的替换代码,但是会受到外界操作符的影响,此时就要注意很多细节。


                宏与函数对比

                函数在调用时,是会开辟内存创建栈帧的,而宏则直接执行,所以速度更快。但是由于宏是在编译阶段就已经处理好了,所以宏不能通过调试观察现象,还要操作符优先级带来的种种问题。因此宏不适合处理复杂的函数,但是很适合短小简单的函数。


                #操作符

                功能:

                #可以将宏的参数转化为字符串

                比如以下代码:

                #define PRINT(n) printf("the value of "#n " is %d", n);

                我们尝试调用:

                int a = 5;PRINT(a);

                代码就会被转化为:

                int a = 5;printf("the value of ""a" " is %d", a);

                可以看到,两个n的替换效果是不同的,对于n其会直接被替换为变量a;而对于#n,其不是简单的替换,而是把参数名转化为了字符串”a“。


                ##操作符

                ##操纵符可以将两个符号合并为一个符号

                示例:

                #define GENERIC_MAX(type) \type type##_max(type x, type y)\{ \return (x>y?x:y); \}

                该宏用于创建不同类型的比大小函数,由于不同函数需要不同的函数名,于是利用##来连接函数名,也就是type##_max部分。type是一个宏参数,当传入float,type##_max整体就被连接为float_max,当传入int,整体就被连接为int_max。也就是##起到一个连接作用。


                条件编译

                条件编译是C语言中的一种编译指令,用于在编译过程中根据指定的条件选择性地包含或排除某些代码。它主要是为了满足不同平台、不同编译选项或不同场景下的需求。

                条件编译使用预处理指令实现,预处理指令以#开头。下面是一些常用的条件编译指令及其用法:

                1. #if / #elif / #else / #endif

                  #if用于基于预处理器常量的值进行条件判断。

                  #elif用于在多个条件之间进行选择。

                  #else用于在没有匹配的#if或#elif时执行。

                  #endif用于结束条件编译块。

                  示例:

                   #define NUM 5      #if NUM > 10       printf("NUM is greater than 10\n");   #elif NUM > 0       printf("NUM is greater than 0\n");   #else       printf("NUM is less than or equal to 0\n");   #endif

                这个代码和C语言的if代码很像,不过多讲解了。

                1. #ifdef / #ifndef

                  #ifdef用于检查一个标识符是否已经定义,如果已定义则编译后面的代码,否则跳过。

                  #ifndef与#ifdef相反,用于检查一个标识符是否未定义。

                  示例:

                #ifdef DEBUG    printf("Debug mode enabled\n");#endif
                1. #define

                  #define用于定义宏。宏是一种将一组指令作为一个整体进行替换的方式。

                  示例:

                  #define MAX(a, b) ((a) > (b) ? (a) : (b))int x = 10;int y = 20;int max = MAX(x, y);
                2. #include

                  #include用于将指定的头文件包含到当前文件中。

                  示例:

                  #include int main() {    printf("Hello, World!\n");    return 0;}
                3. #pragma

                  #pragma用于向编译器发出特定的指令,如优化选项、警告控制等。它的语法和功能因编译器而异。

                  示例:

                  #pragma warning(disable: 4996)

                这些是C语言中常用的条件编译指令和代码用法。通过合理使用条件编译,我们可以根据不同的需求自由地控制代码的编译过程。


                头文件包含

                头文件包含分两种形式:本地头文件与库文件。

                库文件包含

                语法:

                #include 

                查找头⽂件会直接去标准路径下去查找,如果找不到就提⽰编译错误

                我们平常使用的库文件都通过尖括号来包含,其会直接到存放库文件的路径中查找。

                本地文件包含

                #include "filename"

                先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件,如果找不到就提⽰编译错误

                如果是用户自己编写的头文件,我们要用双引号""包含,如果通过这种方式包含头文件,那么会先在当前源文件的目录下查找,如果没有找到,再去库文件中查找。

                也就是说:库文件也可以通过双引号包含,但是会多出额外的查找步骤,所以库文件还是用尖括号包含更好,而自己编写的头文件必须双引号包含。

                嵌套文件包含

                假设我们现在有以下文件结构:

                头文件test.h:

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

                源文件test.c:

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

                我们在test.c中多次包含了头文件,这就会导致test.h反复被展开,产生大量重复代码。这就是嵌套文件包含的问题,那么我们要如何处理这个问题,让其只能被包含一次呢?

                条件编译方法:

                在头文件test.h中加入以下代码:

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

                第一次包含头文件:

                先执行#ifndef __TEST_H__,我们此时没有定义__TEST_H__这个变量,if成立,此时头文件会被展开,同时执行#define __TEST_H__,此时__TEST_H__就已经被定义了


                第二次包含头文件:

                第二次站时,由于上一次展开已经定义了__TEST_H__这个变量,导致#ifndef __TEST_H__判断为假,此时整个头文件都不会再被编译,直接舍弃

                后续再展开头文件,都会因为 __TEST_H__被定义而不会编译,解决了嵌套编译的问题。

                一般而言,我们这个用于判断头文件有没有被展开过的变量,是头文件名通过一定规则转化来的:

                1. 在头文件前后加上两个下划线__头文件.h__
                2. 把头文件中的点.也改为下划线__头文件_h__

                因此test.h的常量就是:__TEST_H__。

                pragma:

                通过条件编译其实是比较传统的写法,我们还有一种更加简洁方便的写法:

                #pragma once

                只要在任何头文件前面加上这句话,就只会被编译一次了。


上一篇:利用ollama和open-webui本地部署通义千问Qwen1.5-7B-Chat模型

下一篇:怎么连接异地ftp服务器?