程序环境和预处理

目录

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

2. C语言程序的编译+链接

2.1. 预处理

2.2. 编译

2.3. 汇编

2.4. 链接

 3. 运行环境的简单介绍

4. 预定义符号介绍

5. 预处理指令 #define

5.1. #define定义标识符

5.2. #define定义宏

5.3. #define替换规则

6. 宏和函数的对比

1. 宏的优点

2. 宏的缺点 

3. 宏和函数的对比

7. 预处理操作符#和##的介绍

7.1. #的作用

7.2. ##的作用

8. 命令约定

9. 预处理指令 #undef

10. 命令行定义 

11. 条件编译

12. 文件包含

 12.1. 头文件被包含的方式

1. 本地文件被包含的方式

2. 库文件被包含的方式

12.2. 嵌套文件包含


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

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

第一种是翻译环境,在这个环境中,我们的源代码会被转化为可执行的机器指令(即二进制指令),翻译环境是由编译器提供的(在vs下,编译器为cl.exe,链接器为link.exe)

第二种是执行环境,它用来实际执行我们的代码(执行环境通常是操作系统提供的)

而我们在这里主要介绍一下翻译环境。

2. C语言程序的编译+链接

不知道各位有没有好奇过,我们写的C源文件是如何变成一个可以执行的二进制文件?其实一个C源文件要变成一个可以执行的二进制文件需要经历两个过程:编译和链接

什么叫编译呢?什么叫链接呢?

编译:将一个程序的所有C源代码经由编译器的各种处理(隔离编译)形成一个可重定位的二进制文件,在windows下具体为(*.obj)。但此时这个二进制文件是不可以直接被运行的,它还需要经过链接处理。

链接: 将一个程序中的所有可重定位的二进制文件经由链接器集中处理(集中链接),并且链接器会将程序需要的各种库(动态库、静态库)链接到程序中,最后会形成一个可执行的二进制文件。

 一个程序中的每个源文件都会通过编译过程(隔离编译)分别转化为可重定位的二进制文件。

链接器会将所有的二进制文件集中处理,并将它们需要的各种库链接到一起,形成一个可执行程序。

然而,编译链接又可以被分为几个阶段:

1、预处理

2、编译

3、汇编

4、链接

接下来我们用一个实例,演示一下这几个过程:

// add.c
extern int add(int x,int y)
{return x + y;
}
// test.c
#include <stdio.h>extern int add(int x,int y);
// 宏定义
#define LEFT_VAL 20
#define RIGHT_VAL 10
// 条件编译
#if _LEFT_VAL
#define UP_VAL 30
#else
#define DOWN_VAL 40
#endifint main()
{// 注释//printf("haha\n");//printf("haha\n");//printf("haha\n");//printf("haha\n");printf("%d\n",add(LEFT_VAL,RIGHT_VAL));return 0;
}

2.1. 预处理

预处理过程会将C源程序进行一些列处理。具体如下:

1、去掉注释

2、去掉条件编译

3、宏替换

4、头文件展开

// 让编译器预处理完后停下来,并把结果输出到test.i文件中
gcc -E test.c -o test.i

此时我们经过对比,我们发现这个test.i文件有849行代码,而我们写的test.c仅有22行,那么多出来的代码是谁的呢?答案是:<stdio.h>这个头文件中的内容。也就是说,预处理会将头文件的内容展开,其次我们发现,我们源文件中的宏定义被替换了(宏常量直接被替换成对应的数字),而条件编译、以及注释都没见了。也就是说,预处理不仅会展开头文件,并且会将宏定义替换,去掉注释以及条件编译。而像上面的 #include以及#define 就是预处理指令,它们完成的都是一些文本操作。

2.2. 编译

编译又会做什么呢?

//让编译器编译完后就停止,并将其结果重定向到test.s这个文件中
gcc -S test.i -o test.s

