C语言最终讲:预处理详解

C语言最终讲:预处理详解

  • 1.预定义符号
  • 2.#define定义常量
  • 3.#define定义宏
  • 4.带有副作用的宏参数
  • 5.宏替换的规则
  • 6.宏和函数的对比
    • 6.1宏的优势
      • 6.1.1\符号
    • 6.2宏的劣势
  • 7.#和##
    • 7.1#运算符
    • 7.2##运算符
  • 8.命名约定
  • 9.#undef
  • 10.命令行定义
  • 11.条件编译
  • 12.头文件的包含
    • 12.1本地文件包含
    • 12.2库文件包含
    • 12.3嵌套文件包含
  • 13.其他预处理指令

结语:这一讲是C语言基础知识的最后一讲了,后续将会学习数据结构相关的知识,坚持不易,希望各位都能坚持在自己所干的事情上,我们共勉

这一讲讲的是预处理相关的内容,上一讲虽然我们已经了解了一些关于#define相关的知识,这一讲我们讲详细阐述它的作用和缺陷

1.预定义符号

C语言中有着一些预定义符号,可以直接使用,是在预处理阶段进行处理的:

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

它们的使用方法为:

int main()
{printf("%s\n", __FILE__);//打印当前文件所在的位置printf("%d\n", __LINE__);//打印行号printf("%s\n", __DATE__);//打印日期printf("%s\n", __TIME__);//打印时间//printf("%d\n", __STDC__);//err,表明VS并不完全遵循ANSI C标准return 0;
}

运行结果如下:
在这里插入图片描述

2.#define定义常量

我们知道,define定义的常量是直接替换的,所以有着下面的几处用法:

#define TEST 20 //方法1,直接定义常量
#define I int//方法2:可以为关键字替换成一个更简便的名字
#define CASE break;case//方法3:这是一种很“奇葩”的用法,建议还是别用了int main()
{//使用起来也很方便,方法1:int a = TEST;//直接使用即可//方法2:I b = 20;printf("%d\n", b);//方法3:int input = 1;scanf("%d", &input);switch (input){case 1:CASE 2 ://这样后面就不用加break了CASE 3 :break;}return 0;
}

对于加;问题,解答如下:

#define ROW 20;//假设#define定义时加上了;int main()
{int a = ROW;//这是a就会被替换成int a = 20;;这是后面就会有两个;,特殊情况下会出现错误//总结:加上;可能会出现错误,不加;肯定不会出现错误,所以还是不要加上;return 0;
}

3.#define定义宏

定义方式:

#define name( parament-list ) stuff
name为宏名称
parament-list是由逗号隔开的符号表,可能出现在stuff中
stuff可以是一个计算方式,也可以是一个指令等等
//需要注意的是,符号表的左括号必须要和name紧密相连,否则括号里的内容也会被当成是stuff中的一部分

使用实例:

#define MUL(x) x*x//定义了一个宏,实现x的平方int main()
{int a = 5;int b = MUL(a);printf("b = %d\n", b);//结果为25return 0;
}

看起来使用起来很好用,但是我们也要注意符号的优先级问题,如下:

#define ADD(x) x+xint main()
{int a = 5;int b = ADD(a) * ADD(a);printf("%d\n", b);//我们可能会误以为结果为10 * 10,这不就是100吗,但是并非如此//b会被替换成int b = 5+ 5*5 +5,根据符号优先级问题,结果为35return 0;
}

知道错误就要改正,所以改正方法如下:

#define ADD(x) ((x)+(x)) //在x上加一个括号,因为x为表达式,如果传入5+5,也要被括起来//总体上再加上一个括号,是为了将它们算成一个整体int main()
{int a = 5;int b = ADD(a) * ADD(a);printf("%d\n", b);//这样才能计算出一个整体的值,此时结果为100return 0;
}

所以,对于宏的定义,我们不能吝惜括号

4.带有副作用的宏参数

什么是带有副作用呢,其实就是表达式求值会改变原来的参数的值,如下:

x + 1;//不带副作⽤
x++;//带有副作⽤

