【C语言】程序环境和预编译

news2024/12/29 9:07:24

如若这一生注定磨难,自由与真我千金不换
在这里插入图片描述

文章目录

  • 一、程序的翻译环境和运行环境
    • 1.翻译环境(编译(预编译、编译、汇编)+链接)
    • 2.运行环境(程序入口main 到 程序终止)
  • 二、预处理(预编译)阶段展开讲解
    • 1.C语言内置预定义符号
    • 2.#define定义标识符
    • 3.#define定义宏(带有参数)
    • 4.#define所定义的标识符和宏的替换规则
    • 5.#和##的作用(替换为字符串 && 合并两边宏参数)
    • 6.带副作用的宏参数(a+1 &&a++)
    • 7.宏和函数对比+两者命名的约定
    • 8.#undef(有点儿鸡肋)
  • 三、命令行定义(不在代码中定义符号)
  • 四、条件编译(随心所欲的编译代码)
  • 五、文件包含(< >包含 && " "包含)
  • 六、offsetof宏的实现
  • 七、交换整数二进制位的奇数位和偶数位



一、程序的翻译环境和运行环境

任何一个C语言程序在执行时,都会存在两个不同的环境。
第一个是翻译环境:在这个环境中C程序的源代码会被转换为可执行的机器指令(二进制指令)
第二个是执行环境:它用于实际执行代码

1.翻译环境(编译(预编译、编译、汇编)+链接)

翻译环境

a.在一个工程当中,由于需求的多种多样,程序猿往往要写出很多很多的代码,并且不同的程序猿所在的小组需要完成的任务也不一样,那么在这个工程当中就一定会出现很多的源文件,等到所有小组完成任务之后产生这么一个.exe的可执行程序文件。

b.但从代码到可执行程序的过程中要经过的工作可是太多了,总不能从代码直接变出来一个可执行程序吧,我们下面就详细的介绍翻译环境中,程序从代码开始,要经历什么养的步骤。

在这里插入图片描述

1.预编译:gcc test.c -E -o test.i
我们在linux下写了一段代码,接下来我们就通过gcc编译器,将这个代码细分为3个步骤执行起来,直到产生目标文件test.o。
在这里插入图片描述
在预处理源文件之后,预处理之后的内容默认显示到我们的显示屏上了,并且我们可以发现#include <stdio.h>这条指令没有了,取而代之的是800多行的代码,只不过我们看不懂这个代码罢了,而且我们还发现注释的内容被删除了,#define所定义的符号被替换,并且替换的同时,符号也被删除了。

在这里插入图片描述

其实#include <stdio.h>被800多行代码替代的原因就是,stdio.h这个文件被展开在我们的test.c源文件里面,如果不相信的话,我们可以查找一下stdio.h这个文件内容是否和test.c源文件中被替换的#include <stdio.h>之后的内容相同,这便可以证明上面我们所说的话。
由图片便可以证明stdio.h这个文件在预编译的时候,确实被展开在我们的test.c源文件里面。
在这里插入图片描述

所以在预编译阶段,编译器做的事情有以下:
在这里插入图片描述

2.编译:gcc test.i -S

gcc编译代码过后会产生一个test.s的文件,test.s文件中的内容其实就是预编译、编译阶段过后产生的文件。

在这里插入图片描述
下面我们通过vim来查看一下编译过后产生的test.s文件会是什么样的呢?
由图片我们可以看到,编译过后,原来的代码已经被转为汇编代码了。
在这里插入图片描述

代码转为汇编其实还需要语法分析、词法分析、符号汇总、语义分析等步骤才可完全转换为汇编代码。
我们重点来说一下符号汇总,这个非常的重要,后面汇编阶段产生目标文件,链接阶段产生可执行程序都会用到。

符号汇总:将全局域里面的变量名,函数名等等都汇总起来。
在这里插入图片描述
所以在编译到汇编这个阶段,编译器做的事情有以下:
在这里插入图片描述

