C语言:预处理

C语言:预处理

    • 预定义符号
    • #define
      • 定义常量
      • 定义宏
      • 宏与函数对比
    • #操作符
    • ##操作符
    • 条件编译
    • 头文件包含
      • 库文件包含
      • 本地文件包含
      • 嵌套文件包含


预定义符号

C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

示例:

printf("file:%s line:%d\n", __FILE__, __LINE__);

输出结果:

file:test.c line:1

以上预定义符号,都是一些常量,可以自己一一尝试。


#define

在C语言中,#define是一个预处理器指令,用于定义宏。

宏是一个被预处理器替换的标识符或一个标识符和参数的组合。宏定义可以用来简化代码、提高代码的可读性和可维护性。

使用#define可以定义常量、函数宏等。

定义常量

  • 定义常量:
 #define PI 3.14

这样就可以在代码中使用PI来代表3.14

  • 定义关键字:
#define reg register

reg 定义为关键字 register,可以将reg这个简写代替register关键字使用。

  • 定义代码段
#define CASE break;case

正常的switch语句每一个case都要加上break,通过这个写法,我们可以在写CASE时自动补齐break


定义宏

#define 机制允许把参数替换到文本中,这种功能叫做 宏(macro) / 定义宏(define macro)

语法:

#define name(parament-list) stuff

其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。

示例:

#define SQUARE(x) x * x

当我们在代码中输入以下代码:

int main()
{
	int a = 5;
	int b = SQUARE(a);
	return 0;
}

在编译后就会转化为:

int main()
{
	int a = 5;
	int b = a * a;
	return 0;
}

也就是直接发生了文本替换,这种宏的形式非常像函数,因此也可以称为宏函数。但是其也有很多需要注意的地方。

比如以下代码:

int a = 5;
int b = SQUARE(a + 1);

我们希望先执行a + 1,然后再传入SQUARE中,但是其不会这样做因为其会将上述代码直接替换为:

int a = 5;
int b = a + 1 * a + 1;

由于操作符优先级的问题,我们不会得到想要的结果。为了处理这个情况,我们需要把参数用小括号括起来:

#define SQUARE(x) (x) * (x)

代码就变成:

int a = 5;
int b = (a + 1) * (a + 1);

这样我们就可以行使预期的功能了。

那么我们再看到一串代码:

#define DOUBLE(x) (x) + (x)

int a = 5;
int b = 10 * DOUBLE(a);

代码编译后为:

int a = 5;
int b = 10 * 5 + 5;  

又出现了一样的问题,我们的 10 * DOUBLE(a)并没有先执行DOUBLE(a),而展开后,又出现了操作符优先级问题,所以我们的宏还要再优化:

#define DOUBLE(x) ((x) + (x))

在宏的最外侧再加一层括号,就可以独立运行,不受外界操作符影响了。

通过以上推断,我们可以发现,宏虽然可以很好的替换代码,但是会受到外界操作符的影响,此时就要注意很多细节。


宏与函数对比

在这里插入图片描述
函数在调用时,是会开辟内存创建栈帧的,而宏则直接执行,所以速度更快。但是由于宏是在编译阶段就已经处理好了,所以宏不能通过调试观察现象,还要操作符优先级带来的种种问题。因此宏不适合处理复杂的函数,但是很适合短小简单的函数


#操作符

功能:

#可以将宏的参数转化为字符串

比如以下代码:

#define PRINT(n) printf("the value of "#n " is %d", n);

我们尝试调用:

int a = 5;
PRINT(a);

代码就会被转化为:

int a = 5;
printf("the value of ""a" " is %d", a);

可以看到,两个n的替换效果是不同的,对于n其会直接被替换为变量a;而对于#n,其不是简单的替换,而是把参数名转化为了字符串”a“


##操作符

##操纵符可以将两个符号合并为一个符号

示例:

#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}

该宏用于创建不同类型的比大小函数,由于不同函数需要不同的函数名,于是利用##来连接函数名,也就是type##_max部分。type是一个宏参数,当传入floattype##_max整体就被连接为float_max,当传入int,整体就被连接为int_max。也就是##起到一个连接作用。


