C语言——预处理详解(下)

news2024/11/13 17:56:30

目录

前言

#和##

1.#运算符

2.##运算符

命名约定

#undef

命令行定义

条件编译

1.单分支条件编译

2.多分支条件编译

3.判断是否被定义

4.嵌套指令

头文件的包含

1.头文件被包含的方式

(1)本地文件包含

(2)库文件包含

2.嵌套文件包含

其他预处理指令

1.#error

2.#pragma

3.#line

4.#pragma pack()

结束语


前言

在上一篇文章——C语言——预处理详解(上),我们学习了预处理中有关宏的部分知识,接下来我们接着学习预处理的知识。

#和##

1.#运算符

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

我们先来看一下这段代码:

int main()
{
	printf("hello"   "world\n");
	printf("helloworld\n");
	return 0;
}

输出结果为:

我们可以看到,这两种写法输出结果是一样的,中间的空格并不会影响输出的结果。

再来看看下面的代码:

int main()
{
	int a = 10;
	printf("a=%d\n", a);

	float b = 3.14f;
	printf("%f\n", b);

	return 0;
}

我们可以看出,这两句代码的逻辑是十分相似的。既然如此,我们可不可以使用什么方法,实现这段代码的功能呢?

我们可以使用宏来实现:

#define Print(n, format) printf("n = " format "\n", n)

#include<stdio.h>
int main()
{
	int a = 10;
	Print(a, "%d");

	float b = 3.14f;
	Print(b, "%f");

	return 0;
}

输出结果如下:

我们看到:n 没有改变,那应该怎么办呢?

这个时候就轮到 # 派上用场了。

# 将宏的一个参数转换成字符串字面量,将 n 变成 "n"。

把宏修改成这样:

#define Print(n, format) printf(""#n" = " format "\n", n)

输出结果:

这样子就符合需求了。

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 GENERIC_MAX(type)              \
		type type##_max(type x, type y)\
		{                              \
			return x > y ? x : y;      \
		}

使用宏,定义不同的函数:

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


GENERIC_MAX(int);    //相当于定义了一个函数int_max
GENERIC_MAX(float);  //相当于定义了一个函数float_max

int main()
{
	int a = int_max(3, 5);
	printf("%d\n", a);
	float b = float_max(3.14f, 1.41f);
	printf("%f\n", b);
	return 0;
}

运行结果:

我们在gcc环境观察一下:

注意:使用宏生成的函数是十分不方便进行调试的。

只用加了##,编译器才会认为它们是符号。

命名约定

一般来讲,函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者。
为了方便我们写代码,我们平时可以保持一个习惯:

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

#undef

#undef 指令用于移除一个宏定义。

来看如下代码:

#include<stdio.h>
#define MAX 10

int main()
{
	printf("%d\n", MAX);
#undef MAX
	printf("%d\n", MAX);
	return 0;
}

我们可以看到,在第6行MAX可以使用,在第7行使用#undef只会,后面MAX就变成未定义的标识符了。

命令行定义

许多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;
}

在这段代码中,ARRAY_SIZE是未定义的,但是我们可以通过命令行对该符号进行定义。

编译指令:

gcc -D ARRAY_SIZE=10 programe.c

使用编译指令对其进行定义,如图所示:

条件编译

在编译一个程序的时候我们如果要将一条语句(⼀组语句)编译或者放弃是很方便的。因为我们有条件编译指令
条件编译指令允许代码在编译时根据一定的条件选择性地包含或排除某些代码段。

我们可以给他设定一个条件,条件为真,这段代码就参与编译,条件为假,这段代码就不参与编译。

例如:

一些调试性的代码,删除可惜,保留又碍事,所以我们可以选择性编译

#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
int main()
{
#if 10	//10为真,执行
	printf("hello world\n");
#endif
	return 0;
}
int main()
{
#if 0	//0为假,不执行
	printf("hello world\n");
#endif
	return 0;
}

