详解预处理(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】

目录
相关文章
|
11月前
|
前端开发
css 设置背景色渐变、字体颜色渐变
css 设置背景色渐变、字体颜色渐变
|
存储 JSON 运维
直击痛点,详解 K8s 日志采集最佳实践
在 Kubernetes 中,日志采集和普通虚拟机的方式有很大不同,相对实现难度和部署代价也略大,但若使用恰当则比传统方式自动化程度更高、运维代价更低。
直击痛点,详解 K8s 日志采集最佳实践
|
Java 应用服务中间件 数据库连接
【229秒 -> 69秒】部署时间缩短69%,ICBU商家技术部应用部署治理实践
部署时长不仅影响线上问题的解决恢复能力,也严重影响了我们日常的开发效率。本文记录了作者部署时的一些提效手段和最终的效果。
|
定位技术 Python
python高德地图webAPI:地理编码将地址信息转化为经纬度坐标
python高德地图webAPI:地理编码将地址信息转化为经纬度坐标
483 0
python高德地图webAPI:地理编码将地址信息转化为经纬度坐标
Liunx怎么安装spdlog(这是用来管理日志部分)
Liunx怎么安装spdlog(这是用来管理日志部分)
313 7
|
传感器 存储 缓存
|
C# 开发者 Windows
WPF在.NET9中的重大更新:Windows 11 主题
这也是一个很好的学习WPF的项目,可以通过看源代码提升自己的WPF水平。 WPF Gallery演示如何在标记中指定 XAML 控件,因为每个控件页都显示用于创建每个示例的标记。它还将显示您的应用程序的所有可能的布局选项。 WPF Gallery应用还包含有关使用颜色、排版和图标开发特殊应用程序的设计指南。它还包括一个示例页,用于演示如何使用不同的控件在 WPF 应用程序中创建用户界面。 WPF在.NET 9中可以使用Win11主题的控件了,有助于WPF开发者们开发出更符合现在设计风格、更美观的界面。 希望WPF越来越好。
191 0
|
传感器 安全 编译器
【C++断言机制】深入理解C/C++ 中静态断言static_assert与断言 assert
【C++断言机制】深入理解C/C++ 中静态断言static_assert与断言 assert
382 0
|
存储 Shell Android开发
【Android 逆向】获取安装在手机中的应用的 APK 包 ( 进入 adb shell | 获取 root 权限 | 进入 /data/app/ 目录 | 拷贝 base.apk 到外置存储 )
【Android 逆向】获取安装在手机中的应用的 APK 包 ( 进入 adb shell | 获取 root 权限 | 进入 /data/app/ 目录 | 拷贝 base.apk 到外置存储 )
718 0
【Android 逆向】获取安装在手机中的应用的 APK 包 ( 进入 adb shell | 获取 root 权限 | 进入 /data/app/ 目录 | 拷贝 base.apk 到外置存储 )