【C语言篇】编译和链接以及预处理介绍(下篇)

文章目录

  • 前言
    • #和##
      • #运算符
      • ##运算符
    • 命名约定
    • #undef
    • 命令⾏定义
    • 条件编译
      • #if和#endif
      • 多个分支的条件编译
      • 判断是否被定义
      • 嵌套指令
    • 头文件被包含
      • 头文件被包含的方式
        • 本地文件包含
        • 库文件的包含
      • 嵌套文件包含
    • 其他预处理指令
  • 写在最后

前言

本篇接前一篇【C语言篇】编译和链接以及预处理介绍(上篇)

#和##

#运算符

#运算符将的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的的替换列表中。

#运算符所执⾏的操作可以理解为“字符串化”。

在这之前我们先铺垫一个知识:

#include <stdio.h>
int main()
{printf("hello world\n");printf("hello"" world\n");return 0;
}

输出结果:

hello world
hello world

在C语言中两个字符串可以天然的合成一个字符串

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

首先:函数是不能完成这一操作的

void print(int n)
{printf("the value of n is %d\n", n);
}

无论参数传什么,字符串里的n都不能根据变量名改变,名字只能是n

可以:

#define PRINT(format, n)    printf("the value of " #n " is " format"\n", n)int main()
{int a = 10;PRINT("%d", a);//预处理替换后如下:#a就变成了字符串"a"//printf("the value of " "a" " is " "%d""\n", a);//字符串然后合并成一个字符串//printf("the value of a is %d\n", a);int b = 20;PRINT("%d", b);printf("the value of b is %d\n", b);float f = 5.5f;PRINT("%f", f);//预处理替换后如下,同理//printf("the value of " "f" " is " "%f""\n", f);return 0;
}

这样打印出来的就是:

在这里插入图片描述


##运算符

##可以把位于它两边的符号合成⼀个符号,它允许定义从分离的⽂本⽚段创建标识符。 ## 被称为记号粘合,这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。

这⾥我们想想,写⼀个函数求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;\
}//使用上面的模板定义函数
GENERIC_MAX(int)//替换到体内后int##_max ⽣成了新的符号 int_max做函数名 
GENERIC_MAX(float) //替换到体内后float##_max ⽣成了新的符号 float_max做函数名 #include <stdio.h>
int main()
{printf("%d\n", int_max(3, 5));printf("%f\n", float_max(3.0, 5.0));return 0;
}

预处理后替换就生成了不同的函数

在这里插入图片描述


命名约定

⼀般来讲函数的的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。

那我们平时的⼀个习惯是:

名全部⼤写

函数名不要全部⼤写

但是也有特例:之前我们在【C语言篇】结构体和位段详细介绍里所讲的offsetof就是一个,但它没有遵守这个规则,对于我们自己来说一般还是遵守这个习惯比较好


#undef

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

很简单,例子如下:

#define M 100int main()
{printf("%d\n", M);
#undef Mprintf("%d\n", M);//报错return 0;
}

命令⾏定义

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

条件编译

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。因为我们有条件编译指令。

⽐如说:

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

#if和#endif

#if 常量表达式//...
#endif
//常量表达式由预处理器求值#if 1//改为0则直到#endif之内的代码不被编译
#define M 2
int main()
{printf("hehe\n");return 0;
}
#endif//一定要是常量表达式
int main()
{int a = 2;
#if a==2 //errprintf("hehe");
#endifreturn 0;
}

多个分支的条件编译

#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif#define M 3//根据M的值来条件性编译
int main()
{
#if M==1printf("hehe\n");
#elif M==3printf("haha\n");
#elif M == 4printf("heihei\n");
#elseprintf("呵呵\n");
#endifreturn 0;
}

判断是否被定义

#if defined(symbol)
#ifdef symbol#define ZHANGSAN 100
int main()
{
#if defined(ZHANGSAN)//如果ZHANGSAN被定义,就编译printf("zhangsan\n");
#endifreturn 0;
}
//另一种写法
#define ZHANGSAN 100
int main()
{
#ifdef ZHANGSAN//如果ZHANGSAN被定义,就编译printf("zhangsan\n");
#endifreturn 0;
}
//上面两种的反义写法
#if !defined(symbol)
#ifndef symbol#define ZHANGSAN 100int main()
{
#if !defined(ZHANGSAN)//如果ZHANGSAN未被定义,就编译printf("zhangsan\n");
#endifreturn 0;
}#define ZHANGSAN 100
int main()
{
#ifdef ZHANGSANprintf("zhangsan\n");
#endifreturn 0;
}

嵌套指令

if...else语句一样可以嵌套

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

条件编译在跨平台性代码的编译中使用广泛


头文件被包含

头文件被包含的方式

本地文件包含
#include "filename"

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

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

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

/usr/include

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

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

注意按照⾃⼰的安装路径去找。


库文件的包含
#include <filename.h>

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

