【C语言进阶深度学习记录】十八 条件编译的使用与分析

文章目录

    • 1 基本概念
      • 1.1 代码分析
      • 1.2 通过命令行定义宏
    • 2 #include 的本质
      • 2.1 解决重复包含头文件的问题
    • 3 条件编译的应用
    • 4 总结

1 基本概念

  • 条件编译的行为类似于C语言中的if … else…
  • 条件编译是预编译指示指令,用于控制是否编译某段代码

比如下图的代码:

在这里插入图片描述

  • 上面的 #if、#else、#endif 就是条件编译的构成当然,还有其他的样式,后面会通过例子详细说明。

1.1 代码分析

如下代码:

  • 22-1.c
#include <stdio.h>#define C 1
int main(){const char* s;#if(C == 1)s = "This is first printf...\n";#else s = "This is second printf...\n";#endifprintf("%s", s);return 0;
}
  • 上述代码编译运行结果为:

This is first printf…

上面的代码,看起来很简单,实际上,因为条件编译虽然像if…else 但是它并不像if…else那样在程序运行的时候进行判断,而是在预编译器编译的时候就决定了。什么意思呢?可以使用预编译指令对上述代码进行预编译(当然,为了简洁,先将1行和第14行需要使用标准库文件的代码注释掉)

  • gcc -E 22-1.c -o 22-1.i
  • 生成的预编译文件22-1.i 内容如下:
# 1 "22-1-lyy.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "22-1-lyy.c"int main(){const char* s;s = "This is first printf...\n";return 0;
}
  • 可以看到,在预编译阶段,就已经将条件编译指令处理完了,预编译结束后,就只剩下,条件编译中为真的那段代码。如上面的代码。所以我们可以总结出下面几条规则:
  1. 预编译器根据条件编译指令有选择的删除代码
  2. 预编译器不知道代码分支的存在
  3. if … else… 在程序的运行期进行判断
  4. 条件编译指令在预编译期就进行了判断,并且将条件为真的代码保留,为假的代码删除。

1.2 通过命令行定义宏

  • 补充一点,我们还可以通过命令行定义宏,而无需再代码中定义,如下图所示:

在这里插入图片描述

如下代码:

  • 22-2.c
#include <stdio.h>int main()
{const char* s;#ifdef Cs = "This is first printf...\n";#elses = "This is second printf...\n";#endifprintf("%s", s);return 0;
}
  1. 如果使用下面的方法编译:
  • gcc 22-2.c -o 22-2.out

运行结果为:

This is second printf…

  1. 如果使用下面的方法编译:
  • gcc -DC 22-2.c -o 22-2.out 或者 gcc -DC=1 22-2.c -o 22-2.out

运行结果为:

This is first printf…

我想上面的示例,已经足以让我们学会使用命令行定义宏了

2 #include 的本质

这个我们已经熟悉的不能再熟悉的东西了,包含头文件,有什么玄机?

  • #include 的本质是将已经存在的文件嵌入到当前文件中
  • #include 的间接包含,同样会产生嵌入文件的操作,这样会导致一个文件包含两个相同的头文件,这会产生错误。

比如下图的形式,编译会出错:

在这里插入图片描述

以下面三个代码为例,来说明:

  • global.h代码
// global.h
int global = 10;
  • test.h
// test.h
#include "global.h"
const char* NAME = "test.h";
char* hello_world()
{    return "Hello world!\n";
}
  • 2-3.c
#include <stdio.h>
#include "test.h"
#include "global.h"int main()
{const char* s = hello_world();int g = global;printf("%s\n", NAME);printf("%d\n", g);return 0;
}

编译运行上述三个代码;

  • gcc 22-3.c -o 22-3.out
  • ./22-3.out

在这里插入图片描述

上面错误的行数不一样是因为我个人的代码行数与上面的不一样,这个可以忽略

  • 从上面的错误可以看出,变量global 重复定义了。很明显,我们并没有重复定义。
  • 对上述代码进行预编译(首先将第1、10、11 行注释掉,方便文件内容查看),gcc -E 22-3.c -o 22-3.i 得出的预编译文件内容如下:
# 1 "22-3.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "22-3.c"# 1 "test.h" 1# 1 "global.h" 1int global = 10;
# 6 "test.h" 2const char* NAME = "test.h";
char* hello_world(){return "Hello world!\n";}
# 3 "22-3.c" 2
# 1 "global.h" 1int global = 10;
# 4 "22-3.c" 2int main()
{const char* s = hello_world();int g = global;return 0;
}

