进阶C语言:程序环境和预处理

news2024/11/25 12:43:59

有关C语言的知识马上就要结束了,在学完了前面的基础之上我们就来深究一下程序底层的逻辑,关于程序的预处理编译指令,话不多说,我们直接开始:

 

目录

 

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

2. 详解编译+链接

2.1翻译环境

2.2编译步骤 

2.2.1预编译 

2.2.2编译

2.2.3汇编

2.3链接 

2.4运行环境

3.预处理详解 

3.1预定义符号 

3.2#define 

3.2.1#定义的标识符 

3.2.2#define定义的宏 

2.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嵌套文件包含 


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

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

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

  

2. 详解编译+链接

2.1翻译环境

 每一个源文件都是单独的进行编译的一个过程,不受其他源文件的干扰。

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

代码演示:

 源文件:test.c

#include <stdio.h>

//声明
extern int Add(int x, int y);
int main()
{
	int a = 100;
	int b = 200;
	int sum = Add(a, b);
	printf("%d %d\n%d", a, b, sum);
	return 0;
}

源文件:add.c 

int Add(int x, int y)
{
	return x + y;
}

 在程序运行之后会产生两个对应的目标文件:

2.2编译步骤 

之前我们写的代码都是在VS上面写的,VS是一个集成开发环境,集成了许多的功能,当我们Ctrl+F5运行代码之后,直接就完成了编译、执行然后在控制窗口里面展示给了我们,因此使用VS不方便观察编译的细节步骤,所以我们需要使用gcc编译器来观察编译阶段的细节步骤,有关搭建gcc编译环境以及使用方法大家可以去我的另外一篇博客中查看: VScode搭建C/C++开发环境详细教程

 源代码的运行需要经过翻译和执行两个步骤,那么在翻译中还需要经过编译和链接,在编译这一步中还有许多细节:预编译、编译、汇编。

2.2.1预编译 

gcc环境下: 

预编译:gcc 文件名 -E

#include <stdio.h>

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    for(i = 0; i<10; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");

    return 0;
}

 可以看到在预编译的这个过程居然有将近900多行的代码,但是我们能认识的只有后面的几行,和我们源代码没有啥区别,但是少了头文件的包含,因此,前面800多行的代码就表示头文件的包含(include这个头文件的内容)。

所以在预编译阶段所要做的第一步就是头文件的包含 。

 当我们在源代码中添加上一些注释#define定义的符号的时候会发生什么呢?

所以在预编译阶段要做的还有注释的删除和#define定义符号的替换 

总结: 

在预编译阶段都会进行以下操作(文本操作):

1.头文件的包含。

2.注释的删除。

3.#define定义的符号的替换。

2.2.2编译

编译:gcc 预编译文件名 -S 

在这个过程中会生成一个test.s的汇编文件

 所以我们就可以理解在编译这个阶段,会将C语言源代码翻译为汇编代码。

总结:

在编译阶段进行的步骤:

将C语言代码翻译为汇编代码

1.语法分析。

2.词法分析。

3.语义分析。

4.符号汇总(汇总全局符号)。 

2.2.3汇编

汇编:gcc 汇编文件 -c 

在这个过程中会生成一个test.o的目标文件(二进制文件)。

在VS中生成的目标文件后缀是:.obj

在VScode中生成的目标文件的后缀是:.o

当我们打开这个文件的时候会发现打不开,表示的是二进制文件:

总结:

在汇编阶段进行的操作:

1.将汇编代码转化为二进制的指令。

2.形成符号表。

2.3链接 

当汇编完之后,得到了二进制文件就可以进行链接生成可执行程序:

gcc test.o -o test.exe

然后对可执行程序进行执行操作:.\+可执行程序名字:

 在链接的过程中进行了合并段表符号表的合并和重定位

2.4运行环境

程序执行的过程:


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


2. 程序的执行便开始。接着便调用main函数。

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


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

3.预处理详解 

3.1预定义符号 

//预定义符号

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

这些预定义符号都是语言内置的。
 代码演示:

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i < 10; i++)
	{
		printf("%d ---%s %s %s line = %d\n", arr[i], 
			__FILE__,   //打印编译的文件路径
			__DATE__,	//打印编译日期
			__TIME__,	//打印编译时间
			__LINE__);	//打印编译的行数
	}
	return 0;
}

