详解预处理(2)

简介: 详解预处理(2)

今天接着继续讲解预处理的点,前面已经深入学习了#define。

#undef

#undef 这条指令用于移除一个宏定义。

#undef NAME

如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

#include<stdio.h>
#define M 10
int main()
{
  int a = M;
  printf("%d\n", a);
#undef M
#define M 100
  int b = M;
  printf("%d", b);
  return 0;
}

命令行定义

命令行定义是什么呢?

许多C 的编译器提供一种能力,允许在命令行中定义符号。用于启动编译预处理阶段过程

命令行是在命令行中给一些符号指定值。


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


下面到gcc编译器上验证一下。

#include<stdio.h>
int main()
{
  int arr[SZ];
  int i = 0;
  for (i = 0; i < SZ; i++)
  {
    arr[i] = i;
  }
  for (i = 0; i < SZ; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

  • 变长数组是指数组大小由变量指定,在程序运行的时候指定变量的大小
  • 命令行定义是指程序在编译阶段指定变量的大小

条件编译

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

条件编译是指满足条件就编译,不满足条件就不编译。

哪些地方会用到条件编译呢?

比如 :调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。还有当我们写的代码有跨平台的使用,一个文件有windows底下使用的代码,又含有Linx底下使用的代码。准对不同的平台,编译环境的不同,我们需要选择性的编译代码。

接下来,我们介绍一下常见的条件编译。


NO1.

常量表达式为真 的时候参与编译,为假 的时候不参与编译,#if 和 #endif 是配套的。

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
#include<stdio.h>
int main()
{
#if 1
  printf("hehe\n");
#endif
  return 0;
}

NO2.多个分支的条件编译

  • 常量表达式为真 的时候参与编译,为假 的时候不参与编译。
  • 只选择一个表达式为真的执行。
  • 如果有多个表达式为真,选择首先出现表达式为真的编译执行。


2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
#include<stdio.h>
int main()
{
#if 0
  printf("hehe\n");
#elif 2==1
  printf("haha\n");
#elif 3==1
  prntf("heihei\n");
#elif 1
  printf("xixi\n");
#endif
  return 0;
}

如果多个表达式为真呢?

#include<stdio.h>
int main()
{
#if 0
  printf("hehe\n");
#elif 1==1
  printf("haha\n");
#elif 3==1
  prntf("heihei\n");
#elif 1
  printf("xixi\n");
#endif
  return 0;
}

NO3.判断是否被定义

  • defined(symbol) 成立编译执行 ;不成立不编译执行
  • !defined(symbol) 成立编译执行 ;不成立不编译执行
  • 两种写法都要掌握
3.判断是否被定义
#if defined(symbol)//第一种写法
#ifdef symbol//第二种写法
//反之
#if !defined(symbol)
#ifndef symbol
//两种写法一样的效果
#include<stdio.h>
#define M 0
int main()
{
#if defined(M)//不是看M的真假而是看M是否被定义过没有
  printf("hehe\n");
#endif
#ifdef M//两种写法都可以
  printf("hehe\n");
#endif
  return 0;
  return 0;
}

同理 !defined

NO4.嵌套指令

#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

在我们的头文件#include,有很多这样的嵌套指令的使用,大家自己下去看一看。关于嵌套指令,在当前我们学习进度下,就是头文件的包含。我们来介绍一下。

文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。

这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次。

头文件被包含的方式

本地文件包含

#include"test.h"

查找策略:

  • 先在源文件所在目录下查找,如果该头文件未找到
  • 编译器就像查找库函数头文件一样在标准位置查找头文件。
  • 如果找不到就提示编译错误。

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


VS环境的标准头文件的路径:(不确定--建议自己按照自己安装的路径去查找)

库文件包含

#include<test.h>
  • 查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。


综上所诉: 这样是不是可以说,对于库文件也可以使用" "的形式包含? 答案是肯定的,可以。

但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。


头文件的包含有两种形式:


  • 包含本地文件(自己.h文件)   #include"xxx.h"
  • 包含标准库的头文件  #include   #include"xxx.h"  


嵌套文件包含

在大型工程项目中出现下面这种情况,该怎么办?


comm.h和comm.c是公共模块。 test1.h和test1.c使用了公共模块。 test2.h和test2.c使用了公共模块。 test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。


解决办法:条件编译。

每个头文件的开头写:

#ifndef __TEST_H__
//定义了的话将不在定义,未定义的话编译执行下面代码
#define __TEST_H__//头文件的内容
//定义
...
....
#endif //__TEST_H__

或者写这个:

#pragma once

就可以避免头文件重复引入的问题了。

注:推荐《高质量C/C++编程指南》里面有这样两道笔试题:

1. 头文件中的 ifndef/define/endif是干什么用的?
2. #include  和 #include "filename.h"有什么区别?

学完上面的知识,相信你心中一定有了答案了。

其他预处理指令

#error
#pragma
#line
...
#pragma pack()在结构体部分介绍。

后前会更新一个专栏去学习《C语言深度解剖》 这本书。大家要乖乖敲代码哦。

✔✔✔✔✔最后感谢大家的阅读,若有错误和不足,欢迎指正!

代码---------→【gitee:唐棣棣 (TSQXG) - Gitee.com

联系---------→【邮箱:2784139418@qq.com】

目录
相关文章
|
6月前
|
编译器 C++
C 预处理器
C 预处理器。
86 10
|
6月前
|
编译器 程序员 Linux
C++系列九:预处理功能
C++系列九:预处理功能
|
1月前
|
编译器 Linux C语言
|
6月前
|
编译器 C语言
预处理深入
预处理深入
38 0
预处理深入
|
6月前
|
编译器 C语言
c预处理器
c预处理器
39 0
|
6月前
|
Linux C语言 Windows
C预处理分析
C预处理分析
41 2
|
6月前
|
安全 C语言
程序预处理:全解版-1
程序预处理:全解版
38 0
|
6月前
|
编译器 C语言
程序预处理:全解版-2
程序预处理:全解版
39 0
|
6月前
|
编译器 C++
c++预处理器
c++预处理器
38 0