资讯专栏INFORMATION COLUMN

楼下大妈看完广场舞都想不跳了!C语言预处理(下)

robin / 599人阅读

摘要:传送门楼下大爷看完直呼简单语言预处理上一命令行编译什么是命令行编译在编译的时候通过命令行的方式对其进行相关的定义,叫做命令行编译。替换方式为,预处理器先删除这条指令,并用包含文件的内容替换。

前言:

本文为C语言预处理的下篇,将继续讲解C语言预处理的基础知识。

? 传送门:楼下大爷看完直呼简单!C语言预处理(上)


一、命令行编译

❓ 什么是命令行编译?

? 在编译的时候通过命令行的方式对其进行相关的定义,叫做命令行编译。

? 介绍:许多C的编译器提供的一种能力,允许在命令行中定义符号。用于启动编译过程。当我们根据同一个源文件要编译出不同的一个程序的不同版本的时,可以用到这种特性,增加灵活性。

? 例子:假如某个程序中声明了一个某个长度的数组,假如机器甲内存有限,我们需要一个很小的数据,但是机器丙的内存较大,我们需要一个大点的数组。

#include int main() {    int arr[ARR_SIZE];    int i = 0;    for (i = 0; i < ARR_SIZE; i++) {        arr[i] = i;    }    for (i = 0; i < ARR_SIZE; i++) {        printf("%d ", arr[i]);    }    printf("/n");        return 0;}

? gcc 环境下测试:(VS 里面不太好演示)

gcc test.c -D ARR_SIZE=5

ls

a.out  test.c

./a.out

0 1 2 3 4 5

gcc test.c -D ARR_SIZE=20

./a.out

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

二、条件编译

0x00 介绍

? 在编译一个程序时,通过条件编译指令将一条语句(一组语句)编译或者放弃是很方便的。

? 调试用的代码删除了可惜,保留了又碍事。我们就可以使用条件编译来选择性地编译:

#include #define __DEBUG__ // 就像一个开关一样int main(void){    int arr[10] = {0};    int i = 0;    for (i = 0; i < 10; i++) {        arr[i] = i;        #ifdef __DEBUG__ // 因为__DEBUG__被定义了,所以为真        printf("%d ", arr[i]); // 就打印数组            #endif // 包尾    }    return 0;}

? 运行结果:1 2 3 4 5 6 7 8 9 10

❗  如果不想用了,就把 #define __DEBUG__ 注释掉:

#include // #define __DEBUG__ // 关int main(void){    int arr[10] = {0};    int i = 0;    for (i = 0; i < 10; i++) {        arr[i] = i;        #ifdef __DEBUG__ // 此时ifdef为假        printf("%d ", arr[i]);              #endif    }    return 0;}

? (代码成功运行)

0x01 条件编译之常量表达式

? 介绍:如果常量表达式为真,参加编译。反之如果为假,则不参加编译。

? 代码演示:常量表达式为真

#include int main(void) {#if 1    printf("Hello,World!/n");#endif    return 0;}

 ? 运行结果:Hello,World!

? 代码演示:常量表达式为假

#include int main(void) {#if 0    printf("Hello,World!/n");#endif    return 0;}

? (代码成功运行)

? 当然也可以用宏替换,可以表示地更清楚:

#include #define PRINT 1#define DONT_PINRT 0int main(void) {#if PRINT    printf("Hello,World!/n");#endif    return 0;}

0x02 多分支的条件编译

? 介绍:多分支的条件编译,直到常量表达式为真时才执行。

? 代码演示:

#include int main(void) {#if 1 == 2 // 假    printf("rose/n");#elif 2 == 2 // 真    printf("you jump/n");#else     printf("i jump/n")#endif    return 0;}

? 代码运行结果:you jump

0x03 条件编译判断是否被定义

? 定义:ifdef if defined() ifndef if !defined() 效果是一样的,用来判断是否被定义。

? 代码演示:

#include #define TEST 0// #define TEST2 // 不定义int main(void) {/* 如果TEST定义了,下面参与编译 */// 1#ifdef TEST    printf("1/n");#endif// 2#if defined(TEST)    printf("2/n");#endif/* 如果TEST2不定义,下面参与编译 */// 1#ifndef TEST2    printf("3/n");#endif// 2#if !defined(TEST2)    printf("4/n");#endif    return 0;}