为了验证此写法的危害性,我们举例来说明:

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{int x = 5;int y = 8;int z = MAX(x++, y++);//这里会被替换成((x++)>(y++) ? (x++):(y++))//先看问号左边,后置++,先使用后++,5>8为假,所以看的是y++的值,还是先使用后++,但是要注意的是//由于++,此时y的值已经变成了9,所以z被赋值为9,y再++,值位10//而x只++了一次,因为第二次只对y进行了++,所以x的值为6printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?//所以输出的结果应该为6 10 9return 0;
}

5.宏替换的规则

1.在调用宏时,首先看参数中是否包含由#define定义的符号,如果有,它们首先被替换:

#define R 2
#define RR R+2
//其中RR中包含了一个常量的定义R,所以R先被替换成2,再计算RR

2.替换文本随后会被插入到原来文本所在的位置
3.最后还会对文件进行扫描,看是否仍然存在#define定义的符号,如果有,继续进行上面的两个步骤

对于宏,需要注意的是,它并不能出现递归

6.宏和函数的对比

上面我们了解了宏,那么既然宏这么好用,那我们是不是就能够将宏代替为函数使用呢,肯定是不能的,下面我们来分析宏相较于函数的利与弊

6.1宏的优势

1.对于简单的任务,宏有着天然的优势:

#define MAX(a, b) ((a)>(b)?(a):(b))
1.首先,宏的参数是类型无关的,也就是说,宏参数能够接受任意类型的值,在特定条件下是很好用的
2.其次,宏在使用过程中也是一个简单的替换,函数不同,函数在使用前后会产生空间的创建和销毁,使用效率较慢

2.相较于函数,宏有着函数不能够实现的情况:

//如果我们要使用函数进行空间的开辟:
int* Mal(int a, int size)
{//我们首先会将返回值和参数确定,也就是说,它们的类型都是固定的,用于整形开辟的函数就不能用于浮点型类型的开辟return (int*)malloc(a * sizeof(int));
}//但是当我们使用宏时:
#define MAL(num, type)  \(type*)malloc(num * sizeof(type))
int main()
{//使用函数开辟空间int* pa = Mal(10, sizeof(int));//假设我们要开辟10个整形的空间//使用宏开辟空间int* pb = MAL(10, int);//1.我们可以将int直接作为参数传入//2.此时宏的使用包含的情况更多,更好用return 0;
}

6.1.1\符号

上面我们使用了一个\符号,我们看看这是怎么个事:

#define MAL(num, type)  \(type*)malloc(num * sizeof(type))
//这个宏定义中使用了\符号,它是一个换行符,当宏里的内容过多时,就可以在后边加上一个\符号进行换行
//但是要注意的是,\后边什么也不能有,包括空格
//可以理解为:一个\抵消了一个\n,所以后边不能跟任何东西

6.2宏的劣势

1.每次使用宏时,一份宏代码就会被插入到程序中,如果宏很长的话,会大幅增加程序的长度
2.宏在预处理阶段就被替换,不能够被调试
3.宏由于类型无关,所以也容易出现问题
4.宏可能造成运算符优先级的问题

对于宏和函数的一个对比,我们可以看图:
图不是很好,大家对付着看
在这里插入图片描述

7.#和##

7.1#运算符

#所实现的操作被称为“字符串化”,作用是将宏的一个参数转换成字符串变量:

#define PRINT(n) printf("The value of " #n " is %d", n);int main()
{int a = 10;PRINT(a);//此时会打印出:The value of a is 10//预处理阶段,代码会被替换成:printf("the value of ""a" " is %d", a);//也就是说,#n就表示"n",对于printf,多个""会被合并成一个return 0;
}

7.2##运算符

该运算符被称为记号粘合,可以将两边的符号合并成一个符号

使用方法如下:

//当我们要求两个数的最大值时:
//使用函数完成较为繁琐,因为不同类型需要分别进行处理:
int int_max(int x, int y)
{return x > y ? x : y;
}float float_max(float x, float y)
{return x > y ? x:y;
}//我们可以使用宏来实现:
#define MAX(type)                      \type type##_max(type x, type y)\{                              \return x > y ? x : y;      \}MAX(int);//这时我们可以依靠宏来创建一个函数,函数名为int_maxint main()
{//使用方法也比较简单:int ret1 = int_max(2, 3);//我们直接使用创建好的函数即可printf("%d\n", ret1);return 0;
}

