c语言编译链接

news2024/11/25 22:25:41

目录

目录

前言

一.c语言的编译链接

1.翻译环境

编译阶段可以分为预处理,编译,汇编三个阶段

预处理阶段

编译阶段

词法分析

语法分析

语义分析

汇编阶段 

链接阶段

2.运行环境

二.预处理详解

   #define定义常量

#define定义宏 

宏和函数的对比

#和##运算符

#运算符

##运算符

条件编译 

头文件的包含




前言

本文将了解到c语言是怎么编译怎么链接的,c语言文件是怎么变成能够被计算机识别的文件底层处理。同时还将详细介绍预处理宏的相关知识。以及了解头文件的相关知识,比如#include<stdio.h>与#include"stdio.h"的区别

一.c语言的编译链接

我们都知道计算机是无法直接识别人类语言的,它只能识别机器语言。如果不会外语而要与外国人交谈,通过翻译就可以把我的语言转变成他所能理解的语言。对于c语言来说,也需要翻译才能把高级语言转变成计算机能识别的机器语言,对于c语言来说翻译工作是通过编译器编译来实现的。

C语言的任何一种实现中,存在两个不同的环境,一个是把源代码转变为机器指令的翻译环境,一个是把已经转变过了的机器指令进行运行的运行环境。实际上说就是两个步骤,翻译我要说的话,然后理解了之后去执行我说的话。

1.翻译环境

翻译环境分为编译和链接两个过程,在一个项目中可以有多个.c文件,填满一同构成了一个程序,通过编译(翻译)可以编译出相对应的目标文件.obj(这个是和.c文件一一对应的)。这些被翻译过后的.obj实际上已经是二进制形式了,但是计算机依旧无法直接识别和执行.obj 文件,因为它仅包含了编译后的机器代码,而没有包含操作系统特定的加载和执行代码。链接器负责将.obj 文件与其他必要的文件(如库文件)进行链接,生成可执行文件或共享库,使其能够在操作系统上运行。说白了链接就是把你需要的目标文件全整合到一起去形成一个可以执行的程序,要由链接器(Linker)将多个目标文件和库文件合并成最终的可执行文件,然后才能被计算机识别和执行。

编译阶段可以分为预处理,编译,汇编三个阶段

预处理阶段

在这个阶段会去处理代码里#define以及头文件#include形式的代码,#define是定义宏的预处理指令,宏定义可以将一个标识符与一个文本片段关联起来,当源代码中出现该标识符时,预处理器会将其替换为对应的文本片段。比如 #define MaxSize 100;代码里本来是 arr[MaxSize];实际上就是arr[100],定义成宏可以方便修改,预处理阶段会直接把所有MaxSize还原成原来的100。预处理器会遍历源代码,查找所有的宏调用,并将其展开为宏定义中的文本。同时会删除所有#define将 ,#define 删除"时,意味着将宏定义从源代码中移除,预处理器将不再对该宏进行替换可以获得源代码中所有宏被替换后的文本内容

对于#include预编译指令来说,用于在源代码中包含其他文件的内容。是在预处理阶段将被包含文件的内容插入到指令所在的位置,实际上可以理解为在上面加了#include文件里的代码,只是我们看不见而已,举个例子在一个文件对函数进行定义声明,在另一个文件里进行函数的实现,但是在实现的这个文件里可以用声明文件里定义的变量,就是因为在预处理阶段另一个文件的内容会直接放到当前文件的上方,所以可以直接使用。

同时预处理阶段还会直接把注释的内容直接删除。把代码添加行号和文件名标识,方便后续编译器生成调试信息或保留所有的#pragma的编译器指令,以便后续使用。

编译阶段

在这个阶段会把高级语言转变成汇编语言,通过词法分析,语法分析,语义分析及优化来转变成汇编语言

词法分析

源代码会被分解成一个个的词法单元(Tokens)。词法单元是编程语言中的最小语法单位,包括关键字、标识符、运算符、常量、字符串字面量等

