C语言第四十弹---预处理(下)

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】

预处理

1、#和##

1.1 #运算符

1.2、##运算符

2、命名约定

3、#undef

4、命令行定义

5、条件编译

6、头文件的包含

6.1、头文件被包含的方式

6.1.1、本地文件包含

6.1.2、库文件包含

6.2、嵌套文件包含

7、其他预处理指令

总结


1、#和##


1.1 #运算符

#运算符将宏的⼀个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为”字符串化“。


当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 .
就可以写:

 

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

当我们按照下面的方式调用的时候:
PRINT(a);

//当我们把a替换到宏的体内时,就出现了#a,而#a就是转换为 "a" ,⼀个字符串代码就会被预处理为:

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

运行代码就能在屏幕上打印:

the value of a is 10

1.2、##运算符

## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称为记号粘合


这样的连接必须产生⼀个合法的标识符。否则其结果就是未定义的。
这里我们想想,写⼀个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。

 

比如:

int int_max(int x, int y)
{
return x>y?x:y;
}
float float_max(float x, float y)
{
return x>yx:y;
}

但是这样写起来太繁琐了,现在我们这样写代码试试:
 

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

使用宏,定义不同函数
 

#include <stdio.h>
//宏定义
#define GENERAL_MAX(type) \
type type##_max(type x, type y)\
{ \
 return (x>y?x:y); \
}
GENERAL_MAX(int)//函数int_max的定义
GENERAL_MAX(float)//函数float_max的定义
int main()
{
    int ret1 = int_max(5, 10);
    float ret2 = float_max(8.8f, 9.9f);
    printf("%d %f", ret1, ret2);
    return 0;
}

输出:


在实际开发过程中##使⽤的很少,很难取出非常贴切的例子。


2、命名约定


⼀般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分⼆者。
那我们平时的⼀个习惯是:

把宏名全部大写
函数名不要全部大写(第一个字母大写)


3、#undef

这条指令用于移除⼀个宏定义。

#undef NAME
//如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。

举例: 

#include <stdio.h>
#define NUM 10
int main()
{
	printf("%d\n", NUM);
#undef NUM
	//printf("%d", NUM);   此处会报错,因为宏定义已经被取消了
#define NUM 20
	printf("%d\n", NUM);//重新定义了名字一样的一个宏
	return 0;
}

4、命令行定义


许多C的编译器提供了⼀种能力,允许在命令行中定义符号。用于启动编译过程。


例如:当我们根据同⼀个源文件要编译出⼀个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了⼀个某个长度的数组,如果机器内存有限,我们需要⼀个很小的数组,但是另外⼀个机器内存大些,我们需要⼀个数组能够大些。)
 

#include <stdio.h>
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

5、条件编译


在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很方便的。因为我们有条件编译指令
比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

 