可以看到,test.s其实就是一个汇编代码。也就是说,在编译阶段编译器会将其C代码翻译成了汇编代码。这个过程包括:1.语法分析 2.词法分析 3.语义分析 4.符号汇总,在这里说一下符号汇总,符号汇总就是将每个源文件(此时的源文件已经经历过了预处理)中的符号汇总到一起,注意符号不会汇总。例如,上面的两个源文件test.c和add.c。

2.3. 汇编

// 告诉编译器汇编完后就停下来,并将结果重定向到test.o文件中
gcc -c test.s -o test.o

此时这个文件就是一个可重定位的二进制文件,在windows环境下可重定位的二进制文件类型为(*.obj),而在Linux环境下,可重定位的二进制文件文件类型(*.o); 也就是说,汇编过程会将汇编指令翻译成为我们的二进制指令。这个过程还会形成一个符号表。那么如何查看呢?要知道,二进制文件我们直接看是看不懂的,在Linux下,它的文件格式是elf(可执行程序和可重定位的二进制文件的格式都是elf),而在Linux下我们可以借助readelf工具来查看这个文件。如下:readelf -s 可重定位的目标文件  ,就可以查看格式为elf文件的符号表

test.o的符号表如下: 

add.o的符号表如下:

因为add的定义是在add.o文件中,test.o中只有其声明,故这里地址为NULL;printf同理,在<stdio.h>这个头文件中只有其声明,没有定义,故地址为NULL。这就是汇编过程每个源文件形成的符号表。

而此时由汇编形成的这些可重定位的二进制文件是不可以运行的。例如上面,test.o这个二进制文件,它只有add和printf的声明,没有其定义,这两个的函数地址为NULL,此时无法运行这个文件。那么如何解决呢?此时我们就需要进行链接,链接会通过链接器将所有可重定位的二进制文件中的符号表进行汇总,同时会将这些文件需要的库函数所在的各种库(例如C标准库,第三方库等等)也链接进来,形成一个可执行程序。

2.4. 链接

1. 合并段表

2. 将由汇编形成的符号表进行合并以及符号表的重定位。

3. 链接库

例如,上面的编译过程中形成了两个符号表,此时它们会进行合并以及重定位:

经由链接过程,此时这个可重定位的二进制文件就会形成一个可执行的二进制文件。

// 此时这个my_test就是一个可执行的二进制文件
gcc test.o add.o -o my_test

 3. 运行环境的简单介绍

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

4. 预定义符号介绍

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

gcc编译器:

void Test1(void)
{printf("file_name:%s\n",__FILE__);printf("file_line:%d\n",__LINE__);printf("file_date:%s\n",__DATE__);printf("file_time:%s\n",__TIME__);printf("STDC:%d\n",__STDC__);
}

vs2013的sl.exe编译器:

相比之下, gcc编译器是对ANSI C的标准遵守的更全面一些的。

5. 预处理指令 #define

5.1. #define定义标识符

#define可以定义标识符,本质上是一种替换机制。这个标识符会在预处理阶段就被替换掉。例如:

#define COUNT 10
#define STR "hehe"void Test2(void)
{// 这些宏定义的标识会在预处理阶段被替换掉// 例如这里本质上是:// int i = 10;int i = COUNT;// 这里是:const char* str = "hehe";const char* str = STR; printf("%d\n%s\n", i, str);
}

注意:由于#define定义标识符本质上是一种替换机制,因此一般情况下,不要在定义符号时加上 ; 例如下面的场景:

// 如果你在这里添加了';'
#define CAREFUL "Don't add ';'";void Test3(void)
{//那么本质上这个CAREFUL 就是 Don't add ';';,此时printf就会报错printf("%s\n", CAREFUL);
}

#define在符合语法的前提下,其用发还是蛮多的,例如:

#define stc staticvoid Test4(void)
{// 此时这个stc就是staticstc int i = 10;
}

又比如:

#define PRINTF printf("hehe\n")
// 注意: #defin的机制是一种替换机制
void Test5(void)
{// 此时这个PRINTF就是一个函数调用PRINTF;
}

5.2. #define定义宏

#define不仅可以定义标识符,还可以定义宏;宏是什么呢?#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(definemacro)。

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

