C和指针:预处理(#include/define/if...)

news/2024/9/19 2:10:42 标签: c语言, 开发语言

预处理器

编译第一步称为预处理(preprocessing)阶段。C预处理器(preprocessor)在源代码编译之前对其进行一些文本性质的操作,包括删除注释、插入被#include 指令包含的文件的内容,替换由#define指令定义的符号以及根据条件编译指令进行编译。

预定义符号

#define
#define name stuff

预处理器把所有name替换成 stuff。

在程序中扩展#define定义符号和宏时,需要涉及几个步骤:

1.在调用宏时,首先对参数进行检查,看看是否包含了任何由#define 定义的符号。如果是,

它们首先被替换。

2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替代。

3.最后,再次对结果文本进行扫描,看看它是否包含了任何由#define 定义的符号。如果是,就重复上述处理过程。这样,宏参数和#define 定义可以包含其他#define定义的符号。但是,宏不可以出现递归。

更多例子

#define reg register
#define do_forever for(;;) 
#define CASE break;case

如果定义中的stuff非常长,它可以分成几行,除了最后一行之外,每行的末尾都要加一个反斜杠。

#define DEBUG_PRINT printf(“File 8s line 8d:”\ 
                      "x=8d, y=8d,z=8d",
                       _FILE__,__LINE__,\
                        X, y, z)

但是尽量使用内联函数,不要使用宏定义函数

#define允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(defined macro)。

宏的声明方式:

#define name(parameter-list) stuff

避免使用宏定义函数

#define SQUARE(x)  x* x
问题:
a = 5;
SQUARE(a+1)=>  a+1*a+1 
修正:
#define SQUARE(x) (x)*(x)

#define DOUBLE(x) (x)+(x)
问题:
a=5;
10*DOUBLE(a);=>10*(5)+(5)
修正:
#define DOUBLE(x) ((x)+(x))
宏与函数

宏非常频繁地用于执行简单的计算,比如在两个表达式中寻找其中较大(或较小)的一个:

#define MAX( a, b )   ((a)>(b)?(a):(b))

函数的参数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用。反之,这个宏可以用于整型、长整型、单浮点型、双浮点数以及其他任何可以用>操作符比较值大小的类型。宏是与类型无关的。

根本无法用函数实现的任务:

#define MALLOC(n, type)
((type *)malloc((n)*sizeof( type)))

pi = MALLOC( 25, int );
pi =(( int * )malloc((25)* sizeof( int )));

类型无法作为参数传到函数中。(在类型萃取中也会用到(bool_type))

带副作用的宏参数

当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当你使用这个宏

时就可能出现危险,导致不可预料的结果。

#define MAX( a,b) ((a)>(b)?(a):(b)) 
x=5; y=8;
Z=MAX( x++, Y++ );=> Z=((x++)>(y++ )?(x++):( y++ ));
printf(“x=8d, y=8d, z=8d\n”,x,Y.z);

#undef

用于移除一个宏定义,如果一个现存的名字需要被重新定义,那么旧定义首先必须用#undef移除。

#undef name
命令行定义

可以在命令行中进行宏定义

int array[ARRAY_SIZE]; 
gcc...  -D ARRAY_SIZE=100 prog.c//大概是这样
条件编译

使用条件编译,可以选择代码的一部分是被正常编译还是完全忽略

(如只用于调试程序的语句)。

用于支持条件编译的基本结构是#if指令和与其匹配的#endif指令。

#if constant-expression
   statements
#endif
//example:
#define DEBUG 1
#if DEBUG
 printf("x=8d, y=8d\n",x,y );
#endif

//is-else
#if constant-expression
statements
#elif constant-expression
other statements
#else
other statements
#endif

//测试符号是否被定义
#ifdef symbol
#ifndef symbol

//指令嵌套
#if #ifdef OPTION1
  unix_version_of_option1();
#endif #ifdef OPTION2
  unix_version_of_option2();
#endif
  defined(OS_MSDOS)#elif #ifdef OPTION2
  msdos_version_of_option2();