这样是不是可以说,对于库⽂件也可以使⽤ “” 的形式包含?

答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件了。


嵌套文件包含

我们已经知道, #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.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷⻉5份在test.c中。 如果test.h⽂件⽐较⼤,这样预处理后代码量会剧增。如果⼯程⽐较⼤,有公共使⽤的头⽂件,被⼤家都能使⽤,⼜不做任何的处理,那么后果真的不堪设想。

类似的例子如下:

当工程很大时,一个头文件很可能在不经意间被包含了多次

在这里插入图片描述

如何解决头⽂件被重复引⼊的问题?答案:条件编译

每个头⽂件的开头写:

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

第一次包含的时候,没有定义__TEST_H__,所以下面的头文件代码内容会参与编译,在第二次包含相同头文件时,一来先判断发现__TEST_H__已经被定义了,所以下面头文件内容就不会再参与编译了,通过这种方式让相同头文件只会被包含一次

或者:

#pragma once

在VS上当我们创建了一个头文件时,最上面都是有这一句的,这是一种比较现代的写法,很多编译器都使用这种来防止头文件的重复包含


其他预处理指令

#error
#pragma
#line
...
#pragma pack()在结构体部分介绍了

下图出自C语言深度解剖
在这里插入图片描述

写在最后

在这两篇我们笼统的介绍了关于编译了链接的过程,并对编译阶段的预处理过程进行了比较深入的讲解,希望对各位读者有所帮助😘

以上就是编译和链接以及预处理介绍(下篇)内容啦,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️

在这里插入图片描述


http://www.ppmy.cn/devtools/95287.html

相关文章

【随记】终于知道样本方差无偏估计中 n-1 的来源了!

创建时间&#xff1a;2024-08-16 首发时间&#xff1a;2024-08-16 最后编辑时间&#xff1a;2024-08-16 作者&#xff1a;Geeker_LStar 顾名思义&#xff0c;【随记】这个专栏没有固定的主题。它可能会包含一些有趣的数学问题&#xff08;咳咳咳&#xff0c;论我的突发奇想&…

1分钟了解pandas

Pandas 是一个强大的 Python 库&#xff0c;用于数据分析和数据处理。它为 Python 提供了高效的数据结构和数据分析工具&#xff0c;使得数据操作变得简单而直观。Pandas 由 Wes McKinney 在 2008 年创建&#xff0c;并迅速成为数据科学领域中最受欢迎的库之一。 安装 Pandas …

ECMAScript性能优化技巧

ECMAScript&#xff0c;作为JavaScript的标准化形式&#xff0c;其性能优化是前端开发中的重要环节。随着Web应用的日益复杂&#xff0c;优化ECMAScript代码的性能变得尤为重要。以下将详细探讨ECMAScript性能优化的技巧与陷阱&#xff0c;旨在帮助开发者编写更高效、更快速的代…

docker网络

一、docker的网络模式&#xff1a; 1.桥接模式&#xff1a;是docker的默认模式&#xff0c;当我们部署好docker服务&#xff0c;启动之后就会创建一个虚拟网桥就是docker0&#xff0c;这是一个虚拟的网络设备&#xff0c;类似于交换机&#xff0c;每一次运行容器之后&#xff…

网盘变身磁盘:开启存储新时代

前言 本文分享如何通过Alist一站式本地化管理多网盘&#xff0c;再也不用反复登录不同网盘管理资料&#xff0c;一个Alist即可轻松管理所有网盘。 本文所有软件都已打包好&#xff0c;先下载文件然后再按照本文流程进行一步一步执行即可。 后台回复&#xff1a;Alist即可 A…

PDF文件转换为HTML文件

推荐使用 pdf2htmlEX&#xff08;因为确实做的比较全&#xff09; pdf2htmlEX 是一个开源工具&#xff0c;可以将PDF文件转换为HTML文件。你需要先安装pdf2htmlEX工具&#xff0c;并确保它在你的系统路径中可用。&#xff08;花时间最多就是找包&#xff09; 安装 pdf2htmlEX …

中英双语介绍金融经济中的鹰派 (Hawkish)和鸽派 (Dovish)

中文版 在金融和经济政策中&#xff0c;“鹰派”和“鸽派”是两种对货币政策和经济管理有不同立场的群体。 鹰派 (Hawkish) 鹰派倾向于担心通货膨胀的风险&#xff0c;通常支持较高的利率和更紧的货币政策&#xff0c;以防止经济过热和控制物价上涨。具体特征包括&#xff1…

Unity安卓IOS根据不同国家语言显示不同的APP名字

安卓篇 把res文件放在Plugins下&#xff0c;然后修改string.xm里的app名字即可; 如果需要别的国家&#xff0c;增加文件夹即可 IOS篇 info.list中增加Boolean类型的Application has localized display name&#xff0c;值为YES 然后把多语言放在ATT弹窗的多语言里面 CFBun…

