C语言:预处理

C语言:预处理

预处理

C语言的编译步骤

  1. 预处理
  2. 编译
  3. 汇编
  4. 链接、

在这里插入图片描述

什么是预处理

预处理就是在源文件(如.c文件)编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动完成;当源文件在编译时,编译器会自动调用预处理程序来完成预处理执行的解析,预处理执行解析完成才能进入下一步的编译过程。

我们为了能够方便的看到这个编译细节,我们可以使用下面命令:

gcc 源文件 -E -o 程序名[.后缀]

预处理功能

宏定义
  • 不带参数的宏定义

    • 语法:

      #define 宏名 常量数据
      
    • 预处理:此时的预处理只做数据替换,不做类型检查

    • 注意:我们定义的宏是不会占用内存空间,还没有进入到编译环节,更不要说运行了,在编译前已经将宏名替换成了常量数据

    • 宏展开;在预编译时将宏名替换成字符串的过程称为“宏展开”。这里的常量数据其实就是一个不带双引号的字符串。

    • 案例:

#include <stdio.h>
#define PI 3.1415926
int main(int argc, char *argv[])
{
    float l, s, r, v;
    printf("请输入圆的半径:\n");
    scanf("%f", &r);
    // 计算周长
    l = 2.0 * PI * r;
    // 计算面积
    s = PI * r * r;
    printf("l=%10.4f\ns=%10.4f\n", l, s);
    return 0;
}
  • 带参数的宏定义

    • 语法:

      #define 宏名(参数列表) 参数表达式
      

      实现

      #include <stdio.h>
      // 定义宏
      #define multi(a, b) a *b
      int main(int argc, char *argv[])
      {
          int result = multi(7 + 2, 3);
          printf("%d\n", result); // 13
          return 0;
      }
      

      在这里插入图片描述

  • 宏定义的作用域

    • #define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。通常#define命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。

    • 可以用#undef命令终止宏定义的作用域

    • 案例:

      #include <stdio.h>
      #define PI 3.14
      #define DAY 20
      void fun()
      {
          float r = 4;
          float s = PI * r * r;
          int day = DAY;
      }
      #undef PI // 终止了PI的范围
      #define PI 3.1415926
      void fun1()
      {
          float r = 4;
          float s = PI * r * r;
          int day = DAY;
      }
      int main(int argc, char *argv[])
      {
          fun();
          fun1();
          return 0;
      }
      
  • 在宏定义中引用已定义的宏名

    • 案例:

      #include <stdio.h>
      #define R 3.0
      #define PI 3.14
      #define L 2 * PI *R
      #define S PI *R *R
      int main(int argc, char *argv[])
      {
          printf("L=%f\nS=%f\n", L, S);
          return 0;
      }
      

文件包含

概念

所谓“文件包含”处理是指一个源文件可以将另一个源文件的全部内容包含进来。这个适用于多文件开发。通常,一个常规的C语言程序会包含多个源码文件(*.c),当某些公共资源需要在各个源码文件中使用时,为了避免多次编写相同的代码,我们一般会进行代码的抽取(*.c),并提供公共的访问文件(*.h),然后在各个源码文件中直接包含即可。

在这里插入图片描述

注意:*.h中函数的声明必须在*.c中有对应函数的定义(函数的实现),否则没有意义。