8.命名约定

在命名宏和函数时,有着一个不成文的约定:

1.把宏名全部大写
2.函数名不要全部大写

9.#undef

用于移除一个宏定义,使用方法如下:

#define A 20int main()
{int a = A;printf("%d\n", a);//20#undef Aint b = A;//errreturn 0;
}

10.命令行定义

在不同的场景下,我们定义的数组长度需求可能不同,所以许多C的编译器提供了一种命令行定义的能力,允许在命令行中,也就是使用前对一些变量进行赋值

例子:

int main()
{int array[ARRAY_SIZE];int i = 0;for (i = 0; i < ARRAY_SIZE; i++){array[i] = i;}for (i = 0; i < ARRAY_SIZE; i++){printf("%d ", array[i]);}printf("\n");return 0;
}

编译指令:

//linux 环境演⽰
gcc - D ARRAY_SIZE = 10 programe.c
这里表示给ARRAY_SIZE赋值为10,然后再执行程序

11.条件编译

对于一些为了调试而使用的代码,删除了可惜,保留了又费事,所以我们可以通过条件编译来选择性编译

常见的条件编译指令:

1.
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__//..
#endif2.多个分⽀的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

总结:

1.#if#endif是一体的,它们两个必须同时使用
2.#if#elif就相当于if else
3.#if defined(symbol)表示如果定义了symbol,也可以写成:#ifdef symbol
4.#if !defined(symbol)表示如果没有定义symbol,也可以写成:#ifndef symbol

它的原理为:

//使用原理为:
//当条件为真时,保留代码
//条件为假时,删除代码
int main()
{
#if 1printf("haha\n");//条件为真,保留代码
#elif 0printf("hehe\n");//条件为假,删除代码
#endifreturn 0;
}
//所以上面的代码在预处理之后就变成了:
int main()
{printf("haha\n");//条件为真,保留代码return 0;
}

12.头文件的包含

头文件有两种包含方式:本地文件包含和库文件包含,它们的包含方式是什么呢,我们来看:

12.1本地文件包含

#include "filename"
使用“”来包含

查找策略:

1.现在源文件目录下查找,也就是这个路径:

在这里插入图片描述

2.如果没有找到,在库函数头文件中进行查找
3.如果找不到,提示编译错误

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

/usr/include

VS环境下(现在VS环境下头文件的路径应该会有差异,这里只当成一个代表):

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

12.2库文件包含

直接去标准路径下查找,如果没有找到就提示编译错误

那么我们就要问了,是不是库文件的包含也可以使用""呢,原理上来说可以,但是缺点也很明显:
1.效率低
2.这样就不能够分辨出哪个是包含的是标准库文件,哪个是包含本地文件

12.3嵌套文件包含

当我们使用#include包含多个头文件时,会发生什么呢:

在这里插入图片描述
在预处理阶段,竟然会有5个头文件被写入了.c文件中,因为头文件的包含其实就是将头文件的内容写入.c文件中,情况如下:

struct Stu
{int id;char name[20];
};
struct Stu
{int id;char name[20];
};
struct Stu
{int id;char name[20];
};
struct Stu
{int id;char name[20];
};
struct Stu
{int id;char name[20];
};#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{return 0;
}

为了避免这种情况,所以我们用到了:条件编译,方法如下:

//头文件的改进如下:
//方法1:条件编译
#ifndef __TEST_H__
#define __TEST_H__//也就是说,如果没有__TEST_H__的话,定义一个__TEST_H__,这样后续就有了__TEST_H__,就不会再包含这个头文件了
struct Stu
{int id;char name[20];
};
#endif//方法2:
#pragma once
//这是一个预处理指令,用于告诉编译器该头文件之应该被包含一次,通常推荐放在头文件开头

13.其他预处理指令

#error
#pragma
#line
...
不做介绍,⾃⼰去了解

