网络入门---可变参数原理和日志模拟实现

news2024/11/16 15:51:11

目录标题

  • 前言
  • 有关函数的几个性质介绍
  • 可变参数的用法介绍
  • 可变参数的一个注意事项
  • 可变参数的底层原理
    • va_list
    • va_end
    • va_start
    • va_arg
    • _INTSIZEOF
  • 可变参数的注意事项
  • 日志的实现
  • 日志的测试

前言

在上一篇文章中我们介绍了TCP协议有关的函数,大致就是服务端先通过listen函数将自己的套接字设置为监听状态,然后客户端通过connect函数向对应的服务端发起链接请求,最后服务端使用accept函数接收客户端发送过来的链接请求并通过返回值来进行通信,那么这就是TCP协议通信的大致过程,因为上面的每一个函数调用都有可能会出现问题,所以在出现问题的时候我们都是直接朝屏幕上进行打印比如说:create socket errorbind error等等等,但是问题也是分等级的啊,如果这样无脑的朝屏幕上进行打印的话肯定会影响到程序的使用体验所以我们可以对问题进行分类,并且将不同程度的问题输出到不同的文件当中,这样未来想要查找问题的时候就只需要打开指定的文件即可,那么我们将程序问题分为以下几种:

#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

数字越大表示出现的问题越严重,DEBUG( 0 )和NOEMAL( 1 )基本上就表示没什么问题,WARNING(3)就表示稍微有点问题但是不影响程序的正常运行,ERROR就表示当前的比较严重会影响程序的正常运行,FATAL就表示当前的问题是非常致命的会造成严重的后果,有了分类还不够因为一个系统下会存在多个进程,每个进程都可能会发送问题,那我们怎么知道当前这个问题是属于哪个时间段哪个进程发送的呢?所以在发送报错消息的时候我们往往是会添加一些系统信息的,比如说时间搓和进程pid等等,有了这些信息之后我们就可以知道当前问题是谁发送的,该问题属于哪一个等级,最后就可以添加问题的内容,所以我们这里可以创建一个函数来专门完成这个错误发送的功能,该函数的声明如下:

void logMessage(int level, const char *format)

发送数据的格式就是下面这样:

[日志等级] [时间戳/时间] [pid] [messge]
[WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]

但是有时候问题的内容是需要添加一些变量的值就好比printf函数一样,那么这个时候就存在一个问题我们怎么知道使用者会传递什么类型的参数呢?我们怎么知道参数的个数是多少呢?所以这里就得在函数当中添加可变参数列表,也就是在参数声明的末尾添加三个点:

void logMessage(int level, const char *format, ...)

这样我们就可以传递任意类型任意数量的参数就好比下面这样:

logMessage(WARNING,"创建socket失败 sokcet:%d,%f,%c",-1,3.14,c);

那么这就是我们要实现的logMessage函数的功能,在一个程序中调用该函数就可以向指定的位置打印该程序在运行过程中的信息,我们把这里的信息所组成的集合称之为日志。因为大多数同学在次之前并没有了解可变参数的原理和用法,所以在实现logMessage函数之前我们先来介绍一下可变参数。

有关函数的几个性质介绍

第一个性质:如果函数没有形式参数,也可以给函数传递参数,比如说下面的代码:

void print()
{
	printf("当前函数不需要传递参数");
}
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	print(a,b,c);
	return 0;
}

程序在运行的时候是不会出现问题的:
在这里插入图片描述
第二个性质:在c语言中只要发生了函数调用并且传递了参数就必定会形成临时变量,这里依然用上面的代码来举例子,对其进行反汇编就可以看到下面这样的内容:
在这里插入图片描述
可以看到在使用指令call调用print函数之前先用move指令进行了赋值操作,因为不同版本的编译器看到的效果不太一样,这里没有看到push指令的压栈操作但是不妨碍传递参数就必定形成临时变量的结论,其次大家不难发现这里在传递参数的时候是以c b a的顺序来进行传递,但是在函数调用中参数传递的顺序却是a,b,c所以这里就可以得到函数调用的第三个性质:临时拷贝是以从右往左的顺序形成的,那么这就是函数调用有关的三个性质。