我们可以实现一个乘法的宏

#define MUL(x) x * xvoid Test6(void)
{int x = 10;printf("ret: %d\n", MUL(x));
}

诶,结果正确的,但是如果是这样的呢? 

void Test7(void)
{printf("ret: %d\n", MUL(9 + 1));
}

 

此时看到这个结果,有人就懵逼了,什么情况,我只是换了一种表现形式,结果就不一样了?注意:我们应该牢记宏的机制是一种替换机制,也就是说这个宏会被替换成如下的场景:

printf("ret: %d\n", 9 + 1 * 9 + 1);

此时结果当然是19,那么为了防止产生这种问题,我们应该如何解决呢?解决方案:宏的实现我们要多用(),如下: 

// 不要吝啬括号
#define MUL(x) (x) * (x)
void Test8(void)
{printf("ret: %d\n", MUL(9+1));// 上面的代码会被替换成如下代码://printf("ret: %d\n", (9 + 1) * (9 + 1));
}

 

此时才符合我们的预期 ,但有时候我们可能会遇到这种情况,如下:

#define DOUBLE(x) (x) + (x)void Test9(void)
{int x = 5;int ret = 2 * DOUBLE(10) * DOUBLE(10);// 我们预期的结果应该是 2 * 20 * 20 = 800printf("ret: %d\n", ret);
}

为什么呢会出现这种现象呢?原因是我们的宏写的有问题,上面的这个表达式会被替换成如下形式: 

#define DOUBLE(x) (x) + (x)void Test9(void)
{int x = 5;int ret = 2 * DOUBLE(10) * DOUBLE(10);// ret = 2 * (10) + (10) * (10) + (10)  刚好是130printf("ret: %d\n", ret);
}

因此,我们的宏应该做出如下改变:

#define DOUBLE(x) ((x) + (x))void Test9(void)
{int x = 5;int ret = 2 * DOUBLE(10) * DOUBLE(10);// 此时会被替换成如下形式// int ret = 2 * ((10) + (10)) * ((10) + (10));  此时才是800printf("ret: %d\n", ret);
}

总结,在写宏的时候,不要吝啬括号的使用

5.3. #define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define定义的符号。如果包含,那么首先会将这些参数进行替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和 #define 定义中可以出现其他 #define定义的变量。但是对于宏,不能出现递归。
2. 当预处理器搜索 #define定义的符号的时候,字符串常量的内容并不被搜索。

6. 宏和函数的对比

1. 宏的优点

1. 宏的方式是替换,不做计算,也不做表达式的求解,不需要建立函数栈帧,对于某些短小的逻辑处理,如果使用函数,那么可能会存在函数栈帧的建立和销毁函数逻辑处理更为复杂;因此,在某些情况下,宏比函数在速度方面更胜一筹,其处理过程也更为简单。例如:

#define ADD(x,y) ((x) + (y))int add(int x, int y)
{return x + y;
}void Test16(void)
{int x = 10;int y = 20;int c = ADD(x, y);c = add(x, y);
}

1. 宏的处理:

2. 函数的处理:

 

可以看到,函数的处理是十分复杂的,其函数栈帧的建立与销毁比实际执行代码逻辑更为复杂。但是宏却的处理就更为简单。

2. 函数必须要求传参的时候要有类型,但是宏没有这个要求。 例如上面我们写的ADD这个宏和add函数,add函数已经限制了参数类型,在不发生类型转换的前提下,只能比较整形,但是我的宏没有这个要求啊,你传什么,我就比较什么,相比之下,函数更为灵活。

3. 其次宏还可以传递类型,但是函数是做不到的。例如:

// type 就是你需要的数据类型
// size 代表多少个字节
#define GETMEMORY(type,size) (type*)malloc(size)

2. 宏的缺点 

上面就是一些宏的优点,但是它也有缺点,例如:

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

3. 宏和函数的对比

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

7. 预处理操作符#和##的介绍

7.1. #的作用