可以参考《C语言深度解剖》一书进行学习

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

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

相关文章

四川蔚澜时代电子商务有限公司抖音电商服务怎么样?

随着数字经济的蓬勃发展&#xff0c;电商行业已成为推动经济增长的重要引擎。在这个充满变革与机遇的时代&#xff0c;四川蔚澜时代电子商务有限公司凭借对抖音电商的深入理解和专业服务&#xff0c;迅速崛起为行业的佼佼者&#xff0c;引领着潮流营销的新风尚。 四川蔚澜时代…

java自学阶段二:JavaWeb开发--day80(项目实战2之苍穹外卖)

《项目案例—黑马苍穹外卖》 目录&#xff1a; 学习目标项目介绍前端环境搭建(前期直接导入老师的项目&#xff0c;后期自己敲&#xff09;后端环境搭建&#xff08;导入初始项目&#xff0c;新建仓库使用git管理项目&#xff0c;新建数据库&#xff0c;修改登录功能&#xff…

视觉大模型(VLLM)学习笔记

视觉多模态大模型&#xff08;VLLM&#xff09; InternVL 1.5 近日&#xff0c;上海人工智能实验室 OpenGVLab 团队、清华大学、商汤科技合作推出了开源多模态大语言模型项目InternVL 1.5&#xff0c;它不仅挑战了商业模型巨头例如 GPT-4V 的霸主地位&#xff0c;还让我们不禁…

vscode编译文件夹下所有文件的配置(包含插件和 .json 文件)

文章目录 我所使用的插件.json 文件配置1. c_cpp_properties.json2. launch.json3. settings.json4. tasks.json 如何运行 我所使用的插件 红框中的五个插件是必备的&#xff0c;其中 Code Runner 插件可以在写完一个 .c 或 .cpp 文件后&#xff0c;按下 Crtl R 快捷键快速编…

HTML LocalStorage

一篇关于HTML本地存储的文章 Window.localStorage 只读的localStorage 属性允许你访问一个Document 源&#xff08;origin&#xff09;的对象 Storage&#xff1b;存储的数据将保存在浏览器会话中。 localStorage 类似 sessionStorage&#xff0c;但其区别在于&#xff1a;存储…

Linux守护进程揭秘-无声无息运行在后台

在Linux系统中&#xff0c;有一些特殊的进程悄无声息地运行在后台&#xff0c;如同坚实的基石支撑着整个系统的运转。它们就是众所周知的守护进程(Daemon)。本文将为你揭开守护进程的神秘面纱&#xff0c;探讨它们的本质特征、创建过程&#xff0c;以及如何重定向它们的输入输出…

快准稳的文档解析工具,帮助构建性能优越的金融领域知识库问答产品

随着大模型应用落地速度加快&#xff0c;企业级应用相关技术模块日渐成熟&#xff0c;在各个行业领域&#xff0c;企业改革现有业务流程与生产方式、使用AI提高运作效率的可行性大幅度提升。其中&#xff0c;金融行业作为数据密集、更新快速的代表性行业之一&#xff0c;经常与…

【Go语言精进之路】构建高效Go程序:了解map实现原理并高效使用

&#x1f525; 个人主页&#xff1a;空白诗 &#x1f525; 热门专栏&#xff1a;【Go语言精进之路】 文章目录 引言一、什么是map1.1 map的基本概念与特性1.2 map的初始化与零值问题1.3 map作为引用类型的行为 二、map的基本操作2.1 插入数据2.2 获取数据个数2.3 查找和数据读取…

【必会面试题】自旋中的ABA问题

目录 ABA问题描述ABA问题的影响解决ABA问题的方案 \qquad 自旋锁&#xff08;spinlock&#xff09;是一种用于实现互斥同步的锁机制&#xff0c;其基本思想是让线程在等待获取锁的过程中不断地检查锁是否可用&#xff0c;而不是进入睡眠状态。自旋锁适用于锁被持有的时间较短&a…

TF-2.10 实现用 BERT 从文本中抽取答案