例如,对于源代码中的表达式 int a = 10 + b;,词法分析器可能会生成以下词法单元序列:

  1. 词法单元类型:关键字,内容:int
  2. 词法单元类型:标识符,内容:a
  3. 词法单元类型:运算符,内容:=
  4. 词法单元类型:常量,内容:10
  5. 词法单元类型:运算符,内容:+
  6. 词法单元类型:标识符,内容:b
  7. 词法单元类型:运算符,内容:;         

 词法分析阶段的输出结果将作为下一阶段的输入,例如语法分析器(Parser)将使用词法分析器生成的词法单元序列来构建语法树,进一步分析和理解源代码的结构和语义。

语法分析

语法分析阶段也称为解析器(Parser)阶段,它接收词法分析器生成的词法单元序列,并根据预定义的语法规则验证源代码的语法正确性,并构建抽象语法树(Abstract Syntax Tree,AST)或语法分析树(Parse Tree)。

语法分析器使用上下文无关文法(Context-Free Grammar)来描述语言的语法结构,并通过语法规则进行递归下降、LR分析、LL分析等算法来进行语法分析。它会检查词法单元序列是否符合语法规则,并生成一个结构化的表示形式,以便后续的编译步骤使用

int a = 10 + b语法树如下

语义分析

语义分析阶段对抽象语法树进行进一步的分析,以确定源代码的语义是否合法,并进行类型检查、作用域分析、语义约束检查等操作。

语义分析器会检查变量的声明和使用是否匹配、函数调用的参数是否正确、类型转换是否合法等。它还会处理作用域规则,确保变量和函数在正确的作用域内使用,并进行类型推导和类型检查,以保证源代码的语义正确性。

如果源代码中存在语义错误,语义分析器会产生相应的错误信息,指示出错误的位置和类型

如下是语义标识后的语法树

汇编阶段 

汇编阶段是把汇编语言汇编成机器语言的过程,将汇编代码转变成机器可执行的指令,每一个汇编语句都对应一条机器指令。根据汇编指令和机器指令的对照表一一的进行翻译。

当将汇编代码转换为机器可执行的指令,我可以举一个简单的例子来说明。

假设我们有以下汇编代码:

mov eax, 10
add eax, ebx

这段汇编代码的作用是将寄存器 eax 的值设置为 10,然后将 ebx 的值加到 eax 上。

下面是这段汇编代码转换为机器可执行的指令的示例(使用x86架构):

Opcode     Operands     Explanation
----------------------------------
B8 0A 00 00 00    mov eax, 10    ; 将立即数 10 移动到 eax 寄存器
03 C3             add eax, ebx   ; 将 ebx 寄存器的值加到 eax 寄存器

在这个示例中,每条指令都有一个特定的操作码(Opcode)和操作数(Operands)。操作码表示指令的类型和功能,操作数表示指令的操作对象。在这个例子中,mov 指令使用操作码 B8,并且操作数是立即数 10 和寄存器 eaxadd 指令使用操作码 03,并且操作数是寄存器 ebx 和寄存器 eax

这些指令的执行将根据特定的计算机架构和指令集体系结构进行解释和执行。通过将汇编代码转换为机器指令,计算机可以按照指令的顺序和操作数执行相应的操作,实现程序的功能。

链接阶段

链接其实就是多个目标文件合并成一个可执行文件的过程,主要解决的是一个项目中多文件,多模块之间相互调用的问题。分为静态链接和动态链接两种方式,静态链接将所有代码和数据复制到可执行文件中,而动态链接通过引用动态链接库中的代码和数据来实现。

假设我们有一个项目,包含以下两个源文件和一个头文件:

file1.cpp

#include "file2.h"

void function1() {
    function2();
}

file2.cpp

#include <iostream>

void function2() {
    std::cout << "Hello, World!" << std::endl;
}

file2.h

#ifndef FILE2_H
#define FILE2_H

void function2();

#endif

在这个例子中,file1.cpp 中的 function1 调用了 file2.cpp 中的 function2。为了让 function1 能够正确调用 function2,我们需要进行链接。

在链接过程中,链接器会解析符号引用和符号定义,将 function1 中对 function2 的引用与 function2 的定义进行匹配。在这个例子中,链接器会将 function1 中对 function2 的引用解析为 file2.o 中的 function2 的定义,并将其替换为正确的内存地址。

最终生成的可执行文件可以执行 function1,当调用 function1 时,它会调用 function2,并打印 "Hello, World!"。