#的作用:使用 # ,可以将一个宏参数变成对应的字符串。不过在这之前,我们需要见一个东西:
void Test11(void)
{printf("cowsay hello\n");// 下面打印的结果是什么呢?printf("cowsay " "hello" "\n");
}

我们发现,在printf中,字符串是有自动连接的功能的。上面两个的打印其实是等价的。有了这个认识,我们在来了解#的作用。

void Test12(void)
{int x = 10;printf("the val of x is %d\n", x);int y = 20;printf("the val of y is %d\n", y);
}

上面的两条打印是不是差异很小,如果我想将其封装成一个函数呢?即如果是x,则打印上面的语句,如果是y打印下面的语句。可是我们发现,函数是很难做到这件事情的。例如:

void print(int val)
{// 函数很难达到预期目的,中间这个val限制死了  printf("the val of val is %d\n", val); 
}

但是我们的宏可以做到:

//#val 的作用就是: 将这个宏参数不经过任何替换,直接把它转换为一个字符串,即 "val"
//此时如果宏参数是x,那么 #val 就相当于 "x"
//同理,如果宏参数是y,那么 #val 就相当于 "y"
//而我们之前说过,printf可以将多个字符串连接起来
#define PRINT(val) printf("the val of " #val " is %d\n",val);void Test13(void)
{int x = 10;int y = 20;PRINT(x);PRINT(y);
}

其实我们还可以这样玩,上面的这个宏其实也很有限制,它局限于打印整形,但如果此时我想根据类型打印呢,即你是整形,我就用"%d"打印,你是浮点型,我就用"%lf"打印,该如何实现呢?

// 其中printf会将这五个字符串连接起来
// 此时我们就可以达到目的: 根据显示传递的打印格式,打印特定值
#define PRINT(val,format) printf("The val of " #val " is " format "\n",val);void Test14(void)
{int x = 10;double PAI = 3.14;PRINT(x,"%d"); PRINT(PAI, "%lf");
}

总结:#的作用:可以把一个宏参数转换为对应的字符串

7.2. ##的作用

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

void printhehe()
{printf("hehe\n");
}// 这个宏的作用: 将x和y合成为一个符号
#define CAT(x,y) x##yvoid Test15(void)
{// 此时CAT(print,hehe)的结果就是 printhehe// 而printfhehe是一个函数,调用这个函数CAT(print, hehe)();
}

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

8. 命令约定

通常情况下,宏名我们全部大写,函数名不要全部大写。

9. 预处理指令 #undef

#undef这条指令可以移除一个宏定义。例如:

#define GETMEMORY(type,size) (type*)malloc(size)void Test17(void)
{int* ptr = GETMEMORY(int, sizeof(int)*10);#undef GETMEMORY   // 移除GETMEMORY这个宏int* str = GETMEMORY(char, 5);  // 此时就不认识这个GETMEMORY,编译报错int tmp = 0;
}

10. 命令行定义 

许多C的编译器提供了一种能力,允许在命令行中定义符号。例如gcc编译器:

#include <stdio.h>int main()
{int arr[sz] = {0};for(size_t i = 0; i < sz; ++i){arr[i] = i;}for(size_t i = 0; i < sz; ++i){printf("%d ",arr[i]);}printf("\n");
}

可以看到,上面的代码中的sz没有定义,如果此时直接编译,是会报错的。

 但是我们可以利用命令行定义,用于启动编译过程。例如:

gcc -o my_test test.c -std=c99 -D sz=10   //命令行定义

11. 条件编译

条件编译顾名思义,满足条件就编译,不满足条件就不编译。常见的条件编译指令如下:

1. 格式如下:

#if   常量表达式

        如果符合条件,就进行编译

#endif 

例如:

void Test18(void)
{
#if 5printf("hehe\n");   // 满足条件,进行编译
#endif#if 0printf("haha\n");   // 不满足条件,不编译
#endif
}

注意哦,每个#if都要有#endif与之配对。

2. 多个分支的条件编译,格式如下:

#if 常量表达式

      //符合条件就编译,否则,不编译

#elif 常量表达式

     //符合条件就编译,否则,不编译