3.汇编:gcc test.s -c
汇编阶段过后,汇编代码就会被转成机器指令或称之为二进制指令,这些指令会存到心心念念的目标文件.obj中。
接下来还是用gcc继续编译下一个阶段,并用vim查看一下test.o里面的内容。
在这里插入图片描述
通过vim打开test.o可以查看到其中的内容,当然我们肯定啥都看不懂,因为这个文件已经不是文本文件,而是变成了一个二进制文件,只有机器能读得懂他。
在这里插入图片描述

在汇编阶段还有一件非常重要的事情就是形成符号表,在之前的编译阶段,编译器已经给我们把符号都汇总起来了,现在在汇编阶段我们要将汇总的符号以及每个符号所在的地址粘合在一起,形成符号表。

在这里插入图片描述

综上所述,在汇编阶段,编译器做的事情如下:

在这里插入图片描述

4.链接:符号表的合并(很重要)
前面编译(宏观)阶段生成的.o目标文件,例如add.o,test.o等目标文件在Linux下有一种格式,叫做elf格式,而且.exe这样的可执行程序文件也是elf格式的,所以在链接期间,编译器会做一个准备工作就是合并段表,将相同格式的文件合并,汇总到.exe可执行程序文件当中。
在这里插入图片描述

链接期间还要做一件事情就是,符号表的合并和重定位,这个工作就要利用到我们前面汇编阶段产生的符号表了,最终的可执行程序文件.exe肯定只有一个符号表,这个符号表就是我们之前各个目标文件中的符号表合并而来的。

合并的符号表是非常重要的,其中全局域中的变量、函数、结构体等等的地址都会在这个合并之后的符号表,我们可执行程序中能否顺利的使用这些不同文件里面的东西,都取决于合并符号表中是否存放他们的有效地址。
如果是:链接阶段不会产生问题,可以顺利的产生可执行程序文件.exe
如果不是:链接阶段在使用某个函数或其他东西时,发现这个地址是无效的,那么在链接阶段就会产生错误。
在这里插入图片描述
下面的错误其实就是典型的链接错误,test.c产生的目标文件test.o中的符号表中存放的就是Add函数的无效地址,所以在链接期间编译器就会报链接错误。
在这里插入图片描述

合并符号表就是为了让我们在链接期间能够跨文件,通过符号表中存放的有效地址找到我们所需要的东西,使得各个文件互相关联,不在是独立的个体,更好的解决项目的多种需求

2.运行环境(程序入口main 到 程序终止)

程序执行过程:
1.程序必须载入内存中。
在有操作系统的环境中:一般这个由操作系统完成。在独立的环境(没有OS环境)下,程序的载入必须由手工安排,也可能是通过可执行代码植入只读内存来完成。

2.开始执行程序:
开始调用main函数 (程序的入口)

3.开始执行程序代码:
这个时候程序将使用一个运行时堆栈(stack),也就是函数栈帧,来存储函数的局部变量和返回地址。(运行时堆栈 = = 函数栈帧)
程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值(声明周期长)。

4.终止程序:
正常终止main函数,也有可能是意外终止。

二、预处理(预编译)阶段展开讲解

1.C语言内置预定义符号

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

int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("File:%s line=%d date:%s time:%s i=%d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	return 0;
}

我们可以利用C语言的内置符号,将代码所在的文件位置,修改此文件的日期,时间,代码所在的行号等等都输出到终端上面,这就是内置符号,我们可以直接拿来用。

在这里插入图片描述
一般情况下,这种内置符号在写日志的时候,会有一些用途,例如我可以用文件操作,将一系列的信息存到一个log.txt文件当中。

