【C语言】深入解开指针(四)

  🌈write in front :

🔍个人主页 : @啊森要自信的主页

✏️真正相信奇迹的家伙,本身和奇迹一样了不起啊!

欢迎大家关注🔍点赞👍收藏⭐️留言📝>希望看完我的文章对你有小小的帮助,如有错误,可以指出,让我们一起探讨学习交流,一起加油鸭。 请添加图片描述

文章目录

  • 前言
  • 一、🚣 字符指针变量
  • 二、⛵️ 数组指针变量
    • 2.1 数组指针变量是什么?
    • 2.2 数组指针变量怎么初始化
  • 三、⛵️⼆维数组传参的本质
  • 四、🚤函数指针变量
    • 4.1 函数指针变量的创建
    • 4.2 函数指针变量的使⽤
    • 4.3 两段有趣的代码
      • 4.3.1 typedef关键字
  • 五、🚢函数指针数组
  • ⚓️总结


前言

通过对前面指针的学习,你可能对指针有了一些理解,比如,数字名的理解,然后怎么使用指针来访问数组,二级指针,指针数组 …

有了这些的理解,本小节我们继续深入理解指针,啊森将会带你理解字符指针变量,数组指针变量,二维数组传参的本质,函数指针变量和函数指针数组,通过这些学习,我们最后来通过这些知识来实现转移表,话不多说,让我们启航!


一、🚣 字符指针变量

在C语言中,字符指针变量是一种指向字符型数据的指针变量。它可以用来指向一个字符数组的首地址,也可以用来指向一个字符型变量的地址。
指针类型为字符指针 char*

字符指针变量的声明和初始化如下所示:

char str[] = "Hello"; // 声明一个字符数组
char *ptr; // 声明一个字符指针变量
ptr = str; // 将字符数组的首地址赋给字符指针变量

通过字符指针变量可以访问和操作字符数组中的元素,也可以通过指针运算来访问字符串中的字符。例如:

int main()
{
	char str[] = "Hello";
	char* ptr;
	ptr = str;
	printf("%c\n", *ptr); // 输出字符数组的第一个字符
	printf("%c\n", *(ptr + 1)); // 输出字符数组的第二个字符
	return 0;
}

当然,以上是把字符串放在字符数组里面,不过我们可不可以把字符串直接放在指针里面呢?当然可以!

int main()
{
	const char* pa = "hello,world";//这⾥是把⼀个字符串放到pa指针变量⾥了吗?
	printf("%s\n", pa);
	return 0;
}

思考:这⾥是把⼀个字符串放到pa指针变量⾥了吗?

当然不是!
–>代码 const char* pa = "hello,world"; 特别容易让同学以为是把字符串 hello,world
到字符指针 pa ⾥了,但是本质是把字符串 hello,world. ⾸字符的地址放到了pa中。

在这里插入图片描述

这个是内存布局,"hello,world"是一个字符串常量,它的值存储在内存中,而pa是一个指向这个字符串常量的指针,它的值是字符串常量的地址。所以pa存储的值是104,也就是"hello,world"的第一个字符的ASCII码值,以此我们就可以通过第一个字符串常量的地址遍历后面的字符,顺藤摸瓜的找到字符"\0"才停止。

总结:代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pa 中。
在这里插入图片描述

《剑指Offer》中收录了一道和字符串相关的笔试题,让我们一起来学习一下:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

在这里插入图片描述
这个代码打印结果不一样的原因是:

  1. str1str2 是字符数组,它们分别用"hello bit."初始化了两个不同的数组。虽然内容相同,但它们占用的内存地址是不同的,所以str1 == str2 判断为不相等,打印"str1 and str2 are not same"。

  2. str3str4 是字符指针,它们都指向同一个字符串常量"hello bit."字符串常量存储在只读内存区域,无论如何定义,它们的地址是相同的。所以str3 == str4 判断为相等,打印"str3 and str4 are same"。

换句话说:

- 字符数组比较地址,地址不同就不相等。

- 字符串常量比较内容,内容相同就相等。