3.2#define 

1.#define的定义的标识符

2.#define定义的宏

3.2.1#定义的标识符 

语法:
    #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__ )

 当然#define也不能多去定义,过多的#define定义的标识符会导致代码的可读性变差。

#define定义的标识符在预编译的时候是直接进行替换 。

gcc环境:

问:#define定义标识符的需要在后面加上 ; 吗? 

#define MAX 1000;
#define MAX 1000

我们可以来验证一下

gcc环境 :

不加;

加上; 

 如果在定义的时候加上;时就会导致有的程序出现错误,因此在使用#define来定义标识符的时候最好最好是不要加;

3.2.2#define定义的宏 

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

//语法:
#define name( parament-list ) stuff
//其中的 parament - list 是一个由逗号隔开的符号表,它们可能出现在stuff中

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

代码演示:

gcc环境下:

用宏实现一个数的平方:

#include <stdio.h>
#define SQURE(x) x*x
int main()
{
	int x_squre = SQURE(5);
	printf("%d\n", x_squre);
	return 0;
}

可以看到 #define定义的宏也是在预编译阶段进行替换的。

但是呢,关于这个宏还是存在问题的,如果我们传递的参数是4+1呢?

 可以看到x_squre变成了4+1*4+1 = 9,这样子写的话就出现了问题,所以关于上面的宏我们还是得再改造改造:

#include <stdio.h>
#define SQURE(x) ((x)*(x))  //给参数都加上()
int main()
{
	int x_squre = SQURE(4 + 1);
	printf("%d\n", x_squre);
	return 0;
}

这样子改造就可以得到正确的结果,所以我们在定义宏的时候给单个参数加上(),然后给整个表达式也可以加上(),这样就防止了错误的发生。

练习:

使用宏实现求两个数的较大值:

代码演示:

#include <stdio.h>
#define MAX(x,y) ((a)>(b)? (a): (b))
int main()
{
	int a = 3;
	int b = 5;
	printf("max = %d\n", MAX(a, b));
	return 0;
}

2.2.3#define的替换规则 

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

注意: 

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

3.2.4#和## 

#: 把一个宏参数变成对应的字符串

我们来看这样一段代码:

#include <stdio.h>
int main()
{
    int a = 10;
    printf("The value of a is %d\n", a);
    int b = 20;
    printf("The value of b is %d\n", b);
    float f = 3.14f;
    printf("The value of f is %f\n", f);
    return 0;
}

 这段代码的打印步骤有点冗余,我们可不可以使用一个函数来实现这个打印过程呢?

答案是不可以的:因为这个打印过程中不能确定打印的格式和元素,所以我们可以使用宏来实现这个打印:

C语言中的字符串可以这样子打印:

#include <stdio.h>
int main()
{
	printf("Hello World!\n");
	printf("Hello " "World!\n");  //可以使用两个双引号将两个字符串连接在一起
	return 0;
}

因此我们可以结合双引号和#来实现宏用来打印:

#define PRINT(num,type) \
        printf("The value of "#num" is "type,num)


int main()
{
    int a = 10;
    PRINT(a, "%d\n");

    int b = 20;
    PRINT(b, "%d\n");

    float f = 3.14f;
    PRINT(f, "%f\n");

    return 0;
}

 使用双引号将多个字符串连接在一起,再使用#将宏参数变成字符串。

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

#include <stdio.h>

int Class2023 = 100;
#define CAT(x,y) x##y

int main()
{
	printf("%d\n", CAT(Class, 2023));
	return 0;
}

 ##将两边的符号合成为一个符号。

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

3.2.5带有副作用的宏参数 

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

例如:

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

代码演示:

#include <stdio.h>

#define MAX(x,y) ((x)>(y)? (x):(y))

int main()
{
	int a = 5;
	int b = 8;
	int max = MAX(a++, b++);
	printf("a = %d b = %d max = %d", a, b, max);
	return 0;
}

 

 可以看到打印出的结果完全不是我们想要的,在正常情况下:a++先使用再加加,所以传递的是5,a在加一之后变成了6,b++先使用再加加,所以传递的是8,加一之后变成了9,所以打印的结果应该是a = 6,b = 9, max = 8,那为什么不一样呢?我们在gcc环境下面进行预编译处理来观察一下:

 在预编译可以看到max被替换成了这样一个表达式:

 我们可以来分析一下:

3.2.6宏和函数的对比 

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

比如求两个数的较大值:我们通常会选择使用宏 来实现:

#define MAX(x,y) ((x)>(y)? (x):(y))

那为什么不用函数来完成这个任务?
原因有二:
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹。(函数调用时间的花费:1.函数调用前的准备(传参,函数栈帧空间的维护)2.主要运算。3.函数的返回值的处理,函数栈帧的销毁
2. 更为重要的是函数的参数必须声明为特定的类型
所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点等可以用于>来比较的类型。
宏是类型无关的。

当然宏也有宏的缺点:

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

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
在之前我们使用动态内存开辟的函数时,是直接进行创建,那如果我们需要开辟多次,而且都是类型不一样的,依次进行开辟就会很麻烦,因此我们就可以使用一个宏来实现这个开辟的过程:

#include <stdio.h>
#include <stdlib.h>

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

int main()
{
	int* p = MALLOC(10, int);    //传递类型
	if (p == NULL)
	{
		perror("p:");
		return 1;
	}
	//使用
	//...
	float* f = MALLOC(5, float);  //传递类型
	if (f == NULL)
	{
		perror("f:");
		return 1;
	}
	//使用
	//...
	//释放
	free(p);
	p = NULL;
	free(f);
	f = NULL;
	return 0;
}

预处理之后:

宏和函数的对比: 

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

3.2.7 命名约定

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

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

3.3#undef 

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

代码演示:

#include <stdio.h>

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

#undef M
	//取消定义之后就不能使用了
	printf("%d\n", M);  //err
	return 0;
}

3.4命令行定义 

 许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

代码演示:

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

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 
	}
	return 0;
}

 那这段代码是什么意思呢?#ifdef后面的__DEBUG__如果使用#define没有定义了,那么它与#endif之间的语句将不会执行,如果使用#define定义了,那么将会执行。

常见的条件编译指令: 

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

 1.

#if 常量表达式

...

#endif

代码演示:

#include <stdio.h>
int main()
{
#if 1 == 1   //如果这个常量表达式为真,则执行下面的语句,如果为假则不执行
	printf("Yes\n");
#endif

	return 0;
}

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

代码演示:

#include <stdio.h>
int main()
{
#if 1 == 2   
	printf("hehe\n");
#elif 2 == 3
	printf("haha\n");
#elif 3 == 4
	printf("heihei\n");
#else
	printf("YES\n");
#endif

	return 0;
}

3.判断是否被定义
#if defined(符号名)

...

#endif


#ifdef 符号名

...

#endif

这两种定义的结果都是一样的只不过形式不一样,二者选其一。


#if !defined(符号名)

...

#denif


#ifndef 符号名

...

#denif

这两种定义的结果都是一样的只不过形式不一样,二者选其一。

代码演示:

#include <stdio.h>

#define MAX 
 int main()
 {
#if defined(MAX)  
          priintf("hehe\n");
#endif
  
     return 0;
 }

#include <stdio.h>
 
 int main()
 {
#if !defined(MAX)  //没有定义表示为真
         printf("hehe\n");
#endif
     return 0;
 }

 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"

查找策略:

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

库文件包含:

#include <filename.h>

 查找策略:

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

两种方式都可以查找到对应文件,那么是不是在任何包含头文件的地方都使用双引号的形式呢?答案是可以的,但是呢,在某些情况下会一定程度的降低代码的运行效率,当然,这样也是不容易区分是库文件还是本地文件了。

3.6.2嵌套文件包含 

 在一个程序中,如果不小心重复包含头文件,那么头文件的内容就会反复的编译,这样就会使得代码的效率下降,那我们为了防止这种情况该怎么做呢?有两种方法:

1.条件编译指令:

在每个头文件中使用条件编译指令

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

2.在头文件的开头使用#pragma once

#pragma once

在VS2019这个编译器中,当我们创建了一个头文件,它就会自动帮我们加上这段代码,很方便。