int main()
{
	int i = 0;
	FILE* pf = fopen("log.txt", "w");
	if (pf == NULL)
	{
		return 1;
	}
	for (i = 0; i < 10; i++)
	{
		printf("File:%s line=%d date:%s time:%s i=%d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

文件操作结束后,我们便可以在log.txt文件当中,看到我们刚刚输入到文件的信息了。
在这里插入图片描述

第五条内置符号__STDC__ 可以检测一下我们的编译器是否严格遵循ANSI C标准。

下面我们在vs和gcc两个编译器中测试到,vs是不支持的,gcc顺利的输出了结果1,也就说明他是严格支持ANSI C标准的。

vs不支持ANSI C 标准
在这里插入图片描述
gcc编译器是遵循ANSI C标准的
在这里插入图片描述

如果有某些语法问题,vs和gcc两个平台是不一样的时候,以gcc编译器为标准。
因为gcc编译器是严格符合ANSI C标准的

2.#define定义标识符

语法:#define name stuff

#define MAX 1000
#define STR "hello wyn"
#define print printf("hello wyn\n")
//不要加分号,因为他是纯粹的替换

#define DEBUG_PRINT printf("file:%s\tline:%d\tdate:%s\ttime:%s\n",\
__FILE__,\
__LINE__,\
__DATE__,\
__TIME__)

int main()
{
	printf("%s\n", STR);
	printf("%d\n", MAX);
	print;
	DEBUG_PRINT;
	return 0;
}

1.在用#define定义标识符的时候,我们不要加;分号,这很容易导致下面使用标识符时,发生语法错误。
2.如果定义的名字过长,可以利用反斜杠\来当作续行符
3.额外的一个小知识点,vs上可以利用工程中的属性–>预处理器–>预处理到文件–>test.i文件,我们便可以在文件中查找到预处理之后的代码

3.#define定义宏(带有参数)

#define除可以用来定义表示符外,还可以定义宏,与标识符不同的是,在定义宏时,#define机制允许我们将参数替换到文本中,这样的实现我们称之为宏。

宏的声明方式:#define name(parament-list) stuff,name里面是一个参数表,这些参数会出现在stuff内容当中,等到我们使用宏的时候,stuff中的参数就会被直接替换掉。

#define SQUARE(X) X*X
#define SQUARE(X) (X)*(X)//修改之后的定义宏

int main()
{
	int r = SQUARE(5);
	printf("%d\n", r);

	r = SQUARE(5+1);//你以为答案是6,可惜答案是11
	// 5 + 1 * 5 + 1
	printf("%d\n", r);

	return 0;
}

#define DOUBLE(X) (X)+(X)
#define DOUBLE(X) ((X)+(X))//定义宏的时候,不要吝啬括号

int main()
{
	int r = DOUBLE(3 * 2);
	printf("%d\n", r);

	r = 10 * DOUBLE(3);//你以为答案是60,可惜答案是33
	//10*(3)+(3)
	//用修改之后的宏,替换后就是10*((3)+(3)),就是你想要的答案60
	printf("%d\n", r);

	return 0;
}

值得注意的是,在使用宏的时候,他是在预编译阶段进行替换的,编译器可不会管你参数之间有什么运算符之类的东西,编译器只管替换,并且替换之后,还会把我们定义的宏给删除掉。

所以在使用宏的时候,难免就会出一些运算上的问题,为了避免产生不必要的麻烦,大家在定义宏的时候,不要吝啬我们的括号,它可以给我们省去许多在运算值上面所产生的问题。

4.#define所定义的标识符和宏的替换规则

a. 在调用宏时,首先对宏参数进行检查,看看是否包含由#define定义的标识符。如果有,标识符首先会被替换掉。

例如,我们在使用宏DOUBLE(X)时,传的参数中含有上面定义好的标识符,那编译器在预编译阶段会首先将这个标识符用100给替换掉.

#define M 100
#define DOUBLE(X) ((X)+(X))
int main()
{
	"M";//这些常量字符串如果和宏或标识符重名,预编译阶段是不会被替换的。
	"DOUBLE(3)";//都不会被替换
	DOUBLE(M + 2);
	//替换
	//((100+2)+(100+2))
	return 0;
}

b. 代码中使用宏的地方,会被我们定义宏时的替换文本给替换掉。

例如上面的代码,预编译结束之后,DOUBLE(M+2)会被替换为((100+2)+(100+2))

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

注意:
预处理器在搜索#define所定义的标识符或宏时,字符串常量的内容是不会被搜索到的

例子可见上面代码的注释部分

5.#和##的作用(替换为字符串 && 合并两边宏参数)

1.#的作用:并不是宏参数的单纯替换,而是替换为带有宏参数的字符串

官方定义: ANSI C 会自动将相邻的两个字符串常量连接,如果它们之间没有逗号隔开的话。

//我们先来看这样一段代码,如果你运行后,会发现结果其实是一模一样的,
//所以字符串其实是具有自动链接的特点的
int main()
{
	printf("hello wyn\n");
	printf("hello ""wyn\n");

	return 0;
}

有了上面知识的铺垫,我们用一个需求来引出#的作用

我们想要利用一个宏来同时输出下面的the value of a is …和the value of b is …这些内容,值得注意的是,函数是无法完成这样的功能的,函数输出的字符串中的n不能变成我们想要传的a或b的值,它只能是一个固定的值。

这个时候就需要#和宏来实现了。

void print(int n)
{
	printf("the value of n is %d\n", n);//传过来的是一个参数怎么完成下面那样的功能呢?
	//这时候就要用到我们所学的宏了。
}
int main()
{
	int a = 10;
	printf("the value of a is %d\n", a);
	int b = 20;
	printf("the value of a is %d\n", b);
	return 0;
}

这样我们就可以利用字符串自动连接的特点以及#+宏参数替换为字符串等等,来实现上面的需求。
预编译阶段过后,PRINT(a)—>printf(“the value of a is %d\n”,a)和PRINT(b)—>printf(“the value of b is %d\n”,b);自然而然我们的需求就会被解决了

#define PRINT(N) printf("the value of " #N " is %d\n",N)
//#N会被预处理为"N"也就是被处理为字符串,然后替换。
int main()
{
	int a = 10;
	PRINT(a);
	int b = 20;
	PRINT(b);
	return 0;
}

2.##的作用:合并左右两边的宏参数
预编译阶段过后,宏就会被替换为lovewyn,也就是word与name两个宏参数进行合并

#define CAT(word,name) word##name
int main()
{
	int lovewyn= 100;
	printf("%d\n", CAT(love, wyn));
	return 0;
}

6.带副作用的宏参数(a+1 &&a++)

由于a++会自增,所以在预编译阶段宏参数进行替换时,会产生副作用,所以我们尽量使用a+1这样不带有副作用的宏参数,因为如果一旦宏参数过多,宏的内容过大,在替换时,我们极大概率是不能快速分析出宏替换后的答案的,所以建议大家不要使用带有副作用的宏参数。

宏预编译阶段替换后的结果为((a++) > (b++) ? (a++) : (b++)),大家可自行分析答案。

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

int main()
{
	int a = 5, b = 4;
	int m = MAX(a++, b++);

	printf("m=%d\n", m);
	printf("a=%d b=%d\n", a, b);

    答案:m=6,a=7,b=5
	return 0;
}

7.宏和函数对比+两者命名的约定

一、宏的优点:
a.宏比函数在程序运行速度和性能开销方面更好一些。

宏通常被应用于执行简单的计算,例如求出两个数的最大值,等等

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

不使用函数来完成这样简单的计算是因为调用函数代价太大,在预编译、编译、汇编、链接等阶段,函数一直都要参与,而宏只需要参与预编译阶段即可,代价非常小,所以我们选择用宏来实现这些简单的计算。

b.宏是与类型无关的,它只负责替换

函数的参数是必须有类型的,所以我们在给函数传参时,必须考虑类型,但宏根本不需要考虑类型,整型,浮点型,长整型都可以作为我们的宏参数,但函数必须考虑这些问题。

c.宏的参数中可以直接出现类型

例如下面的代码,宏参数中的type允许出现数据的类型

#define MALLOC(num,type) (type*)malloc(sizeof(type)*(num))
int main()
{
	MALLOC(10, int);//如果想用malloc开辟空间,我们可以直接使用宏
	return 0;
}

二、宏的缺点:
a.宏较长的话,可能大幅增加程序的长度

如果代码中出现多次使用宏的情况,并且宏的内容是较长的话,那极有可能增加我们程序的长度,不容易走读代码和调试。

b.宏是无法调试的

宏在预编译阶段就已经完成替换了,并且#define定义的所有东西都会在预编译阶段被删除的干干净净,而当我们开始调试时,宏的内容已经被替换的面目全非了。
我们肉眼看到的代码和实际的代码已经是不同了,所以我们无法进行调试的工作,因为我们看到的已经不是实际的代码了。

c.宏由于类型无关,也就导致它不够严谨

d.宏可能会带来运算符优先级的问题,容易导致程序出现错误

如果我们定义宏时,括号使用的不到位,在替换时就很有可能出现错误,但函数是不会存在这样的问题的

e.宏的参数可能带有副作用&&宏不可以递归

三、命名的约定:

驼峰法命名函数,全部大写命名宏
当然也不一定宏必须全大写,只不过我们约定俗成全大写,例如下面两个虽然不是全大写,但他们在某些地方的的确确就是宏。

offsetof – 宏 getchar – 宏

8.#undef(有点儿鸡肋)

我们可以使用#define来定义宏,也可以使用#undef来取消我们的宏定义

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

三、命令行定义(不在代码中定义符号)

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

我们写了一段代码,但是sz的长度是未知的,自然代码编译也无法通过,这个时候我们可以采用命令行定义的方式,给出sz的大小
在这里插入图片描述
在编译test.c时,我们利用-D选项给出了sz的定义。允许后,就打印出了0到99的数字。

在这里插入图片描述

四、条件编译(随心所欲的编译代码)

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

比如说为了检测代码是否正确,我们有时会选择写一些调试性的代码,这些代码删除了有些可惜,保留下来又很碍事,所以我们可以选择性的编译。

满足条件我们就让这条指令参与代码的编译,不满足条件就让这条代码不参与编译。

#include <stdio.h>
#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10];
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__ //这个地方判断为真endif和ifdef之间的语句才会参与编译,否则不参与编译
		printf("%d ", arr[i]);
#endif
	}
	return 0;
}