#else

     //既不符合#if的常量表达式也不符合#elif的常量表达式那么就进行编译,否则不编译

#endif

例如:

#define NUM 1void Test19(void)
{
#if NUM == 0printf("haha\n");   // 当NUM == 0时,进行编译
#elif NUM == 1printf("hehe\n");  // 当NUM == 1时,进行编译
#elseprintf("heihei\n"); // 当NUM != 0 && NUM != 1时,进行编译
#endif   // 最后要以#endif 结束
}

3. 判断是否被定义,格式如下:

#if defined(symbol)   
     // 如果symbol被定义了,那么编译,否则不编译
#endif
#ifdef symbol
    // 如果symbol被定义了,那么编译,否则不编译
#endif
#if !defined(symbol)
    // 如果symbol没定义,那么编译,否则不编译
#endif
#ifndef symbol
    // 如果symbol没定义,那么编译,否则不编译
#endif
例如:
#define BLUESKYvoid Test20(void)
{
#if defined(BLUESKY)printf("hehe\n");   // 如果BLUESKY定义了,那么编译
#endif#ifdef BLUESKYprintf("heihei\n");  //如果BLUESKY定义了,那么编译
#endif#if !defined(BLUESKY)printf("haha\n");  // 如果BLUESKY没定义,那么编译
#endif#ifndef BLUESKYprintf("xixi\n");  // 如果BLUESKY没定义,那么编译
#endif
}

4. 嵌套定义

具体演示如下:

#define PLANT
#define FLOWERvoid Test21(void)
{
#ifdef PLANT            // 如果定义了PLANT,则编译#ifdef FLOWER       // 如果定义了FLOWER,则编译printf("rose\n");#endif#ifdef GRASS       // 如果定义了GRASS,则编译printf("green grass\n");#endif#elif defined(ANIMAL)   // 如果定义了ANIMAL,则编译#ifdef LION         // 如果定义了LION,则编译printf("lion\n");#endif
#endif
}

条件编译的运用非常广泛,尤其是一些库实现。

12. 文件包含

 12.1. 头文件被包含的方式

1. 本地文件被包含的方式

#include "filename"

本地文件的查找策略:

第一步:会先在源文件所在的目录下进行查找,找到了,就结束。如果没找到,那么进行第二步。

第二步:第二次查找,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。

// Linux下的标准库文件搜索路径:
/usr/include
//vs2013环境的标准头文件的路径:
E:\vs2013\Microsoft\VC\include

2. 库文件被包含的方式

#include <filename>  
查找库的头文件直接会去标准路径下去查找,如果找不到就提示编译错误。

对比这两种包含方式,我们发现,其实库文件也可以用 #include "filename"去查找,但是会导致效率下降,因为它首先会在源文件所在目录进行查找,找不到,然后会去标准路径下去查找;不仅如此,此时也不容易区分到底是本地文件还是库文件了,因此,我们还是建议区分开来,本地文件就用"filename"的形式,库文件就用<filename>的方式。

12.2. 嵌套文件包含

在以后的编写代码的过程中,难免会出现头文件重复出现的情况。例如下面的情况:

// add.h
int add(int x,int y)
{return x + y;
}
// test.c
#include "add.h"
#include "add.h"
#include "add.h"int main()
{int a = 10;int b = 20;int c = add(a,b);return 0;
}
// 我们看一下预处理后的结果
gcc -E test.c -o test.i

在预处理阶段,头文件会被展开, 如果此时相同的头文件头有多份,并且假设每份的头文件代码量很多,这样就会造成文件内容的大量重复,如何解决这个问题呢?  

这时候,我们的条件编译就派上用场了,如下:

// 方案一:
#ifndef __ADD_H_   // 如果没有定义 __ADD_H_,那么编译下列代码
#define __ADD_H_   // 定义__ADD_H_
int add(int x,int y)
{return x + y;
}
#endif// 上面只是一种方式,或者可以用 #pragma once
// 方案二:
#pragma once     
int add(int x,int y)
{return x + y;
}// 上面的两种方式,就可以保证一份源文件中只会有一份这个头文件
//重新查看预处理后的结果: 
gcc -E test.c -o test.i