可变参数的用法介绍

这里通过一段代码来带着大家理解可变参数的使用方法:

#include<stdio.h>
#include<Windows.h>
//num表示可变参数的个数
int FindMax(int num, ...)
{
	va_list arg;
	va_start(arg, num);
	//创建变量记录当前最大值
	int max = va_arg(arg, int);
	for (int i = 0; i < num - 1; i++)
	{
		int cur = va_arg(arg, int);
		if (max < cur)
		{
			max = cur;
		}
	}
	va_end(arg);
	return max;
}
int main()
{
	int max = FindMax(5, 101, 32, 324, 178, 78);
	printf("最大的值为:%d", max);
	return 0;
}

首先该函数实现的功能就是在一定数量的可变参数中找到最大值,程序的运行结果如下:
在这里插入图片描述
对于这段代码大家可能就对这几点感到困惑:va_list,va_start,va_arg,va_end,首先这几个东西都是与
可变参数有关的宏,va_list本质上就是一个char类型的指针,用它创建变量arg指向可变参数中的变量

typedef char* va_list;

因为当前函数的参数分为固定参数和可变参数,而传递参数又是从右向左进行传递的,函数的栈帧又是从下往上进行生长的所以我们当前需要一个功能让arg指向可变参数部分,那么宏va_start就是负责实现该功能
在这里插入图片描述

但是va_start也不知道可变参数的起始位置在哪?所以我们得把紧挨着可变参数的固定参数传递给va_start,因为参数的传递是连续的,所以他就可以根据该参数的地址和该参数的类型找到第一个可变参数的地址,然后就可以随水推舟再根据可变参数的类型和地址找到其他可变参数,那么这就是va_start的功能(初始化va_list定义的变量)以及为什么上面的代码将固定参数num传递给va_start的原因,这里大家可能会有疑问:如果图片是下面这样该如何传递紧挨着的固定参数呢?
在这里插入图片描述
我们可以在尝试一下然后就可以发现这里是直接报错的,可变参数列表的右边是不能添加固定参数的
在这里插入图片描述

找到了可变参数我们就可以读取可变参数的内容以及向下找到其他的可变参数,那么这里就可以用到宏va_arg,他就可以返回当前arg指针指向的内容以及帮助arg指针找到下一个可变参数,使用这个宏的时候得传递当前指针指向的数据类型,因为我们传递的都是int类型参数所以第二个参数就填int,最后一个arg_end就是用来将指针变量arg置空,那么这就是与可变参数有关的4个宏的作用,通过上面的介绍大家应该能够知道可变参数基本的使用方法。

可变参数的一个注意事项

我们将传递的参数类型修改成char类型而不改变va_arg会不会出现什么问题呢?比如说下面的代码:

#include<stdio.h>
#include<Windows.h>
//num表示可变参数的个数
int FindMax(int num, ...)
{
	va_list arg;
	va_start(arg, num);
	//创建变量记录当前最大值
	int max = va_arg(arg, int);
	for (int i = 0; i < num - 1; i++)
	{
		int cur = va_arg(arg, int);
		if (max < cur)
		{
			max = cur;
		}
	}
	va_end(arg);
	return max;
}
int main()
{
	char a = 'a';
	char b = 'b';
	char c = 'c';
	char d = 'd';
	char e = 'e';
	char max = FindMax(5,a,b,c,d,e);
	printf("最大的值为:%c", max);
	return 0;
}