条件编译

条件编译是C语言中的一种编译指令,用于在编译过程中根据指定的条件选择性地包含或排除某些代码。它主要是为了满足不同平台、不同编译选项或不同场景下的需求。

条件编译使用预处理指令实现,预处理指令以#开头。下面是一些常用的条件编译指令及其用法:

  1. #if / #elif / #else / #endif
    #if用于基于预处理器常量的值进行条件判断。
    #elif用于在多个条件之间进行选择。
    #else用于在没有匹配的#if#elif时执行。
    #endif用于结束条件编译块。
    示例:
   #define NUM 5
   
   #if NUM > 10
       printf("NUM is greater than 10\n");
   #elif NUM > 0
       printf("NUM is greater than 0\n");
   #else
       printf("NUM is less than or equal to 0\n");
   #endif

这个代码和C语言的if代码很像,不过多讲解了。

  1. #ifdef / #ifndef
    #ifdef用于检查一个标识符是否已经定义,如果已定义则编译后面的代码,否则跳过。
    #ifndef#ifdef相反,用于检查一个标识符是否未定义。
    示例:
#ifdef DEBUG
    printf("Debug mode enabled\n");
#endif

以上代码中,只要我们定义了DEBUG这个变量,就会输出"Debug mode enabled\n"语句。

  1. #define
    #define用于定义宏。宏是一种将一组指令作为一个整体进行替换的方式。
    示例:

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
    int x = 10;
    int y = 20;
    int max = MAX(x, y);
    
  2. #include
    #include用于将指定的头文件包含到当前文件中。
    示例:

    #include <stdio.h>
    
    int main() {
        printf("Hello, World!\n");
        return 0;
    }
    
  3. #pragma
    #pragma用于向编译器发出特定的指令,如优化选项、警告控制等。它的语法和功能因编译器而异。
    示例:

    #pragma warning(disable: 4996)
    

这些是C语言中常用的条件编译指令和代码用法。通过合理使用条件编译,我们可以根据不同的需求自由地控制代码的编译过程。


头文件包含

头文件包含分两种形式:本地头文件库文件

库文件包含

语法:

#include <filename.h>

查找头⽂件会直接去标准路径下去查找,如果找不到就提⽰编译错误

我们平常使用的库文件都通过尖括号<>来包含,其会直接到存放库文件的路径中查找。

本地文件包含

#include "filename"

先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件,如果找不到就提⽰编译错误

如果是用户自己编写的头文件,我们要用双引号""包含,如果通过这种方式包含头文件,那么会先在当前源文件的目录下查找,如果没有找到,再去库文件中查找

也就是说:库文件也可以通过双引号包含,但是会多出额外的查找步骤,所以库文件还是用尖括号包含更好,而自己编写的头文件必须双引号包含。

嵌套文件包含

假设我们现在有以下文件结构:
头文件test.h

void test();
struct Stu
{
	int id;
	char name[20];
};

源文件test.c

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
	return 0;
}

我们在test.c中多次包含了头文件,这就会导致test.h反复被展开,产生大量重复代码。这就是嵌套文件包含的问题,那么我们要如何处理这个问题,让其只能被包含一次呢?

条件编译方法
在头文件test.h中加入以下代码:

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__

第一次包含头文件:
先执行#ifndef __TEST_H__,我们此时没有定义__TEST_H__这个变量,if成立,此时头文件会被展开,同时执行#define __TEST_H__,此时__TEST_H__就已经被定义了


第二次包含头文件:
第二次站时,由于上一次展开已经定义了__TEST_H__这个变量,导致#ifndef __TEST_H__判断为假,此时整个头文件都不会再被编译,直接舍弃

后续再展开头文件,都会因为 __TEST_H__被定义而不会编译,解决了嵌套编译的问题。

