程序环境+预处理

news2024/12/24 9:06:33

  • 💓博客主页:江池俊的博客
  • ⏩收录专栏:C语言进阶之路
  • 👉专栏推荐:✅C语言初阶之路 ✅数据结构探索✅C语言刷题专栏
  • 💻代码仓库:江池俊的代码仓库
  • 🎉欢迎大家点赞👍评论📝收藏⭐

在这里插入图片描述

文章目录

  • 1. 程序的翻译环境和执行环境
  • 2. 详解编译+链接
    • 2.1 翻译环境(编译+链接)
    • 2.2 编译的三个阶段:(预编译、编译、汇编)
    • 2.3 运行环境
  • 3. 预处理详解
    • 3.1 预定义符号
    • 3.2 #define
      • 3.2.1 #define 定义标识符
      • 3.2.2 #define 定义宏
      • 3.2.3 #define 替换规则
      • 3.2.4 #和##
      • 3.2.5 带副作用的宏参数
      • 3.2.6 宏和函数对比
      • 3.2.7 命名约定
    • 3.3 #undef
    • 3.4 命令行定义
    • 3.5 条件编译
    • 3.6 文件包含
      • 3.6.1 头文件被包含的方式:
      • 3.6.2 嵌套文件包含
  • 4. 其他预处理指令

1. 程序的翻译环境和执行环境

ANSI C的任何一种实现中,存在两个不同的环境。

  • 第1种是 翻译环境 ,在这个环境中源代码被转换为可执行的机器指令。
  • 第2种是 执行环境 ,它用于实际执行代码。

在这里插入图片描述


2. 详解编译+链接

2.1 翻译环境(编译+链接)

翻译环境主要包括以下几个步骤:

  1. 编写代码: 使用文本编辑器编写C语言代码,并保存为以“.c”为扩展名的文件。
  2. 编译代码: 使用C编译器将源代码翻译成机器码。编译器将源代码转换为可执行文件(.exe文件),该文件包含可以在计算机上运行的可执行代码。
  3. 链接代码: 如果我们的程序使用了其他库或共享对象文件中的函数或变量,则需要链接这些文件以将它们与我们的的程序组合在一起。链接器将我们的程序与所需的库和共享对象文件合并成一个可执行文件。

在这里插入图片描述
【解释】:

  • 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
  • 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
  • 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

2.2 编译的三个阶段:(预编译、编译、汇编)

  1. 预编译(预处理):
    • 注释的替换(删除)注释被替换成一个空格
    • 头文件的包含 #include <>
    • #define 符号的替换(所有的预处理指令都在预处理阶段处理的)
    • .c)文件转换为(.i)文件
  2. 编译:
    • 把C语言代码编译成汇编代码
    • 此时将(.i)文件转换为(.s)文件。
    • 语法分析、词法分析、语义分析、符号汇总
    • 汇总的符号都是全局的
  3. 汇编:
    • 把汇编代码翻译成二进制指令
    • .s)文件转换为(.o)文件(目标文件)
    • 生成符号表

在这里插入图片描述

2.3 运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

3. 预处理详解

3.1 预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的。
举个栗子:

printf("file:%s\nline:%d\n", __FILE__, __LINE__);

在这里插入图片描述在这里插入图片描述

3.2 #define

3.2.1 #define 定义标识符

语法:
 #define name stuff

举个栗子:

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,       \
                          __DATE__,__TIME__ ) 

【注意】:

  • 在C语言中,使用#define指令定义标识符时,不需要在最后添加分号。这是因为#define宏定义本身并不是一个语句,它仅作为预处理指令,将指定的标识符替换为对应的文本。
  • 例如,你可以使用#define PI 3.1415926来定义一个代表π的标识符。
  • 此外,还需要注意的是,如果定义的标识符过长,可以分成多行书写,除了最后一行外,其余每行的末尾都可以加上一个反斜杠(续行符)。
  • 不过,务必避免在宏定义的字符串中加入分号,因为分号会被编译器计算进去,可能导致预处理器错误地将分号视为宏参数的一部分

3.2.2 #define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

下面是宏的申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中。

注意:

  1. 参数列表的左括号必须与 name 紧邻。
  2. 如果两者之间有任何空白存在,参数列表就会被解释为stuff 的一部分。

如:

#define SQUARE( x ) x * x

这个宏接收一个参数 x .
如果在上述声明之后,你把 SQUARE( 5 ); 置于程序中,预处理器就会用下面这个表达式替换上面的表达式:5 * 5

警告:
这个宏存在一个问题:
观察下面的代码段:

int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

