文件包含的本质、预处理符号、# vs ##

news2024/12/24 20:18:33

何为头文件?

        在C语言中,文件包含是一种常见的编程技术,它允许程序员在一个源文件中使用另一个源文件中的函数或变量。

        文件包含通常使用`#include`预处理指令来实现。`#include`指令告诉预处理器将文件的内容插入到当前文件的指定位置中。

        例如,在一个C源文件中,如果想要使用另一个源文件中的函数,可以使用以下语句:

#include "otherfile.c"

        这个语句会告诉编译器将`otherfile.c`中的代码插入到当前文件的位置,然后再进行编译。当编译器遇到调用`otherfile.c`中的函数时,它能够找到函数的定义,并将它们编译到可执行文件中。

        需要注意的是,文件包含应该遵循一些最佳实践:

  • 为了避免重复包含,应该使用头文件而不是源文件进行文件包含。例如,使用`#include "otherfile.h"`而不是`#include "otherfile.c"`。
    #include "otherfile.h"  //使用
    #include "otherfile.c"  //不使用
  • 应该避免在头文件中放置函数或变量的定义。头文件应该只包含函数和变量的声明。
  • 应该避免在头文件中使用全局变量。全局变量会在包含文件的每个源文件中创建一个独立的实例,这样可能会导致命名冲突和意外行为。

为何所有头文件,都推荐写入下面代码?本质是为什么?

#ifndef XXX
#define XXX

//TODO

#endif

        这是为了避免头文件重复包含多次,导致编译错误或者不必要的浪费。当一个头文件被多次包含时,如果没有预处理器指令的保护,就会重复定义同一个符号,从而出现编译错误。

        为了避免这种问题,使用了 `#ifndef`、`#define`、`#endif` 三个预处理器指令,将头文件的内容包含在一个条件编译的块中。第一次包含头文件时,`XXX`未被定义,`#ifndef` 判断为真,进入条件编译块,`#define XXX` 定义符号 `XXX`,然后包含头文件的内容。

        当再次包含同一头文件时,`XXX`已被定义,`#ifndef` 判断为假,直接跳过条件编译块,从而避免了重复定义的问题。

#include究竟干了什么?

#include本质是把头文件中相关内容,直接拷贝至源文件中!

那么,在多文件包含中,有没有可能存在头文件被重复包含,乃至被重复拷贝的问题呢?

test.h
#ifndef _TEST_H_
#define _TEST_H_ //注意,这里没有包含<stdio.h>防止信息太多干扰我们
extern void show(); //任意一个函数声明
#endif
test.c
#include "test.h" //故意包含两次
#include "test.h"
int main()
{
    return 0;
}

经过预编译后的结果

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"
# 1 "test.h" 1 //test.h只被包含了1次
    extern void show();
# 2 "test.c" 2
int main()
{
    return 0;
}

但是当我们去掉条件编译呢?

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"
# 1 "test.h" 1
    extern void show(); //内容被拷贝第一次
# 2 "test.c" 2
# 1 "test.h" 1
    extern void show(); //内容被拷贝第二次
# 3 "test.c" 2
int main()
{
    return 0;
}

结论:

        所有头文件都必须带上条件编译,防止被重复包含! 那么,重复包含一定报错吗?不会! 重复包含,会引起多次拷贝,主要会影响编译效率!同时,也可能引起一些未定义错误,但是特别少。

#error 预处理

`#error`指令是C语言预处理器中的一个预编译指令,用于在预处理阶段产生编译错误。

#include <stdio.h>
#define __welcome
int main()
{
#ifdef __welcome
#error 老铁,非常感谢观看此篇文章哟!!!
#endif
	return 0;
}

        程序使用了条件编译指令`#ifdef`和`#endif`来判断`__welcome`宏是否已经定义。如果已经定义,则使用`#error`指令生成一个编译错误,输出一条提示信息。

#include <stdio.h>
//#define __welcome
int main()
{
#ifndef __welcome
#error 老铁,非常感谢观看此篇文章哟!!!
#endif
	return 0;
}

        程序定义了一个名为`__welcome`的宏,并使用了条件编译指令`#ifndef`和`#endif`来判断`__welcome`宏是否已经定义。如果未定义,则使用`#error`指令生成一个编译错误,输出一条提示信息。