常见的条件编译指令:

a.单分支条件编译

#if 常量表达式(不可以含有变量哦)
//…
#endif
//常量表达式由预处理器求值。

int main()
{
#if 2==3 //逻辑表达式,判断为true,参与编译,判断为false,不参与编译。
	printf("hehe");
#endif
}

b. 多分支条件编译

#if 常量表达式
//…
#elif 常量表达式
//…
#else
//…
#endif

#define M 3
int main()
{
#if M<5
	printf("hehe\n");//预处理阶段这些预定义的东西都会被删除
#elif M==5
	printf("haha\n");
#else M<5
	printf("heihei\n");
#endif
	return 0;
}

我们可以看到预编译之后的代码,所有的预定义符号都被删除掉了。
在这里插入图片描述

c.判断是否被定义

条件编译还可以用来判断某些标识符是否被定义。
例如下面代码,如果MAX被定义我们可以让编译器输出一个语句,如果没有定义我们也可以让它输出语句,这完全取决于我们的需求,我们可以控制是否编译的条件。

#define MAX 1000
int main()
{
#if defined MAX
	printf("max\n");
#endif

#if !defined MAX//!反逻辑操作符,入伏哦没定义MAX,我们输出max语句
	printf("max\n");
#endif
//上下两段代码表达的意思都是相同的,只是写法上有些不同而已,随便选一种写法即可。
#ifdef MAX
	printf("max\n");
#endif

#ifndef MAX//反逻辑的另一种写法#ifndef
	printf("max\n");
#endif
	return 0;
}

