【程序环境和程序预处理】万字详文,忘记了,看这篇就对了

news2024/9/23 21:32:15

在这里插入图片描述
本章介绍一个test.c文件是如何生成一个test.exe文件。首先了解程序环境和程序预处理的大致流程,本章会分别介绍各个流程,但重点是翻译中的编译中的预编译阶段。

文章目录:

1.程序翻译环境和运行环境

  • 1.1程序翻译中的的编译和链接

2.预编译详解

  • 2.1预定义符号

  • 2.2 #define用法

  • 2.2.1 #define定义标识符

  • 2.2.2 #define 定义宏

  • 2.2.3 #define 替换规则

  • 2.2.4 #和##

  • 2.2.5 宏和函数对比

  • 2.2.6 宏和函数对比

  • 2.2.7 命名约定

  • 2.3#undef的用法

  • 2.4命令行定义

  • 2.5条件编译

  • 2.6文件包含

  • 2.6.1#include<>和#include" "

  • 2.6.2嵌套文件包含

1.程序翻译环境和运行环境

假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程:

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

在翻译环境阶段,会进行编译和链接操作。
在汇编阶段,是将汇编指令转换成二进制指令。

1.1程序翻译中的的编译和链接

我们先来看这段代码:

extern Add(int a, int b);

int main()
{
	printf("%d\n", Add(2, 5));
}

这是在test.c文件中的代码,

int Add(int a, int b)
{
	return a + b;
}

这是在Add.c文件中的代码。

编译运行后,我们走到代码源文件所在目录下,
在这里插入图片描述
会发现有两个obj文件,这两个obj文件就是通过编译器编译源码生成的目标文件。

而这仅仅是目标文件,想要生成可执行程序,还需要通过链接器链接,调用链接库,才能生成可执行文件。

在这里插入图片描述

在链接器将目标文件链接成可执行程序期间,会做两件事:
1.合并段表

一个目标文件:可能是一个.o文件,该文件内部有一个许多关于该文件的信息,并且是分区存放的,也就是一段一段的,如下图:在这里插入图片描述
以上面的例子为例,既然目标文件test.o是这样的,那么另一个目标文件Add.o的分段也应该是如上图,只是里面的内容存放不同而已。
所以我们可以将test.o和Add.o中的各个段的信息合并,就叫做合并段表
在这里插入图片描述

2.符号表的合并和重定位

在上面的两个文件中,我们假设main函数在内存中的地址是0x20000000,Add函数的地址是0x10000000,如下图:
在这里插入图片描述
执行test.c程序时,首先进入执行extern Add语句,发现这是一个函数声明,意思就是我只知道有Add这个函数,但是具体在哪里不知道,接着进入main函数,发现main函数在内存中的地址是0x20000000,记录下来,执行完test.c文件后,接着进入Add.c文件中,发现Add.c文件中有一个Add.c函数,地址是0x10000000,记录下来。

随后,将两个目标文件通过链接器合并时,会将test.c和Add.c文件中的地址合并,即在这里插入图片描述

这就是符号表的合并,那么重定位呢?
重定位就是在符号表合并后,程序只认识新的合成后的符号表,并将该符号表作为运行时的信息,不再以之前的符号表作为信息,这个就是重定位

当然,上述的讲解只是表层的介绍,具体的内容还会更加深入。

2. 预编译详解

2.1 预定义符号

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

这是几个c语言内置的符号,来看他们的用法:

1.__ FILE __
在这里插入图片描述
我们打印__FILE__时,显示的是该源文件所在的路径。

2.__ LINE__

在这里插入图片描述
__LINE __ 显示打印的位置。

3.__DATE __

显示编译代码的日期
在这里插入图片描述
4.__TIME __
显示文件被编译的时间

在这里插入图片描述
5.__ STDC__

而在VS2019环境下,__STDC __没有被定义,如果被定义,其值为1。

注意,上述的五个预定义符号,在书写时均为大写!

2.2 #define 用法

2.2.1#define定义标识符

语法:
#define name stuff

凡是以#开头的,都是预处理指令。 后续还会讲到#pragma,#include,#line等等

举个例子:

#define MAX 1000
int main()
{
	printf("%d\n",MAX);
}

注意:#define定义标识符在程序运行的时候进行的是替换!是替换!替换!

#define定义的标识符不会参与任何运算。

