当前位置: 首页 > news >正文

C复习-预处理器:define+条件编译+文件包含

参考: 里科《C和指针》


预定义符号

__FILE__ // 进行编译的源文件名
__LINE__ // 文件当前行的行号
__DATE__ // 文件被编译的日期 Jan 31 1997
__TIME__ // 文件被编译的时间

#define宏

写法:

1)宏的名字全大写,以区分宏与函数。

2)如果一个现存的名字需要重新定义,首先需要使用#undef name移除旧定义。

3)在那些对表达式进行求值的宏中,每个宏参数出现的地方都应该加上括号,并且在整个宏定义的两边也加上括号(避免因运算符优先级不同造成歧义)

使用宏而非函数的情况

1)宏的规模和速度更快(函数存在调用、返回的开销)

2)宏与类型无关,函数的参数必须声明为一种特定类型

3)某些无法使用函数的情况

宏的缺点是每次使用时,必须将一份宏定义代码拷贝到程序中,因此除非宏非常短,不然可能大幅增加程序的长度

// 可以有多行,但是要加\
// 这个适合多次打印信息的情况,比如我需要知道某个变量在某些操作后的结果
// 因为define是直接替换的,一定不要多输;
#define DEBUG_PRINT printf("xxx" \xxxx \)#define SQUARE(x) x * x
int main()
{int i = 2;// 输出25printf("%d\n", SQUARE(5));// 实际替换为 i + 1 * i + 1,所以是5// 可以将define语句改为:#define SQUARE(x) (x) * (x)printf("%d\n", SQUARE(i + 1 ));return 0;
}#define DOUBLE(x) (x) + (x)
a = 5;
// 此时会先计算乘法,所以应该修改define为#define DOUBLE(x) ( (x) + (x) )
printf("%d\n", 10 * DOUBLE( a ));//利用邻近字符串自动连接特性
#define PRINT(FORMAT, VALUE) \printf("The value is " FORMAT "\n", VALUE)
int i = 2;
PRINT("%d", i + 3);
// 如果希望将表达式转为字符串,用#
#define PRINT(FORMAT, VALUE) \printf("The value of " #VALUE \" is " FORMAT "\n", VALUE)
// The value of i + 3 is 5
PRINT("%d", i + 3);// 如果需要传递一个标识符名字,比如想把一个值加到sum1、sum2、sum3...
// 使用##。但是需要保证该名字的变量是有的
#define ADD_TO_SUM( NUM, VAL ) \sum ## NUM += VAL
int sum1 = 0;
ADD_TO_SUM(1, 25);
printf("%d", sum1); // 25// 无法使用函数的情况下,使用宏
#define MALLOC(n, type) \( (type *)malloc( (n) * sizeof( type ) ) )
int* pi = MALLOC(25, int);

带副作用的宏参数

副作用指表达式求值时会出现永久后果。比如 x + 1不会改变表达式里x的值,但是x++就会。getchar()也有副作用,因为它会消耗输入的一个字符,后续调用会得到不同的字符。

#define MAX( a, b ) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX( x++, y++);
// 此时x = 6, y = 10, z = 9
// 因为带入后z = ( (x++) > (y++) ? (x++) : (y++) )
// 所以y增加了两次,z取的是y增加一次时的值

命令行定义

许多C编译器允许在命令行中定义符号,比如跨平台运行时,某个数组的大小可以根据平台能力修改。下面的ARRAY_SIZE可以在命令行中指定。

int array[ARRAY_SIZE];

比如unix中,编译命令可以是:(格式:-Dname=value)

cc -DARRAY_SIZE=100 prog.c

但是在程序中只能是用参数的地方才可以用,比如循环内部需要使用字面值常量访问数组时,就不能用。如果要去除这个定义,使用-Uname。

条件编译

例如,调试代码可以写成下面这样,如果要debug,用#define DEBUG 1即可,如果要忽略这段代码,定义为0。

#if DEBUGprintf("x=%d", x);
#endif

elif可以有很多个

#if constant-expression
...
#elif constant-exp
...
#else
..
#endif

是否被定义可以使用#ifdef或者#ifndef,但是用#if更好些,因为可以扩展条件

// symbol是否被定义?
#ifdef symbol
#ifndef symbol// 等价
#if defined(symbol)
#if !defined(symbol)// 但是if可以判断多个条件
#if X > 0 || defined( ABC ) && defined( BCD )

#if和#ifdef这些可以嵌套,如果某个部分非常长,最好在#endif后加一些注释说明每个选择的含义

#if defined( OS_UNIX )#ifdef OPTION1unix_version_of_option1();#endif /* sth about OPTION1 */#ifdef OPTION2unix_version_of_option2();#endif /* sth about OPTION2 */
#elif defined( OS_MSDOS )#ifdef OPTION2msdos_version_of_option2();#endif /* sth */
#endif

文件包含

#include

包含库头文件用<>,本地头文件用“”。也可以使用绝对路径

#include <stdio.h>
#include "errno.h"

标准要求编译器必须支持至少8层的头文件嵌套,但没有规定最大值,但实际操作中,一般会避免#include指令的嵌套深度超过2层。嵌套可能有多次包含同一个文件,为了消除这个可以使用条件编译,这样第二次被包含时其内容会被忽略,但是读取文件仍然会拖慢编译速度,所以还是应该避免多次包含。

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

其他指令

#error

产生错误信息

#elif ...
...
#else#error No option selected!

#line

很少用,最常用在将其他语言的代码转成C代码时。C编译器产生的错误信息可以引用源文件而非翻译程序产生的C中间源文件的文件名和行号。

它通知预处理器number是下一行输入的行号。如果给出了“string”,预处理器就把string当作当前文件的名字。因此会修改__LINE__和__FILE__的值。

#line number "string"

#progma
因编译器而异,不能移植,允许编译器提供不标准的处理过程,比如向一个函数插入内联的汇编代码。

无效指令null directive

就是一个#开头的空行,跟空行一样起到一个分隔作用,但是预处理器会直接删除

相关文章:

  • Redis05-集群方案
  • 新版软考高项试题分析精选(三)
  • SoftwareTest6 - 用 Selenium 怎么点点点
  • Alter database open fails with ORA-00600 kcratr_nab_less_than_odr
  • CSS BFC是什么,应用实例
  • 阿里云国际站:云备份
  • 【MybatisPlus】条件构造器、自定义SQL、Service接口
  • Java排序算法之基数排序
  • 物联网AI MicroPython学习之语法 network网络配置模块
  • 2010年数据结构408
  • Realistic fault detection of li-ion battery via dynamical deep learning
  • JimuReport积木报表 v1.6.5 版本发布—免费报表工具
  • AIOT数字孪生智慧工地一体化管理平台源码
  • Vue3 源码解读系列(五)——响应式
  • [Socket]Unix socket 运行权限问题
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • iOS | NSProxy
  • js对象的深浅拷贝
  • JS基础之数据类型、对象、原型、原型链、继承
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • Netty 4.1 源代码学习:线程模型
  • Promise面试题2实现异步串行执行
  • rc-form之最单纯情况
  • SpringBoot几种定时任务的实现方式
  • 前端面试总结(at, md)
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 小程序测试方案初探
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • 容器镜像
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​Python 3 新特性:类型注解
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • ![CDATA[ ]] 是什么东东
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • .equals()到底是什么意思?
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET Reactor简单使用教程
  • .Net的DataSet直接与SQL2005交互
  • .Net中的设计模式——Factory Method模式
  • .sh 的运行
  • @Async注解的坑,小心