learn_C_deep_13 (深刻理解宏定义)

news2024/10/7 8:28:35

目录

                  宏定义

数值宏常量

字符串宏常量

用定义充当注释符号宏

用 define 宏定义表达式

宏定义中的空格


宏定义

数值宏常量

        在C语言中,宏定义可以用于定义数值宏常量。数值宏常量是一个值,在宏定义中用一个常量名称来表示,该值在后续的代码中可以被多次引用。

数值宏常量主要有以下几个特点:

1. 可以是整数或小数:可以定义整数或小数的数值宏常量,例如:#define PI 3.14。

2. 没有类型:宏定义中的数值宏常量没有类型,因此可以在代码中使用时自动转化为相应的类型。

3. 通常使用大写字母:为了方便识别,通常将数值宏常量的名称使用大写字母来表示,例如:#define MAX_NUM 100。   

为何这些字面值建议定义成宏?

        数值宏常量的主要优点是能够提高代码的可读性和可维护性。通过将数值宏定义为常量名称,可以避免在后续代码中多次出现相同的数值常量,减少冗余代码,从而使代码更加简洁和易于理解。此外,如果需要更改常量的值,只需要修改宏定义即可,这样可以更加方便和快捷地修改代码。

字符串宏常量

        在C语言中,宏定义还可以用于定义字符串宏常量。字符串宏常量是一个字符串,在宏定义中用一个常量名称来表示,该字符串在后续的代码中可以被多次引用。

字符串宏常量主要有以下几个特点:

1. 必须使用双引号:在宏定义中,字符串宏常量必须使用双引号将字符串括起来。

2. 可以包含转义字符:字符串宏常量可以包含转义字符,例如:#define PATH "D:\\胡巴"。

3. 没有类型:宏定义中的字符串宏常量没有类型,因此可以在代码中使用时自动转化为相应的类型。

4. 通常使用大写字母:为了方便识别,通常将字符串宏常量的名称使用大写字母来表示,例如:#define PATH "D:\\胡巴"。

#include<stdio.h>
/*第一个宏,字符串没有带双引号,直接报错*/
#define PATH D:\胡巴
int main()
{
	printf("%s\n", PATH);
	return 0;
}


#include<stdio.h>
/*第二个宏,字符串带上双引用,有告警,能够编译通过。
不过windows中路径分割符需要\\,输出乱码,改过之后,正常*/
//#define PATH "D:\胡巴"  ---  输出-> D:喊?
#define PATH "D:\\胡巴"  // 正常
int main()
{
	printf("%s\n", PATH);
	return 0;
}


#include<stdio.h>
/*第三个宏,不带双引号,进行\续行 直接报错*/
//第三个\是续行符的意思
#define PATH D:\\\
胡巴
int main()
{
	printf("%s\n", PATH);
	return 0;
}


#include<stdio.h>
/*第四个宏,带双引号,进行\续行 正常输出*/
#define PATH "D:\\\
胡巴"
int main()
{
	printf("%s\n", PATH);
	return 0;
}

结论:宏定义代表字符串的时候,一定要带上双引号,可以用\续行符进行续行。

用定义充当注释符号宏

程序翻译是什么?

C语言程序翻译指的是将C语言编写的源代码(即人类可读的代码)转化为计算机可执行的代码。这个过程叫做编译(Compilation)。

为什么要进行程序翻译?

C语言需要进行程序翻译,是因为计算机只能理解由二进制代码(机器码)组成的指令,而人类编写的C语言程序是文本形式的,计算机不能直接运行。

程序翻译的步骤

1. 预处理(Preprocessing):预处理器(Preprocessor)根据程序中包含的预编译指令,如#include等,将源代码中的宏定义、注释、条件编译等转换为实际的C代码。预处理的输出结果通常是一个扩展名为.i的文件。

2. 编译(Compilation):编译器(Compiler)将扩展名为.i的预处理后的文件编译成汇编语言(Assembly Language)代码,这个汇编代码是计算机可以理解的中间语言。编译的输出结果通常是一个扩展名为.s的汇编语言代码文件。