#define还可以定义各式各样的东西,甚至可以定义代码

#define reg register      //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)   //用更形象的符号来替换一种实现
#define CASE break;case     //在写case语句的时候自动把 break写上。
#define DEBUG_PRINT printf("file:%s\tline:%d\tdate:%s\ttime:%s\n",__FILE__,__LINE__ ,__DATE__,__TIME__ ) 

这样写也可以实现。
注意这里有个问题:

#define do_forever for(;;)
1.
int main()
{
	do_forever
	return 0;
}
2.
int main()
{
	do_forever;
	return 0;
}

当我们这样定义for循环时,请问运行1和2的结果分别是什么?

答案:第一个运行的结果是,直接程序什么都不做,就结束。
第二个运行的结果是,程序死循环。

因为
#define定义标识符在程序运行的时候进行的是替换!

所以do_forever会替换成for(; ; ) , 当for循环后面不跟大括号时,默认跟一条语句。
对于第一个代码,return 0 是for循环里面的语句,
对于第二个代码,for循环内部的语句是一个分号,

for(; ; )
    ;

也就是这样,所以会死循环。

那么就有一个问题,
在define定义标识符的时候,要不要在最后加上 ; ?

到底需不需要加呢?

举个简单的例子

#define MAX 100;

int main()
{
	int a = MAX;
	printf("%d\n", a);
	return 0;
}

我们知道,MAX会被替换成100;
所以在赋值给a时,是这样的:

int a = 100;;  有两个分号

这样打印出来可能没什么问题,但是当我们把打印a换成打印MAX时,就有问题了在这里插入图片描述
打印的是

printf("%d\n",100;);

就会有错误

所以,在使用#define定义标识符的时候最好还是不要加分号

2.2.2 #define 定义宏

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

下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

意思就是:name要和小括号紧紧相连在一起,如果中间有空格,那么(parament-list)就会被当作是stuff的一部分。

举个例子:

#define SQUARE( x ) x * x

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

结果会输出什么?
在这里插入图片描述
5将会被传入x中,x就是5,宏也一样,是被替换的,所以
SQUARE(5) 会被替换成 5*5。

但是,这样的写法会有一些问题:
看下面的例子:

#define SQUARE( x ) x * x

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

请问输出结果是什么呢?乍一看,你可能会认为是36,但是,不对。
原因是,还是那句话,

宏一样是被替换的!!!
宏一样是被替换的!!!
宏一样是被替换的!!!

a是5,a+1会被放入SQUARE(a+1), 然后被替换成 a+1a+1,计算的是这个结果,5+15+1,结果就是11.
所以记住这句话:
宏一样是被替换的!!!

所以我们这样改就可以了

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

这样结果就是36了。

这里还有一个宏定义:

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

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

请问结果是什么?
可能会说,100,但是,结果不正确,记住那句话
宏一样是被替换的!!!
打印结果是10*(5)+ (5),结果是55

那么,我们怎么改呢?

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

这样改正才是正确的,才是完美的。

总结,对宏进行定义时,应该对每个替换后的参数都加上括号,避免操作顺序不当出现错误。

2.2.3 #define 替换规则

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

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

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

2.2.4 #和##

首先来看这段代码:

void print(int x)
{
	printf("the value of a is %d\n", x);
}

int main()
{
	int a = 10;
	int b = 20;
	print(a);
	print(b);
	return 0;
}

我们想把a和b的值都打印出来,
然而结果不是我们想要的,两次都是打印a,虽然值不同。
在这里插入图片描述
那么我们应该怎么做才能达到我们想要的效果呢?

先看下面,

int main()
{
	printf("Hello World\n");
	printf("Hello " "World\n");
	return 0;
}

这两个打印的结果一样吗?
在这里插入图片描述
一样的,原因是,在同一个printf中,两个双引号引起来的两个字符串会被当成同一个字符串处理。

在这里插入图片描述

当然,中间的空格可有可无,可以有很多个,也可以没有,这里放一个空格隔开只是方便看。

了解了这个之后,我们就可以改造上面如何打印a和b的值出来了。

#define PRINT(X) printf("the value of " #X "is %d\n",X)

当我们这样改造时,前面的双引号引起的是the value of(这里有个空格) ,后面的双引号引起的是is %d\n ,两个字符串之间使用一个#来吧X也变成一个字符串,这样三个字符串连在一起,就完成了。
相当于