代码的运行结果如下:
在这里插入图片描述
可以看到当前是没有出现问题的,那这是为什么呢?为什么我们传递char类型的参数以int类型的方式进行读取却不会出现问题呢?原因很简答因为可变参数在传递的过程中会发生以4字节为倍数的向上整形提升,如果你传递的参数大小为1字节那么在传参的时候就会自动提升到4个字节,如果你传递的参数大小为2个字节那么在传参的时候也会自动的提升到4个字节,如果你传递的参数大小为6个字节那么在传参的时候就会自动的提升到8个字节以此类推,这也是为什么我们上面传递char类型的数据以int的方式进行读取却没有出错的原因,这里我们还有两个方法来进行验证首先就是查看可变参数传递时的汇编指令:
在这里插入图片描述
上面传递int类型时是采用move指令但是这里采用的确实movsx指令,movsx是x86汇编语言中的一种指令,用于将一个有符号数扩展到一个更大的寄存器中。它的作用是将一个有符号数从一个小的寄存器或内存位置移动到一个大的寄存器中,并将高位扩展为符号位。这个指令可以用来处理有符号数的转移,避免出现符号位扩展错误的情况,简单的来说就是整形提升。另外一个方法就是通过内存来进行查看,首先找到参数num对应的地址:
在这里插入图片描述
因为参数是从右向左连续传递的上面是低地址下面是高地址,并且这里的内存监视是以4个字节为一行来进行显示,所以004FAB0下面5行就是对应的参数在内存上的数据
在这里插入图片描述
不难发现这5行的内容刚好就是abcde,一行就是4个字节所以这也间接的证明了可变参数在传递的时候会发生整形提升的特点,那如果我们传递char类型数据以char类型为单位进行读取的话,会不会出现问题呢?这里我们可以通过下面的代码来尝试一下:

void test(int num, ...)
{
	va_list arg;
	va_start(arg, num);
	for (int i = 0; i < num; i++)
	{
		int cur = va_arg(arg, char);
		printf("%c ", cur);
	}
}
int main()
{
	char a = 'a';
	char b = 'b';
	char c = 'c';
	char d = 'd';
	char e = 'e';
	test(5,a,b,c,d,e);
	return 0;
}

代码的运行结果如下:
在这里插入图片描述
答案是没有问题的,因为va_arg也会对你传递过来的类型进行整形提升,这里我们在可变参数的底层原理给大家讲解,那么这就是可变参数的使用方法和特性。

可变参数的底层原理

va_list

va_list的作用就是创建一个char类型的指针用来指向可变参数,他的底层起始就是char的重命名
在这里插入图片描述

va_end

va_end的作用就是将va_list创建的变量的值置为空,我们来看看这个宏的定义如何:
在这里插入图片描述
底层是__crt_va_end,这个东西也是一个宏我们再来看看他的定义:
在这里插入图片描述
ap是一个char类型的指针,那么这里的意思就是将数字0强制类型转换为char*类型然后再赋值给指针ap,也就是将指针ap置为空的意思。

va_start

va_start的作用就是初始化指针让其指向可变参数的第一个参数,该参数的底层如下:
在这里插入图片描述
底层套了一个宏__crt_va_start,该宏的定义如下:
在这里插入图片描述
可以看到__crt_va_start的在底层又套了一个宏__crt_va_start_a,该宏的定义如下:
在这里插入图片描述
__crt_va_start_a依靠两个宏来实现,第一个AARESSOF的作用就是提取参数的地址
在这里插入图片描述
第二个_INTSIZEOF的作用就是计算类型或者变量整形提升之后创建变量所占的空间大小,如果n的类型为char那么这个宏计算的结果就是4,具体是如何计算的我们后面再说
在这里插入图片描述
知道了这两个宏的作用我们再来看__crt_va_start_a的底层意思就是先获取固定参数的地址,然后再获取固定参数所占的地址大小,最后将两个值相加的结果赋值给ap即可,这样ap就指向了可变参数的第一个参数,我们可以通过图片再来理解理解:
在这里插入图片描述
因为最近的变量的类型为int所以_INTSIZEOF计算得到的结果就是4,009CFD64加上4之后得到的结果就刚好是009CFD68也就是可变参数第一个参数的地址,然后将该地址赋值给ap图片就变成下面这样:
在这里插入图片描述
这就是初始化的作用和原理。