一般而言,我们这个用于判断头文件有没有被展开过的变量,是头文件名通过一定规则转化来的:

  1. 在头文件前后加上两个下划线__头文件.h__
  2. 把头文件中的点.也改为下划线__头文件_h__

因此test.h的常量就是:__TEST_H__

pragma
通过条件编译其实是比较传统的写法,我们还有一种更加简洁方便的写法:

#pragma once

只要在任何头文件前面加上这句话,就只会被编译一次了。


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ctf_show笔记篇(web入门---php特性)

目录 php特性 89&#xff1a;直接数组绕过preg_match当遇到数组时会直接报错输出0 90&#xff1a;这里利用了intval的特性 91&#xff1a;这里需要细节一点 92-93&#xff1a;这两题的方法很多可以发散思维 94&#xff1a;还是利用小数绕过例如4476.0 95&#xff1a;这里…

mysql根据指定顺序返回数据--order by field

在查询数据的时候&#xff0c;在in查询的时候&#xff0c;想返回的数据根据 in里的数据顺序返回&#xff0c;可以直接在orderby中通过 FIELD(字段名称逗号分隔的值的顺序) 进行指定&#xff1b;示例没有加 order by field添加 order by field效果

宝塔面板安装各种组件以及部署应用服务

在linux服务器安装宝塔面板 一、从宝塔官网下载exe安装包&#xff0c;安装命令从宝塔官网&#xff08;https://www.bt.cn/&#xff09;获取 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh二、安…

C++复习笔记——多态

1 多态 基本概念&#xff1a; 静态多态&#xff1a;编译时多态&#xff0c;运算符重载和函数重载动态多态&#xff1a;运行时多态&#xff0c;派生类重写基类的虚函数实现 静态多态和动态多态的区别就是函数地址是早绑定(静态联编&#xff0c;静态绑定)还是晚绑定(动态联编&a…

使用pyannote-audio实现声纹分割聚类

使用pyannote-audio实现声纹分割聚类 # GitHub地址 https://github.com/MasonYyp/audio1 简单介绍 pyannote.audio是用Python编写的用于声纹分割聚类的开源工具包。在PyTorch机器学习基础上&#xff0c;不仅可以借助性能优越的预训练模型和管道实现声纹分割聚类&#xff0c;还…

一文了解docker与k8s

随着 k8s 作为容器编排解决方案变得越来越流行&#xff0c;有些人开始拿 Docker 和 k8s 进行对比&#xff0c;不禁问道&#xff1a;Docker 不香吗&#xff1f; k8s 是 kubernetes 的缩写&#xff0c;8 代表中间的八个字符。 其实 Docker 和 k8s 并非直接的竞争对手两者相互依存…

蜻蜓FM语音下载(mediadown)

一、介绍 蜻蜓FM语音下载&#xff08;mediadown&#xff09;&#xff0c;能够帮助你下载蜻蜓FM音频节目。如果你是蜻蜓FM会员&#xff0c;它还能帮你下载会员节目。 二、下载地址 本站下载&#xff1a;蜻蜓FM语音下载&#xff08;mediadown&#xff09; 百度网盘下载&#…

【JS】解构赋值注意点,解构赋值报错

报错代码 const 小明 { email: 6, pwd: 66 } const 小刚 { email: 9, pwd: 99 }const { email } 小明 const { email } 小刚 报错图 原因 2个常量重复&#xff0c;重复在同一个作用域内是不能重复的&#xff0c;例如大括号内{const a 1; const a 2} 小伙伴A提问 问&…

微信小程序云开发教程——墨刀原型工具入门(编辑页面)

引言 作为一个小白&#xff0c;小北要怎么在短时间内快速学会微信小程序原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;这意味着学习时必须把握微信小程序原型设计中的重点、难点&#xff0c;而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…

探索Manticore Search:开源全文搜索引擎的强大功能

在当今信息爆炸的时代&#xff0c;数据的快速检索变得至关重要。无论是在电子商务网站、新闻门户还是企业内部文档&#xff0c;高效的搜索引擎都是确保用户满意度和工作效率的关键因素之一。而在搜索引擎领域&#xff0c;Manticore Search 作为一款开源的全文搜索引擎&#xff…