d.嵌套指令

在实际当中,嵌套指令用的还是比较多的,这其实和我们以前学的分支语句是比较相似的,这里也就不再细致的讲解了。

#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

我们可以看一下stdio.h文件中的源码,嵌套式的条件编译指令用的还是非常多的,所以我们要掌握并且了解条件编译,以后的工作中可能会频繁的用到。

在这里插入图片描述

五、文件包含(< >包含 && " "包含)

头文件如果被包含多次的话,会出现代码冗余的这样一种现象,如果你的头文件有个1000行代码,我们一不小心在test.c文件中包含多了,不小心的厉害了,那可就完了。
我们的test.c文件在预编译的时候,就会展开你刚刚包含的头文件,所以test.c文件一下子就多了5000多行代码,这给我们程序可带来了不小的开销啊。

由于我们注释的代码过多,test.i文件中代码和头文件相隔太大了,我截图没办法给大家截全,但是我们只要知道,头文件被包含多次,在一个大型的工程中,还是一个不容忽视的错误的。
在这里插入图片描述

解决这样的问题有两种办法:
a.利用条件编译指令
利用条件编译指令,让我们的头文件代码只会参与编译一次,即可解决问题。

如果没有定义标识符__TEST_H,下面语句会参与编译。等到第二次想要再包含头文件时,__TEST_H已经被定义过了,所以下面语句就不会再次参与编译了,这也就变相地实现了test.h文件只会被包含一次的效果。