printf("the value of ""a""is %d\n",a)
printf("the value of ""b""is %d\n",b)

在这里插入图片描述
所以#的作用是,把宏参数对应的内容变成一个字符串

有些东西是函数无法做到但是宏能够做到的。比如上面这个例子。

##

(2) ## 的作用
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。

举个例子:

#define CAT(X,Y) X##Y

int main()
{
	int student = 100;
	printf("%d\n", CAT(stu,dent));
	printf("%d\n", student);
	return 0;
}

该段代码的输出结果是两个100,##的作用就是,把X,Y宏参数合成一个新的符号。

stu是参数X,dent是参数Y,合成后成为一个新的符号student,打印出来就是100.

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

2.2.5 带副作用的宏参数

副作用就是后遗症的意思,

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

举个例子:

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

int main()
{
	x = 2;
	y = 5;
	printf("%d\n", MAX(a++, b++));
	printf("%d\n", a);
	printf("%d\n", b);
}

请问上面的这段代码输出结果是什么?

输出结果是:6,3,7
原因是,宏是被替换的,首先x是2,y是5,在使用宏时,参数是a++和b++,然后进行替换,替换结果是:

	printf("%d\n", ((a++) > (b++) ? (a++) : (b++)));

所以在比较时,先使用,后++,2和5比较完,再各自++,此时2小于5,执行b++,此时b已经是6了,先使用后++,所以打印第一个结果是6,对于a,a只++一次,结果是3,
最后再打印b出来时,b已经++完成了,所以b打印出来是7.

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

2.2.6 宏和函数对比

宏是经常被用来执行计算量较小的计算,如比较大小,那为什么不使用函数来比较呢?

1.我们知道,函数在调用的时候会有返回的开销,反观宏,则没有类似的问题。

以比较大小为例:

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

float MAX2(float c, float d)
{
	return c > d ? c : d;
}

int main()
{
	int a = 2;
	int b = 5;
	float c = 3.0f;
	float d = 4.0f;

	float max2 = MAX2(c, d);//函数调用
	printf("%f\n", max2);

	printf("%f\n", MAX(c, d));//宏调用
	return 0;
}

我们分别使用函数和使用宏来比较大小,
在这里插入图片描述
我们调试起来,转到反汇编后,注意看,现在准备进入函数调用,在此之前是准备工作。

当我们调用该函数时,会发现这么一大堆东西,这些都是函数在调用时需要做的工作,以及返回值需要做的工作。
在这里插入图片描述

再来看宏的开销:
在这里插入图片描述

对比函数和宏调用的开销,会发现,仅仅是比较大小,函数的开销比宏的开销多出了很多。

所以宏比函数在程序的规模和速度方面更胜一筹。

2. 函数的参数必须声明为特定的类型,而宏是类型无关的。

以上面的例子为例:

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

float MAX2(float c, float d)
{
	return c > d ? c : d;
}

int MAX1(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int a = 2;
	int b = 5;
	float c = 3.0f;
	float d = 4.0f;

	float max2 = MAX2(c, d);//函数调用
	printf("%f\n", max2);

	int max1 = MAX1(c, d);//函数调用
	printf("%d\n", max1);

	printf("%f\n", MAX(c, d));//宏调用
	printf("%d\n", MAX(a, b));//宏调用

	return 0;
}

分别使用函数和宏对整型和浮点型数据进行大小比较,此时两个没有任何问题,但是接下来,

当我们更改图中数据,用浮点型函数比较整型大小时,回出现警告,可能会丢失数据,
在这里插入图片描述
结果也不符合,
在这里插入图片描述
所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。

记住,宏是类型无关的。

宏的缺点:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序
的长度。

#define TEST() printf("Test Successfully!\n")

int main()
{
	TEST();
	TEST();
	TEST();
		//等价于
	printf("Test Successfully!\n");
	printf("Test Successfully!\n");
	printf("Test Successfully!\n");
}

我们这样复制三份宏,替换后就已经出现代码较冗余的情况, 假如宏定义的代码有五十行,复制三份后就有一百五十行,情况更加严重。

2. 宏是没法调试的
在这里插入图片描述
调试起来的时候,按下F11,并没有跳转到宏所在的地方,因为宏在预编译的时候就已经完成了替换。