3. 汇编(Assembly):汇编器(Assembler)将扩展名为.s的汇编语言代码文件转换成计算机可执行的机器码(Machine Code)。汇编的输出结果通常是一个扩展名为.o的目标文件。

4. 链接(Linking):链接器(Linker)将程序中使用到的各个目标文件和库文件(Library)链接成一个完整的可执行文件(Executable File)。器链接的输出结果通常是扩展名为.exe的可执行文件。

 最终得到的可执行文件就是计算机可以直接运行的程序。

库文件(Library)的介绍

        C语言中的库是一些预编译好的代码文件,可以包含很多常用的函数和变量。它们可以被其他程序或应用程序调用,以便在程序中使用它们的功能而不必重新编写代码。这有效地简化了代码编写过程并提高了程序员的工作效率。

        在C语言中,库一般被分为两种类型:静态库动态库

        静态库是一组已经编译好的目标文件的集合,可以直接链接至程序中。这意味着,在程序运行时,静态库的所有函数和代码都会被打包成一个单独的可执行文件。静态库的优点是调用速度快,运行效率高,并且程序独立性也很高。缺点是占用磁盘空间比较大,而且多个程序使用同一个库会导致程序冗余。

        动态库是一种在程序运行时才会被链接的代码文件。通常以.dll或.so文件的形式存在。程序在运行时,只会在需要使用某个函数时才会去链接相关动态库。这样可以有效减小程序的体积,并且多个程序共用同一个库不会导致程序体积冗余。但是,动态库调用速度相对静态库较慢,且程序执行过程中可能存在动态链接错误等风险。

C语言的库通常可以分为以下几类:

1. 标准库:也称为C标准库,在编译C程序时默认会链接这个库,包括stdio.h、stdlib.h、string.h、math.h等等。

2. 第三方库:由独立的开发者或团队编写的库,在程序编辑过程中需要手动链接,例如Boost库。

3. 自定义库:由程序员自己编写的库文件,可以在程序中重复使用,提高代码的可重用性和可维护性。自定义库可以是静态库或动态库。

4. 操作系统库:涵盖了与操作系统通信相关的函数和变量。例如,Windows操作系统提供了Windows API库,Linux操作系统则提供了Glibc库。

        总之,使用C语言的库可以大大简化代码编写过程,提高程序员的工作效率,同时也可以提高程序的可重用性和可维护性。

为什么计算机在编译的过程中,需要将高级语言转为汇编语言再转为二进制代码,为什么不能直接将高级语言转为二级制代码,省略去中间的转化汇编代码的过程呢?

        高级语言是人类可读写的,可以方便地表达复杂的算法和逻辑,但是计算机无法直接理解高级语言,需要将其转换为计算机可以执行的二进制代码。在将高级语言转换为二进制代码的过程中,计算机会经过编译器的多个阶段,其中包括语法分析、语义分析、代码优化等步骤,这些步骤都需要将高级语言转换为汇编语言再转换为二进制代码。

        之所以不能直接将高级语言转换为二进制代码,是因为高级语言和计算机的指令集是不同的,要将高级语言转换为计算机指令需要经过复杂的转换,其中涉及到很多细节问题。而汇编语言作为一种比较接近底层的语言,可以更容易地和计算机指令对应起来,因此在编译的过程中使用汇编语言作为中间语言可以大大简化编译器的实现难度和编译速度。

预处理过程中是先处理注释还是先处理宏替换呢?

#include <stdio.h>
//当前,我们用BSC充当C++风格的注释
#define BSC //
int main()
{
	BSC printf("hello world\n");
	return 0;
}

倘若,先执行宏替换,那么先得到的代码应该是:

int main()
{
	//将BSC替换成为‘//’
	// printf("hello world\n");
	return 0;
}


//再执行去注释,那么代码最终的样子,应该是
int main()
{
	return 0; //printf被注释掉
}
//并且,最终运行的时候,应该没有输出

但是当我们查看输出结果时,发现结果并非如此。

程序输出结果了,和我们预期的并不一样 ,所以程序实际上,是先执行去注释,再进行宏替换。