//防止头文件被重复多次包含
#ifndef __TEST_H
#define __TEST_H
int Add(int x, int y);
#endif

b.利用预处理指令

再头文件中包含一个预处理指令#pragma once 也可以解决头文件被重复包含的问题

在这里插入图片描述

还有一个要补充的知识点:

<>和"“两种文件包含方式,其实就是在查找的方式上不同。
1.<>直接去include的库目录下去查找我们的头文件
2.”"先去代码所在的路径下面查找,如果找不到在去库目录下查找

六、offsetof宏的实现

写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明

这个题还是比较简单的,只要我们结构体的地址从0开始,那么后面每个结构体成员的地址默认其实就是他们相对于首地址的偏移量了。

所以我们只要将数字0强制转换成结构体的地址,也就是struct S*结构体类型的指针,我们便可以通过这个指针找到所有结构体的成语,然后我们对成员变量进行取地址操作符,这样我们就拿到了每个成员的地址,为了输出整数形式的偏移量,我们再把成员变量的地址强转成size_t就好了。

#include <stdio.h>
#include <stddef.h>

struct S
{
	char c1;
	int i;
	char c2;
};
#define OFFSETOF(type,M_name) (size_t)&((type*)0)->M_name //如果地址从0开始,那么地址强制类型转换后就是成员的偏移量
//默认对齐数是8,取较小的作为对齐数
int main()
{
	struct S s = { 0 };
	printf("%d\n", OFFSETOF(struct S, c1));
	printf("%d\n", OFFSETOF(struct S, i));
	printf("%d\n", OFFSETOF(struct S, c2));//总共占了9个字节,但得是4的倍数,所以结构体大小是12字节
	printf("%d", sizeof(struct S));//答案就是12

	//printf("%d\n", offsetof(struct S, c1));
	//printf("%d\n", offsetof(struct S, i));
	//printf("%d\n", offsetof(struct S, c2));//总共占了9个字节,但得是4的倍数,所以结构体大小是12字节
	//printf("%d", sizeof(struct S));//答案就是12
	return 0;
}

七、交换整数二进制位的奇数位和偶数位

我们利用1和0或1按位与还是它本身的特点,分别拿出这个整数的偶数部分的二进制位并且向右移动一个比特位,再拿出这个整数的奇数部分的二进制位向左移动一个比特位,最后重新加起来就可以了,这样就交换了整数的奇数位和偶数位。

#include <stdio.h>

#define CHANGE(N) ((N&0xaaaaaaaa)>>1)+((N&0x55555555)<<1)
int main()
{
	int num = 0;
	scanf("%d", &num);
	printf("%d", CHANGE(num));
}

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

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

相关文章

