初识C语言·预处理详解

news2024/10/5 16:24:23

目录

1 预定义符号

2 define定义常量

3 #define定义宏

4 带有副作用的宏

5 宏替换的规则

6 宏和函数的对比

7 # 和 ##

i) #运算符

ii) ##运算符

8 命名约定

9 命令行定义

10 条件编译

条件编译1:

条件编译2:

条件编译3:

条件编译4:

11 头文件的包含


1 预定义符号

C语言里面设置了预定义符号,在预处理阶段就被处理,有以下符号:

__FILE__//进行编译的源文件
__LINE__//文件当前的行号
__DATE__//文件被编译的日期
__TIME__//文件被编译的时间
__STDC__//是否支持ANSI C标准	

使用就是直接打印就好了,因为VS是不支持ANSI  C标准的,支持的话返回值就是1

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	return 0;
}

需要注意的就是LINE打印的时候占位符是%d,这些符号都是可以直接使用的,并且都在预处理阶段就处理完了。


2 define定义常量

define定义常量不是宏,宏和define定义常量是有一定的区别的,宏是带有参数的,定义常量就是给常量一个值,或者是一个表达式,在预处理阶段完全替换就行了。

比如:

#define MAX 1314
int main()
{
	int a = MAX;
	printf("%d ", a);
	return 0;
}

这是最简单的定义常量,定义好了预处理阶段直接替换就行,那么如果换成表达式呢?

比如我们在工作中有时候需要一个死循环,那么我们知道for循环后面三个空如果什么都不写就是自动判断为真,我们利用这一点,就可以写出如下简单的死循环:

#define forever for( ; ; )

当需要死循环的时候,加一个forever就行了

但是define定义常量的功能远不止于此,比如switch语句,每句后面都要加一个break,有的人就嫌麻烦,于是有了如下代码:

#define CASE break;case
int main()
{
	int num = 2;
	switch (num)
	{
	case 1:
		//操作1
	CASE 2:
		//操作2
	CASE 3:
		//操作3
	CASE 4:
		//操作4
	default:
		break;
	}
	return 0;
}

直接看可能有点抽象,在预处理阶段代码就会被替换成如下代码:

#define CASE break;case
int main()
{
	int num = 2;
	switch (num)
	{
	case 1:
		//操作1
		break;case 2:
		//操作2
			break;case 3:
		//操作3
				break;case 4:
		//操作4
	default:
		break;
	}
	return 0;
}

看起来可能有点不顺眼,但是代码逻辑确实是对的,这也是define定义常量的一个妙用。

当有的时候定义常量的时候代码有点长,影响整体的观看,就可以用到续行符。

#define PRINT printf("%s\n%s\n%d\n%s",__FILE__,__DATE__,__LINE__,__TIME__)

这是使用续行符之前,相较于目前写的代码是比较长的,那么可以做出如下改变:

#define PRINT printf("%s\n%s\n%d\n%s",\
					__FILE__,__DATE__,\
					__LINE__,__TIME__)

也就是在末尾加一个\就行,在VS2022里面最后一行不用加续行符,在Linux环境下的gcc是每行都需要加一个续行符的。

#define PRINT printf("%s\n%s\n%d\n%s",\ 
					__FILE__,__DATE__,\ 
					__LINE__,__TIME__)
int main()
{
	PRINT;
	return 0;
}

这段代码看似和上述无异,实际上这是一段错误代码,因为两个续行符之后加了一个空格,这就会导致报错,续行符后面是什么都不能加的,续行符后面只能是回车,可以理解为后面加了一个\n。

现在思考一个问题:

define常量的时候需不需要加上分号?

实际上有时候加上了,对代码影响不大,无非就是多了一条空语句而已。

#define M 100;
int main()
{
	int a = M;
	return 0;
}

预处理阶段就会变成int a = 100;;  也就是两个分号,但是假如我们用printf打印的时候,那问题可就大了:

printf("%d ", 100;);

代码就会变成这样,肯定就是有错误的,所以真正写的时候,是不推荐加上分号的。


3 #define定义宏

定义宏有个机制就是允许把参数替换到文本里面,这种实现方式叫做宏或者是定义宏

#define name( parament-list ) stuff

这是宏的一般形式,name和(parament-list)中间不能有任何空格,否则参数就会被解释为stuff的一部分。

现在尝试用宏实现计算一个数的平方:

#define DOUBLE_NUM(x) x * x
int main()
{
	int a = 3;
	printf("%d ", DOUBLE_NUM(a));
	return 0;
}

乍一看是没有问题的,但是如果是DOUBLE_NUM(a + 1)呢?

那么我们想要的结果是16,因为传的参数是4嘛,但是实际结果:

这是因为预处理阶段进行了完全替换,所以就变成了:

3 + 1*3 + 1

所以问题出在没有括号,在进行宏定义的时候我们尽量不要吝啬括号,不然很容易因为优先级导致出问题。

#define DOUBLE_NUM(x) ((x) * (x))
int main()
{
	int a = 3;
	printf("%d ", DOUBLE_NUM(a + 1	));
	return 0;
}

 最后的结果,严谨起见还是加上括号。如果不加括号,就会像这串代码一样:

#define DOUBLE_NUM(x) (x) + (x)
int main()
{
	int a = 3;
	printf("%d ", 10 * DOUBLE_NUM(a));
	return 0;
}

我们实际想要的结果是60,可是最后的结果是33,因为最后的结果我们没有加括号,由此可见加括号的重要性。

所以在宏定义有关于求值都应该加上括号,避免因为操作符优先级或者是临近操作符之间的作用导致不可预料的结果。

随着代码的写入,有时候宏用完了我们需要重新定义,可不能直接在原定义的下面直接加一个

#define重新定义,这是极其错误的,我们应该用到#undef:

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

当我们把光标放在下面的MAX上的时候就会发现有错误信息,告诉你未定义标识符MAX,这是因为我们已经移除了#define MAX,那么我们想要再次使用这个标识符只需要重新定义一下就行了。

#define MAX 100
int main()
{
	printf("%d ", MAX);
#undef MAX
#define MAX 111
	printf("%d ", MAX);
	return 0;
}

4 带有副作用的宏

上面的宏定义因为括号已经导致了一些问题,实际上,还有一些看似正确的宏,会导致传的参数的值发生不可预测的变化。

int a = b + 1;
int a = b++;

比如赋值的时候,上述两种方式,第一种就是不带副作用的,第二种就是带副作用的,因为会导致b的值发生改变,往宏定义的方向去靠的话,如下:

#define MAX_NUM(x,y) ((x) > (y)? (x) : (y))
int main()
{
	int a = 3, b = 5;
	int m = MAX_NUM(a++, b++);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("m = %d\n", m);
	return 0;
}

判断a b m的值各是多少?

宏替换的时候,实际代码是int m = (a++) > (b++)? (a++):(b++);

因为3<5,所以a实际上使用了一次,那么就加一次,b同理,使用了两次,所以就加两次,但是b加一次之后整个表达式的值就已经成立了,所以m的值是6,a是4,b是7

由上面的代码,可得一个参数在宏定义里面出现多次可能会导致副作用,副作用就是宏之后,值永久被改变,所以一个参数尽量不在宏里面使用多次。


5 宏替换的规则

1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换。

2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

需要注意的点是:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

宏替换的时候无非就是查找-> 替换->插入->循环上述步骤直到检索完毕。


6 宏和函数的对比

#define MAX_NUM(x,y) ((x) > (y)? (x) : (y))

当实现比较两个数的大小的时候,如果是为了效率考虑,一般都是选择宏,比如用上述代码,那么为什么不考虑函数呢?

int Num_Max(int x, int y)
{
	return x > y ? x : y;
}