//先去掉宏后面的//,因为是注释
#define BSC // //最终宏变成了#define BSC 空
int main()
{
	BSC printf("hello world\n"); 
    //因为BSC是空,所以在进行替换之后,就是printf("hello world\n");
	return 0;
}

结论:预处理期间:先执行去注释,在进行宏替换。

#include <stdio.h>
#define BSC //
#define BMC /*
#define EMC */ 
int main()
{
	BSC printf("hello today\n");
	BMC printf("hello tomorrow\n"); EMC
	return 0;
}

那我们的上面的程序的输出语句有没有被注释,按照上面的结论,并没有。

用 define 宏定义表达式

#include <stdio.h>
#define SUM(x) (x)+(x) //定义宏求两个数的和
int main()
{
	printf("%d\n", SUM(10));
	printf("SUM(20)\n");
	return 0;
}

 第七行 printf("%d\n", SUM(10)); 是一个printf函数,用于将SUM(10)的值打印到控制台上。在程序中,SUM(10)的值为(10)+(10) = 20,因此该语句会输出数字20。

第八行 printf("SUM(20)\n"); 是另一个printf函数,用于将字符串SUM(20)打印到控制台上。在程序中,该语句并不会调用SUM宏,因此输出的是一个字符串而不是数字。

从上面的预编译结果看,我们发现只有第个输出的代码进行替换了。说明宏不是无脑文本替换的

#include <stdio.h>
//该宏最大的特征是,替换的不是单个变量/符号/表达式,而是多行代码
#define INIT_VALUE(a,b)\    //对变量a、b进制初始化
                a = 0;\
                b = 0;
int main()
{
	int flag = 0;
	scanf("%d", &flag);
	int a = 100;
	int b = 200;
	printf("before: %d, %d\n", a, b);
	if (flag)
		INIT_VALUE(a, b);
	else  //error C2181: 没有匹配 if 的非法 else
		printf("error!\n");
	printf("after: %d, %d\n", a, b);
	return 0;
}

为什么报错了呢?从下面的预编译结果看我们就清楚原因了。

int main()
{
	int flag = 0;
	scanf("%d", &flag);
	int a = 100;
	int b = 200;
	printf("before: %d, %d\n", a, b);
	if (flag)
		a = 0; b = 0;; //if倘若没有带{},那么if后面只能跟一条语句
	else //else匹配if时,直接报错了
		printf("error!\n");
	printf("after: %d, %d\n", a, b);
	return 0;
}

怎么修正呢?

//方案一 - 给if语句后的语句加上花括号
#include <stdio.h>
#define INIT_VALUE(a,b)\
				a = 0;\
				b = 0;
int main()
{
	int flag = 0;
	scanf("%d", &flag);
	int a = 100;
	int b = 200;
	printf("before: %d, %d\n", a, b);
	if (flag) {    //直接带上花括号就好了
		INIT_VALUE(a, b);
	}
	else {
		printf("error!\n");
	}
	printf("after: %d, %d\n", a, b);
	return 0;
}
//失败方案二 - 在宏这里直接加上花括号
#include <stdio.h>
#define INIT_VALUE(a,b) {a = 0; b = 0;}	//在宏这里直接加上花括号,这样行不行呢?	
int main()
{
	int flag = 0;
	scanf("%d", &flag);
	int a = 100;
	int b = 200;
	printf("before: %d, %d\n", a, b);
	if (flag)    
		INIT_VALUE(a, b);
        //这里会被宏替换为{a = 0; b = 0;};  -  后面多了一个分号;
	else 
		printf("error!\n");
	printf("after: %d, %d\n", a, b);
	return 0;
}
//方案二 - 使用do-while-zero结构
#include <stdio.h>
#define INIT_VALUE(a,b)\
					do{\
						a = 0;\
						b = 0;\
					}while(0)
//使用do-while-zero结构
int main()
{
	int flag = 0;
	scanf("%d", &flag);
	int a = 100;
	int b = 200;
	printf("before: %d, %d\n", a, b);
	if (flag) {
		INIT_VALUE(a, b);
        //这里会被宏替换为 do{a = 0; b = 0;}while(0); --- ;正好匹配do-while语句
	}
	else {
		printf("error!\n");
	}
	printf("after: %d, %d\n", a, b);
	return 0;
}