此时就只有一份add.h的内容了,这就是防止头文件的重复引入。

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

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

相关文章

带你从0开始学习自动化框架Airtest

现在市面上做UI自动化的框架很多&#xff0c;包括我们常用的Web自动化框架Selenium&#xff0c;移动端自动化框架Appium。 虽然Selenium和Appium分属同源&#xff0c;而且API都有很多相同的地方&#xff0c;可以无损耗切换&#xff0c;但是还是需要引入不同的库&#xff0c;而…

Debug技巧-不启用前端访问后端

在日常开发中&#xff0c;我们经常会遇到各种问题需要调试&#xff0c;前后端都启动需要耗费一定的时间和内存&#xff0c;方便起见&#xff0c;可以直接用抓包数据访问后端&#xff0c;这里我们需要用到Postman或者ApiFox 抓包数据 在系统前台触发后端请求&#xff0c;在控制…

【MATLAB第81期】基于MATLAB的LSTM长短期记忆网络预测模型时间滞后解决思路(更新中)

【MATLAB第81期】基于MATLAB的LSTM长短期记忆网络预测模型时间滞后解决思路&#xff08;更新中&#xff09; 在LSTM预测过程中&#xff0c;极易出现时间滞后&#xff0c;类似于下图&#xff0c;与一个以上的样本点结果错位&#xff0c;产生滞后的效果。 在建模过程中&#xf…

负载均衡深度解析:算法、策略与Nginx实践

引言 如今&#xff0c;网站和应用服务面临着巨大的访问流量&#xff0c;如何高效、稳定地处理这些流量成为了一个亟待解决的问题。负载均衡技术因此应运而生&#xff0c;它通过将流量合理分配到多个服务器上&#xff0c;不仅优化了资源的利用率&#xff0c;还大大提升了系统的…

服务器数据恢复—EMC存储pool上数据卷被误删的数据恢复案例

服务器数据恢复环境&#xff1a; EMC Unity某型号存储&#xff0c;连接了2台硬盘柜。2台硬盘柜上创建2组互相独立的POOL&#xff0c;2组POOL共有21块520字节硬盘。21块硬盘组建了2组RAID6&#xff0c;1号RAID6有11块硬盘. 2号RAID6有10块硬盘。 服务器故障&检测&#xff1…

[ACTF2023]复现

MDH 源题&#xff1a; from hashlib import sha256 from secret import flagr 128 c 96 p 308955606868885551120230861462612873078105583047156930179459717798715109629 Fp GF(p)def gen():a1 random_matrix(Fp, r, c)a2 random_matrix(Fp, r, c)A a1 * a2.Treturn…

Vue入门——核心知识点

简介 Vue是一套用于构建用户界面的渐进式JS框架。 构建用户界面&#xff1a;就是将后端返回来的数据以不同的形式(例如&#xff1a;列表、按钮等)显示在界面上。渐进式&#xff1a;就是可以按需加载各种库。简单的应用只需要一个核心库即可&#xff0c;复杂的应用可以按照需求…

AR的光学原理?

AR智能眼镜的光学成像系统 AR眼镜的光学成像系统由微型显示屏和光学镜片组成&#xff0c;可以将其理解为智能手机的屏幕。 增强现实&#xff0c;从本质上说&#xff0c;是将设备生成的影像与现实世界进行叠加融合。这种技术基本就是通过光学镜片组件对微型显示屏幕发出的光线…

[Machine Learning][Part 7]神经网络的基本组成结构

这里我们将探索神经元/单元和层的内部工作原理。特别是,与之前学习的回归/线性模型和逻辑模型进行比较。最后接介绍tensorflow以及如何利用tensorflow来实现这些模型。 神经网络和大脑的神经元工作原理类似&#xff0c;但是比大脑的工作原理要简单的多。大脑中神经元的工作原理…

python自动化测试(九):EcShop添加商品功能