乍一看,你可能觉得这段代码将打印 36 这个值。
事实上,它将打印 11 .
为什么?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );

这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。
在宏定义上加上两个括号,这个问题便轻松的解决了:

#define SQUARE(x) (x) * (x)

这样预处理之后就产生了预期的效果:

printf ("%d\n",(a + 1) * (a + 1) );

这里还有一个宏定义:

#define DOUBLE(x) (x) + (x)

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

这将打印什么值呢?

警告:
看上去,好像打印 100,但事实上打印的是 55 .
我们发现替换之后:

printf ("%d\n",10 * (5) + (5));

乘法运算先于宏定义的加法,所以出现了 55 .

这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE( x)   ( ( x ) + ( x ) )

提示:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作

3.2.3 #define 替换规则

在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
    被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:
4. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

3.2.4 #和##

  1. # 的作用

一个小技巧是:
使用 #把一个宏参数变成对应的字符串
比如:

int i = 10;
#define PRINT(FORMAT, VALUE)\
    printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);//产生了什么效果?

代码中的 #VALUE 会预处理器处理为:
"VALUE" .
最终的输出的结果应该是:

the value of i+3 is 13
  1. ## 的作用
  • ## 可以把位于它两边的符号合成一个符号。
  • 它允许宏定义从分离的文本片段创建标识符。
#define ADD_TO_SUM(num, value) \
    sum##num += value;
...
ADD_TO_SUM(5, 10); //作用是:给sum5增加10.

注:
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

3.2.5 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题。

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

这里我们得知道预处理器处理之后的结果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));
执行过程:5  >  8    x = 6,y = 9  所有执行y++,即 9++,z = 9
故:  x = 6,y = 10,z = 9

所以输出的结果是:

x=6 y=10 z=9

3.2.6 宏和函数对比

宏通常被应用于执行简单的运算。

比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))

那为什么不用函数来完成这个任务?

原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
    所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。
    所以函数只能在类型合适的表达式上使用。反之这个宏则可以适用于整形、长整型、浮点型等可以用于>来比较的类型。
    宏是类型无关的。