头文件(.h)的内容
  • 头文件中所存放的内容,就是各个源码文件的彼此可见的公共资源,包括:

    1. 全局变量的声明
    2. 普通函数的声明
    3. 静态函数的声明
    4. 宏定义
    5. 结构体、联合体的定义
    6. 枚举常量列表的定义
    7. 其他头文件?
  • 示例代码:

    // head.h
    
    extern int global;// 全局变量的声明
    extern void f1();// 普通函数的声明
    static void f2() // 静态函数的定义
    {
        ...
    }
    #define MAX(a,b) ((a)>(b)?(a):(b)) // 宏定义
    
    struct node  // 结构体的定义
    {
        ...
    };
    
    union attr  // 联合体的定义
    {
        ...
    };
    
    #include <stdio.h> // 其他头文件
    #include "mylib.h" // 其他头文件-自定义文件
    
  • 特别说明:

    1. 全局变量、普通函数的定义一般出现某个源文件(*.c,*.cpp)中,其他的源文件想要使用都需要进行说明,因此一般放在头文件中更方便
    2. 静态函数、宏定义、结构体、联合体的定义都只能在其所在的文件可见(说白了,就是在预处理阶段直接从头文件拷贝)因此如果多个源文件都需要的话,放到头文件中定义是最方便,也是最安全的选择。
  • 文件包含预处理机制:

    此时的预处理,是将文件中的内容替换,文件包含指令。

  • 包含方式:

    • 第1种:#include <xxxx.h>

      系统会到标准库文件目录(Liunxc下/usr/include

    • 第2种:#include "xxxx.h"

      在当前工作路径下查找,如果未找到,仍然会到标准库文件目录查找。建议对于自定义库采用这种写法

  • 案例:

    myhead.h

    #ifndef _MYHEAD_H
    #define _MYHEAD_H
    // 数组的累加和计算
    extern int sum(const int *, int);
    // extern int sum(const int *p,int len);
    #endif //_MYHEAD_H
    

    myhead.c

    #include "myhead.h"
    /**
     * 数组的累加和计算
     */
    int sum(const int *p, int len)
    {
        int sum = 0;
        register int i = 0;
        for (; i < len; i++)
        {
            sum += *(p + i);
        }
        return sum;
    }
    

    app.c

    #include <stdio.h>
    #include "myhead.h"
    int main(int argc, char *argv[])
    {
        int arr[5] = {12, 13, 14, 15, 16};
        int res = sum(arr, 5);
        printf("数组累加和的结果是:%d\n", res);
        return 0;
    }
    

    多文件编译命令

    gcc app.c myhead.c -o app
    
    条件编译
    • 概念:根据设定的条件选择待选择编译的语句代码
    • 条件编译预处理机制:将满足条件的语句进行保留

      • 语法1:

        #ifdef 标识 -- 判断标识符定义与否,定义为真,未定义为假
        ..#else
        ..#endif
        
      • 语法2:

        #ifndef 标识 -- 判断标识符定义与否,未定义为真,定义为假
        ..#else
        ..#endif
        
      • 语法3:

        #if 表达式 -- 判断表达式的结果,表达式成立为真(1),不成立为假(0)
        ..
        #else
        ..
        #endif
        
  • 案例:

    #include <stdio.h>
    #define LETTER 0 // 默认大写
    int main(int argc, char *argv[])
    {
        // 测试用的字母字符
        char str[20] = "C Language";
        char c;
        int i = 0;
        while ((c = str[i]) != '\0')
        {
            i++;
    #if LETTER
            if (c >= 'a' && c <= 'z')
            {
                c -= 32;
            }
    #else
            if (c >= 'A' && c <= 'Z')
            {
                c += 32;
            }
    #endif
            printf("%c", c);
        }
        printf("\n");
        return 0;
    }
    
避免头文件重复包含的方法

由于头文件包含指令#include的本质是复制粘贴,并且一个头文件中可以嵌套包含其他头文件,因此很容易出现一种情况是:头文件被重复包含。

语法:

#ifndef __XXXX_H // 一般取头文件名称的大写
#define __XXXX_H
..
#endif

案例:

myhead.h

#ifndef _MYHEAD_H
#define _MYHEAD_H
// 数组的累加和计算
extern int sum(const int*,int);
// extern int sum(const int *p,int len);
#endif //_MYHEAD_H
#### 避免头文件重复包含的方法

由于头文件包含指令#include的本质是复制粘贴,并且一个头文件中可以嵌套包含其他头文件,因此很容易出现一种情况是:头文件被重复包含。

语法:

```c
#ifndef __XXXX_H // 一般取头文件名称的大写
#define __XXXX_H
..
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值