#endif
#endif
文件包含 (#include)

#include指令使另一个文件的内容被编译,预处理器删除#include,并用包含文件的内容取而代之。

使用#include 文件涉及一些开销但不大。

(1)如果两个源文件都需要同一组声明,把这些声明复制到每个源文件中所花费的编译时间跟把这些声明放入一个头文件,然后再用#include 指令把它包含于每个源文件所花费的编译时间相差无几。

(2)开销只是在程序被编译时才存在,对运行时效率无影响。

把这些声明放于一个头文件中,如果其他源文件还需要这些声明,就不必把这些拷贝逐一复制到这些源文件中,维护简单。

模块化的设计:

把使用几个头文件,每个头文件包含用于某个特定函数或模块的声明的做法更好一些。

函数库文件包含
#include <filename>

由编译器定义的"一系列标准位置"查找函数库头文件,如UNIX系统上的C编译器在/user/include目录查找函数库头文件。编译器允许把其他目录添加到这个列表,这样就可以创建自已的头文件函数库。

本地文件包含

处理本地头文件的一种常见策略就是在源文件所在的当前目录进行查找,如果该头文件并未找到,编译器就像查找函数库头文件一样在标准位置查找本地头文件。

#include "filename"
嵌套文件包含

头文件中包含头文件

// functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include <stdio.h>  // 包含以获取EOF等定义
int read_valid_int(void);  // 函数声明
#endif /* FUNCTIONS_H */

嵌套文件包含可能会将头文件包含多次,出现重复编译的问题。可以使用条件编译。所有的头文件都像下面这样编写:

#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
/*
** All the stuff that you want in the header file
*/
#endif

那么,多重包含的危险就被消除了。当头文件第 1 次被包含时,它被正常处理,符号_HEADERNAME_H 被定义为1。如果头文件被再次包含,通过条件编译,它的所有内容被忽略。

其他指令

#error 指令允许你生成错误信息

#error 指令允许你生成错误信息
#error text of error message
#line number "string

#line number通知预处理器 number是下一行输入的行号。

如果给出了可选部分“string”,预处理器就把它作为当前文件的名字。值得注意的是,这条指令将修改__LINE__符号的值,如果加上可选部分,它还将修改__FILE_符号的值,这条指令常用于把其他语言的代码转换为C代码的程序。

#pragma 向编译器发送特定的命令或请求,这些命令通常是编译器特定的,用来控制编译过程中的某些方面

#pragma once:这是非标准但广泛使用的指令,用于防止头文件被多次包含。

#pragma warning(disable: warning-number):关闭特定警告编号的编译器警告。

#pragma optimize("level"):指定优化级别。

#pragma message("message"):生成一个编译器信息消息。

#pragma source_encoding("encoding"):指定源文件的字符编码。

包规范:在某些编译器中,可以用来指定或控制某些编译特性。

调试辅助:#pragma pack(push, n) 和 #pragma pack(pop):用于控制结构体成员的对齐方式。


http://www.niftyadmin.cn/n/5660196.html

相关文章

【深度学习】【图像分类】【OnnxRuntime】【C++】ResNet模型部署

【深度学习】【图像分类】【OnnxRuntime】【C】ResNet模型部署 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【图像分类】【OnnxRuntime】【C】ResNet模型部署前言Windows平台搭建依赖环境模型转换--pytorch转onnxONNXRuntime推…

38. 带浮动分区标题的列表 移动卡片

带浮动分区标题的列表 创建一个列表,每个分区都有浮动标题。 使用 overflow-y: auto 允许列表容器垂直溢出。在内部容器(<dl>)上使用 display: grid 创建一个两列的布局。将标题(<dt>)设置为 grid-column: 1,将内容(<dd>)设置为 grid-column: 2。最后,对标题…

C++(三)----内存管理

1.C/C内存分布 看下面这个问题&#xff08;考考你们之前学的咋样&#xff09;&#xff1a; int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char* pCh…

【c++】自定义函数必须写在main函数外部吗?这篇文章带你一探究竟

在C++中,自定义函数不一定必须写在main函数外部。然而,为了代码的可读性、可维护性和重用性,通常建议将自定义函数(包括成员函数和非成员函数)定义在main函数外部。但是,有几种情况下函数可以或需要在main函数内部定义,尽管这些做法有其限制和特定用途。 1. 局部函数(…

【智路】智路OS Perception Fusion Service

Perception Fusion Service https://gitee.com/ZhiluCommunity/airos-edge/raw/r2.0/docs/02_Service/Perception_Fusion_Service.md 多传感器融合感知模块的主要任务是接收各传感器感知的障碍物信息&#xff0c;融合这些障碍物信息&#xff0c;得到融合后的障碍物信息。 智…

企业客户|基于springboot的企业客户管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 本论文主要论述了如何使用JAVA语言开发一个企业客户管理系统&#x…

探索Python的Excel世界:openpyxl的魔法之旅

文章目录 探索Python的Excel世界&#xff1a;openpyxl的魔法之旅背景&#xff1a;为什么选择openpyxl&#xff1f;什么是openpyxl&#xff1f;如何安装openpyxl&#xff1f;简单的库函数使用方法场景应用&#xff1a;openpyxl在实际工作中的应用常见bug及解决方案总结 探索Pyth…

Spring Cloud集成Gateaway

Spring Cloud Gateway 是一个基于 Spring 生态的网关框架&#xff0c;用于构建微服务架构中的API网关。它可以对请求进行路由、过滤、限流等操作&#xff0c;是Spring Cloud微服务体系中常用的组件之一。下面介绍 Spring Cloud Gateway 的核心概念、应用场景以及简单的示例。 …