宏定义中的空格

#include <stdio.h>
//#define INC (a) ((a)++) //error
#define INC(a) ((a)++) //定义不能带空格
int main()
{
	int i = 0;
	INC (i); //使用可以带空格,但是不推荐
	printf("%d\n", i);
	return 0;
}

#undef

1. 宏只能在main上面定义吗?

        不是的。宏定义可以出现在任何位置,包括main函数之后。宏可以在当前源文件内的任何位置使用,也可以在包含该宏定义的头文件所在的其他源文件中使用。为了方便复用和避免冲突,建议将宏的定义放在头文件中。如果没有多文件,建议放在main上面定义。

  2. 在一个源文件内,宏的有效范围是什么?

        在一个源文件中定义的宏的有效范围是从定义宏的位置开始,到文件结束或者该宏被#undef指令取消定义之前。在这个范围内,宏可以在源文件中的任何函数或语句中使用。同时,头文件中定义的宏可以在包含该头文件的任何源文件中使用,但是需要通过#include指令将头文件包含进来。宏的作用域仅限于定义宏的源文件或者包含该宏定义的头文件内部,在其他源文件中无法直接使用宏。因此,为了避免宏命名的冲突,建议在头文件中使用条件编译来防止多次包含同一个头文件和重复定义同一个宏。

#include <stdio.h>
#define M 10
int main()
{
	printf("%d, %d\n", M, N);//error:未定义的标识符"N"
#define N 100
	printf("%d, %d\n", M, N);
	return 0;
}

结论:宏的有效范围,是从定义处往下有效,之前无效

在C语言中,#undef是一个预处理指令,用于取消一个之前定义的宏的定义,即将该宏从预处理符号表中删除。语法格式为:

```
#undef macro
```

其中,macro是要取消定义的宏的名称。

        通过#undef指令可以在预处理阶段取消一个之前定义的宏的定义。取消定义后,宏就不能再被使用,并且如果在源文件中使用该宏,会被当做普通的符号处理。如果想要重新定义该宏,需要重新使用#define指令来定义。

        取消宏的定义可以用于避免宏定义的名字冲突,或者在某些情况下需要动态修改宏定义的情况下使用。但是,在实际编程中一定要谨慎使用#undef指令,因为这会导致代码的可读性和可维护性降低。

//undef本质作用
#include <stdio.h>
#define M 10
int main()
{
#define N 100
	printf("%d, %d\n", M, N);
#undef M //取消M

#undef N //取消N
    //从该位置开始,M,N,不在被识别
	printf("%d, %d\n", M, N);
	//error:未定义的标识符"M"、"N"
	return 0;
}

结论:undef是取消宏的意思,可以用来限定宏的有效范围。

小练习一下

#include <stdio.h>
int main()
{
#define X 3
#define Y X*2
#undef X
#define X 2
    int z = Y;
    printf("%d\n", z);
    return 0;
}

我们来看一下预编译的结果

首先,在第一行代码中,使用`#define X 3` 定义了X为常量3。

接着,在第二行代码中,使用`#define Y X*2` 定义了Y为X的两倍。此时,由于X被定义为常量3,因此Y的值为6。

然后,在第三行代码中,使用`#undef X` 取消了X的定义。这意味着在之后的代码中,X将不再表示常量3。

在第四行代码中,使用`#define X 2` 重新定义X为常量2。这样,之后如果使用X时,它将被替换为常量2。

接下来,在第五行代码中,使用`int z = Y` 将Y的值赋给整型变量z。但是,由于在宏定义中使用了X,而此时X已被重新定义为2,因此编译器会将宏定义`Y X*2` 中的X替换为2,也就是将Y的值替换为4。

最终,在第六行代码中,使用`printf("%d\n", z);` 将变量z的值输出到控制台。由于z的值为4,因此输出结果为4。

总体来说,这段代码的作用是测试宏定义中使用常量的替换规则,以及取消和重新定义宏定义的方法。