首先最直观的就是函数的返回值是有类型的,这也就意味着比较的时候我们只能比较用一类的数值,反观宏,因为没有类型,所以可以比较不同类型的数据。

第二,函数创建的时候涉及到函数栈帧的创建,创建空间的时候需要压栈出栈,是比较浪费时间的,我们可以利用汇编指令看看:

#define MAX_NUM(x,y) ((x) > (y)? (x) : (y))
int Num_Max(int x, int y)
{
	return x > y ? x : y;
}
int main()
{
	int a = 4, b = 6;
	int m = MAX_NUM(a, b);
	int n = Num_Max(a, b);
	return 0;
}

我们可以看到宏定义的汇编指令好像很多,函数对应的汇编指令好像少,可实际进行调试的时候,按F11逐语句的走就会发现函数走的语句是宏定义的是几倍,这是因为函数需要创建空间,返回值,传值,计算,这些都对应了汇编语句,⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。

所以宏的优势在于:

1 没有类型的局限性

2 占用的时间短

宏的劣势在于:

1 宏不能调试     

2 宏没有类型,所以相对来说不严谨   

3 每次使用宏相当于往程序里面加代码,除非宏比较短,不然会大幅度增加代码的长度 

4 宏会带来运算符优先级的问题

这是宏和函数的具体对比。


7 # 和 ##

i) #运算符

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

#的操作可以理解为是字符串化

先看一段代码:

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

这段代码也是没有问题的,最后两个字符串打印出来也是hello world。

那么,现在的问题是如果我们有一个整型a  = 10,想要打印the value of a is 10,

int main()
{
	int a = 10;
	printf("The value of a is %d\n", a);
	return 0;
}

这是一般写法,如果我们想要用宏定义实现呢?宏定义实现有一个难点就是%d那里怎么做,这里可以结合上面的printf来操作。

#define PRINT(val,format) printf("The value of val is "format"\n",val)
int main()
{
	int a = 10;
	PRINT(a, "%d");
	return 0;
}

因为宏定义的参数是没有类型的,所以我们为了所有参数都可以实现这个功能,就用双引号传占位符进去,但是打印的时候,会出现这么个情况:

我们发现val并没有被替换:

#define PRINT(val,format) printf("The value of " #val " is "format"\n",val)

当我们做了如上修改之后,也就是单独给val领出来,加个#运算符,#a就会被替换成"a",这样就可以实现我们想要的功能了。

ii) ##运算符

##运算符可以把两边的符号黏在一起,但是前提是粘在一起组成的新标识符是合法的,不然结果就是未定义的,所以##运算符也被称为记号粘合

当我们求两个值里面的最大值的时候,我们如果创建函数,那每一个类型我们都要对应一个函数来写,就非常的麻烦,采用宏的方式,就会简单很多:

#define NUM_MAX(type)\
type type##_max(type x,type y)\
{\
	return (x > y?x : y);\
}
NUM_MAX(int)
int main()
{
	int m = int_max(1, 5);
	printf("%d ", m);
	return 0;
}

创建好宏直接,NUM_MAX(int),就表示有个函数专门比较整型大小的,好奇心强点的就会去把##删了,因为感觉加不加都一样,但是一下就报错了,像这样:

没有加记号粘合的时候就会报错,说int_max是未定义的,这是为什么?函数就是这样,我们也不知道为什么,但是实际上这两个运算符用的比较少,我们以后看到了知道怎么用就行了,不必深究。


8 命名约定

我们在定义函数的时候和定义宏的时候取名是有所区别的,我们定义宏的时候一般都是全大写

定义,函数名一般都不全大写,一般首字母大写,或者是_后面的是首字母大写,这样具有区分度,当然,这是一般情况,有特殊情况再看咯。


9 命令行定义

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

在VS操作里面是无法实现的:

#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;
}