0x04 条件编译的嵌套

? 和 if 语句一样,是可以嵌套的:

#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 指令的地方一样。替换方式为,预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。

0x00 头文件被包含的方式

< > " " 包含头文件的本质区别:查找的策略的区别

" " 的查找策略:先在源文件所在的目录下查找。如果该头文件未找到,则在库函数的头文件目录下查找。(如果仍然找不到,就提示编译错误)

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

/usr/include

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

C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/include

< > 的查找策略:直接去标准路径下去查找(如果仍然找不到,就提示编译错误)

❓ 既然如此,那么对于库文件是否也可以使用 " " 包含?

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

? 代码演示:

①  add.h

int Add(int x, int y);

②  add.c

int Add(int x, int y) {	return x + y;}

③  test.c

#include #include "add.h"int main(void) {	int a = 10;	int b = 20;	int ret = Add(a, b);	printf("%d/n", ret);	return 0;}

? 运行结果:30

0x01 嵌套文件的包含

❗  头文件重复引入的情况:

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

⚡ 如果嫌麻烦,还有一种非常简单的方法:

#pragma once // 让头文件即使被包含多次,也只包含一份

 

? 笔试题:选自《高质量C/C++编程指南》

① 头文件中的 ifnde / define / endif 是干什么用的?

答:防止头文件被重复多次包含。

#include <filename.h>#include "filename.h" 有什么区别?

答:尖括号是包含库里面的头文件的,双引号是包含自定义头文件的。它们在查找策略上不同,尖括号直接去库目录下查找。而警号双引号是现去自定义的代码路径下查找,如果找不到头文件,则在库函数的头文件目录下查找。

 


参考资料:

陈正冲. 《C语言深度解剖》[M]. 第三版. 北京航空航天大学出版社, 2019.

比特科技. C语言进阶[EB/OL]. 2021[2021.8.31]. .

林锐博士. 《高质量C/C++编程指南》[M]. 1.0. 电子工业, 2001.7.24.

本章完。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/118779.html

相关文章

  • UCloud快杰云主机 提升糖豆App运营与质量实战

    摘要:快杰云主机搭建。快杰云主机,搭载选用第二代处理器,主频,领先的制程工艺带来显著的性能提升,使之打破总体性价比世界记录。因此,糖豆与神策数据此次最终共同选择了快杰服务器。在凉风习习的夜晚里,璀璨的灯火映照下,随处都能碰到翩翩起舞的人群,这就是广场舞,在这嘹亮、节奏鲜明的歌声里有一款大妈们热爱的APP—- 糖豆APP。  一、挑战 在发展之初,多家企业看好广场舞这个赛道,而糖豆创业初...

    figofuture 评论0 收藏0
  • 众里寻她千百度--正则表达式

    摘要:如果经过一系列输入,最终如果能达到状态,则输入内容一定满足正则表达式。正则表达式可以转换为,已经有成熟的算法实现这一转换。不过有时候转换为可能导致状态空间的指数增长,因此直接用识别正则表达式。 原文地址 先来看一个让人震撼的小故事,故事来自知乎问题PC用户的哪些行为让你当时就震惊了? 同学在一个化妆品公司上班,旁边一个大妈(四十多岁)发给他一个exl表,让他在里面帮忙找一个经销商的资料...

    golden_hamster 评论0 收藏0
  • 网络协议 20 - RPC 协议(上)- 基于XML的SOAP协议

    摘要:传输协议问题我们先解决第一个,传输协议的问题。信封里面的信分抬头和正文板栗焖鸡协议我们学过,这个请求使用方法,发送一个格式为的正文给,从而下一个单,这个订单封装在的信封里面,并且表明这是一笔交易,而且订单的详情都已经写明了。 【前五篇】系列文章传送门: 网络协议 15 - P2P 协议:小种子大学问 网络协议 16 - DNS 协议:网络世界的地址簿 网络协议 17 - HTTPDN...

    Caicloud 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<