再来看这个代码 

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

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

相关文章

计算机视觉的应用5-利用PCA降维方法实现简易人脸识别模型

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用5-利用PCA降维方法实现简易人脸识别模型&#xff0c;本文将介绍如何使用主成分分析&#xff08;PCA&#xff09;实现简易的人脸识别模型。首先&#xff0c;我们将简要介绍PCA的原理及其在人脸识别中…

26 KVM热迁移虚拟机

文章目录 26 KVM热迁移虚拟机26.1 总体介绍26.1.1 概述26.1.2 应用场景26.1.3 注意事项和约束限制 26.2 热迁移操作26.2.1 前提条件26.2.2 热迁移脏页率预测&#xff08;可选&#xff09;26.2.3 设置热迁移参数&#xff08;可选&#xff09;26.2.4 热迁移操作&#xff08;共享存…

Linux:文本三剑客之awk

Linux&#xff1a;文本三剑客之awk 一、awk编辑器1.1 awk概述1.2 awk工作原理1.3 awk与sed的区别 二、awk的应用2.1 命令格式2.2 awk常见的内建变量&#xff08;可直接用&#xff09; 三、awk使用3.1 按行输出文本3.2 按字段输出文本3.3 通过管道、双引号调用 Shell 命令 一、a…

【模电实验】日光灯电路及功率因数的提高

实验4 日光灯电路及功率因数的提高 一、实验目的 1&#xff0e;理解提高功率因数的意义并掌握其方法。 2&#xff0e;掌握日光灯电路的联接。 二、原理说明 日光灯电路结构及工作原理 日光灯电路如图4-1所示&#xff0c;日光灯由灯管、镇流器和启辉器三部分组成。 &…

复制带随机指针的链表

&#x1f495;“如果你关注自己已经拥有的&#xff0c;你就会拥有更多。如果你只关注自己没有得到的&#xff0c;你永远不会满足。” - 奥普拉温弗瑞&#x1f495; &#x1f43c;作者&#xff1a;不能再留遗憾了&#x1f43c; &#x1f386;专栏&#xff1a;Java学习&#x1f3…

11. Redis集群(cluster)

11. Redis集群cluster 是什么&#xff1f;能干嘛&#xff1f;集群算法-分片-槽位slot官网出处redis集群的槽位slotredis集群的分片他两的优势slot槽位映射&#xff0c;一般业界有3种解决方案哈希取余分区—致性哈希算法分区3大步骤算法构建一致性哈希环redis服务器IP节点映射k…

【Python sqlite3】零基础也能轻松掌握的学习路线与参考资料

Python sqlite3是Python语言自带的轻量级关系数据库管理系统&#xff0c;它可以让我们在不需要额外的安装和配置下&#xff0c;使用SQLite数据库进行操作和管理。SQLite是一个功能强大的嵌入式数据库&#xff0c;它非常适合在轻量级应用程序中使用&#xff0c;如桌面应用程序、…

ROS学习(4)——launch文件的编写

对于一个复杂的系统,会有十几个、几十个甚至是上百个节点在运行,如果我们每次都是采取“打 开终端、运行 rosrun 指令”来启动应用程序,显得效率非常低。我们需要一个更方便的方式来启动系统。ROS 中提供了“使用 launch 文件 roslaunch”命令来完成系统的启动。具体的实现方法…

Java·Lambda

文章目录 ⚽️1 背景⚽️&#x1f34f;1.1 Lambda表达式的语法&#x1f34f;&#x1f34e;1.2 函数式接口&#x1f34e; &#x1f3c0;2 Lambda表达式的基本使用&#x1f3c0;&#x1f348;2.1 语法精简&#x1f348; &#x1f3c8;3 变量捕获&#x1f3c8;&#x1f3c6;3.1 匿…

Linux Audio (7) DAPM-4 Path/Route添加过程

DAPM-4 Path/Route添加过程 route分类&#xff1a;route转化为Pathcodec驱动add widgetMechine驱动add kcontrol route分类&#xff1a; 常规route {“sink”, NULL, “source”}&#xff0c;其path->connect1 sink widget是Mixer {“Mixer”, name1, “source1”} {“Mixe…