通过链接,我们可以将多个文件和模块组合在一起,使它们能够相互调用,并最终生成可执行的程序。这样,我们可以更好地组织和管理大型项目,并实现模块化的开发和代码复用。

2.运行环境

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

二.预处理详解

   #define定义常量

这个都挺熟悉的,就是用自定义的关键字去代替代码要代替的常量

比如这个扫雷初始化和打印的函数,都用到了数组char board[11][11],如果我已经完全写完了所有程序代码,现在要改需求把char board[11][11]改成9X9大小的数组,那么我改完了初始化函数的大小还得去改输出函数的大小。如果这个程序代码有很多函数都用到了这个数组,那么都要找出来改掉。直接#define 定义常量,那么在第一行改了,所有的都会变更,简化成了操作。

也许会有人疑问,#define 常量后面加不加分号呢,加分号和不加分号区别差别大不大

如果#define MaxSize 100加了分号,那么在替换的时候会把分号一块替换进去,如arr[MaxSize][MaxSize]会被替换成arr[100;][100;],不符合数组的语法这样肯定会报错

再如下面一个例子

#include <stdio.h>
#define MaxSize 1000
int main()
{
	int max = 0;
	if (1)
		max = MaxSize;
	else
		max = 0;
}

如果我加上分号的话,直接就报错了,因为这样实际上等价成max=100;;  ,产生了两个分号,肯定会报错

 如果我把原来文本里的分号去掉,然后#define MaxSize加分号其实就不会报错了,因为它把100连同分号一块替换过去。其实就等价于max=100; 

 

所以#define加不加分号其实都可以,但是还是推荐不加分号

#define定义宏 

这个其实与函数类似,都是传参数过去进行一系列操作

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define  add(a,b) a+b
int add1(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 5;
	int b = 5;
	printf("宏处理的结果:%d\n", add(a, b));
	printf("函数处理的结果:%d\n",add1(a,b));
}

宏处理结果和函数结果相同,函数是传参数a,b过去,进行操作之后返回结果。宏也是接受参数,然后把操作之后的结果直接替换。

但是差别也明显,宏适合那种简单一点的操作,如果很复杂的话可能大幅度增加程序长度。调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜⼀筹。
更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之,宏可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏是类型⽆关的 

预处理阶段的操作我们是看不见的,是在底层自动实现的,所以宏是没法调试的,而且可能带来运算符优先级的问题,容易出现错误

在传参的时候,如果参数是表达式,函数传参会直接传表达式的结果,而宏会原模原样传参过去