VS里面这段代码肯定是会报错的,但是在Linux环境下的gcc编译器就可以实现命令行定义,在实现编译的时候我们加上如下指令:

gcc -D ARRAY_SIZE=10 programe.c

也就是说我们给ARRAY_SIZE一个值,然后才开始编译,由此可得,命令行编译就是在编译之前给某个变量一个值,然后才会开始编译。

具体应用比如我们需要一个数组,根据不同因素需要指定数组的不同大小,我们不仅可以利用到柔性数组,也可以用到命令行定义,代码的局限性就又少了一些。


10 条件编译

在代码调试的时候,因为有了条件编译指令的存在,使得我们舍弃一段代码或者一条语句是非常方便的,比如:

条件编译1:

#define MAX
int main()
{
#ifdef MAX
	printf("hahahaha");
#endif
	return 0;
}

当我们不想要打印hahaha的时候,我们就在define前面注释一下就行了,一段语句这样做有点大动干戈的样子,但如果是一段代码呢?

#ifdef #endif是配套使用的,标识如果定义了MAX,就返回1,表示为真,那么下面的语句就执行。

条件编译2:

#if 常量表达式
#endif
#define M 100
int main()
{
#if M==2
	printf("666");
#endif
	return 0;
}

这是一般用法,也可以直接放一个M上去。

这里需要注意的是常量表达式由预处理器处理结果,所以如果出现这种代码:

#define M 100
int main()
{
	int a = 100;
#if M==a
	printf("666");
#endif
	return 0;
}

这种代码就是犯糊涂了的代码,看似结果应该是打印666的,但是并不会打印,因为a是局部变量,局部变量创建是在程序开始运行的时候才开始创建,而预处理阶段领先于main函数栈帧的创建,所以自然比较结果就是0。

条件编译3:

#if 1
	//操作1
#elif 0
	//操作2
#else
	//操作3
#endif

到这里相信给你的感觉是很熟悉的,因为确实和if语句很相似,只不过这是判断的是define定义的常量或者是常量而已,但是endif是万万不能掉的,掉了编译器都不会让你过的。

条件编译4:

#define M
int main()
{
#ifndef M
	printf("ddd");
#endif
	return 0;
}

其实条件编译1和条件编译4是一类的,无非就是判断标识符有没有被定义而已,一个用到#ifdef 也就是if define,另一个就是#ifndef,也就是if not define,所以这里不过多介绍了。

这几个给你感觉很像if语句吧?那么它们也会存在嵌套使用的,这里就留给你自行探索咯。


11 头文件的包含

头文件的包含有两种方法:

#include "stdio.h"
#include <stdio.h>

实际上这两种方法也是有差别的,如果是用的双引号包含的,那么寻找头文件的时候就会先从该工程的本地目录开始找头文件,如果没有就去标准位置里面找,找不到就报错,如果是尖括号包含的,寻找头文件的时候就会从标准位置开始寻找,找不到就报错。

这样看起来好像是双引号包含了尖括号,那我们实际使用的时候为什么不都使用双引号呢?因为效率相对来说低一些,我们明明知道头文件是在标准位置,结果我们还非要在根目录找一下浪费时间,效率自然就低了一下。

有的时候引用头文件不小心引用了多次,原本是会对编译造成比较大的压力的,因为多次包含头文件,预处理阶段就会多出来很多行代码,更别提嵌套引用的头文件了,那么如何解决这个问题呢?

答:使用条件编译

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

在头文件里面加这个就行,第一次是没有定义东西的,第二次引用之后,发现定义了,于是头文件的内容就不会加进去,也就达到了只引用一次头文件的效果。

或着是:

#pragma once

也可以避免头文件的多次引用,这是其他的预处理指令了,有兴趣的话可以多了解一下

#error
#pragma
#line

这些是一部分预处理指令,有兴趣君可自行探索。


感谢阅读!

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

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

相关文章

Vue源码系列讲解——变化侦测篇【下】(Array的变化侦测)