所以结果不同的原因是str1、str2是数组,str3、str4是指针,它们的比较规则不同。

如果将str3和str4也定义为字符数组,则它们的比较结果也会是不相等,打印"str3 and str4 are not same"。

二、⛵️ 数组指针变量

2.1 数组指针变量是什么?

之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。
数组指针变量是指针变量?还是数组?

答案是:指针变量。
我们已经熟悉:
整形指针变量int * pa; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点型指针变量float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
下⾯代码哪个是数组指针变量?

在这里插入图片描述
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
下⾯代码哪个是数组指针变量?

 1. int *p1[10];
 2. int (*p2)[10];

思考⼀下:p1, p2分别是什么?
在这里插入图片描述
数组指针变量
数组指针变量

1 int (*p)[10];

解释:p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以
p是⼀个指针,指向⼀个数组,叫 数组指针
这⾥要注意:[]的优先级要⾼于 * 号的,所以必须加上 () 来保证p先和 * 结合。

2.2 数组指针变量怎么初始化

数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名

int arr[10] = {0};
&arr;//得到的就是数组的地址

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

1 int(*p)[10] = &arr;

在这里插入图片描述
我们调试也能看到 &arrp 的类型是完全⼀致的。
数组指针类型解析:


 1. int    (*p)    [10] = &arr;
 2.  |       |       |
 3.  |       |       |
 4.  |       |       p指向数组的元素个数
 5.  |       p是数组指针变量名
 6.  p指向的数组的元素类型

三、⛵️⼆维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