va_arg

va_arg的作用就是根据你传递过来的类型获取指针指向的空间内容并将指针向后移动类型对应的大小,比如说一开始指针指向的内容如下:
在这里插入图片描述
传递的类型为int的话,va_arg的作用获取方括号中的内容并将指针指向方括号下面的62,那么图片就变成下面这样:
在这里插入图片描述
va_arg的底层如下:
在这里插入图片描述
再转换一下便可以看到真正的底层:
在这里插入图片描述
这里的结构比较复杂首先执行的运算是:(ap += _INTSIZEOF(t),得到结果的就是让指针ap向下移动t类型整形提升后大小的长度,也就是从图片中的a指向了b:
在这里插入图片描述
表达式都会返回值,那么上一步的返回值就是b的地址,将这个地址减去_INTSIZEOF(t)就又得到了a的地址,但是指针ap指向的依然是b,将地址a的类型转换成为t*这样就可以得到方括号中的内容而指针ap依然指向的b:
在这里插入图片描述
那么这就是va_arg的底层实现。

_INTSIZEOF

_INTSIZEOF的底层如下:

((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

为了后面方便表述我们假设sizeof(n)的值是n(char 1,short 2, int 4)我们在32位平台,vs2013下测试sizeof(int)大小是4,其他情况我们不考虑_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式比如n是:1,2,3,4对n进行向 sizeof(int) 的最小整数倍取整也就是4,比如n是:5,6,7,8对n进行向 sizeof(int) 的最小整数倍取整就是8,对于这个公式我们有三步理解:
第一步理解:4的倍数
既然是4的最小整数倍取整,那么本质是:x=4*m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8而m具体是多少,取决于n是多少如果n能整除4,那么m就是n/4如果n不能整除4,那么m就是n/4+1
上面是两种情况,如何合并成为一种写法呢?常见做法是 ( n+sizeof(int)-1) )/sizeof(int) -> (n+4-1)/4
如果n能整除4,那么m就是(n+4-1)/4->(n+3)/4, +3的值无意义会因取整自动消除,等价于 n/4如果n不能整除4,那么n=最大能整除4部分+r,1<=r<4 那么m就是 (n+4-1)/4->(能整除4部分+r+3)/4,其中4<=r+3<7 -> 能整除4部分/4 + (r+3)/4 -> n/4+1。

第二步理解:最小4字节对齐数
搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,就变成了
((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] -> ((n+4-1)/4)*4这样就能求出来4字节对齐的数据了,其实上面的写法,在功能上,已经和源代码中的宏等价了。

第三步理解:理解源代码中的宏
拿出简洁写法:((n+4-1)/4)* 4,设w=n+4-1, 那么表达式可以变化成为 (w/4)*4,而4就是2^2,w/4,不就相当于右移两位吗?,再次*4不就相当左移两位吗?先右移两位再左移两位,最终结果就是最后2个比特位被清空为0!需要这么费劲吗?w & ~3 不香吗?所以简洁版:(n+4-1) & ~(4-1)原码版:( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ),无需先/,在*。

可变参数的注意事项

第一: 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是如果你想一开始就访问参数列表中间的参数那是不行的。

第二: 参数列表中至少有一个命名参数。如果连一个命名参数都没有就无法使用 va_start ,这些宏是无法直接判断实际存在参数的数量,如果想要知道可变参数的个数就可以跟我们上面一样传递一个固定的参数用来表示可变参数的数量或者像printf函数一样根据特殊的标记来判断参数的个数,比如说printf("hello word:%d %c",3.14,c)我们就可以创建一个字符指针p指向文本信息,然后创建一个循环每读取一个字符就进行判断如果不为字符%就直接跳过,如果为%就继续判断下一个字符是否为d是否为f等等等,如果下一个字符为d我们就可以以int形式提取可变参数,如果为f我们就可以以float的形式提取可变参数等等,比如说下面的代码:

va_list start;    
va_start(start);
while(*p){
    switch(*p)
    {
       case '%':
           p++;
            if(*p == 'f') arg = va_arg(start, float);
            if(*p == 'd') arg = va_arg(start, int);
        ...
    }
}
va_end(start); 

那么通过这样的方式我们也能够判断可变参数个数。

第三: 这些宏无法判断每个参数的是类型,所以我们得自己传递具体的类型。

第四: 如果在va_arg 中指定了错误的类型,那么其后果是不可预测的。

日志的实现

因为宏的本质就是数字

#define LOG_NORMAL "log.txt"//运行正常发送的文件
#define LOG_ERR "log.error"//运行不正常发送的文件

#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

所以我们首先得创建一个函数用来将数字转换成为问题等级的字符:

const char * to_levelstr(int level)
{
    switch(level)
    {
        case DEBUG : return "DEBUG";
        case NORMAL: return "NORMAL";
        case WARNING: return "WARNING";
        case ERROR: return "ERROR";
        case FATAL: return "FATAL";
        default : return nullptr;
    }
}

问题内容由问题属性和问题信息组成,所以我们先创建一个缓冲区通过snprintf函数将问题属性输出到缓冲区中:

void logMessage(int level, const char *format, ...)
{
// [日志等级] [时间戳/时间] [pid] [messge]
// [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]
#define NUM 1024
    char logprefix[NUM];
    snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
    to_levelstr(level), (long int)time(nullptr), getpid());
}

再创建一个缓冲区用来记录问题信息,因为问题信息存在可变参数所以还得创建一个va_list变量并将其初始化,然后就可以使用vsnprintf函数将文本信息和可变参数以特定的形式输出到缓冲区中,这里的特殊形式就是%d对应的是整形%f对应的是浮点型等等等就和printf函数一样,vsnprintf的参数如下:
在这里插入图片描述
第一个参数表示往哪个缓冲区输出,第二个参数表示大小是多少,第三个参数表示输出的内容,第四个参数就是可变参数列表,那么这里的代码如下:

void logMessage(int level, const char *format, ...)
{
// [日志等级] [时间戳/时间] [pid] [messge]
// [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]
#define NUM 1024
    char logprefix[NUM];
    snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
    to_levelstr(level), (long int)time(nullptr), getpid());
    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg)
}
;