3. 宏由于类型无关,也就不够严谨。
宏是类型无关的,既是优点,也是缺点。

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

5.宏还可以做到传一个类型,然而函数做不到。

比如说,宏的参数可以是一个int,但是没有函数传参传一个int的说法,函数只能传一个int类型的值,但是绝对不会传一个int。

举个例子,好好体会一下。

#define MALLOC(num,type)  (type*)malloc(num*sizeof(type))

int main()
{
	int ret1 = (int*)malloc(10 * sizeof(int));
	int ret = MALLOC(10, int);
	//等价于
	printf("%d\n", ret1);
	printf("%d\n", ret);

	return 0;
}

2.2.7命名约定

一般函数的宏的使用语法很相似。
所以语言本身没法帮我们区分二者。

我们平时的一个习惯是:

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

2.3#undef的用法

#undef 是用来移除一个宏定义的
举个例子:

#define MAX 100

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

在这里插入图片描述
可以发现,编译都无法编译成功,说明MAX已经被移除了。

2.4 命令行定义

通俗地讲,命令行定义就是再程序预编译的时候改变一些参数,使他们能够随时地发生变化。
比如说下面:

给一个数组赋值,ARRARY_SIZE代表数组的大小,数组的大小通过命令行定义,在预编译阶段是可以发生改变的。

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

总结:命令行定义就是再程序预编译的时候改变一些参数,使他们能够随时地发生变化。

2.5 条件编译

条件编译,也就是有选择性地编译,把想要的留下。

比方说:

int main()
{
	int arr[10] = { 0 };
	for (int i = 0; i < 10; i++)
	{
#ifdef DEBUG
		arr[i] = i;
#endif
		printf("%d ", arr[i]);
	}

	return 0;
}

请问这段代码输出结果是什么?

结果输出10个0。

因为这里我们使用了条件编译,#ifdef DEBUG,意思就是如果定义有DEBUG,就使用下面的语句,结束编译语句是#endif,在这区间内,如果条件成立,则执行,不成立就不执行。

由于未定义有DEBUG,所以条件不成立,不执行赋值语句,当我们在前面定义DEBUG,就可以了。
在这里插入图片描述
在这里可以给DEBUG一个替换对象,也可以仅仅定义DEBUG。

常见的条件编译指令:

1.常量表达式

int main()
{
#if 1
	printf("hehe\n");
#endif
	return 0;
}

2.多分支的条件编译

int main()
{
#if 1==1
	printf("hehe\n");
#elif 2==1
	printf("haha\n");
#else
	printf("heihei\n");
#endif
	return 0;
}

3.判断是否被定义

#define DEBUG 0 //即使DEBUG被定义为0,为假,但是它已经被定义过了,就打印hehe
int main()
{
#if !defined(DEBUG)  // 只要定义过,不管定义什么,满足条件就参与编译
	printf("hehe\n");
#endif 
	return 0;
}

注意,只要被定义过,不管被定义成什么,都成立。
并且,define后面加了一个字母d,表示defined,定义过的意思。

还有一个是

#ifndef DEBUG //注意这里多了个n,表示no
	printf("hehe\n",);
#endif

表示如果没有定义DEBUG,就打印hehe。

4,嵌套定义

嵌套定义可以跟嵌套的条件判断类比,也就是 if 中还有 if 。

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

2.6文件包含

2.6.1 #include <> 和#include " "

我们知道,对于文件来说,假如我们需要打印东西,就需要引一个头文件,引#include<stdio.h>

那假如我用 #include "stdio.h "
这样的写法呢?能否通过?
在这里插入图片描述
仍然可以打印出来。

#include "filename"

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

用#include " "的方式包含文件,会先在源文件所在目录下查找,
在这里插入图片描述
也就是这些文件里查找,如果找不到,才会去标准库函数里面查找。
在这里插入图片描述
如上图.
而#include< >,则是直接在目录里面查找了。

所以,当我们有成千上万个源文件时,应该使用#include<>去查找。

2.6.2 嵌套文件包含

在这里插入图片描述
如果出现了这样的情况,也就是一个多个文件中都包含了同一个头文件,这会重复调用头文件,造成代码冗余,也会造成文件的速度的减慢。

解决办法:
1.条件编译
每个头文件的开头写:

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