目录 1. 前言 2. 在哪里收集依赖 3. 使Array型数据可观测 3.1 思路分析 3.2 数组方法拦截器 3.3 使用拦截器 4. 再谈依赖收集 4.1 把依赖收集到哪里 4.2 如何收集依赖 4.3 如何通知依赖 5. 深度侦测 6. 数组新增元素的侦测 7. 不足之处 8. 总结 1. 前言 上一篇文…

gh0st远程控制——客户端界面编写(三)

◉ 主控端界面添加右键弹出菜单的功能 为Onlie_List区域添加右键弹出菜单项的功能&#xff1a; 3个视图&#xff1a;类视图、解决方案视图、资源视图 在资源视图下添加一个Menu&#xff1a; 更改Menu的ID为IDR_MENU_ONLINE&#xff1a; 为各控件添加便于区分的ID&#xff1a…

SERVLET线程模型

1. SERVLET线程模型 Servlet规范定义了两种线程模型来阐明Web容器应该如何在多线程环境中处理servlet。第一种模型称为多线程模型,默认在此模型内执行所有servlet。在此模型中,每次客户机向servlet发送请求时Web容器都启动一个新线程。这意味着可能有多个线程同时访问servle…

【高阶数据结构】B-树详解

文章目录 1. 常见的搜索结构2. 问题提出使用平衡二叉树搜索树的缺陷使用哈希表的缺陷 3. B-树的概念4. B-树的插入分析插入过程分析插入过程总结 5. B-树的代码实现5.1 B-树的结点设计5.2 B-树的查找5.3 B-树的插入实现InsertKey插入和分裂测试 6. B-树的删除&#xff08;思想&…

机器人工具箱学习(一)

一、机器人工具箱介绍 机器人工具箱是由来自昆士兰科技大学的教授Peter Corke开发的&#xff0c;被广泛用于机器人进行仿真&#xff08;主要是串联机器人&#xff09;。该工具箱支持机器人一些基本算法的功能&#xff0c;例如三维坐标中的方向表示&#xff0c;运动学、动力学模…

软件定义网络 SDN 简介、OpenFlow

目录 软件定义网络 SDN 简介 1 SDN 与 协议 OpenFlow 1.1 SDN 1.2 OpenFlow 1.2.1 协议 OpenFlow 1.2.2 OpenFlow 数据层面 &#xff08;1&#xff09;匹配 动作 &#xff08;2&#xff09;流表 1.流表由远程控制器管理 2.流表结构 2 SDN 体系结构 3 SDN 控制器 软…

Matplotlib雷达图教程:学会绘制炫酷多彩的多维数据可视化【第53篇—python:Seaborn大全】

文章目录 Matplotlib雷达图绘制指南&#xff1a;炫酷雷达图参数解析与实战1. 普通雷达图2. 堆叠雷达图3. 多个雷达图4. 矩阵雷达图5. 极坐标雷达图6. 定制化雷达图外观7. 调整雷达图坐标轴范围8. 雷达图的子图布局9. 导出雷达图总结 Matplotlib雷达图绘制指南&#xff1a;炫酷雷…

D7 Elasticsearch-Mongodb(搜索记录)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

Vue3 之 Pinia

什么是Pinia Pinia是一个Vue的专属的最新状态管理库&#xff0c;是vuex状态管理工具的替代品 Pinia的优势 1.提供更加简单的API&#xff08;去掉了 mutation&#xff09; 2.提供符合组合式风格的API&#xff08;和vue3语法统一&#xff09; 3.去掉了modules的概念&#xff0…

推荐一款开源的跨平台划词翻译和OCR翻译软件:Pot

Pot简介 一款开源的跨平台划词翻译和OCR翻译软件 下载安装指南 根据你的机器型号下载对应版本&#xff0c;下载完成后双击安装即可。 使用教程 Pot具体功能如下&#xff1a; 划词翻译输入翻译外部调用鼠标选中需要翻译的文本&#xff0c;按下设置的划词翻译快捷键即可按下输…