前置条件&#xff1a; 本地部署&#xff1a;ECShop的版本是3.0.0、Google版本是 Google Chrome65.0.3325.162 (正式版本) &#xff08;32 位&#xff09; py的selenium版本是3.11.0 目录 一、前置代码 二、添加商品操作 2.1 点击添加商品 2.2 添加名称、分类、品牌 2…

flask 实践

flask框架研究&#xff1a; https://blog.csdn.net/shifengboy/article/details/114274271 https://blog.csdn.net/weixin_67531112/article/details/128256170 实现下载文件功能 vim test.py import io from flask import Flask, send_fileapp Flask(__name__) app.route(/…

QML 创建 Web 混合应用

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 随着互联网的快速发展,Web 应用在各个领域中变得越来越流行。为了满足用户对多样化功能的需求,我们经常需要将 Web 技术和原生应用相结合,来创建混合应用程序。 混合应用程序:是一种应用程序开发方法,它…

程序员不得不知道的三大编程语言,看看你了解吗?

作为一名合格的程序员&#xff0c;不仅要有过硬的技术&#xff0c;还要了解许多基础知识。编程语言可是程序员工作的主力军&#xff0c;但是它是如何产生和发展的&#xff0c;你知道吗&#xff1f;接下来就让我们一起来看看编程语言和它们的发展吧&#xff01;记得点赞加收藏哦…

自学SLAM(6)相机与图像实践:OpenCV处理图像与图像拼接(点云)

前言 如果写过SLAM14讲第一次的作业&#xff0c;或者看过我之前的运行ORB_SLAM2教程应该都安装过OpenCV了&#xff0c;如果没有安装&#xff0c;没关系&#xff0c;可以看我之前的博客&#xff0c;里面有如何安装OpenCV。 链接: 运行ORB-SLAM2&#xff08;含OpenCV的安装&…

【AI视野·今日NLP 自然语言处理论文速览 第六十一期】Tue, 24 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Tue, 24 Oct 2023 (showing first 100 of 207 entries) Totally 100 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers LINC: A Neurosymbolic Approach for Logical Reasoning by Combining …

Ajax学习笔记第4天

做决定之前仔细考虑&#xff0c;一旦作了决定就要勇往直前、坚持到底&#xff01; 【1 模仿百度招聘】 整个流程展示&#xff1a; 1.文件目录 2.页面效果展示及代码 data中的page1数据展示 2.1 主页 index.html:index里面代码部分解释 underscore.js :模板页面的相关代码 &…

清华大模型GLM

2022年,清华大学发布了一款具有重要意义的 GLM 大模型,它不仅在中文语言处理方面取得了显著的进展,还在英文语言处理方面表现出了强大的能力。GLM大模型区别于OpenAI GPT在线大模型只能通过API方式获取在线支持的窘境,GLM大模型属于开源大模型,可以本地部署进行行业微调、…

基于Langchain+向量数据库+ChatGPT构建企业级知识库

▼最近直播超级多&#xff0c;预约保你有收获 近期直播&#xff1a;《基于 LLM 大模型的向量数据库企业级应用实践》 1— LangChain 是什么&#xff1f; 众所周知 OpenAI 的 API 无法联网的&#xff0c;所以如果只使用自己的功能实现联网搜索并给出回答、总结 PDF 文档、基于某…

主从复制(gtid方式)

基于事务的Replication&#xff0c;就是利用GTID来实现的复制 GTID&#xff08;全局事务标示符&#xff09;最初由google实现&#xff0c;在MySQL 5.6中引入.GTID在事务提交时生成&#xff0c;由UUID和事务ID组成.uuid会在第一次启动MySQL时生成&#xff0c;保存在数据目录下的…

一台服务器安装两个mysql、重置数据库用于测试使用

文章目录 一、切数据库数据存储文件夹已经存在数据库数据文件夹新建数据库数据文件夹 二、安装第二个mysql安装新数据库初始化数据库数据启动数据库关闭数据库 三、mysqld_multi单机多实例部署参考文档 一、切数据库数据存储文件夹 这个方法可以让你不用安装新的数据库&#x…