以上两种方式就可以防止我们重复包含头文件 

 各位小伙伴,讲到这里有关C语言的知识就完结了,喜欢的朋友可以留下你们的三连,感谢大家的支持!

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

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

相关文章

IT培训有靠谱的机构吗,长什么样的?

关于IT培训的问题&#xff0c;网上有一大波劝退的声音&#xff1a;现在的IT越来越卷&#xff0c;高校计算机专业毕业生每年那么多&#xff0c;作为小白转行的你竞争力又在哪里呢&#xff1f;而且去年互联网大厂那么多裁员&#xff0c;还有大幅度降薪等等&#xff0c;IT行业已经…

通达信破底翻选股公式,用缠论底分型进行优化

上次在写《通达信破底翻形态选股公式&#xff0c;选出破底之后再翻回的股票》这篇文章时&#xff0c;编写破底翻选股公式就考虑使用缠论底分型&#xff0c;但是底分型的包含关系较为复杂&#xff0c;不容易处理&#xff0c;只能暂时搁置&#xff0c;采用了一种简单的方式&#…

【PyTorch】第九节:Softmax 函数与交叉熵函数

作者&#x1f575;️‍♂️&#xff1a;让机器理解语言か 专栏&#x1f387;&#xff1a;PyTorch 描述&#x1f3a8;&#xff1a;PyTorch 是一个基于 Torch 的 Python 开源机器学习库。 寄语&#x1f493;&#xff1a;&#x1f43e;没有白走的路&#xff0c;每一步都算数&#…

低延迟流式语音识别技术在人机语音交互场景中的实践

美团语音交互部针对交互场景下的低延迟语音识别需求&#xff0c;提出了一种全新的低出字延迟流式语音识别方案。本方法将降低延迟问题转换成一个知识蒸馏过程&#xff0c;极大地简化了延迟优化的难度&#xff0c;仅通过一个正则项损失函数就使得模型在训练过程中自动降低出字延…

靶机精讲之Holynix

找不到ip 就设置两个网络适配器 再添加一个NAT 主机发现 nmap扫描 端口扫描 UDP扫描 服务扫描 脚本扫描 拒绝服务攻击 sql注入 枚举 web渗透 sql注入 证明有注入 sql注入语句 语句 ‘ or 11 --&#xff08;空格&#xff09; 目录结构像有文件包含 有报错但无法利用 调用系统…

从零开始学架构-计算高性能

一、概述 高性能是每个程序员的追求&#xff0c;无论做一个系统、还是写一组代码&#xff0c;都希望能够达到高性能的效果。而高性能又是最复杂的一环&#xff0c;磁盘、操作系统、CPU、内存、缓存、网络、编程语言、数据库、架构等&#xff0c;每个都可能影响系统的高性能&…

ChatGPT API接口使用+fine tune微调+prompt介绍

目录1 接口调用1.1 生成key1.2 接口功能1.2.1 图片生成 (image generation)1.2.2 对话(chat)1.2.3 中文纠错 (Chinese Spelling Correct)1.2.4 关键词提取 &#xff08;keyword extract)1.2.5 抽取文本向量 (Embedding)1.2.6 微调 (fine tune)2 如何写好prompt2.1分类任务2.2 归…

工业智能网关应用场景:高层楼宇智慧消防解决方案

随着城市化建设的飞速发展&#xff0c;人员聚集与土地资源稀缺的矛盾越来越明显。为了让有限的空间满足更多人的居住需求&#xff0c;高层楼宇越来越多&#xff0c;对于安全消防形成更大的挑战。 基于物联网和云计算平台的智慧消防在消防管理、火灾报警和实时监管方面发挥越来…

java内部类入门(接口)

我有一个玩具狗&#xff0c;有一个接口用于启动它&#xff0c;按照传统方法就是写一个类并实现该接口&#xff0c;且该类只使用一次&#xff08;在启动时使用&#xff0c;后面再不使用&#xff09; 但是如果我有一堆玩具&#xff0c;我每个玩具都要去写一个类来实现start这个接…

GPT-3.5还没研究明白,GPT-4又来了,chatGPT会进化成什么样?

基于GPT-3.5的chatGPT热度才稍稍减退没多久&#xff0c;GPT-4又来了&#xff0c;文新一言的发布会也槽点满满&#xff0c;差距似乎越来越大了。 chatGPT到底厉害在哪&#xff1f;为什么突然就爆火了呢&#xff1f; 它的爆火&#xff0c;一方面&#xff0c;和它的出现形态有关…