已解决:tpm2_createpriimay: command not found

出现错误如下&#xff1a; ERROR: Could not change hierarchy for Owner. TPM Error:0x9a2 ERROR: Could not change hierarchy for Endorsement. TPM Error:0x9a2 ERROR: Could not change hierarchy for Lockout. TPM Error:0x98e ERROR: Unable to run tpm2_takeownership…

【经典项目】Java实现打地鼠小游戏(附源码)

一、游戏回顾 打地鼠游戏是一款简单而有趣的反应游戏。游戏中&#xff0c;你需要在地洞中出现的地鼠出现时迅速点击它们&#xff0c;以获得分数。以下是一般的打地鼠游戏玩法介绍&#xff1a; 准备阶段&#xff1a;游戏开始时&#xff0c;你会看到一个由多个地洞组成的游戏界面…

百面嵌入式专栏(面试题)C语言面试题22道

沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将介绍C语言相关面试题 。 宏定义是在编译的哪个阶段被处理的?答案:宏定义是在编译预处理阶段被处理的。 解读:编译预处理:头文件包含、宏替换、条件编译、去除注释、添加行号。 写一个“标准”宏MIN,这个…

C#,栅栏油漆算法(Painting Fence Algorithm)的源代码

1 刷油漆问题 给定一个有n根柱子和k种颜色的围栏&#xff0c;找出油漆围栏的方法&#xff0c;使最多两个相邻的柱子具有相同的颜色。因为答案可以是大的&#xff0c;所以返回10^97的模。 计算结果&#xff1a; 2 栅栏油漆算法的源程序 using System; namespace Legalsoft.Tr…

机器学习 | 揭示EM算法和马尔可夫链的实际应用

目录 初识EM算法 马尔可夫链 HMM模型基础 HMM模型使用 初识EM算法 EM算法是一种求解含有隐变量的概率模型参数的迭代算法。该算法通过交替进行两个步骤&#xff1a;E步骤和M步骤&#xff0c;从而不断逼近模型的最优参数值。EM算法也称期望最大化算法&#xff0c;它是一个基…

C++进阶(十二)lambda可变参数包装器

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、新的类功能1、默认成员函数2、类成员变量初始化3、 强制生成默认函数的关键字default:4、…

拿捏循环链表

目录&#xff1a; 一&#xff1a;单链表&#xff08;不带头单向不循环&#xff09;与循环链表&#xff08;带头双向循环&#xff09;区别 二&#xff1a;循环链表初始化 三&#xff1a;循环链表头插 四&#xff1a;循环链表尾插 五&#xff1a;循环链表头删 六&#xff1…

【leetcode】深搜、暴搜、回溯、剪枝(C++)1

深搜、暴搜、回溯、剪枝&#xff08;C&#xff09;1 一、全排列1、题目描述2、代码3、解析 二、子集1、题目描述2、代码3、解析 三、找出所有子集的异或总和再求和1、题目描述2、代码3、解析 四、全排列II1、题目解析2、代码3、解析 五、电话号码的字母组合1、题目描述2、代码3…

华为 Huawei 交换机 黑洞MAC地址的作用和配置示例

黑洞mac作用&#xff1a;某交换机上配置某个PC的mac地址为黑洞mac&#xff0c;那么这台PC发出来的包都会被交换机丢弃&#xff0c;不会被转发到网络中。 组网需求&#xff1a; 如 图 2-13 所示&#xff0c;交换机 Switch 收到一个非法用户的访问&#xff0c;非法用户的 MAC 地址…

Java实现民宿预定管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色2.2.2 房主角色2.2.3 系统管理员角色 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿4.3 新增民宿评价4.4 查询留言4.5 新增民宿订单 五、免责说明 一、摘要 1.1 项目介绍 基于…