SpringMVC(四)域对象共享数据(干货、超详细)

【环境说明】 SpringMVC项目中使用的是thymeleaf视图解析器 <!-- Spring和thymeleaf的整合--><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId><version>3.0.12.RELEASE</version>&l…

网络爬虫数据解析的四种方式之XPath

文章目录前言四种数据解析方式数据解析之XPathXPath介绍在浏览器中安装XPath helper插件XPath使用方法通过浏览器测试插件是否安装成功通过python代码实现用XPath来解析数据前言 快期末了&#xff0c;有个数据挖掘的大作业需要用到python的相关知识&#xff08;这太难为我这个以…

C语言基础—数据类型和规范

基本的数据类型和规范 C语言基础—数据类型和规范&#x1f51c; 首先 C 语言所允许的合法符❗ 数据类型和关键字图示❗❗ string 不是C语言关键字&#x1f914; 关于关键字更多的参考⚛ 常量与变量常量定义和分类指数表示法字符常量的转义字符变量定义❗变量(标识符)的命名规范…

『NLP学习笔记』长序列预测论文LTSF-Linear解读

长序列预测论文LTSF-Linear解读 文章目录一. 引言二. 直接多步(DMS)和迭代多步(IMS)的对比三. 基于变压器的Transformer解决方案3.1. 预处理(preprocessing)3.2. 词向量(embedding)3.3. 编码(encoder)3.4. 解码(encoder)四. 一个简单得令人尴尬的Baseline五. 代码解读5.1. 数据…

MySQL字符串索引创建方案

字符串字段添加索引 MySQL是支持前缀索引的&#xff0c;也就是说&#xff0c;你可以定义字符串的一部分作为索引。默认地&#xff0c;如果你创建索引的语句不指定前端长度&#xff0c;那么索引就会包含整个字符串。 # 包含整个字符串 alter table table_name add index index…

liteos连接器脚本隐藏的指针问题

一,数据拷贝引起的指针问题 大家想一下,一个指针指向的内存地址处的数据,假设拷贝到了另外一个地方,那么这个时候我们的指针还是指向原来的位置,那么就会导致问题。为什么提出这个问题呢?因为我们前面讲到了liteos的data段的拷贝,那么对于data段的访问的指针是不是也要…

供应Pyrene-PEG-Biotin,Biotin-PEG-Pyrene,芘丁酸-聚乙二醇-生物素

一&#xff1a;产品描述 1、名称 英文&#xff1a;Pyrene-PEG-Biotin&#xff0c;Biotin-PEG-Pyrene 中文&#xff1a;芘丁酸-乙二醇-生物素 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Biotin PEG Pyrene PEG 4、分子量&#xff1a;可定制&#xff0c;Pyrene-PE…

知识点15--k8s应用入门

本篇我们用上一篇知识点搭建的k8s集群&#xff0c;认识k8s的入门级使用方法&#xff0c;无法完全体现出k8s的性能&#xff0c;注意不是高级的应用。顺便说一点k8s的操作都依靠于命令&#xff0c;甚至是修改配置文件也提供了编辑命令&#xff0c;命令很像js的选择器&#xff0c;…

视频截图修复方法是什么?这些方法和步骤教会你

在日常的生活中&#xff0c;你是否会遇到过这样的情况&#xff1a;每当在观看一些视频的时候&#xff0c;看到自己喜欢的剧情片段时&#xff0c;就会想要将其截取下来留作收藏&#xff0c;但有时候可能会出现截出来的图片画面是模糊的&#xff0c;这该怎么办呢&#xff1f;其实…

MODBUS-TCP协议

modbus-tcp顾名思义是基于TCP网络连接的MODBUS协议。 和RS485总线不同&#xff0c;TCP协议是通过设备的IP地址和端口来寻址。 modbus-tcp的默认端口号是502&#xff0c;设备端作为TCP服务器监听502端口&#xff0c;主机为TCP客户端主动去连接外设。与RS485一样&#xff0c;主机…