代码随想录第18天 | 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 var getMinimumDifference function (root) {//中序遍历法&#xff1a;左中右let res []if (!root) return res;const st [root] //栈&#xff0c;pop(),push()while (st.length) {let x st.pop()if (!x) {res.push(st.pop().val)continue}if (…

Linux环境下搭建composer私服及memory_limit问题

Composer是 PHP项目中用来管理依赖&#xff08;dependency&#xff09;关系的工具&#xff0c;允许声明项目所依赖的代码库 &#xff0c;然后在项目的某个目录中(默认是vendor目录) 中安装相关的依赖包。 在介绍如何安装私服之前&#xff0c;我们先熟悉下 composer 相关 compo…

对话框与子窗口控件(写给大忙人看的快速复习掌握)

对话框与子窗口控件&#xff08;写给大忙人看的快速复习掌握&#xff09;1、对话框的概念2、控件的概念我更喜欢称控件为预定义的窗口类3、我们一步一步写代码熟悉常用的预定义的窗口类3.1 什么叫模板呢&#xff1f;3.2 什么是资源文件4、消息处理函数&#xff08;有这么几个消…

护眼灯哪些牌子好?2023护眼灯品牌推荐

护眼灯就是保护眼睛的&#xff0c;很多人长时间工作和学习&#xff0c;主要还是光的刺激和错误的坐姿&#xff0c;会引起眼睛的近视&#xff0c;导致视觉疲劳的主要原因就是灯光的频闪&#xff0c;而护眼灯就能很好减少频闪。 特别是青少年们的视力发育为成熟&#xff0c;视力…

使用Sentieon加速甲基化WGBS数据分析

全基因组甲基化测序(WGBS)是一种研究DNA甲基化的方法&#xff0c;以全面了解在基因组水平上的表观遗传变化。在进行WGBS数据分析时&#xff0c;通常需要使用专门的比对工具&#xff0c;因为这些工具需要能够处理亚硫酸盐转化后的数据。 以下是四个不同的WGBS比对分析流程&…

ADIDAS阿里纳斯励志广告语

系列文章目录 精选优美英文短文1——Dear Basketball&#xff08;亲爱的篮球&#xff09;精选优美英文短文2——Here’s to the Crazy Ones&#xff08;致疯狂的人&#xff09;“我祝你不幸并痛苦”——约翰罗伯茨毕业致辞“亲爱的波特兰——CJ麦科勒姆告别信” Hi, I’m Gilb…

七、Django进阶:第三方库Django-extensions的开发使用技巧详解(附源码)

Django-extensions是 Django 的扩展应用&#xff0c;给django开发者提供了许多便捷的扩展工具(extensions)&#xff0c;它提供了许多有用的工具和命令行工具&#xff0c;帮助 Django 开发者更高效地进行开发和调试。它的作用包括&#xff1a; - 提供了更多的Django命令&#x…

循环依赖详解及解决方案

介绍 上图就是循环依赖的三种情况,虽然方式不同,但是循环依赖的本质是一样的,就A的完整创建要依赖与B,B的完整创建要依赖于A,相互依赖导致没办法完整创建造成失败. 循环依赖代码演示 public class Demo {public static void main(String[] args) {new Demo1();} }class Demo1…

电子信息工程有哪些SCI期刊推荐? - 易智编译EaseEditing

以下是电子信息工程领域的一些SCI期刊推荐&#xff1a; IEEE Transactions on Information Theory&#xff1a; 该期刊由IEEE出版&#xff0c;专注于信息理论领域的研究&#xff0c;包括编码理论、信道编码、信息传输、信息论应用等方面的研究。 IEEE Transactions on Signal…

Apache网页与安全优化

系列文章目录 文章目录系列文章目录一、1.构建虚拟web主机2.一、基于域名的虚拟主机二、Apache 日志分割1.三、Apache的网页优化总结一、 1.构建虚拟web主机 虚拟Web主机指的是在同一台服务器中运行多个Web站点&#xff0c;其中每一个站点实际上并不独立占用整个服务器&#…