从上面的预编译文件可以看出:

  1. 11行和23行中,该程序包含了两次 global.h 这个头文件
  2. 15行和27行中,该程序定义了两次global 这个变量,导致了变量的重复定义
  3. 所以代码编译出错。将源代码test.h中包含的global.h文件注释掉,重新编译即可。

2.1 解决重复包含头文件的问题

在一个大型软件中,重复包含头文件是非常普遍的,如果我们一个一个的去找,那就会太浪费时间。

使用条件编译,是可以解决该问题的。比如将上面的global.h 与test.h改为如下样式:

  • global.h
// global.h
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
int global = 10;#endif
  • test.h
// test.h
#ifndef _TEST_H_
#define _TEST_H_#include "global.h"const char* NAME = "test.h";char* hello_world(){ return "Hello world!\n";}#endif

改好上面两个文件后,重新编译运行代码,结果如下:

在这里插入图片描述

  • 上面改变后的global.h与test.h 只是加了个条件编译指令,如果某一头文件被包含过一次,再要包含它的时候,由于不符合条件编译的条件,就不会有后面的代码,这样就避免了一个头文件被重复包含的错误。(如果不理解,回头从头看起,多看两遍)

3 条件编译的应用

条件编译可以定义软件产品的发布版与调试版

可以在代码中使用条件编译来定义需要调试的代码,当整个软件产品都调试通过最终需要发布的时候,可以直接去掉相关的宏定义(或者直接使用命令行的时候,不定义调试需要的宏即可),就可以使得代码在编译的时候,直接删除调试部分的代码,而无需自己动手删除。

本部分就不写实验了。

4 总结

  • 条件编译可以使得我们按照不同的条件编译不同的代码片段,因而可以产生不同的目标代码
  • 通过编译器命令行,可以定义预处理器使用的宏
  • 条件编译,可以避免重复包含同一个头文件多次
  • 条件编译可以定义软件产品的发布版和调试版

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422812.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言进阶深度学习记录】十九 #pragma使用与分析

文章目录1 #pragma 概念简介1.1 #pragma message 的用法1.2 #pragma once 的用法1.3 #pragma pack 的用法1.31 struct占用的内存大小如何计算2 总结在学习 #pragma 之前 &#xff0c;我们首先要明白一点&#xff0c; #pragma 的实现&#xff0c;在不同的编译器之间是不同的&…

【C语言进阶深度学习记录】二十一 # 和 ## 号操作符的使用与分析

文章目录1 # 运算符1.1 # 运算符的基本用法1.2 # 运算符的妙用2 ## 运算符2.1 ##运算符的基本用法2.2 ##运算符的工程运用3 总结1 # 运算符 #运算符用于在预处理期将宏参数转换为字符串#的转换是在预处理期间完成的&#xff0c;因此&#xff0c;只在宏定义中有效编译器并不知道…

生命游戏

生命游戏其实是一个零玩家游戏。在一个二维世界中&#xff0c;每一个格子看作一个细胞&#xff0c;每个细胞都有生和死两种状态。 每个细胞周围有8个邻居&#xff0c;这个细胞时刻关注着这些邻居的状态。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量…

【C语言进阶深度学习记录】二十 结构体大小计算与结构体内存布局的详细方法

结构体大小的计算往往是面试笔试常考的知识。对于简单的结构体&#xff0c;可以一眼看出来&#xff0c;对于复杂的结构体&#xff0c;该如何计算结构体占用内存的大小呢?本文学习所使用的编译器是gcc 4.4.5 使用其他编译器或者使用Windows 上的编译器有可能不太一样。 文章目录…

【C语言进阶深度学习记录】二十二 指针的本质分析

在C语言中&#xff0c;最难的也就是指针了。如果我们了解了指针的本质&#xff0c;它就会变得简单 文章目录1 回顾&#xff1a;什么是变量&#xff1f;1.1 *号的意义1.2 指针使用示例2 传值调用与传址调用2.1 利用指针交换两个变量3 const与指针的配合3.1 const指针代码分析4 总…