结论:核心作用是可以进行自定义编译报错。

#line 预处理

 `#line`指令是C语言预处理器中的一个预编译指令,用于更改源代码中的行号和文件名,从而影响编译器错误和警告信息中的行号和文件名。

//本质其实是可以定制化你的文件名称和代码行号,很少使用
#include <stdio.h>
int main()
{
	printf("%s, %d\n", __FILE__, __LINE__); //C预定义符号,代表当前文件名和代码行号
#line 60 "welcome.h" //定制化完成
	printf("%s, %d\n", __FILE__, __LINE__);
	return 0;
}

        程序使用了`#line`指令将当前行号设置为60,并将当前文件名设置为`"welcome.h"`,从而定制化了文件名称和代码行号。在后续的`printf`函数中,预处理器会将`__FILE__`和`__LINE__`再次替换成定制化后的文件名和行号,从而输出新的信息。

#pragma 预处理

`#pragma`指令是一种不可依赖的、非标准的预处理指令,它通常被编译器用来提供一些与平台、编译器或者其他特殊需求相关的功能。

一些常见的`#pragma`指令包括:

  1.  `#pragma once`:告诉编译器只包含一次某个头文件,避免重复定义。
  2.  `#pragma GCC optimize`:指示GCC编译器优化代码。
  3.  `#pragma warning`:指示编译器输出警告信息。
  4.  `#pragma pack`:指示编译器对结构体进行字节对齐。
  5.  `#pragma message()`:可以用来进行对代码中特定的符号(比如其他宏定义)进行是否存在进行编译时消息提醒。
#include <stdio.h>
#define READ 
int main()
{
#ifdef READ
#pragma message("谢谢宝子阅读文章!!!")
#endif
	return 0;
}

 # 运算符

`#`运算符是C/C++语言中的一个预处理运算符,用于将宏定义参数转换成字符串常量。

#include<stdio.h>
int main()
{
	printf("hello world\n");
	printf("hello""world""\n");
	const char* msg = "hello""world""\n";
	//printf("%s\n",msg);
	printf(msg);
	return 0;
}

结论:相邻字符串自动连接特性 

#include<stdio.h>
#define STR(s) #s
int main()
{
	printf("PI = "STR(3.1415926)"\n");
	return 0;
}

 ## 预算符

`##`预算符是C/C++语言中的一个预处理运算符,用于将两个符号拼接成一个新的符号。

#include<stdio.h>
#define XNAME(n) student##n
int main()
{
	XNAME(1);
	XNAME(2);
	XNAME(3);
	XNAME(4);
	XNAME(5);
	XNAME(6);
	return 0;
}

 ##的实质:将##相连的两个符号,连接成为一个符号。

小练习实例:计算一个的科学计数法值

#include<stdio.h>

#define CONT(x,n) (x##e##n)
int main()
{
	//计算浮点数科学计数法,相当于1.1 * (10^2)
	printf("%f\n", 1.1e2);
	printf("%f\n", CONT(1.1, 2)); 
	return 0;
}

这段代码定义了一个宏`CONT`,用于将两个参数拼接成一个科学计数法格式的浮点数。在`main`函数中,首先使用`1.1e2`的科学计数法直接输出了`110.000000`,然后使用`CONT(1.1, 2)`宏对参数进行拼接,得到了相同的结果。由于`1.1`和`2`经过了拼接,因此最终展开的结果相当于`1.1e2`,即`110.000000`。这说明了`##`预算符可以用于将数字、字符串、变量名等不同类型的记号拼接在一起,从而得到想要的结果。

#include <stdio.h>

#define CONCAT(x, y) x##y

int main() {
    int xy = 10;
    printf("%d\n", CONCAT(x, y));//10
    return 0;
}

        使用`##`运算符和`CONCAT`宏定义输出变量`xy`的值。首先,在`main`函数中定义了一个名为`xy`的整型变量,并赋值为`10`。然后,通过`CONCAT(x, y)`宏调用,将参数`x`和`y`拼接在一起,得到了记号`xy`。最后,使用`printf`函数输出了`xy`变量的值,结果为`10`。由此可见,`##`运算符可以将字符串、变量名等记号拼接在一起,从而实现更加灵活的程序设计。

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

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