#include <stdio.h>
void test(int a[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

在这里插入图片描述
C语言二维数组传参的本质是:

二维数组在传参时会自动退化为一维数组指针。

具体来说:

  • 二维数组名代表整个二维数组,它其实就是一维数组指针,指向该数组的首行地址。

  • 当二维数组作为参数传递给函数时,它会自动退化为一维数组指针。

  • 函数内部无法得知传入的参数原本是二维数组,它只能看到一个一维数组指针。

举个例子:

void func(int arr[][3]) 
{
  // arr在这里实际类型是int (*arr)[3]  
}

int main() 
{
  int a[4][3] = {0};
  
  func(a); // a传参时自动退化为一维数组指针

  return 0;
}

这里a是二维数组,但传给func函数时,func内部的arr参数实际上是一个指向int[3]类型一维数组的指针。

所以二维数组传参的本质,就是自动退化为一维数组指针。

数组指针变量,它也可以指向二维数组首行地址,从而实现对二维数组的操作。

例如:

int (*ptr)[3] = a; // ptr指向a二维数组的首行

所以二维数组传参本质是退化为一维数组指针,而数组指针变量也可以指向二维数组,两者联系在一起,都可以看作是一维数组指针来操作二维数组。

那我们就来试试数组指针变量来遍历数组吧---------->

⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
如下图:
在这里插入图片描述

根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。
根据上面的例子,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着二维数组传参本质上也是传递了地址传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:

#include <stdio.h>
void test(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

在这里插入图片描述

总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

四、🚤函数指针变量

4.1 函数指针变量的创建

类比一下:
数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
C语言中的函数指针变量是指向函数的指针变量。

函数指针变量的定义格式是:

返回类型 (*变量名)(参数类型列表);

例如:

int (*ptr)(int, char); // ptr是一个指向返回类型为int,参数为int和char的函数的指针

函数指针变量和普通指针变量一样,它也需要指向具体的函数地址才能调用该函数。

例如:

int func(int a, char b) 
{
  return a + b;
}

int main() 
{
  int (*ptr)(int, char); // 函数指针变量声明
  
  ptr = func; // 指向func函数
  
  ptr(10, 'a'); // 通过函数指针调用func函数
  
  return 0;
}

函数指针变量的主要特点:

  • 可以指向不同类型的函数
  • 通过它可以调用被指向的函数
  • 可以作为函数参数或返回值进行传递
  • 常用在回调函数机制中

函数指针变量是用来存放函数地址的,通过这个地址可以调用函数。函数确实有地址!

#include <stdio.h>

void print()
{
	printf("lalala\n");
}
int main()
{
	printf("test: %p\n", print);
	printf("&test: %p\n", &print);

	return 0;
}

输出:
在这里插入图片描述
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:

void print()
{
	printf("lalala\n");
}
void (*pf1)() = &print;
void (*pf2)() = print;

int Add(int x, int y)
{
	return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

 1. int      (*pf3)       (int x, int y)
 2.  |          |              ------------
 3.  |          |                      |
 4.  |          |                     pf3指向函数的参数类型和个数的交代
 5.  |         函数指针变量名
 6.  pf3指向函数的返回类型
 7. 
 8.  int (*) (int x, int y) //pf3函数指针变量的类型

看到这里,你可能会发现数组指针变量和函数指针变量其实好像也是差别不大呀!

4.2 函数指针变量的使⽤

通过函数指针调⽤指针指向的函数。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf3)(int, int) = Add;

	printf("%d\n", (*pf3)(1, 2));
	printf("%d\n", pf3(3, 4));
	return 0;
}

在这里插入图片描述
代码输出的结果是一样的,为什么这两种方式都可以呢?
解释:
首先, pf3是一个函数指针变量,它指向Add函数。

在C语言中,函数指针与一般指针运算方式是一致的。

也就是说,对函数指针进行解引用(*pf3)后的结果,就是被指向的函数本身。

所以:

- (*pf3)(1, 2)
- 等价于 解引用pf3,得到Add函数,然后调用Add(1, 2)

- pf3(3, 4)
- 等价于 调用函数指针pf3,它指向的函数Add(3, 4)

两者调用的函数都是Add,参数也一致,所以结果是相同的。

函数指针与一般指针在语法上表现形式不同,但本质上都是指向函数地址的指针。所以对函数指针进行解引用或直接调用效果是一致的。

因此上述代码两种打印方式结果相同,原因就是函数指针与普通指针在语法和语义上是一致的。

4.3 两段有趣的代码

注:两段代码均出⾃:《C陷阱和缺陷》这本书
在这里插入图片描述

代码1

1 (*(void (*)())0)();

让我们分析一下:

  1. (void (*)())0 意思是把0这个整数值,强制类型转换成一个地址,这个函数没有参数,返回类型是void。

  2. *操作符对它进行解引用,得到void ()类型的匿名函数。

  3. 对这个匿名函数进行调用(),也就是调用0地址处的地址

所以整个表达式:

(void (*)())0 - 获取函数指针,指向0地址

    • 解引用函数指针,得到匿名函数
      () - 调用匿名函数

换句话说,这个代码是:

获取一个指向0地址的函数指针,然后解引用它得到一个匿名函数,并对这个匿名函数进行调用。

由于指针指向0地址,实际调用的是内核NULL地址下的代码。这通常会触发异常或者崩溃。

所以这个代码展示了一个通过函数指针调用匿名函数的语法,它实际上是在尝试访问空指针下的代码从而触发错误。

代码2

1 void (*signal(int , void(*)(int)))(int);

精简理解:

  1. 首先上述代码是函数声明
  2. signal是一个函数
  3. signal函数的参数有2个,第一个是int类型
  4. 第二个是函数指针类型,该指针指向的函数参数是int,返回类型是void
  5. signal函数的返回类型是这种类型的void(*)(int)函数指针
  6. 该指针指向的函数参数是int,返回类型是void

细节分析拓展如下:
此时注意signal并没有与前面的*用括号结合 这个代码定义了一个signal函数,它的功能是设置信号处理函数。

  1. void(*)(int) 定义了一个函数指针,该函数指针指向一个返回void,接受一个int参数的函数。

  2. signal是函数名,它有两个参数:

    • int: 表示信号编号

    • void(*)(int): 函数指针,表示要设置的信号处理函数

  3. signal函数的返回值是一个函数指针:void (*)(int)

  4. 这个返回值函数指针也指向一个返回void,接受一个int参数的函数。

所以整个函数声明可以解释为:

signal函数用于设置信号处理函数。它接收两个参数:

  • 信号编号
  • 要设置的信号处理函数

signal函数返回原来的信号处理函数。

所以这个函数声明定义了一个典型的设置信号处理函数的接口 - signal(),它可以用来设置和获取信号的处理回调函数。
**总结来说:**这个函数声明使用了嵌套的函数指针定义了signal函数的接口格式,目的是为了设置和获取信号处理回调函数。

4.3.1 typedef关键字

当你看到了这里,你可能在想,这么长void (*signal(int , void(*)(int)))(int);的代码,写出来真麻烦,有没有办法可以简化他的长度呢,看起来可观,容易理解呢?可不能自己写着写着把自己转忽悠了哈哈哈。
当然!
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
C语言中的typedef主要用于定义类型别名。

typedef语法:

typedef 旧类型名 新类型名;

例如:

typedef int Int; 

这行代码定义Intint类型的别名。

typedef的主要用途:

  1. 为复杂类型定义简短的名称
    比如定义指针、函数指针等:
typedef int (*FuncPtr)(int);
  1. 隐藏实现细节

比如用结构体指针替换结构体:

typedef struct {
  int x;
  int y;
} Point;

typedef Point* PointPtr;
  1. 向下兼容

如果需要修改类型定义,可以使用typedef避免修改大量代码。

  1. 提高代码可读性

给类型起个有意义的名称,比如用Person替换struct Person。

  1. 标准库也广泛使用typedef

size_tptrdiff_t等标准类型都是通过typedef定义的。

所以总体来说,typedef主要用于为类型起别名,简化和隐藏类型,提高代码可读性和兼容性。它广泛应用于C标准库和程序开发中。
本小节由于篇幅有限,我们先讲第一点:

⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

1 typedef int(*parr_t)[5]; //新的类型名必须在*的右边

函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

1 typedef void(*pfun_t)(int);//新的类型名必须在*的右边

那么要简化代码2,可以这样写:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

五、🚢函数指针数组

在这里插入图片描述

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,
⽐如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

这三个捏个才是正确的呢?

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];

答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
这个定义相当于:

  • 定义一个函数指针数组parr1
  • 数组长度为3
  • 每个元素都是一个函数指针
  • 指向一个返回int,无参数的函数

这里给出一个C语言函数指针数组的简单实现示例:

// 定义函数原型
int func1(void);
int func2(void);

int func1(void)
{
	printf("func1 called\n");
	return 0;
}

int func2(void)
{
	printf("func2 called\n");
	return 0;
}

int main()
{

	// 定义函数指针数组,可以存储2个函数指针
	int (*funcPtrArr[2])(void);

	// 初始化函数指针数组元素
	funcPtrArr[0] = func1;
	funcPtrArr[1] = func2;

	// 通过索引调用函数指针数组元素指向的函数
	funcPtrArr[0]();
	funcPtrArr[1]();

	return 0;
}

输出结果为:
在这里插入图片描述
主要实现步骤

  1. 定义函数原型
  2. 定义函数指针数组
  3. 初始化数组元素,使每个元素指向对应的函数
  4. 通过数组索引,调用函数指针指向的函数

这个示例演示了如何定义和使用函数指针数组来管理和调用多个函数。

实际应用中,可以通过函数指针数组实现回调函数、插件等机制。函数也可以作为参数传递给其他函数。

总之,函数指针数组提供了一种灵活高效的方式来管理和调用多个函数在C语言中。怎么高效?下一届我们做一个计算器,转移表就可以清楚理解他的巧妙之处!

⚓️总结

一、字符指针变量
字符指针变量用来存储字符串,可以通过字符指针访问字符串中的每个字符。

二、数组指针变量
2.1 数组指针变量实际指向数组第一个元素的地址。
2.2 可以通过数组名直接初始化数组指针,也可以通过地址运算符&初始化。

三、二维数组传参的本质
二维数组传参实际上是传一级指针,等同于传数组指针。

四、函数指针变量
4.1 通过函数原型声明函数指针变量类型,并使用地址运算符&初始化。
4.2 通过函数指针调用函数,等同于使用普通函数名调用。
4.3 typedef可以简化函数指针变量类型定义。

五、函数指针数组
函数指针数组可以存储和管理多个函数指针,通过数组索引调用不同函数。

总之,C语言指针变量提供了一种灵活的方式来操作和管理数据,如字符串、数组、函数等。指针变量的概念和使用需要熟练掌握,它是C语言的重要知识点。感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个帮助,可以给博主点一个小小的赞😘

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Pytorch框架多人多摄像头摔倒跌倒坠落检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习在计算机视觉领域的应用已经取得了显著的进展&#xff0c;特别是在多人多摄像头场景下的摔倒跌倒检测。通过…

OpenSearch开发环境安装Docker和Docker-Compose两种方式

文章目录 简介常用请求创建映射写入数据查询数据其他 安装Docker方式安装OpenSearch安装OpenSearchDashboard Docker-Compose方式Docker-Compose安装1.设置主机环境2.下载docker-compose.yml文件3.启动docker-compose4.验证 问题问题1&#xff1a;IPv4 forwarding is disabled.…

给数据库查询结果添加一个额外的自增编号

1、在mysql数据库可以执行的sql SELECT( i : i 1 ) num,M.* FROMuser M,( SELECT i : 0 ) AS ID GROUP BYM.ID ORDER BYM.create_time SELECT (i :i 1) 是为了生成自增的序列号字段 SELECT i : 0 是为了将i进行初始化每次查询的序列号都会从1开始进行排序生成序列号 在…

docker部署paddleocr

内容仅供参考学习 欢迎朋友们V一起交流&#xff1a; zcxl7_7 环境 1. CentOS7  2. docker  3. PaddleOCR2.5.2 1.准备 1. 首先准备好需要打包的项目 2. 在该项目中创建Dockerfile文件 touch Dockerfile2. 编写Dockerfile # 从Python 3.8的官方镜像中创建(pyt…

快速排序演示和代码介绍

快速排序的核心是(以升序为例)&#xff1a;在待排序的数据中指定一个数做为基准数&#xff0c;把所有小于基准数的数据放到基准数的左边&#xff0c;所有大于基准数的数据放在右边&#xff0c;这样的话基准数的位置就确定了&#xff0c;然后在两边的数据中重复上述操作

5G智慧工地整体解决方案:文件全文115页,附下载

关键词&#xff1a;5G智慧工地&#xff0c;智慧工地建设方案&#xff0c;智慧工地管理平台系统&#xff0c;智慧工地建设调研报告&#xff0c;智慧工地云平台建设 一、5G智慧工地建设背景 5G智慧工地是利用5G技术、物联网、大数据、云计算、AI等信息技术&#xff0c;围绕“人…

SANSAN新鲜事|理清!如何降低数字工厂建设成本

根据调研结果&#xff0c;在中国&#xff0c;只有10%的企业已经完全实施数字化工厂解决方案或目前处于最后阶段。工厂数字化的进展比企业的预期要慢得多&#xff0c;原因包括复杂的系统环境和多样化的机器版图&#xff0c;以及在整个生产网络中推广单个解决方案的相关挑战。实施…

深度学习之基于Tensorflow卷积神经网络鸟类目标识别检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Tensorflow的卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;在鸟类目标识…

景区智慧旅游智能化系统方案:PPT全文58页,附下载

关键词&#xff1a;智慧景区解决方案&#xff0c;智慧文旅解决方案&#xff0c;智慧旅游解决方案&#xff0c;智慧文旅综合运营平台 一、景区智慧旅游智能化系统建设背景 近年来&#xff0c;随着信息技术的快速发展和普及&#xff0c;以及旅游市场的不断扩大和升级&#xff0…

Me-and-My-Girlfriend-1

Me-and-My-Girlfriend-1 一、主机发现和端口扫描 主机发现&#xff0c;靶机地址192.168.80.147 arp-scan -l端口扫描&#xff0c;开放了22、80端口 nmap -A -p- -sV 192.168.80.147二、信息收集 访问80端口 路径扫描 dirsearch -u "http://192.168.80.147/" -e * …

用好说 AI 玩转奥特曼表情包,居然还能和他们聊个天

你喜欢奥特曼吗&#xff1f;你相信光吗&#xff1f; 如果你已经追完了特摄剧、刷完了大电影、用滥了那几个表情包&#xff0c;那不如来试试用 AI 给自己整点活儿新 “物料”。 不管是和奥特曼 “面对面” 聊天还是 “无中生有” 表情包&#xff0c;AI 都能做&#xff01; (※…

重磅!2023两院外籍院士增选名单公布

根据《中国科学院院士章程》《中国科学院外籍院士选举办法》等规定&#xff0c;2023年中国科学院选举产生了30名中国科学院外籍院士。 现予公布。 中国科学院 2023年11月23日 中国工程院2023年外籍院士增选共选举产生16位中国工程院外籍院士。 现予公布。 中国工程院 2023年…

Pyqt5实现多线程程序

主从架构 Pyqt常常使用**主从架构&#xff08;Master-Workers 架构&#xff09;**来避免界面卡死的情况。 Master-Workers 架构就像它的名字&#xff0c;一个master统领着几个workers一起干活。其中某个worker倒下了不会导致整体任务失败。matser不用干活&#xff0c;因此可以…

UltraCompare 23 for Mac文件对比工具

UltraCompare是一款功能强大的文件比较和合并工具&#xff0c; 以下是它的特色介绍&#xff1a; 多种文件格式支持&#xff1a;UltraCompare支持比较和合并多种文件格式&#xff0c;包括文本文件、二进制文件、office文档、PDF文件等。 文件差异高亮显示&#xff1a;UltraComp…

动态规划求二维网格中从左上角到右下角的最短路径( 每次只能向下、向右、向右下走 ) java 实现

dp[i][j] 表示在以点(0,0)作为左上角&#xff0c;点(i,i) 作为右下角的二维网格中 左上角到右下角的最短路径&#xff0c; 动态转移方程为&#xff1a;dp[i][j] min{ dp[i][j-1],dp[i-1][j],dp[i-1][j-1] }.distance weight[i][j] ImageUtils.java&#xff1a; import java.a…

凸问题与非凸问题

凸函数&#xff1a;曲线上任意两点连线上的点对应的函数值不大于该两点对应的函数值得连线上的值&#xff0c;例如yx^2&#xff1b; 非凸函数&#xff1a;曲线上任意两点连线上的点对应的函数值既有大于该两点对应的函数值得连线上的值的部分也有小于的部分&#xff0c;例如&am…

filebeat配置解析【待续】

目录 filebeat概览filebeat是如何工作的工作原理采集日志注册表发送日志 容器日志采集的三种方式方式一&#xff1a;Filebeat 与 应用运行在同一容器&#xff08;不推荐&#xff09;方式二&#xff1a;Filebeat 与 应用运行不在同一容器方式三&#xff1a;通过 Kubernetes File…

springboot使用redis缓存乱码(key或者 value 乱码)一招解决

如果查看redis中的值是这样 创建一个配置类就可以解决 package com.deka.config;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; i…

最新EasyRecovery14中文版安装下载步骤

EasyRecovery是一款功能强大的数据恢复软件&#xff0c;可以一直免费使用下去&#xff0c;可以将受损的文件恢复过来&#xff0c;也可以检索数据格式化或损坏卷&#xff0c;还可以初始化磁盘。它拥有强大的数据恢复能力和快速地查找恢复机制&#xff0c;非常的简洁实用。拥有4大…

2023年亚太数学建模C题数据分享+详细思路

在报名截止的前一天&#xff0c;我尝试进行了报名。到那时&#xff0c;已有11,000个队伍注册参赛。在我的了解中&#xff0c;在数模比赛中除了国赛美赛外&#xff0c;几乎没有其他竞赛的参赛队伍数量能与此相媲美。即便不考虑赛题的难度和认可度&#xff0c;亚太地区的这场竞赛…