然后我们就可以根据level的等级选着对应的打开文件,将两个缓冲区的内容按照顺序输出到文件中:

void logMessage(int level, const char *format, ...)
{

#define NUM 1024
    char logprefix[NUM];
    snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
        to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg);
    // std::cout << logprefix << logcontent << std::endl;
    FILE *log =  fopen(LOG_NORMAL, "a");
    FILE *err = fopen(LOG_ERR, "a");
    if(log != nullptr && err != nullptr)
    {
        FILE *curr = nullptr;
        if(level == DEBUG || level == NORMAL || level == WARNING) curr = log;
        if(level == ERROR || level == FATAL) curr = err;
        if(curr) fprintf(curr, "%s%s\n", logprefix, logcontent);
        fclose(log);
        fclose(err);
    }

那么这就是日志发送函数。

日志的测试

我们可以使用下面的代码来进行测试:

int sock=3;
logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?
logMessage(DEBUG, "accept error, next");
logMessage(WARNING, "accept error, next");
logMessage(FATAL, "accept error, next");
logMessage(NORMAL, "accept error, next");

logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?
logMessage(DEBUG, "accept error, next");
logMessage(WARNING, "accept error, next");
logMessage(FATAL, "accept error, next");
logMessage(NORMAL, "accept error, next");

logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?
logMessage(DEBUG, "accept error, next");
logMessage(WARNING, "accept error, next");
logMessage(FATAL, "accept error, next");
logMessage(NORMAL, "accept error, next");