相关文章

今天面了个阿里拿 38K 出来的,让我见识到了测试界的天花板

一直觉得自己的技术已经很不错了&#xff0c;直到最近遇到了一个阿里来的大佬 5年测试&#xff0c;应该是能达到资深测试的水准&#xff0c;即不仅能熟练地开发业务&#xff0c;而且还能熟悉项目开发&#xff0c;测试&#xff0c;调试和发布的流程&#xff0c;而且还应该能全面…

第04章 IDEA的安装与使用

【Why IDEA ?】 【注】JetBrains官方说明&#xff1a; 尽管我们采取了多种措施确保受访者的代表性&#xff0c;但结果可能会略微偏向 JetBrains 产品的用户&#xff0c;因为这些用户更有可能参加调查。 此外&#xff0c;2022年&#xff0c;某美国软件开发商在对近千名专业的J…

chatgpt赋能python:Python中的Split函数:去空操作详解

Python中的Split函数&#xff1a;去空操作详解 在Python编程中&#xff0c;我们经常需要对字符串进行操作。而字符串的分割操作在其中是非常常见的操作。Python中的split函数便是用来实现字符串分割的函数。不过&#xff0c;在使用split函数时通常还需要经过去除空格等操作。 …

Inno软件打包发布并后台安装相关驱动

bat安装 bat脚本的具体书写可自行百度。 其主要思路是将Driver文件存放在Bin下面&#xff0c;先安装Bin&#xff0c;然后在执行bat脚本来安装driver 具体些步骤&#xff1a; &#xff08;1&#xff09;编写一个install.bat的脚本 echo off ::下面三行代码是不显示dos界面 i…

加急!指定日本| 教育学老师9天获邀请函申报CSC

S老师拟申报今年的国家留学基金委&#xff08;CSC&#xff09;公派访问学者项目&#xff0c;因所在高校要求提前上报&#xff0c;所以委托时只留给我们11天申请时间&#xff0c;且指定日本高校。最终我们在第9天获得熊本大学邀请函&#xff0c;提前完成了客户的委托。 S老师背景…

Android平台GB28181设备接入模块如何对接NV21、YV12、RGB、YUV等外部数据

技术背景 我们在对接Android平台GB28181设备接入模块的开发者时&#xff0c;遇到这样的场景&#xff0c;除了Android设备&#xff08;如执法记录仪、智能安全帽等&#xff09;自带的camera或camera2前后摄像头数据外&#xff0c;还有些场景是需要外部编码前或编码后数据&#…

淡季不淡,满帮一季度净利创历史新高的背后原因是什么?

进入五月&#xff0c;经济复苏的成果越发体现在很多基础行业的表现中。经济的“大动脉”货运行业&#xff0c;也迎来一份新答卷。 北京时间5月22日美股盘前&#xff0c;数字货运平台满帮集团&#xff08;NYSE:YMM&#xff0c;简称&#xff1a;满帮&#xff09;&#xff0c;发布…

预约直播领券,1%服务费,视频号618大促激励来了!

视频号直播6.18大促激励计划来了&#xff01; 激励有效期为2023年05月31日20:00:00至2023年06月18日23:59:59&#xff1b;参与对象为活动期间满足视频号开播条件的。 通过视频号直播选择“购物”类目开播开通橱窗功能的商家和达人&#xff0c;可参与4大激励计划。 预约领券激…

pix2pixHD---model---生成器

然后是model的搭建&#xff1a; 在creat_model函数中&#xff1a; import torch def create_model(opt):if opt.model pix2pixHD:from .pix2pixHD_model import Pix2PixHDModel, InferenceModelif opt.isTrain:model Pix2PixHDModel()else:model InferenceModel()else:fro…

【FFH】OpenHarmony——ArkTs应用开发+正则表达式

【FFH】OpenHarmony——ArkTs应用开发正则表达式 文章目录 【FFH】OpenHarmony——ArkTs应用开发正则表达式1. 前言——系列介绍2. 本文摘要及背景2.1 摘要2.2 背景2.3 MindMap 3. 正则表达式在ArkTs的使用3.1 变量 RegExp3.2 使用3.3 贪婪模式与懒惰模式:eye_speech_bubble:Ar…