前端学习(284):纯css实现翻书效果

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>纯css3实现翻书效果</title><link href"reset.css" rel"stylesheet" type"text/css"><style>body {…

【C语言进阶深度学习记录】二十三 数组的本质分析

文章目录1 数组的概念1.1 数组的大小1.2 数组的初始化2 数组的地址与数组名3 数组名不能作为左值使用4 总结1 数组的概念 数组是相同类型的变量的有序集合数组中的元素没有名字 如下图是一个数组&#xff1a; 1.1 数组的大小 数组的大小是数组元素个数乘以元素的数据类型数组…

【C语言进阶深度学习记录】二十四 指针与数组的本质分析一

文章目录1 回顾--数组的本质2 指针的运算2.1 指针运算代码案例分析3 指针的比较3.1 指针运算的应用代码案例分析4 总结1 回顾–数组的本质 在之前的文章&#xff0c;已经学习了数组的本质分析。下面再来回顾一下&#xff1a; 数组是一段连续的内存空间数组名可以看做是指向数…

【C语言进阶深度学习记录】二十五 指针与数组的本质分析二

文章目录1 数组的访问方式1.1 数组的访问方式代码分析2 数组和指针不同3 a 和 &a 的区别3.1 指针运算的经典代码案例分析4 数组作为函数的参数4.1 数组作为函数参数的代码案例分析5 总结开题问&#xff1a;数组名可以当做常量指针使用。那么指针是否可以当做数组来使用? …

设计模式_第二篇_策略模式

本文是我通过学习《Head First 设计模式》而写。 作为我要描述的第一个模式&#xff0c;首先要说什么是设计模式&#xff0c;然后&#xff0c;用一个实例&#xff0c;并对这个实例不断的改进&#xff0c;引出策略模式。 与其空泛地给出一堆描述&#xff0c;倒不如给出通过一个实…

【C语言进阶深度学习记录】二十六 C语言中的字符串与字符数组的详细分析

之前有一篇文章是学习了字符和字符串的&#xff0c;可以与之结合学习&#xff1a;【C语言进阶深度学习记录】十二 C语言中的&#xff1a;字符和字符串 文章目录1 字符串的概念1.1 字符串与字符数组1.2 字符数组与字符串代码分析2 字符串字面量2.1 字符串字面量的本质的代码分析…

3天搞定的小型B/S内部管理类软件定制开发项目【软件开发实战10步骤详解】

2010-10-07 21:39 by 通用权限组件源码, 16580 visits, 收藏, 编辑 十一休假&#xff0c;杭州西湖边逛了一圈只能用人山人海来形容&#xff0c;浙大紫金港校区也逛了一圈风景如画&#xff0c;建设得真不错很棒&#xff0c;假期就去了这2个地方&#xff0c;然后在家里陪老婆、看…

【C语言进阶深度学习记录】二十八 数组指针与指针数组的分析

数组指针与指针数是非常重要的概念。面试中也是经常会被问到的 文章目录1 数组的类型1.1 定义数组的类型2 数组指针2.1 数组类型和数组指针的代码分析3 指针数组3.1 指针数组代码案例分析4 总结1 数组的类型 C语言中的数组有自己特定的类型。比如 int a[5]&#xff1b; 数组a…

【C语言进阶深度学习记录】二十九 main函数与命令行参数

文章目录1 main函数的返回值2 main函数的参数2.1 main函数的参数的代码案例分析3 main函数不一定是程序中第一个执行的函数4 总结1 main函数的返回值 main函数是操作系统调用的函数操作系统总是将main函数的返回值作为程序的退出状态main函数的返回值正常来说是0&#xff0c;如…

【C语言进阶深度学习记录】三十 二维数组与二维指针

文章目录1 二维指针&#xff08;指向指针的指针&#xff09;2 二维数组3 二维数组的类型3.2 如何动态申请二维数组4 总结1 二维指针&#xff08;指向指针的指针&#xff09; 指针的本质是变量指针的指针是保存指针变量的地址。如下面的代码&#xff1a; 为什么需要指向指针的存…

【C语言进阶深度学习记录】三十一 数组作为函数参数时退化为指针

之前的学习数组的文章中&#xff0c;已经知道一维数组作为函数参数的时候&#xff0c;最终会被编译器编译为指针。今天来看看二维数组的情形 文章目录1 为什么C语言中的数组作为函数参数会退化为指针&#xff1f;2 二维数组作为函数参数如何退化2.1 代码案例分析&#xff08;传…

使用HTMLParser模块解析HTML页面

HTMLParser是python用来解析html和xhtml文件格式的模块。它可以分析出html里面的标签、数据等等&#xff0c;是一种处理html的简便途径。HTMLParser采用的是一种事件驱动的模式&#xff0c;当HTMLParser找到一个特定的标记时&#xff0c;它会去调用一个用户定义的函数&#xff…

前端学习(294):rem小实例

altz转换为rem <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible"…

【C语言进阶深度学习记录】三十二 函数指针与使用函数指针实现回调函数

回调函数是非常重要的概念 文章目录1 函数的类型2 函数指针2.1 函数指针的使用2.2 使用函数指针实现回调函数3 总结1 函数的类型 跟以前学数组的时候是一样的&#xff0c;C语言中的数组是有自己的类型的。函数也是有自己的类型的。 函数的类型由返回值、参数的类型、参数的个…