代码的运行结果如下:
在这里插入图片描述

log.error中全是FATAL类型的错误
在这里插入图片描述

log.txt中全是NORMAL,WARNING,DEBUG类型的错误,那么这就本篇文章的全部内容。

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

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

相关文章

Android多国语言翻译 国际化

语言目录详细对应关系 Arabic, Egypt (ar-rEG) —————————–阿拉伯语&#xff0c;埃及 Arabic, Israel (ar-rIL) ——————————-阿拉伯语&#xff0c;以色列 Bulgarian, Bulgaria (bg-rBG) ———————保加利亚语&#xff0c;保加利亚 Catalan, Spain (ca-r…

函数栈帧的创建和销毁(编程底层原理)

本篇的内容格外的难写&#xff0c;里面包含了许多的专业术语名和汇编指令等晦涩难懂的东西&#xff0c;既不利于讲解&#xff0c;也不利于读者的理解。但我会尽力去讲述出里面的底层逻辑&#xff0c;帮助大家去理解里面的过程&#xff0c;理解编程的底层原理可以为我们后续更为…

YOLOv8 | 代码逐行解析(一) | 项目目录构造分析

一、本文介绍 Hello&#xff0c;大家好这次给大家带来的不是改进&#xff0c;是整个YOLOv8项目的分析&#xff0c;整个系列大概会更新7-10篇左右的文章&#xff0c;从项目的目录到每一个功能代码的都会进行详细的讲解&#xff0c;同时YOLOv8改进系列也突破了三十篇文章&#x…

助力工业产品质检,基于yolov5l集成CBAM注意力机制开发构建智能PCB电路板质检分析系统

AI助力工业质检智能生产制造已经有很多成功的实践应用了&#xff0c;在我们前面的系列博文中也有很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读前面的博文即可&#xff0c;这里本文的核心目的就是想要基于改进的yolov5l来开发构建用于PCB电路板智能检测分析的模型&…

GZ015 机器人系统集成应用技术样题1-学生赛

2023年全国职业院校技能大赛 高职组“机器人系统集成应用技术”赛项 竞赛任务书&#xff08;学生赛&#xff09; 样题1 选手须知&#xff1a; 本任务书共 25页&#xff0c;如出现任务书缺页、字迹不清等问题&#xff0c;请及时向裁判示意&#xff0c;并进行任务书的更换。参赛队…

【Trino权威指南(第二版)】Trino的架构、trino架构组件、 trino连接器架构的细节、trino的查询执行模型

文章目录 一. Trino架构1. 架构概览2. 协调器3. 发现服务4. 工作节点 二. 基于连接器的架构三. 查询执行模型1. 解析—>查询计划2. 查询计划 —> 分布式查询计划3. 运行阶段3.1. 基础概念切片&#xff1a;并行单元page 与 exchange算子pipeline切片的driverOperator 3.2.…

C#上位机与欧姆龙PLC的通信01----项目背景

最近&#xff0c;【西门庆】作为项目经理负责一个70万的北京项目&#xff0c;需要在工控系统集成软件开发中和欧 姆龙PLC对接&#xff0c;考虑项目现场情况优先想到了采用FinsTCP通讯协议&#xff0c;接下来就是记录如何一步步实现这些通讯过程的&#xff0c;希望给电气工程师&…

Netty常见的设计模式

简介 设计模式在软件开发中起着至关重要的作用&#xff0c;它们是解决常见问题的经过验证的解决方案。而Netty作为一个优秀的网络应用程序框架&#xff0c;同样也采用了许多设计模式来提供高性能和可扩展性。在本文中&#xff0c;我们将探讨Netty中使用的一些关键设计模式&…

探索Linux服务器配置信息的命令