宏的缺点: 当然和函数相比宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num, type)\
    (type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

宏和函数的一个对比:

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

3.2.7 命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:

  • 把宏名全部大写
  • 函数名不要全部大写

3.3 #undef

这条指令用于移除一个宏定义。

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

3.4 命令行定义

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

3.5 条件编译

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

比如说:

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

#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
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
     //..
#endif

//2.多个分支的条件编译
#if 常量表达式
     //...
#elif 常量表达式
     //...
#else
     //...
#endif

//3.判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol

//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

3.6 文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。

这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含 10 次,那就实际被编译 10 次。

3.6.1 头文件被包含的方式:

  • 本地文件包含
#include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。

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

/usr/include

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

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

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

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

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

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以

但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

3.6.2 嵌套文件包含

如果出现这样的场景:
在这里插入图片描述
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。

如何解决这个问题?
答案:条件编译

每个头文件的开头写:

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

或者:

#pragma once

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

【笔试题】:

1. 头文件中的 ifndef/define/endif是干什么用的?
2. #include <filename.h> 和 #include "filename.h"有什么区别?
  1. 头文件中的ifndef/define/endif是用于条件编译的预处理指令。它们的作用是防止头文件被重复包含,以避免编译错误和代码冗余。具体来说:

    • ifndef:如果没有定义标识符(通常是宏名),则执行后续的代码块。
    • #define:定义一个标识符,通常是一个宏名。
    • endif:结束条件编译块。
  2. #include <filename.h>#include "filename.h" 的区别主要在于搜索路径和处理方式:

    • #include <filename.h>:使用尖括号表示在标准库头文件目录中查找指定的头文件。编译器会在系统的预定义头文件目录中查找该文件,并将找到的文件插入到当前位置。如果找不到该文件,编译器会报错。
    • #include "filename.h":使用双引号表示在当前源文件所在的目录下查找指定的头文件。编译器首先在当前源文件所在的目录下查找该文件,如果找不到,再在系统的预定义头文件目录中查找。如果还是找不到,编译器会报错。

总结起来,#include <filename.h> 用于引入标准库头文件,而 #include "filename.h" 用于引入用户自定义的头文件。


4. 其他预处理指令

  1. #error: 此预处理指令用于在编译时生成一个错误。当编译器遇到#error指令时,它会停止编译并输出指定的错误消息。这对于在编译阶段引入错误检查非常有用。例如:
#error "This is an error message"

当编译器遇到上述代码时,它将输出错误消息:“This is an error message”。

  1. #pragma: 这是一个更通用的指令,它允许你向预处理器传递一个特定的指示。这些指示通常用于控制编译器的特定行为,尽管具体的指示可能因编译器而异。例如,#pragma once是一种常见的用法,它防止头文件被多次包含。

  2. #line: 此预处理指令用于改变编译器的行计数器和文件名跟踪。这在预处理器生成的代码或宏中非常有用,以维护正确的行号和文件名跟踪。例如:

#line 10 "new_file.h"

这将使得编译器将接下来的代码行计入"new_file.h"中的第10行。

  1. #pragma pack(): 这个指令用于改变编译器内存对齐的方式。这对于性能优化和跨平台兼容性非常重要。例如,你可以使用#pragma pack(1)来告诉编译器以1字节对齐所有的结构体和联合体 #pragma pack() 来取消设置的结构体默认对齐数。这可以减少内存的使用,但可能会降低性能。

希望这个简短的介绍对你有所帮助!如果你对任何内容有更多的问题,或者需要更多的详细信息,请随时提问!

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

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

相关文章

设计模式:解释器模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

上一篇《责任链模式》 下一篇《设计模式学习顺序》 简介&#xff1a; 解释器模式&#xff0c;它是一种行为型模式&#xff0c;它给定一门语言&#xff0c;定义其文法的一种表示&#xff0c;并定义一个解释器&#…

KDL库在VS2022上配置博客

一、前提准备 1.下载kdl库和kdl所依赖的eigen库&#xff0c; &#xff08;在kdl库中找到FindEigen3可以查看里面所需要的Eigen3库所需要的最低版本&#xff09;下载完成后重命名压缩包为kdl和eigen3之后解压缩文件 2.安装好cmake工具 二、建立build目录 ..\kdl\orocos_kine…

SpringCloud(一) 服务架构的演变及注册RestTemplate实现服务的远程调用

目录 一, 服务架构的演变 1.1 单体架构 1.2 分布式架构 1.3 微服务 1.4 SpringCloud 二, 服务拆分和远程调用 2,1 服务拆分原则 2.2 服务拆分示例 2.3 创建相应数据库 2.4 实现远程调用示例 1, 更改需求 2, 注册RestTemplate实现远程调用 2.5 服务消费者和提供者 一…

Spring的条件注解,一篇文章盘得清清楚楚明明白白

前言 在Spring中&#xff0c;条件注解可根据特定的条件来决定是否创建或配置Bean&#xff0c;这些条件可以基于类、属性、环境等因素。通过使用条件注解&#xff0c;我们可以在Spring容器中更加灵活地管理和控制组件的创建和注入&#xff0c;帮助我们更加灵活地管理和控制Bean…

3.5每日一题(求齐次方程组的特解)

1、判断类型选择方法&#xff1a;看出为齐次方程&#xff08;次幂都一样&#xff09; 2、 化为变量可分离&#xff1b;按变量可分离的方法求出通解&#xff08;此题等式两边同时除以 x &#xff09; 3、把x1&#xff0c;y0带入通解&#xff0c;定常数C&#xff0c;求出特解 …

Android 优质的UI组件汇总

1、RuleView &#xff1a;Android自定义标尺控件(选择身高、体重等) 链接&#xff1a;https://github.com/cStor-cDeep/RuleView 2、DashboardView &#xff1a;Android自定义仪表盘View&#xff0c;仿新旧两版芝麻信用分、炫酷汽车速度仪表盘 链接&#xff1a;https://git…

业务连续性的重要性及关键因素

在今天的竞争激烈的商业环境中&#xff0c;保持业务连续性至关重要。业务连续性是指企业能够在面对各种不可预测的挑战和灾难情况下&#xff0c;保持运营&#xff0c;提供产品和服务&#xff0c;以确保客户满意度和可持续发展。本文将探讨业务连续性的重要性、关键因素和最佳实…

探营云栖大会:蚂蚁集团展出数字人全栈技术,三大AI“机器人”引关注

一年一度的科技盛会云栖大会将于10月31日正式开幕。30日&#xff0c;记者来到云栖大会展区探营&#xff0c;提前打卡今年上新的“黑科技”。 记者在蚂蚁集团展馆看到&#xff0c;超1亿人参与的亚运“数字火炬手”全栈技术首次公开展示&#xff0c;还可体验基于数字人技术的“数…

【工具使用】NPS内网穿透工具介绍

文章目录 前言一、内网穿透二、NPS概述三、NPS原理四、NPS服务器搭建(一)云服务器配置 五、NPS内网穿透演示(一)演示案例一(二)演示案例二 六、NPS内网穿透检测建议(一)流量监控(二)流量协议分析(三)网络行为异常检测 七、NPS内网穿透防范建议(一)阻止或隔离流量(二)更新和强化…

GitHub经常打不开或者访问解决办法

访问慢或无法访问的原因&#xff1a;DNS解析是最为基础的一个环节。由于Github的服务器在全球各地&#xff0c;域名解析所需的时间也会不同&#xff0c;这就导致了在特定地区可能会出现Github无法正常访问的情况。 解决&#xff1a;查询到github对应的IP&#xff0c;然后在host…

Java - JDK演变之路和JDK21新特性

Java - JDK演变之路和JDK21新特性 前言一. JDK演变之路JDK9 新特性&#xff08;2017年9月&#xff09;JDK10 新特性&#xff08;2018年3月&#xff09;JDK11 新特性&#xff08;2018年9月 - LTS版本&#xff09;☆JDK12 新特性&#xff08;2019年3月&#xff09;JDK13 新特性&a…

《低代码指南》——我想将维格云与别的系统打通,自动同步数据,怎么实现?

与其他系统打通的3种形式 ​ 人工复制粘贴:操作难度低,时效性差,适合于少量数据定时更新Excel导入:可追加导入,作难度低,时效性差,适和于定期更新数据API对接:可实现实时数据对接,有一定操作门槛API对接的3种方法​ 维格机器人:可以通过维格机器人直接调用对接系统的…

MES与AGV对接浅谈

昨天分享一些有关数字工厂与立体库的对接经验&#xff0c;随着智能物料技术的越来越成熟&#xff0c;硬件设施成本的下降&#xff0c;很多制造业在工厂规划时已经开始考虑在物料搬运、物料配送等使用无人化的智能搬运机器。今天聊聊有关在智能工厂实施中MES与AGV的对接方式一些…

VM搭建虚拟机(CentOS镜像)

文章目录 VMware下载安装CentOS往下滑&#xff0c;找到alternative downloads向下滑找到Archived Versions进入isos目录点击x86/64选择镜像文件 下载很慢emo然后百度网盘直接下载就好 搭建虚拟机选择cenos下载目录&#xff0c;并选择稍后安装选择Linux&#xff0c;找到cenos7版…

跨域解决方案有哪些?

跨域 因为浏览器出于安全考虑&#xff0c;有同源策略。也就是说&#xff0c;如果协议、域名或者端口有一个不同就是跨域&#xff0c;Ajax 请求会失败。 我们可以通过以下几种常用方法解决跨域的问题 JSONP JSONP 的原理很简单&#xff0c;就是利用 <script> 标签没有…

基于轻量级yolov5n开发构建涵洞场景下洞体墙体缺陷病害检测分割系统

在前文&#xff1a; 《AI助力隧道等洞体类场景下水泥基建缺陷检测&#xff0c;基于DeeplabV3Plus开发构建洞体场景下壁体建筑缺陷分割系统》 我们基于DeepLabv3Plus尝试构建了洞体类建筑缺损病害问题分割系统&#xff0c;本文的核心思想是想要基于yolo这一经典的模型来开发构…

VIRTIO-Virtual IO Based On VPP/DPDK at front

简介 虚拟化技术是云计算的基石&#xff0c;是构建上层弹性计算、弹性存储、弹性网络的基本成份。所谓虚拟化&#xff0c;即对计算所需的资源进行模拟&#xff0c;提供与物理资源一般无二的特性和运行环境。如Qemu将整个VM所需环境进行虚拟化&#xff1a;一个Qemu进程代表一台…

【Vue】初步认识<script setup>语法糖和组合式 API

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ &#x1f6eb; 导读 需求 最近写代码的时候&#xff0c;发现<script setup>这样的代码&#xff0c;没见过&#xff0c;好奇&#xff0c;想知道。 所以就有了这篇文章。 很多文章都说setup是vue3的特权。但是&#xff…

2023.10.29 关于 HashTable 和 ConcurrentHashMap 区别

目录 HashTable ConcurrentHashMap 优化点一 优化点二 优化点三 优化点四 不关键的小区别 HashTable HashMap 和 HashTable 都是常见的哈希表数据结构&#xff0c;用于存储键值对 注意&#xff1a; HashMap 是线程不安全的HashTable 是线程安全的&#xff0c;其关键方法…

MapBox获取点位高程的三种方式

以下提供了三种方法和思路 1&#xff0c;通过mapbox全球dem数据获取高程 这里我们利用了mapbox的tilequery 官网地址在这里 https://docs.mapbox.com/api/maps/tilequery/ 以下是示例代码&#xff0c;这个方式是简单快捷&#xff0c;缺点就是精度不高&#xff0c;大概是以10m…