意思就是,如果没有定义TEST.H这个头文件,那么就定义它,如果定义了,就不再次定义。
在这里插入图片描述
比如说这个,上面的红色框框,是test.h文件的内容,下面红色框框是test.c文件内容,在test.c文件中包含test.h文件,然后进入test.h文件,执行#ifndef,如果自己没有被定义,那就定义,如果定义过了,那就不重复定义了。

直接写下面这句话就可以了。

#pragma once

更加推荐第二种写法。

这篇文章到这里就结束了!
如果对于有帮助,不妨点赞关注吧!

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

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

相关文章

Flowable进阶学习(四)任务分配与流程变量

文章目录一、任务分配1. 固定分配2. 表达式分配值表达式&#xff1a;Value expression方法表达式&#xff1a;Method expression3. 监听器分配二、流程变量1. 全局变量2. 局部变量案例&#xff1a;一、任务分配 1. 固定分配 在绘制流程图时或在流程文件中通过Assignee来指定的…

3.2 特征图尺寸计算与参数共享|池化层的作用|整体网络架构|VGG网络架构|残差网络Resnet|感受野的作用

文章目录特征图尺寸计算与参数共享池化层的作用整体网络架构VGG网络架构&#xff08;了解向&#xff0c;背景向&#xff09;残差网络Resnet&#xff08;了解向&#xff0c;背景向&#xff09;感受野的作用特征图尺寸计算与参数共享 给个例子&#xff1a; 如果输入的数据是32323…

GDB的常用命令

GDB是一个调试程序&#xff0c;可以用来调试C/C程序。这个C/C要产生符号表才能使用GDB调用。例如C&#xff1a; g -g source.cppGDB常用命令 首先要生成一个有调试符号的ELF文件&#xff08;exe curable and linkable format),简单点说就是一个带调试符号可执行文件。加-g参数…

client-go实战之八:更新资源时的冲突错误处理

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 系列文章链接 client-go实战之一&#xff1a;准备工作client-go实战之二:RESTClientclient-go实战之三&#xff1a;Clientsetclient-go实战之四&#xff1a;…

matlab控制理论学习

一、求传递函数表达式residue() 1、极点不同的情况 分子和分母的矩阵分别为&#xff1a; >> num[2 5 3 6]; >> den[1 6 11 6]; 使用下列命令&#xff0c;即可对分式进行展开&#xff0c;展开后有多项&#xff0c;每一项的分子一定是数字&#xff0c;而分母则是一个…

mysql底层解析——连接层,包括连接、解析、缓存、引擎、存储等

1、 前言 打算写一个系列的文章&#xff0c;主要是mysql底层解析。 很多时候&#xff0c;程序员对mysql处于频繁使用&#xff0c;但都一知半解的程度&#xff0c;除了会加个索引&#xff0c;貌似也没啥优化的技能了。事实上&#xff0c;mysql能有今日的成就&#xff0c;必然不…

Selenium4新特性-关联定位策略

Selenium 4 引入了关联元素定位策略(Relative Locators)。这种方式主要是应对一些不好定位的元素&#xff0c;但是其周边相关联的元素比较好定位。实现步骤是先定位周边较容易定位的元素&#xff0c;再根据关联元素定位策略定位到想定位的那个元素。如下以具体案例讲解用法。 以…

UDS诊断系列介绍15-FIM模块功能介绍

本文框架1. 系列介绍1.1 FIM模块概述2. FIM相关概念2.1 FID概念2.2 FIM数据结构3. FIM模块作用过程4. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&#xff0c;即统一的诊断服务&#xff0c;是面向整车所有ECU的一种诊断…

English Learning - L1-13 主动脉修饰 (上) 2023.1.16 周一

English Learning - L1-13 主动脉修饰 &#xff08;上&#xff09; 2023.1.16 周一11 主动脉修饰11.1 定语两大核心when 引导的定语从句where 引导的定语从句为什么有时关系词前会有介词到底用 which 还是用 that ?1. 先行词是 all, much, little, few, the one 等不定代词&a…

Java---微服务---SpringCloud(1)

SpringCloud011.认识微服务1.0.学习目标1.1.单体架构1.2.分布式架构1.3.微服务1.4.微服务技术对比1.5.SpringCloud1.6.总结2.服务拆分和远程调用2.1.服务拆分原则2.2.服务拆分示例2.2.1.导入Sql语句2.2.2.导入demo工程2.3.实现远程调用案例2.3.1.案例需求&#xff1a;2.3.2.注册…