目录 前言1 uname2 lscpu3 free4 df5 lspci6 lsusb7 lshw结语 前言 Linux系统提供了许多命令&#xff0c;用于获取和查看服务器的软硬件配置信息。这些命令可以帮助管理员和用户了解系统的状态、资源使用情况以及硬件设备的相关信息。以下是一些常用的命令以及它们的作用、使用…

【单调栈]LeetCode84: 柱状图中最大的矩形

作者推荐 【动态规划】【广度优先搜索】LeetCode:2617 网格图中最少访问的格子数 本文涉及的知识点 单调栈 题目 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形…

解决kernel32.dll丢失的修复方式,kernel32.dll预防错误的方法

kernel32.dll文件是电脑中的一个重要文件&#xff0c;如果电脑出现kernel32.dll丢失的错误提示&#xff0c;那么电脑中的一些程序将不能正常使用&#xff0c;那么出现这样的问题有什么解决办法呢&#xff1f;那么今天就和大家说说解决kernel32.dll丢失的修复方式。 一.kernel32…

elasticsearch|大数据|kibana的安装(https+密码)

前言&#xff1a; kibana是比较好安装的&#xff0c;但https密码就比较麻烦一些了&#xff0c;下面将就如何安装一个可在生产使用的kibana做一个简单的讲述 一&#xff0c; kibana版本和下载地址 这里我想还是强调一下&#xff0c;kibana的版本需要和elasticsearch的版本一…

数据库基础(实体,管理系统,日志,数据类型,键与约束)

基本概念 数据&#xff08;Data&#xff09;&#xff1a; 数据是描述事物的信息&#xff0c;可以是数字、文字、图像、音频等形式。数据库中存储的就是这些数据&#xff0c;这些数据可以是具体的实体&#xff08;如一个人的信息&#xff09;&#xff0c;也可以是抽象的概念&…

数据持久化与临时存储的对决:localStorage 与 sessionStorage(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Elasticsearch的 8.x常用api汇总

ES的查询语法比较复杂,对于初学者需要在不断练习中才会逐渐掌握,本文汇总了ES各种查询语法以及常用api,可以作为新手的实用笔记 首先,安装 Kibana! 下载Elasticsearch,官方下载页面;Elasticsearch 参考,官方文档;<

智能优化算法应用:基于静电放电算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于静电放电算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于静电放电算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.静电放电算法4.实验参数设定5.算法结果6.…

目标检测图片截取目标分类图片

如果要训练一个分类模型却没有特定的分类数据集怎么办呢&#xff1f;可以换一种思路&#xff0c;将带有该目标的图片对所有想要的目标进行画标注框然后进行截图&#xff0c;就能得到特定的分类数据了。这么做的目的是&#xff1a;带有该目标的图片可能不会少&#xff0c;但是带…

【系统设计】如何确保消息不会丢失?

一、前言 对于大部分业务系统来说&#xff0c;丢消息意味着数据丢失&#xff0c;是完全无法接受的。其实&#xff0c;现在主流的消息队列产品都提供了非常完善的消息可靠性保证机制&#xff0c;完全可以做到在消息传递过程中&#xff0c;即使发生网络中断或者硬件故障&#xf…

Initial用法-FPGA入门3

Initial是什么 FPGA Initial是一种在FPGA中进行初始化的方法。在FPGA设备上&#xff0c;初始值决定了逻辑门的状态和寄存器的初始值。FPGA Initial可以通过设置初始值来控制电路在上电后的初始状态。 Initial的作用 2.1&#xff0c;控制电路启动时的初始状态 通过设置FPGA Ini…

迅为RK3568开发板使用OpenCV处理图像-ROI区域-位置提取ROI

在图像处理过程中&#xff0c;我们可能会对图像的某一个特定区域感兴趣&#xff0c;该区域被称为感兴趣区域&#xff08;Region of Interest, ROI&#xff09;。在设定感兴趣区域 ROI 后&#xff0c;就可以对该区域进行整体操作。 位置提取 ROI 本小节代码在配套资料“iTOP-3…