09 闭环线程 LoopClosing

文章目录 09 闭环线程 LoopClosing9.1 主函数&#xff1a;Run()9.1.1 闭环检测9.1.2 计算Sim3变换&#xff08;相似变换&#xff09;: ComputeSim3()9.1.4 闭环矫正: CorrectLoop() 09 闭环线程 LoopClosing 9.1 主函数&#xff1a;Run() 成员函数/变量访问控制意义CheckNewK…

第一篇【传奇开心果系列】Python的自动化办公库技术点案例示例:深度解读Pandas库

传奇开心果博文系列 系列博文目录Python的自动化办公库技术点案例示例系列 博文目录前言一、主要特点和功能介绍二、Series 示例代码三、DataFrame示例代码四、数据导入/导出示例代码五、数据清洗示例代码六、数据选择和过滤示例代码七、数据合并和连接示例代码八、数据分组和聚…

Numpy教程

文章目录 数组基础文件与字符串多项式分布实战 Numpy绝对可以说是支撑Python地位的最重要的包了&#xff0c;几乎所有能叫出名的Python计算库&#xff0c;都不可避免地调用了Numpy&#xff0c;Numpy官网也列出了一些&#xff0c;大致如下图这样&#xff0c;在科学计算领域&…

Hololens 2应用开发系列(1)——使用MRTK在Unity中设置混合现实场景并进行程序模拟

Hololens 2应用开发系列&#xff08;1&#xff09;——使用MRTK在Unity中进行程序模拟 一、前言二、创建和设置MR场景三、MRTK输入模拟的开启 一、前言 在前面的文章中&#xff0c;我介绍了Hololens 2开发环境搭建和项目生成部署等相关内容&#xff0c;使我们能生成一个简单Ho…

基于图数据库构建知识图谱平台应用实践

▏摘要 中信证券基于分布式图数据库StellarDB&#xff0c;替代国外开源图数据库产品&#xff0c;打造全新的企业级知识图谱平台&#xff0c;应用于同一客户集团画像、科创板关联发现、风险事件报告、全球企业关联图谱、产业链图谱、投研图谱、反洗钱与稽核图谱、元数据图谱等应…

提升工作能力的方法

1.接受工作&#xff0c;只问标准 2.请示工作&#xff0c;必带方案&#xff1b; 3.汇报工作&#xff0c;突出成果&#xff1b; 4.分享工作&#xff0c;细说流程&#xff1b; 5.复盘工作&#xff0c;总结SOP 如果能这么做&#xff0c;那将是一个非常通透的高手&#xff0c;所以高…

初探2b blender

总结 按照youtube 教程 做了个雏形 心得 从正面, 侧面 视图整体上调整外轮廓流线型趋向, 比如正面看这个发型像个鸡蛋的外轮廓头发重要的是丝滑, 流畅 集束 层次 交错

git的安装、使用

文章目录 安装gitgit学习网站git初始配置具体配置信息 新建版本库&#xff08;仓库&#xff09;git的工作区域和文件状态工作区域文件状态git文件提交的基础指令 git基础指令1. 版本提交2. 分支创建3. 分支切换4. 分支合并(1) git merge(2) git rebase 5. 在git的提交树上移动(…

国内哪个工具可以平替chatgpt?国内有哪些比较好用的大模型gpt?

我自己试用了很多的平台&#xff0c;发现三个比较好的大模型平台&#xff0c;对普通用户也比较的友好的&#xff0c;而且返回内容相对来说&#xff0c;正确率更高的&#xff0c;并且相关场景插件比较丰富的国内厂商。 本文说的&#xff0c;是我自己觉得的&#xff0c;比较有主观…

vue2结合electron开发桌面端应用

一、Electron是什么&#xff1f; Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 。允许您保持一个 JavaScript 代码代码库并创建可在Windows、macOS和Linux上运行的跨平台应用 。 Electron 经常与 Ch…