2.多分支条件编译

#if  常量表达式
	//···
#elif  常量表达式
	//···
#else
	//···
#endif

哪条为真则执行哪条语句

#define M 1
int main()
{
#if M == 0
	printf("hello\n");
#elif M == 1
	printf("world\n");
#endif
	return 0;
}

3.判断是否被定义

#if defined(symbol)
#ifdef symbol

//与上面相反
if !defined(symbol)
#ifndef symbol

比如这样使用:

#define M 1
int main()
{
#if defined(M)
	printf("hello\n");
#endif

#if defined(N)
	printf("world\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

头文件的包含

1.头文件被包含的方式

(1)本地文件包含

# include "filename"

查找策略:

先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件⼀样在标准位置查找头文件。

如果找不到就提示编译错误。

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

/usr/include

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

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

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

(2)库文件包含

#include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也可以使用 " " 的形式包含?

答案是肯定的,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
因此,最好还是区分库文件包含和和本地文件包含,这样也有利于我们维护代码。

2.嵌套文件包含

我们已经知道,预处理阶段, #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

这样子写比较麻烦,下面有个更简单的方式:

#pragma once

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

其他预处理指令

在C和C++中,#error 、#pragma 、#line 以及 #pragma pack() 是预处理指令,它们在编译之前由预处理器处理,用于控制编译过程或提供编译时的信息。

#error
#pragma
#line
//···
#pragma pack()

1.#error

#error指令用于在编译时生成一个编译错误。

#include <stdio.h>  

// 假设我们有一个宏定义,用于控制是否包含某个特性  
#define FEATURE_ENABLED 0  

// 使用 #error 来检查 FEATURE_ENABLED 是否被设置为 1  
#if !FEATURE_ENABLED  
#error "FEATURE_ENABLED 必须被设置为 1 以启用此功能!"  
#endif  

// 接下来是代码的其他部分,但由于上面的 #error 指令,如果 FEATURE_ENABLED 不是 1,  
// 编译器将在这里停止编译,并显示错误消息。  

int main() 
{
    // 如果 FEATURE_ENABLED 是 1,则这段代码会被编译和执行。  
    // 但如果 FEATURE_ENABLED 不是 1,由于 #error 指令,编译器将不会到达这里。  
    printf("此功能已启用。\n");
    return 0;
}

2.#pragma

#pragma 是一个通用的预处理指令,用于向编译器提供特定的指令。

例如:

当我们使用#pragma:

3.#line

 #line 是一个预处理指令,它允许你重新设置当前文件的行号和文件名。

#line 100 "newfile.c"  
// 接下来的代码将报告为来自newfile.c的第100行

4.#pragma pack()

#pragma pack()  是 #pragma 指令的一个特定用法,用于控制结构体或联合体的字节对齐。

在 C语言——结构体 中我们已经接触过了。

#pragma pack(1)	//设置默认对齐数为1
struct example5
{
	char c1;
	int i;
	char c2;
}s5;
 
int main()
{
	printf("%zu\n", sizeof(s5));
	return 0;
}

输出结果为:6

由于我们设置默认对齐数为1,因此结构体成员在内存中是连续储存的,这时结构体大小等于每个结构体成员大小之和。

结束语

花了一段时间把C语言——预处理部分的内容大致的讲了讲。

感谢看到这篇文章的朋友们!!!十分感谢大家的支持!!!

希望能得到朋友们的支持!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何用Python进行数据可视化、科技图表绘制?

目录 写在前面 推荐图书 推荐理由 写在最后 写在前面 有了它&#xff0c;科技图表绘制、数据可视化真的毫无难度&#xff01; 推荐图书 《Python数据可视化&#xff1a;科技图表绘制》(芯智)【摘要 书评 试读】- 京东图书 图书简介 《Python数据可视化:科技图表绘制》结…

mfc140.dll丢失如何修复,一步步教你如何解决mfc140.dll丢失,让电脑快速恢复正常状态!

mfc140.dll是 Microsoft Foundation Class (MFC) Library 的一部分&#xff0c;它是一个用于开发 Windows 应用程序的 C 库。当系统报告mfc140.dll丢失时&#xff0c;通常意味着某个应用程序需要这个 DLL 文件来运行&#xff0c;但系统中没有找到它。那么mfc140.dll丢失如何修复…

Ubuntu下提升高并发socket最大连接数限制

文章目录 前言1. limits.conf修改2. /etc/pam.d修改3. /etc/sysctl.conf修改4. ulimit设置5.重启系统即可生效参考文档 前言 linux系统默认ulimit为1024个访问 用户最多可开启的程序数目。一般一个端口&#xff08;即一个进程&#xff09;的最高连接为2的16次方65536。 查看全…

TPshop商城的保姆教程(Ubuntu)

1.上传TPSHOP源码 选择适合自己的版本下载 TPshop商城源文件下载链接&#xff1a; 百度网盘 请输入提取码 上传tpshop的源码包到特定目录/var/www/html 切换到/var/www/html 目录下 cd /var/www/html修改HTML目录下所有文件权限 chmod -R 777 * 2.打开网址配置 TPshop安…

如何以编程方式解析 XCResult 包的内容

文章目录 介绍查找 XCResult 包分享 XCResult 包 解析 XCResult 包自动解析 XCResult 包的内容 使用 XCResultKit 解析包的内容初始化库获取调用记录 获取测试信息导出屏幕录制 可运行 Demo初始化 Swift Package编写主文件代码解释运行 Demo 结论 介绍 XCResult 包是一个包含运…

Apache SeaTunnel 2.3.5 Zeta-Server集群环境搭建与使用

作者 | 月影幽篁 在当前数据驱动的业务环境中&#xff0c;快速且高效的数据处理能力至关重要。Apache SeaTunnel以其卓越的性能和灵活性&#xff0c;成为数据工程师和开发者的首选工具之一。本文将介绍如何在集群环境中搭建Apache SeaTunnel 2.3.5版本的 Zeta-Server&#xff…

期权强大优势之一的杠杆是什么?!

今天带你了解期权强大优势之一的杠杆是什么&#xff1f;&#xff01;期权是一种合约&#xff0c;该合约赋予持有人在某一特定日期以固定价格买入或卖出一种资产的权利。 期权杠杆是指使用较少的资金控制相对较大金额的股票或其他资产的能力。 期权提供了买入或卖出标的资产的…

U盘救星在此!年度免费数据恢复软件TOP榜

现在这社会&#xff0c;数字信息太重要了&#xff0c;工作文件、学习笔记&#xff0c;还有那些记录美好时光的照片和视频&#xff0c;要是一不小心丢了&#xff0c;那可真是急死人。不过&#xff0c;幸运的是&#xff0c;现在有数据恢复软件&#xff0c;它们就像是数据的救星&a…

Qt多线程编程-run()方法

本文介绍Qt多线程编程-run()方法。 Qt多线程编程主要有2种方法&#xff0c;前面已经介绍了moveToThread()方法&#xff0c;本文介绍另外一种方法run()方法&#xff0c;并给出一个实例参考。 1.基本原理 run()方法首先需要定义一个基于QThread的派生类&#xff0c;QThread类是…

cAdvisor+prometheus+grafana搭建监控页面并嵌入自定义页面中

三者关系 一般公司会有很多docker主机&#xff0c;那么就需要对docker进行监控了&#xff0c;docker监控可以采用docker stats配合shell命令来取值做监控&#xff0c;但是无法传递给prometheus进行采集&#xff0c;zabbix监控docker又比较麻烦&#xff0c;因此就有了谷歌的cad…

Python开源项目周排行 2024年第13周

#2024年第13周2024年8月5日1roop一款基于深度学习框架TensorFlow和Keras开发的单图换脸工具包&#xff0c;提供了丰富的功能和简洁易用的界面&#xff0c;使得用户可以轻松实现单图换脸操作。支持多张人脸替换成同一个人脸&#xff0c;勾选多人脸模式即可 人脸替换 高清修复自…

RCE绕过方式

目录 小于8个字符突破限制 无字母数字执行 php7的做法 php5的思考 PHP5shell 深入理解glob通配符 构造POC&#xff0c;执行任意命令 无参数读文件和RCE总结 代码解读 构造. 另一种构造方法 小于8个字符突破限制 但也只能执行一些非常短的命令&#xff0c;没有什么意义…

【JavaSec】 代码审计01-SpringMVC图书购物系统

【JavaSec】 代码审计01-SpringMVC图书购物系统 文章目录 【JavaSec】 代码审计01-SpringMVC图书购物系统前期部署用户管理修改删除 商品管理修改 普通用户注册 源码地址&#xff1a;https://github.com/Laverrr/bookstore 前期部署 问题一&#xff1a; 启动后报错 Cookie值…

RabbitMQ应用问题 - 消息顺序性保证、消息积压问题

文章目录 MQ 消息顺序性保证概述原因分析解决方案基于 spring-cloud-stream 实现分区消费 消息挤压问题概述原因分析解决方案 MQ 消息顺序性保证 概述 a&#xff09;消息顺序性&#xff1a;消费者消费的消息的顺序 和 生产者发送消息的顺序是一致的. 例如 生产者 发送消息顺序…

centos7 xtrabackup mysql(8)压缩 增量备份(3)

centos7 xtrabackup mysql&#xff08;8&#xff09;压缩 增量备份&#xff08;3&#xff09; 添加数据1 添加数据测试一下 测试主从是否可以 主机端 mysql -u root -p 1234aA~1 show databases ; use company_pro; show tables ; insert into employee(name) value (‘2024…

C++实现单例模式/工厂模式

单例模式 单例模式即一个类只创建一个实例&#xff0c;提供一个全局访问点。单例模式主要是为了控制资源访问&#xff0c;在一些功能如&#xff1a;数据库连接池&#xff0c;日志类实例&#xff0c;线程池等都可以采用单例模式。 // 实现一个单例 #include<iostream> #…

户外上网黑科技|续航能力大比拼,飞猫、闪鱼、格行、品胜,哪个好

在当今的移动互联网时代&#xff0c;随身WiFi已成为我们日常生活中不可或缺的一部分&#xff0c;特别是在租房、出差、旅行或户外活动时&#xff0c;其续航能力成为了用户选择的重要因素。本文将针对飞猫、闪鱼、格行、品胜这四款热门随身WiFi产品的续航能力进行详细比较&#…

C#高级:在SQLserver中使用视图、存储过程、索引和触发器

目录 一、视图 1.视图是什么&#xff0c;有什么作用&#xff1f; 2.视图和存储过程有什么区别&#xff1f; 3.建立一个视图&#xff0c;名为PersonBorrowView&#xff0c;SQL已给出&#xff1a; 4.如果往BorrowInfo加一条记录&#xff0c;我原本的SQL会增加一条记录&#…

JAVA毕业设计635—基于Java+ssm的仓库管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于Javassm的仓库管理系统(源代码数据库)635 一、系统介绍 分为员工、管理员两种角色 1、员工&#xff1a; 登录、库存管理、出入库管理、密码修改 2、管理员&#xff1a; 库…

(自用)交互协议设计——protobuf序列化

protobuf是一种比json和xml等序列化工具更加轻量和高效的结构化数据存储格式&#xff0c;性能比json和xml真的强很多&#xff0c;毕竟google出品。 protobuf原理 protobuf如何使用 创建xxx.proto文件 开头写上 syntax"proto2"package tutorial; 表明使用的proto…