前言 本文详细介绍了用 tensorflow-gpu 2.10 版本实现一个简单的从文本中抽取答案的过程。 数据准备 这里主要用于准备训练和评估 SQuAD&#xff08;Standford Question Answering Dataset&#xff09;数据集的 Bert 模型所需的数据和工具。 首先&#xff0c;通过导入相关库…

jetson nano 系统克隆及烧录

一、背景 二、系统克隆 1、硬盘挂载 查看块设备&#xff0c;执行命令lsblk&#xff0c;结果如下 其中&#xff0c;nvme0n1是我们要挂载的设备。 2、将块设备nvme0n1分成1个分区&#xff0c;执行命令sudo fdisk /dev/nvme0n1&#xff0c;出现分区界 面如下 输入m可以查看分区…

记录vue一个echarts页面 柱状图加平均分横线 双柱状图 横向双柱状图

<template><div class"app-container"><el-form :model"queryParams" ref"queryForm" size"small" v-show"showSearch" label-width"85px"><el-form-item label"园所名称" prop&q…

windows11 将此电脑放在桌面上

桌面--右键--个性化 然后右侧&#xff0c;找到主题: 然后桌面图标设置&#xff1a; 然后勾选计算机: 最后&#xff0c;点击【确定】按钮&#xff0c;桌面上就会有此电脑的图标了。

AI大模型-机器学习中的集成学习

机器学习中的集成学习 集成学习概述及主要研究领域 1.1 集成学习概述&#x1f4a5; “众人拾柴火焰高”、“三个臭皮匠顶个诸葛亮”等词语都在表明着群体智慧的力量&#xff0c;所谓的“群体智慧”指的就是一群对某个主题具有平均知识的人集中在一起可以对某一些问题提供出更…

UE 像素流与 Web 协同开发

UE 像素流与 Web 协同开发 创建Web端应用Web向UE发送消息emitCommandemitConsoleCommandemitUIInteraction UE接收Web的消息UE向Web发送消息Web接收UE的消息UE 冻结帧 与Web交互主要涉及两个方面&#xff0c;一个是UE向Web发送消息&#xff0c;另一个就是Web端向UE程序发送消息…

5分钟在阿里云上部署了超级玛丽小游戏,是一种什么样的体验?

大家好&#xff0c;我是java1234_小锋老师&#xff0c;作为程序设计开发人员&#xff0c;云部署项目是最基本的技能。所以锋哥分享下如何在阿里云上部署项目&#xff0c;我们以部署超级玛丽网页小游戏为例&#xff0c;教大家熟悉Linux云服务器&#xff0c;熟悉宝塔应用&#xf…

影视制作的未来:云渲染+虚拟制作+AI生成技术

在计算机技术和人工智能技术飞速发展的2024年&#xff0c;影视制作正在经历一场前所未有的变革。云渲染、虚拟制作和AI生成等新影视制作技术的结合&#xff0c;正在重新定义数字内容的创作流程&#xff0c;为影视产业带来了全新的可能性和机遇。这些前沿技术不仅提高了制作效率…

Elasticsearch 为时间序列数据带来存储优势

作者&#xff1a;来自 Elastic Martijn Van Groningen, Kostas Krikellas 背景 Elasticsearch 最近投资了对存储和查询时间序列数据的更好支持。存储效率一直是关注的主要领域&#xff0c;许多项目取得了巨大的成功&#xff0c;与将数据保存在标准索引中相比&#xff0c;可以节…

电子设计新纪元:三品PLM系统在快速变革中的适应性

随着科技的飞速发展&#xff0c;电子行业正经历着前所未有的变革。产品生命周期的缩短、技术迭代的加速以及市场竞争的加剧&#xff0c;都对电子行业提出了更高的管理要求。在这样的背景下&#xff0c;传统的产品数据管理PDM和产品生命周期管理PLM系统显得力不从心。本文将探讨…

H.264官方文档下载

H.264是ITU&#xff08;International Telecommunication Union&#xff0c;国际通信联盟&#xff09;和MPEG&#xff08;Motion Picture Experts Group&#xff0c;运动图像专家组&#xff09;联合制定的视频编码标准。其官方文档可以在ITU官网上下载&#xff1a;https://www.…