声学气膜馆与普通气膜馆的投资回报影响—轻空间

在投资气膜馆项目时&#xff0c;声学气膜馆和普通气膜馆在功能和性能上的差异&#xff0c;直接影响着投资人的收益和回报。 声学气膜馆的独特优势 声学气膜馆专为声学需求设计&#xff0c;不仅具备普通气膜馆的所有优势&#xff0c;还提供卓越的音质控制能力&#xff0c;是高端…

【区块链+金融服务】基于区块链的一站式绿色金融开放平台 | FISCO BCOS应用案例

科技的进步为绿色金融发展提供了新的机遇&#xff0c;但银行、企业、第三方金融机构等在进行绿色金融业务操作过程中&#xff0c; 存在着相关系统和服务平台建设成本高、迭代难度大、数据交互弱、适配难等痛点。 基于此&#xff0c;中碳绿信采用国产开源联盟链底层平台 FISCO …

【Story】编译器的基础概念与类型分类

目录 编译器详解1. 编译器的工作流程1.1 词法分析&#xff08;Lexical Analysis&#xff09;词法分析的例子 1.2 语法分析&#xff08;Syntax Analysis&#xff09;语法分析的例子 1.3 语义分析&#xff08;Semantic Analysis&#xff09;语义分析的例子 1.4 中间代码生成&…

解决 Kibana 中的 “Invalid character in header content” 错误

在使用 Kibana 进行数据可视化和分析的过程中&#xff0c;我们可能会遇到一些配置相关的问题。本文将介绍一个常见的错误&#xff1a;“Invalid character in header content”&#xff0c;并提供详细的解决步骤。 问题背景 当启动 Kibana 服务时&#xff0c;如果遇到以下错误…

华为鸿蒙Core Vision Kit 骨骼检测技术

鸿蒙Core Vision Kit 是华为鸿蒙系统中的一个图像处理框架&#xff0c;旨在提供各种计算机视觉功能&#xff0c;包括物体检测、人脸识别、文本识别等。骨骼检测是其中的一项功能&#xff0c;主要用于检测和识别人类身体的骨骼结构。 骨骼检测的关键点 骨骼点检测&#xff1a;通…

Scout Suite:开源云安全审计工具

Scout Suite 是一个开源、多云安全审计工具&#xff0c;旨在评估云环境的安全态势。 Scout Suite 利用云供应商提供的 API 来收集和整理配置数据&#xff0c;从而更轻松地识别潜在风险。 Scout Suite 无需手动筛选云 Web 控制台上的大量页面&#xff0c;而是会自动生成全面清…

什么是凤凰雪球期权?和雪球期权有什么区别?

凤凰结构&#xff0c;和经典雪球结构类似&#xff0c;属于障碍期权的一种。凤凰结构中包括敲入事件&#xff0c;也包括敲出事件&#xff0c;最后的收益取决于挂钩标的走势和敲入、敲出事件发生的时间&#xff0c;不过在收益计算规则上与雪球有所不同&#xff0c;下文为大家科普…

从铜都到数字先锋:贵溪市铜产业链办件智能化

江西省贵溪市&#xff0c;被誉为中国的“铜都”&#xff0c;其铜产业的蓬勃发展已成为城市经济的强大引擎。铜产业营收占其工业营收的 90%&#xff0c;是贵溪市工业产值的重要支柱。在产业规模的不断扩大和市场需求的日益增长的背景下&#xff0c;现有的铜产业信息化基础设施和…

JavaScript基础(二)函数、数组、对象

1.函数 1.1函数声明 function 函数名&#xff08;&#xff09;{} let 函数名 function(){//函数体} 1.2函数参数 function 函数名&#xff08;num1&#xff0c;num2&#xff09;{ 【return num1num2】//只有有return才能写参数 } let 函数名 function(){//函数体} 1.3…

vue3二次封装element-puls

将表单的通用信息给设置出来 如: label 的提示信息 , type 的类型 // 定义表单的配置项 const formConfig{ formItems:[ { type:"input", label:"用户ID", placeholder:"请输入用户ID" } ] } 页面配置如 <template v-for"(it…

家里养有宠物浮毛多、异味大,宠物空气净化器有用吗

我家收养了12只流浪猫&#xff0c;掉毛量是很多人想象不到的&#xff0c;对于猫掉毛和人掉头发一个道理&#xff0c;情绪压力&#xff0c;长期熬夜&#xff0c;营养不良&#xff0c;年龄原因都会掉毛或掉头发&#xff0c;猫更是如此&#xff01;但确实之前也不知道一只猫的掉毛…

child_process.spawn事件解析并举例

child_process.spawn 是 Node.js 中 child_process 模块提供的一个用于异步创建子进程的方法。这个方法返回一个 ChildProcess 对象&#xff0c;该对象继承自 Node.js 的 EventEmitter 类&#xff0c;因此可以触发和监听多种事件。以下是对 child_process.spawn 所触发事件的深…