chatgpt赋能Python-python88

Python88 简介 Python88 是一个优秀的 Python 开源库&#xff0c;它提供了许多有用的函数和工具&#xff0c;可用于创建高效的 Web 应用程序&#xff0c;简化数据分析、数据可视化以及机器学习和人工智能任务等。 Python88 帮助开发人员以更简单、更快捷的方式编写代码&#…

VS Code Remote Development

1、Ubuntu 上启用 SSH &#xff08;1&#xff09;打开终端&#xff0c;并且安装openssh-server软件包&#xff1a; sudo apt update sudo apt install openssh-server 当被提示时&#xff0c;输入你的密码并且按 Enter&#xff0c;继续安装。 &#xff08;2&#xff09;安…

RocketMQ消息发送

消息发送示例代码&#xff1a; public static void main(String[] args) throws MQClientException, InterruptedException {DefaultMQProducer producer new DefaultMQProducer("please_rename_unique_group_name");producer.setNamesrvAddr("127.0.0.1:9876…

leetcode-743. 网络延迟时间

1.思路分析&#xff1a; 一道Dijkstra模板题 推荐Dijkstra算法讲解教程 Dijkstra&#xff08;有向图某点到其他所有点的最短路径问题&#xff09; Dijkstra算法的基本思想是贪心策略&#xff0c;每次从未确定最短路径的顶点中选择距离源点最近的一个&#xff0c;然后以该顶点…

Python使用正则表达式

正则表达式&#xff08;Regular Expression&#xff09;&#xff0c;又称规则表达式&#xff0c;是一个计算机科学的概念&#xff0c;通常被用来检索和替换符合某些规则的文本。 1. 正则表达式语法 正则表达式就是记录文本规则的代码。 1. 行定位符 行定位符就是用来描述字…

【STM32G431RBTx】备战蓝桥杯嵌入式→决赛试题→第十二届

文章目录 前言一、题目二、模块初始化三、代码实现interrupt.h:interrupt.c:main.h:main.c: 四、完成效果五、总结 前言 无 一、题目 二、模块初始化 1.LCD这里不用配置&#xff0c;直接使用提供的资源包就行 2.ADC:开启ADCsingle-ended 3.LED:开启PC8-15,PD2输出模式就行了…

MySQL高级(InnoDB引擎)

&#xff08;一&#xff09;逻辑存储结构 表空间&#xff08;ibd文件&#xff09;&#xff0c;会生成ibd文件&#xff0c;一个mysql实例可以对应多个表空间&#xff0c;用于存储记录、索引等数据。 段&#xff0c;分为数据段&#xff08;Leaf node segment&#xff09;、索引段…

学弟研一,有几篇SCI论文,做过前端,读博 or 走开发进国企?

同学你好&#xff0c;在正面先抛开选择就业的方面的问题&#xff0c;其实我觉得生活种的很多选择&#xff0c;都可以从以下的几点进行斟酌与考虑&#xff1a; &#xff08;1&#xff09;你最擅长的是哪个方面&#xff1f;&#xff08;2&#xff09;你的兴趣爱好是在哪个方面&am…

从0开始搭建完整UVM工程(可直接用于实际的工程中)、含源码(包括makefile文件)、可直接运行,及详细注释

一、说明 网上的实现uvm工程代码都是抄自张强所著的《UVM实战》,都是讲所有文件放到一个文件夹,且不涉及到实际工程中的uvm结构,以及多文件层级结构,让人理解起来较为困难,本文则将会从0开始教大家如何搭建一个具有实际工程效果的UVM框架: 其对应的书中的框架图如下所示:…

chatgpt赋能Python-pythoncontinue

简介 Python是一种高级编程语言&#xff0c;受到越来越多的人们的欢迎。其中&#xff0c;continue是Python语言中的一个很重要的关键字&#xff0c;它的出现可以很好地帮助程序员们实现自己的编程目标。在本文中&#xff0c;我们将介绍continue关键字&#xff0c;并解释它在Py…