#include <stdio.h>
#define __DEBUG__
int main()
{
 int i = 0;
int arr[10] = {0};
for(i=0; i<10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}

常见的条件编译指令:

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..此处执行代码
#endif


//举例
 #include<stdio.h>
 #define M 3
 int main()
 {
 #if M==2  //条件成立则执行下面语句
 	printf("hehe\n");
 #endif//条件编译结束
 	return 0;
 }

2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif


//举例
 #include<stdio.h>
 #define M 2
 int main()
 {
 	#if M==1
 		printf("hehe\n");
 	#elif M==2
 		printf("haha\n");
 	#elif M==3
 		printf("heihei\n");
 	#else
 		printf("xixi\n");
 	#endif
 	return 0;
 }


3.判断是否被定义
#if defined(symbol) //定义了symbol执行
#ifdef symbol       //定义了symbol执行
#if !defined(symbol)//没定义symbol执行
#ifndef symbol      //没定义symbol执行


//举例
#include<stdio.h>
 #define M 0
 int main()
 {
 	 #if defined(M)
 	 	printf("hehe\n");
 	 #endif

 	 #ifdef M 
 	 	printf("hehe\n");
 	 #endif

 	 #if !defined(M)
 	 	printf("hehe\n");
 	 #endif

 	#ifndef M
 		printf("hehe\n");
 	#endif
 	return 0;
  }

4.嵌套指令 遵循就近原则
#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

6、头文件的包含


6.1、头文件被包含的方式


6.1.1、本地文件包含
 

#include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件⼀样在标准位置查找头文件。如果依旧找不到就提示编译错误。


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

/usr/include

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

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

注意按照自己的安装路径去找。


6.1.2、库文件包含
 

#include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。


6.2、嵌套文件包含


我们已经知道, #include 指令可以使另外⼀个文件被编译。就像它实际出现于 #include 指令的
地方⼀样。
这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。
⼀个头文件被包含10次,那就实际被编译10次,如果重复包含,对编译的压力就比较大。

 

test.c

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

test.h

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

如果直接这样写,test.c文件中将test.h包含5次,那么test.h文件的内容将会被拷贝5份在test.c中。如果test.h文件比较大,这样预处理后代码量会剧增。如果工程比较大,有公共使用的头文件,被大家都能使用,又不做任何的处理,那么后果真的不堪设想。


如何解决头文件被重复引入的问题?

答案:条件编译。


每个头文件的开头写:

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

或者

#pragma once

就可以避免头文件的重复引入。


注:
推荐《高质量C/C++编程指南》中附录的考试试卷(很重要)。

笔试题:

1. 头文件中的ifndef/define/endif是干什么用的?
2. #include <filename.h>和#include "filename.h"有什么区别?


7、其他预处理指令
 

#error
#pragma
#line
...
不做介绍,自己去了解。
#pragma pack()在结构体部分介绍。

参考《C语言深度解剖》学习

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

蓝桥杯-冶炼金属(二分求最大最小)

P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 二分做法&#xff1a; #include<bits/stdc.h> using namespace std; #define int long long const int N 1e410; int n,a,b; int v[N],cnt[N]; int check(int x){for(int i1;i<n;i…

硬件-1、体系架构

cpu 处理器 arm处理器的七种工作模式 arm寄存器 两张图是一样的&#xff0c;r0---r12是通用寄存器。其他寄存器可参考图一&#xff0c;cpu架构。 程序状态寄存器psr&#xff08;cpsr/spsr&#xff09; 程序异常处理 理解示例 当使用swi&#xff08;软中断指令&#xff09;指令…

RabbitMQ3.13.x之十_流过滤的内部结构设计与实现

RabbitMQ3.13.x之十_流过滤的内部结构设计与实现 文章目录 RabbitMQ3.13.x之十_流过滤的内部结构设计与实现1. 概念1. 消息发布2. 消息消费 2. 流的结构1. 在代理端进行过滤2. 客户端筛选3. JavaAPI示例4. 流过滤配置5. AMQP上的流过滤6. 总结 3. 相关链接 1. 概念 流过滤的思…

大算力芯片,正在拥抱Chiplet

随着摩尔定律走到极限&#xff0c;Chiplet被行业普遍认为是未来5年算力的主要提升技术。 在和业内人士交流时&#xff0c;有人曾表示&#xff1a;“要么业界采用Chiplet技术&#xff0c;维持摩尔定律的影响继续前进&#xff0c;要么就面临商业市场的损失。” 随着摩尔定律走到…

使用ADS确定元器件的等效感值与等效容值

使用ADS确定元器件的等效感值与等效容值 使用Win家的ADS的PDK&#xff0c;里面有一些微带电感结构&#xff0c;但是居然没有标注感值&#xff0c;给设计带来了一定的不便。 那么对于一个电路结构&#xff0c;如微带线、微带螺旋电感&#xff0c;我们如何知道其实际的感值、容…

磁盘压力测试工具(vdbenchfio)

磁盘压力测试工具&#xff08;vdbench&fio&#xff09; 最近有遇到对象挂载为文件系统的需求&#xff0c;为了测试挂载后的读写性能&#xff0c;有了解了一些测试工具。下面给大家分享下我使用的工具vdbench和fio。 1 vdbench 官网文档&#xff1a;https://www.oracle.com/…

【三十五】【算法分析与设计】综合练习(2),22。 括号生成,77。 组合,494。 目标和,模拟树递归,临时变量自动维护树定义,递归回溯,非树结构模拟树

22. 括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["&#xff08;&#xff08;&#xff08;&#xff09;&#xff09;&#xff0…

软件杯 深度学习二维码识别

文章目录 0 前言2 二维码基础概念2.1 二维码介绍2.2 QRCode2.3 QRCode 特点 3 机器视觉二维码识别技术3.1 二维码的识别流程3.2 二维码定位3.3 常用的扫描方法 4 深度学习二维码识别4.1 部分关键代码 5 测试结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天…

电商数据API接口|主流电商平台数据采集的主要方式:电商API接口接入实现大量级数据采集

item_get-获得淘宝商品详情 API测试注册KEY taobao.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,it…

机器学习模型——K—Means算法

目录 无监督学习概念&#xff1a; 有监督学习与无监督学习&#xff1a; 无监督学习 - 聚类分析 &#xff1a; 聚类算法应用场景&#xff1a; 常用聚类算法介绍&#xff1a; 对不同的聚类算法应用选择原则&#xff1a; 基于原型聚类&#xff1a; K-Means聚类算法概念及步…

通过电机转速计算主轴旋转单圈所需时间(CODESYS ST代码)

1、伺服丝杠系统常用算法功能块 伺服丝杠系统常用算法功能块-CSDN博客文章浏览阅读353次。这篇博客主要介绍伺服、丝杠系统常用的运算功能块,其它相关运算可以查看下面文章链接:信捷PLC脉冲频率、位移、转速相关计算(C语言编程应用)_RXXW_Dor的博客-CSDN博客。https://rxxw-…

UE4_如果快速做出毛玻璃效果_假景深

UE4_如果快速做出毛玻璃效果_假景深 2022-08-20 15:02 一个SpiralBlur-SceneTexture材质节点完成效果&#xff0c;启用半透明材质通过修改BlurAmount数值大小调整效果spiralBlur-SceneTexture custom节点&#xff0c;HLSL语言float3 CurColor 0;float2 BaseUV MaterialFloa…

系统思考—领导者

“组织是船&#xff0c;领导者是什么角色&#xff1f;” 对于这个看似简单的问题&#xff0c;很多人可能会直观地想到船长或舵手。但学习型组织的倡导者彼得圣吉给出了另一种视角&#xff1a;如果组织是一艘船&#xff0c;那么领导者首先应该是这艘船的设计师。 在我近期与各个…

Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?

Linux&#xff1a;进程等待究竟是什么&#xff1f;如何解决子进程僵尸所带来的内存泄漏问题&#xff1f; 一、进程等待的概念二、进程等待存在的意义三、如何进行进程等待3.1 wait()是实现进程等待1、wait()原型2. 验证wait()能回收僵尸子进程的空间 3.2 waitpid()实现进程等待…

电子积木方案开发商

东莞市酷得智能科技有限公司电子积木方案开发商 提供消费电子解决方案、提供IC技术支持&#xff0c;全国线上线下服务 积木小车底层驱动开发过程主要涉及到以下几个方面&#xff1a; 首先&#xff0c;需要对小车底盘结构、硬件、模块等有深入的了解。底盘承载着机器人定位、导…

Kubernetes(k8s):Pod 的 Node Selector详解

Kubernetes&#xff08;k8s&#xff09;&#xff1a;Pod 的 Node Selector详解 1、什么是Node Selector&#xff1f;2、Node Selector的工作原理3、Node Selector的用法1、例如&#xff1a;给node01 、node02 分别打上标签2、使用标签调度Pod3、删除节点的标签 &#x1f496;Th…

java面试题(Redis)

事情干的差不多了&#xff0c;开刷面试题和算法&#xff0c;争取在短时间内快速成长&#xff0c;理解java面试的常见题型 一、redis使用场景&#xff1a; 缓存&#xff1a;穿透、击穿、雪崩 双写一致、持久化 数据过期、淘汰策略 分布式锁&#xff1a;setnx、redisson 计数…

武汉星起航推出亚马逊一站式孵化平台,助力合作伙伴快速成长

武汉星起航电子商务有限公司&#xff0c;自2020年正式成立以来&#xff0c;凭借其专业的运营团队和丰富的行业经验&#xff0c;在跨境电商领域取得了显著的成绩。为了进一步满足市场需求&#xff0c;武汉星起航决定推出亚马逊一站式孵化平台&#xff0c;旨在为合作伙伴提供更全…

网盘分享链接

点击打开下面这条链接&#xff0c;保存文件 https://pan.xunlei.com/s/VNuDMRtfBQvmfqqwjsBAIg2pA1?pwdhqd3 网盘里文件太多&#xff0c;找不到&#xff0c;怎么办&#xff1f; 进入我的B站主页【I泠霖I的个人空间-哔哩哔哩】 https://b23.tv/VYxaiJb&#xff0c;点击右上角的…

PC发送指令给单片机控制LED(与上一篇文章相反)

此时要重新配置寄存器 &#xff0c;实现电脑往单片机传输数据 1、配置SCON寄存器的REN 即 REN 1 2、有TI&#xff08;发送中断&#xff09;就有RI&#xff08;接收中断&#xff09; 3、优化 发现发送 o 时&#xff0c;D5亮灯会有延迟 下面就是做到真正的无延迟的全双工通信 …