#include <stdio.h>
#define  square(x) x*x
int square1(int a)
{
	a = a * a;
	return a;
}
int main()
{
	int a = 5;
	int b = 5;
	printf("宏处理的结果:%d\n", square(a+1));
	printf("函数处理的结果:%d\n",square1(a+1));

square传参过去是原模原样传过去,所以是5+1*5+1,先算乘法然后才是加法,所以结果是11

而squeare1函数传参之前会直接把a+1计算出来,把结果传参过去,所以是6*6,结果是36

宏加上括号改变优先级,#define  square(x) (x)*(x)结果也能正确 

宏和函数的对比

属性#define定义宏函数
代码长度每次使用,宏代码都会插到程序中。除了简短的宏外,程序的长度会大幅度增长函数代码只出现一个地方;每次调用函数时,都调用通一个地方的同一份代码
执行速度更快需要调用函数和返回值,更慢一点
操作符优先级如果不加括号邻近操作符的优先级可能会产生不同的结果表达式形式传参时会将结果值传给函数
带有副作用的参数参数可能被替换到宏体的多个位置,如果宏的参数被多次计算,带有副作用的参数求值会产生不可预料的结果函数参数只在传参的时候求值一次,结果容易控制
参数类型宏的参数与类型无关,只要对参数操作是合法的,它就可以使用于任何参数类型函数参数与类型有关,如果类型不同,那么就需要不同的函数
调试不方便调试函数是可以逐语句调试的
递归宏不能递归函数可以递归

#和##运算符

#运算符
#include <stdio.h>

#define Print(x, type) printf("%d is %s", x, #type)

int main() {
    int a = 5;
    Print(a, int);
    return 0;
}

虽然宏可以直接传参类型,但是不能之间用%s去打印type,因为type是标识符还不是字符串,所以用#type字符串化,然后才能用%s打印

再举个例子,如果我有个变量 int a=5; 现在要打印the value of a is 5应该去怎么定义宏,如果是直接#define Print(n) printf("the value of a is%d",n)其实也可以打印出来,但是如果换个变量名b这个打印出来的依旧是the value of a is 5,这样就错了。这时候就需要用#操作符了,将

请注意printf("the value of n is %d",n)这样写里面的n是不会替换成a的,双引号默认不会去替换,会直接默认打印原模原样的双引号内容,所以结果是the value of n is 5。#运算符其实就是一个提示,提示要将宏的参数转换成字符串,所以它会先替换成宏的参数

##运算符

##运算符也是在宏里面用的,所以它必然会伴随着替换宏的参数,它的作用是将两边的符号合成一个符号,比如 type##_max  ##前面的符号会替换成宏的参数,所以结果是int_max

举个例子,一个函数求两个数的较大值,不同的数据类型就得写不同的函数

#include<stdio.h>

int int_max(int x, int y)
{
	return x > y ? x : y;
}
float float_max(float x, float y)
{
	return x > y?x:y;
}

int main()
{
	
	//调⽤函数
	int m = int_max(2, 3);
	printf("%d\n", m);
	float fm = float_max(3.5f, 4.5f);
	printf("%f\n", fm);
	return 0;
}

用宏和##运算符可以简化一点操作 

#include<stdio.h>
//宏定义
#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做函数名

int main()
{
	
	//调⽤函数
	int m = int_max(2, 3);
	printf("%d\n", m);
	float fm = float_max(3.5f, 4.5f);
	printf("%f\n", fm);
	return 0;
}

#undef用于移除一个宏定义

#undef NAME

条件编译 

条件编译是指如果满足条件才去编译以下的句子,如果不满足就不编译,常用于调试性的代码,因为这些代码在最终版本一般都用不上。

一般用法和if语句类似。

#if 常量表达式

//实际需要条件编译的语句

#endif结尾的标志

同样也可以多分支条件编译,通过#elif和#else来实现

#include <stdio.h>

#define OPTION_A 1
#define OPTION_B 2
#define OPTION_C 3

#define OPTION OPTION_A

int main() {
    #if OPTION == OPTION_A
        printf("Option A is selected.\n");
    #elif OPTION == OPTION_B
        printf("Option B is selected.\n");
    #elif OPTION == OPTION_C
        printf("Option C is selected.\n");
    #else
        printf("Invalid option.\n");
    #endif

    return 0;
}

上面那种是通过表达式来判断条件编译,还可以通过判断是否被定义来进行条件编译

一般是通过#ifdef(xxx)或者#ifndef(xxxx)来实现条件编译的

#ifdef等价于#if defined(xxx)     #ifndef(xxxx)等价于#if !defined(xxxx)

#include <stdio.h>

#define OPTION_A
#define OPTION_B

int main() {
    #ifdef OPTION_A
        printf("Option A is defined.\n");

        #ifdef OPTION_B
            printf("Option B is defined.\n");
        #else
            printf("Option B is not defined.\n");
        #endif

    #else
        printf("Option A is not defined.\n");
    #endif

    return 0;
}

在上面的代码中,我们定义了两个选项 OPTION_A 和 OPTION_B。首先,我们使用 #ifdef 检查 OPTION_A 是否定义。如果定义了 OPTION_A,则输出"Option A is defined.",并继续进入嵌套的条件编译部分。

在嵌套的部分中,我们使用 #ifdef 检查 OPTION_B 是否定义。如果定义了 OPTION_B,则输出"Option B is defined.",否则输出"Option B is not defined."。

如果 OPTION_A 没有定义,将跳过嵌套的条件编译部分,直接执行 #else 后面的代码,输出"Option A is not defined."。

通过嵌套使用 #ifdef#ifndef 和 #if,可以根据多个条件进行更复杂的条件编译。这样可以根据不同的条件组合编译不同的代码块,以满足更灵活的需求。

头文件的包含

头文件的包含分为include<stdio.h>和#include"stdio.h"两种

前者是库文件包含,在查找这个文件时会直接去标准路径下去查找,如果找不到就提示编译错误

后者是本地文件包含,查找文件时会现在源文件所在的目录下查找,如果没找到会像查找库文件一样去标准位置查找头文件,如果还没找到就提示编译错误

库文件包含也可以写成双引号本地文件查找的形式,只是查找效率变低了

 #include ?指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的
地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。
⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤。

要么就干脆不写,要么就条件编译#ifdef判断一下是否已经被包含了,如果没被包含才会去执行包含的语句

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

或者直接

#pragma onc

#pragma once 是一种预处理指令,用于确保头文件只被编译一次,以防止重复包含。当编译器遇到 #pragma once 时,它会检查当前的文件是否已经被包含,如果是,则跳过后续的包含操作

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

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

相关文章

ETL概念

ETL ETLELT 技术原理ETL 模式应用场景常见工具ETL未来发展方向 ETL 在BI项目中ETL会花掉整个项目至少1/3的时间&#xff0c; ETL设计的好坏直接关接到BI项目的成败。ETL(Extract-Transform-Load) : 用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&…

VS2019配置Reshaper

参考VisualStudio神级插件。一JetBrains Resharpera2023.3.2学习版 拉到下面下载主程序&#xff0c;下载就点下一步就好了 然后不要打开VS2019&#xff0c;再按上面的地址下载学习补丁&#xff0c;下载好如图&#xff1a; 查看ReadMe 我们已经装好Reshaper了&#xff0c;然后点…

gmpy2与一些python库在vscode下没有自动补全的一种缓解方案

经过一定的研究&#xff0c;该问题的原因初步判断是gmpy2这个库天生没有把补全的函数doc说明附在pip包中。且因gmpy2是由C编译而来&#xff0c;以dll或so的形式作为动态链接库给python调用&#xff0c;这意味着无法从源码薅到可用的源码注释。 接下来先讲解决方案&#xff0c;再…

el-table样式错乱解决方案

bug&#xff1a; 图片的椭圆框住的地方&#xff0c;在页面放大缩小之后就对不齐了。 原因&#xff1a; 主要原因是当你对页面放大缩小的时候&#xff0c;页面进行了重构&#xff0c;页面的宽高及样式进行了变化&#xff0c;但是在这个更新的过程中&#xff0c;table的反应并没…

Python爬虫之协程

Python爬虫之协程 为什么要用协程 协程声明 await aiohttp aiofiles 案例修改 案例完整代码 为什么要用协程 轻量级&#xff1a;协程是轻量级的执行单元&#xff0c;可以在同一个线程中并发执行。相比于多线程或多进程&#xff0c;创建和切换协程的开销更小。高效利用资源&…

78.网游逆向分析与插件开发-背包的获取-背包类的C++还原与获取物品名称

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;77.网游逆向分析与插件开发-背包的获取-物品类的C还原-CSDN博客 码云地址&#xff08;ui显示角色数据 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&…

影响ETL数据传输性能的9大因素及主流ETL应对策略

前言 现在很多企业在选择ETL工具时都特别关注ETL的数据传输性能&#xff0c;而有很多开源ETL工具都说自已是性能如何如何快&#xff0c;而事实上数据传输性能是不是这些工具说的那样快呢&#xff1f; 数据传输性能受制于哪些因素呢&#xff1f;企业在自身数据库性能受制的情况…

Redis(七)复制

文章目录 是什么功能配置配主库不配从库权限细节 案例配置文件修改 一主二仆固定配置文件主从问题命令操作手动指定 薪火相传反客为主复制原理和工作流程存在问题 是什么 https://redis.io/docs/management/replication/ 就是主从复制&#xff0c;master以写为主&#xff0c;S…

农业气象站的工作原理!

TH-NQ8农业气象站的工作原理是基于传感器技术、数据采集技术、数据传输技术和数据处理技术等多个环节相互配合而实现的。 首先&#xff0c;农业气象站通过各种传感器对不同的气象指标进行实时监测和记录。传感器的种类有很多&#xff0c;包括温度传感器、湿度传感器、风速传感…

3dmax渲不出模型是什么原因---模大狮模型网

3DMax无法渲染模型可能有多种原因。以下是一些常见的问题和解决方法&#xff1a; 材质设置错误&#xff1a;检查模型的材质设置是否正确&#xff0c;包括纹理贴图的路径、UV映射是否正确等。确保材质的属性设置正确&#xff0c;如颜色、反射率、透明度等。 灯光设置问题&#…

vue3中form对象无法赋值问题

加上 async await还是不行 有时候对象的值死活赋不上值&#xff0c;这时候可以看下赋值的对象变量名是否和页面组件中的ref相同&#xff0c;如果存在相同&#xff0c;则参照以下解决方案&#xff1a; 问题定位&#xff1a;setup 中抛出的变量不能与页面组件中的 ref 重复 解决…

第40集《佛法修学概要》

请大家打开讲义第一百零六页。我们讲到大乘的果位。大乘佛法的修学跟小乘最大的差别&#xff0c;主要在于一句话&#xff0c;就是大乘佛法是一种“称性起修&#xff0c;全修在性”。大乘佛法的功德第一个“称性”&#xff0c;这个“称”就是随顺。我们一念明了的心&#xff0c;…

Java 异常及处理|Error、Throwable、Exception

目录 一、Java 异常概述 二、异常类 1、Throwable&#xff1a; 1.1 Throwable 类的常用方法包括&#xff1a; 1.2 创建和抛出 Throwable 2、Error&#xff1a; 2.1 Error 与异常处理的关系 3、Exception&#xff1a; 3.1 如何处理 Exception 方式1 、往外抛&#xff1…

Qt6入门教程 10:菜单栏、工具栏和状态栏

目录 一.菜单栏 1.Qt Designer 1.1添加菜单和菜单项 1.2添加二级菜单 1.3给菜单和菜单项添加图标 1.4给菜单项添加功能 2.纯手写 二.工具栏 1.Qt Designer 1.1添加工具栏按钮 1.2工具栏的几个重要属性 2.纯手写 三.状态栏 1.Qt Designer 2.纯手写 用Qt Creator新…

Unity - 简单音频视频

“Test_04” 音频 使用AudioTest脚本控制Audio Source组件&#xff0c;在脚本中声明"music"和"se"之后&#xff0c;在unity中需要将音频资源拖拽到对应位置。 AudioTest public class AudioTest : MonoBehaviour {// 声明音频// AudioClippublic AudioC…

Raspbian安装云台

Raspbian安装云台 1. 源由2. 选型3. 组装4. 调试4.1 python3-print问题4.2 python函数入参类型错误4.3 缺少mjpg-streamer可执行文件4.4 缺失编译头文件和库4.5 python库缺失4.6 图像无法显示&#xff0c;但libcamera-jpeg测试正常4.7 异常IOCTL报错4.8 Git问题 5. 效果5.1 WEB…

CSS文本外观属性内容(知识点1)

知识引入 使用HTML可以对文本外观进行简单的控制&#xff0c;但是效果并不理想&#xff0c;为此CSS提供了一系列的文本外观样式属性&#xff0c;具体如下。 color:文本颜色 color属性用于定义文本的颜色&#xff0c;其取值方式有以下三种。 &#xff08;1&#xff09;预定义…

springboot中一些注解

springboot中一些注解 1:项目启动时会去扫描启动的注解&#xff0c;一般是启动时就想要被加载的方法&#xff1a; 2:springBoot中MSApplication启动类的一些其他注解&#xff1a; EnableAsync&#xff1a;这是一个Spring框架的注解&#xff0c;它用于开启方法异步调用的功能。当…

RTDETR 引入 UniRepLKNet:用于音频、视频、点云、时间序列和图像识别的通用感知大卷积神经网络 | DRepConv

大卷积神经网络(ConvNets)近来受到了广泛研究关注,但存在两个未解决且需要进一步研究的关键问题。1)现有大卷积神经网络的架构主要遵循传统ConvNets或变压器的设计原则,而针对大卷积神经网络的架构设计仍未得到解决。2)随着变压器在多个领域的主导地位,有待研究ConvNets…

Linux 一键部署influxd2-telegraf

influxd2前言 influxd2 是 InfluxDB 2.x 版本的后台进程,是一个开源的时序数据库平台,用于存储、查询和可视化时间序列数据。它提供了一个强大的查询语言和 API,可以快速而轻松地处理大量的高性能时序数据。 telegraf 是一个开源的代理程序,它可以收集、处理和传输各种不…