从HelloWorld深入源码了解SpringSecurity底层逻辑

文章目录 一、环境搭建1、创建项目测试1.1、搭建基础项目1.2、整合Spring Security 二、实现原理1、Spring Security的实现原理1.1、Spring Security 如何完成认证和授权1.2、Security Filters 2、 Spring Security默认配置和如何自定义配置 三、整个HelloWorld的流程分析三、H…

流程用例的签名保障,Python接口自动化框架封装案例!

目录 前言&#xff1a; 1.项目背景及需求 2.框架整体架构设计 3.接口管理模块的封装 4.用例管理模块的封装 5.请求模块的封装 6.签名模块的封装 7.案例展示及代码实现 结语&#xff1a; 前言&#xff1a; 随着互联网技术的不断发展&#xff0c;人们对于软件质量的要求…

臻图信息跟进新基建建设,构建“智慧铁路”“指挥调度”管理系统

铁路作为国民经济的骨干、国家关键性基础建设&#xff0c;在社会经济发展中起到关键性作用&#xff0c;交通在全天运行、运量多、运价少、占地面积小和安全环保等方面有着显著的优势。 近年来&#xff0c;我国高度重视铁路发展&#xff0c;2020年8月国铁集团出台《新时代交通强…

Windows系统自带远程桌面和远程协助怎么连接?

随着IT技术的发展和远程办公的兴起&#xff0c;在日常工作中&#xff0c;远程桌面和远程协助等功能已经成为很多用户需要掌握的技能之一。而对于使用Windows系统的用户来说&#xff0c;Windows系统自带的远程桌面和远程协助功能&#xff0c;更是令人欣喜的利器。下面我们就来一…

油猴安装教程及ChatGPT配置

文章目录 目录 文章目录 前言 一. 安装油猴 二、使用步骤 三.安装插件 (ChatGPT) 四. 脚本推荐 前言 作者简介: zuiacsn 座右铭: 抱怨身处黑暗,不如提灯前行 内容介绍: 油猴 油猴&#xff08;Tampermonkey&#xff09;指的是一个流行的用户脚本管理器&#xff0c;它能使…

GAMES202作业1

目录 Shadow MapCalcLightMVP函数useShadowMap函数Bias函数 最终效果 PCF两个采样函数PCF函数最终效果 PCSSfindBlocker函数PCSS函数最终效果 参考 先放上公式&#xff1a; 后面的积分项是我们在作业0中就做好的blinnphong项&#xff0c;我们要求的就是积分项前&#xff0c;等…

认识 Protobuf 及其简单使用

文章目录 一、序列化与反序列化1.1 序列化1.2 反序列化1.3 序列化与反序列化的使用场景 二、初识 Protobuf三、Protobuf 的安装四、Protobuf 的使用案例4.1 创建并编写 .proto 文件的基本规范与语法4.2 编译 .proto 文件4.3 序列化与反序列化的使用 五、总结 ProtoBuf 的使用特…

spring boot日志

日志介绍日志的使用日志级别日志持久化更简单的输入日志lombok的运行原理 日志介绍 日志的作用&#xff1a; 1&#xff1a;发现问题&#xff1b; 2&#xff1a;定位问题&#xff1b; 3&#xff1a;记录用户的行为&#xff1a;看哪些是方法用户&#xff1b;还能拿到用户的ip&am…

【云原生|探索 Kubernetes 系列 4】理解现代云原生时代的引擎

文章目录 系列文章目录&#x1f479; 关于作者一、前言|回顾二、静态和动态视图三、爆火的容器编排工具 Kubernetes 的诞生四、Kubernetes 要解决的问题是什么&#xff1f;五、理解 Kubernetes 全局架构图Master&#xff08;控制节点&#xff09;Node&#xff08;计算节点&…

源码分析:springboot如何确定当前应用程序类型

文章目录 一、介绍二、源码分析三、测试 一、介绍 大多数java后端开发的朋友们想必都是通过创建springboot项目&#xff0c;然后通过编写Controller进行接口开发的&#xff0c;该接口底层是由非响应式的servlet提供支持的&#xff0c;其接口内部逻辑为阻塞式的。但也有一部分朋…