如何使用Facebook Insights提升SEO策略

搜索流量是所有网站都必须考虑的重要因素&#xff0c;但如何才能吸引到更多的访问者呢&#xff1f;我们都知道 Google的 SEO算法&#xff0c;那 Google针对搜索流量的算法有哪些&#xff1f;Facebook Insights &#xff08;Facebook Search Engine Insight&#xff09;是一款搜…

基于python个性化推荐购物系统的设计与实现

源码获取&#xff1a;https://www.bilibili.com/video/BV1Ne4y1g7dC/ 一、开发工具及技术 Python3.6.8&#xff0c;Django3&#xff0c;mysql8&#xff0c;navicat数据库管理工具&#xff0c;html页面&#xff0c;javascript脚本&#xff0c;jquery脚本&#xff0c;bootstrap…

从零实现一个组件库(一)项目环境搭建

文章目录前言monorepo架构1.monorepo架构的优势2.使用pnpm搭建monorepo架构&#xff08;1&#xff09;全局安装pnpm&#xff08;2&#xff09;初始化项目&#xff08;3&#xff09;新建workspace.yaml文件4.不同包之间的相互引用TypeScript支持1.安装TypeScript2.初始化TypeScr…

6、工程和模块管理

文章目录6、工程和模块管理6.1 IDEA 项目结构6.2 Project和Modoule的概念6.3 创建模块6.4 删除模块6.5 导入别人的模块6.6 当导入的模块字符集问题&#xff0c;导致中文乱码6.7 同时打开两个IDEA项目工程1 新建一个IDEA项目2 两个IDEA项目工程效果3 打开两个IDEA项目【尚硅谷】…

Kubernetes:开源 K8s 管理工具 Rancher 认知

写在前面 博文内容涉及Rancher 的介绍&#xff0c;集群内安装查看 Rancher 的基本功能理解不足小伙伴帮忙指正 我所渴求的&#xff0c;無非是將心中脫穎語出的本性付諸生活&#xff0c;為何竟如此艱難呢 ------赫尔曼黑塞《德米安》 Rancher 介绍 Rancher 是一个 Kubernetes 管…

计算机基础——python数据结构之顺序表和链表

计算机基础——数据结构概述数据结构算法时间复杂度python的代码执行时间测量模块线性表顺序表顺序表的连续存储顺序表的两种基本实现方式链表单向链表顺序表和单链表的对比双向链表单向循环链表概述 数据是信息的载体&#xff0c;是描述客观师傅属性的数、字符以及所有能输入…

抖音小黄车挂淘宝店相关问题记录

不开淘宝店&#xff0c;但作为技术被客户问起来&#xff0c;总得知道个一二&#xff0c;今天研究了一下&#xff0c;大致情况如下&#xff1a; 抖音绑定淘宝推广id 大致流程就是抖音开通橱窗&#xff0c;橱窗和淘宝挂钩&#xff0c;淘宝商品引入橱窗。 首先要满足抖音要求&a…

AlmaLinux 9 安装Oracle GraalVM Enterprise Edition 21.3.x

今天我们尝试一下在AlmaLinux 9 安装Oracle GraalVM Enterprise Edition 21.3.x。 GraalVM Enterprise 21 是一个 Long-Term-Support (LTS) 版本。 注意&#xff1a;下载Oracle GraalVM Enterprise Edition需要有Oracle账户&#xff0c;如果没有&#xff0c;可以通过访问创建您…

【SpringCloud】Nacos注册中心、配置中心用法与原理(上)

【SpringCloud】Nacos注册中心、配置中心用法与原理&#xff08;上&#xff09; 一、Nacos注册中心 1. 安装Nacos 【BUG】请注意Nacos2.0版本与1.0版本是有差别的&#xff01; 2. Nacos的服务注册使用样例 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;配…

PowerShell 学习笔记

一、PowerShell 强大之处使用方便面向对象使用.net平台功能兼容性好&#xff08;兼容CMD等&#xff09;可扩展性好二、快捷键Tab&#xff1a;自动补齐命令或者文件名三、管道和重定向管道&#xff1a;上条命令的输出作为下一条命令的输入举例如下&#xff1a;列出当前目录ls结果…