LVI-SAM源码解析(一):论文阅读

1. 摘要 LVI-SAM是一种基于平滑化和映射实现的激光雷达-视觉-IMU紧耦合SLAM方法&#xff0c;能够实现高精度、高鲁棒性地实时状态估计和地图构建。 LVI-SAM建立于包含两个子系统的因子图上&#xff1a;视觉-IMU子系统&#xff08;VIS&#xff09;和激光-IMU子系统&#xff…

CMake中target_compile_features的使用

CMake中的target_compile_features命令用向target添加预期的编译器功能(compiler features)&#xff0c;其格式如下&#xff1a; target_compile_features(<target> <PRIVATE|PUBLIC|INTERFACE> <feature> [...]) 指定在编译给定的<target>时所需的编…

SpringBoot+Vue+kkFileView实现文档管理(文档上传、下载、在线预览)

场景 SpringBootVueOpenOffice实现文档管理(文档上传、下载、在线预览)&#xff1a; SpringBootVueOpenOffice实现文档管理(文档上传、下载、在线预览)_霸道流氓气质的博客-CSDN博客_vue openoffice 上面在使用OpenOffice实现doc、excel、ppt等文档的管理和预览。 除此之外…

linux下安装nginx

linux下安装nginx 注&#xff1a;此处需要先安装vmware&#xff0c;下载Centos8等工具&#xff0c;配置好一个虚拟机。 1、下载nginx的linux版本 2、上传至搭建好的linux环境上。 3、解压nginx压缩包 4、安装nginx编译需要的相关 安装 nginx 需要先将官网下载的源码进行编译…

Spring源码深度解析十五:@Aspect方式的AOP中篇 - getAdvicesAndAdvisorsForBean

一、前言 文章目录&#xff1a;Spring源码深度解析&#xff1a;文章目录 在上篇中我们概述了Aop 实现的逻辑&#xff0c;但是由于篇幅原因&#xff0c;我们将一部分内容拆成了中篇和下篇内容。本篇即中篇&#xff0c;内容主要是讲述 在 Bean创建过程中Aop 挑选适用于当前Bean…

html简洁风格的个人博客网站模板(源码)

文章目录1.设计来源1.1 博客首界面1.2 个人简介界面1.3 日常记录界面1.4 文章列表界面1.5 文章信息界面2.结构源码2.1 目录结构2.2 源代码源码下载作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/128288153 html简洁风格…

qtday1:2、重新手动实现对象树模型

代码段加注释 #include <iostream> #include<list> using namespace std; class A { public:list<A *> child; //创建一个链表 public:A(A *parent nullptr){if(parent !nullptr) //说明有父组件{parent->child.push_back(this); //有父组件就将该…

数商云SRM供应商系统询比价采购业务流程介绍,重塑汽修企业核心竞争力

众所周知&#xff0c;采购供应是关系企业经营效益的重要工作。在汽修行业&#xff0c;由于汽车配件的车型规格繁多&#xff0c;技术业务性强&#xff0c;各种类商品采购过程的艰难性、销售状况的复杂性等等&#xff0c;汽配采购往往容易陷入种种困境&#xff0c;极大降低汽修企…

Qt-Web混合开发-QWebEngineView加载网页最小示例(1)

Qt-Web混合开发-QWebEngineView加载网页最小示例&#x1f4a5; 文章目录Qt-Web混合开发-QWebEngineView加载网页最小示例&#x1f4a5;1、概述&#x1f4af;2、实现效果&#x1f4a6;3、实现功能&#x1f4ac;4、关键代码&#x1f4a4;5、源代码&#x1f648;更多精彩内容&…

【20天快速掌握Python】day01-Python入门

1、什么是Python&#xff1f; Python是一门解释型的编程语言&#xff0c;而且是现在世界上最流行的编程语言之一。 2、Python优缺点 优点 简单&#xff1a;Python是一种代表简单主义思想的语言。阅读一个良好的Python程序就感觉像是在读英语一样&#xff0c;尽管这个英语的要…