当前位置: 首页 > news >正文

【C语言:深入理解指针二】

文章目录

  • 1. 二级指针
  • 2. 指针数组
  • 3. 字符指针变量
  • 4. 数组指针变量
  • 5. 二维数组传参的本质
  • 6. 函数指针变量
  • 7. 函数指针数组
  • 8. 转移表
  • 9. 回调函数
  • 10. qsort函数的使用与模拟实现

在这里插入图片描述

1. 二级指针

我们知道,指针变量也是变量,它也有自己的地址,使用什么来存放它的地址呢?答案是:二级指针。

int main()
{int a = 10;int* p = &a;int** pp = &p;  //二级指针变量ppreturn 0;
}

在这里插入图片描述

关于二级指针的运算
在这里插入图片描述

  • *pp先解引用,对pp中的地址进行访问,访问的就是p
  • **pp, 先通过*pp找到p,再对p进行解引用,访问的就是a

2. 指针数组

指针数组,顾名思义,它应该是一个数组,是用来存放指针的。
指针数组中的每一个元素又是一个地址,可以指向另一个区域。

int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组名是数组首元素的地址,类型是int*,可以存放在数组指针arr中int* arr[3] = { arr1, arr2, arr3 };return 0;
}

在这里插入图片描述

  • 使用指针数组模拟二维数组
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组名是数组首元素的地址,类型是int*,可以存放在数组指针arr中int* arr[3] = { arr1, arr2, arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);//也可以写成下面这种形式//printf("%d ", *(*(arr + i) + j));}printf("\n");}return 0;
}

3. 字符指针变量

在指针的类型中,我们知道有一种指针类型叫字符指针。
一般使用:

int main()
{char ch = 'c';char* pc = &ch;*pc = 'a';printf("%c\n", ch);return 0;
}

还有一种使用方式:

int main()
{char* pc = "abcdef";printf("%s\n", pc);return 0;
}
  1. 可以把字符串想象为一个字符数组,但是这个数组是不能修改的,因此为了避免出错,常常加上const
const char* pc = "abcdef";
  1. 当常量字符串出现在表达式中的时候,他的值是第一个字符的地址。当我们知道存放的是第一个字符的地址的时候,我们就可以这样玩
int main()
{char* pc = "abcdef";printf("%c\n", "abcdef"[3]);   //dprintf("%c\n", pc[3]);		   //dreturn 0;
}

下面来看一道题目:
在这里插入图片描述
str1 不等于 str2 这个很好理解;str3 等于str4怎么理解呢?
在这里插入图片描述
由于str3 与 str4中存放的都是常量字符串,C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。(内容相等的常量字符串仅保存一份)
但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

4. 数组指针变量

数组指针变量是数组还是指针呢?答案是:指针。
我们已经熟悉:

  • 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量

int main()
{int arr[10] = { 0 };int(*parr)[10] = &arr;  //数组指针变量parrreturn  0;
}

在这里插入图片描述

5. 二维数组传参的本质

我们知道一维数组传参的本质是:传的是数组首元素的地址。
那二维数组呢?
过去我们使用二维数组时,是这样的:

void func(int arr[][5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf("%d ", arr[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} };func(arr, 3, 5);return 0;
}

这里实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
首先,我们应该知道⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的首元素就是第一行,是个⼀维数组。
在这里插入图片描述

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

void func(int (*arr)[5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf("%d ", *(*(arr + i) + j));}printf("\n");}
}

6. 函数指针变量

函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数,那函数是否真的有地址呢?
在这里插入图片描述
确实打印出来了地址,所以函数是有地址的。

  • 函数名就是函数的地址
  • &函数名 也是函数的地址。

这里和数组相比还是有区别的

  • 数组名是数组首元素的地址;
  • &数组名是整个数组的地址

函数指针类型解析:

在这里插入图片描述
函数指针变量的使用

int add(int x, int y)
{return x + y;
}
int main()
{int (*p1)(int, int) = &add;int ret1 = add(3, 5);printf("%d\n", ret1);  //8int ret2 = (*p1)(3, 5);printf("%d\n", ret2);  //8int (*p2)(int, int) = add;int ret3 = (*p2)(4, 6);printf("%d\n", ret3);  //10int ret4 = p2(4, 6);printf("%d\n", ret4);   //10return 0;
}

因为函数名就是地址,我们调用函数的时候没有使用解引用,所以函数指针也可以不解引用, 如ret4。

两段有趣的代码:

(*(void (*)())0)();
  1. 上述代码是一次函数调用
  2. 将0强制类型转换成一个函数指针,这个函数没有参数,返回类型是void
  3. (*0)()调用0地址处的函数
 void (*  signal(int , void(*)(int))  )(int);
  1. signal是一个函数名,这个函数有两个参数,一个是整型int,一个是函数指针类型的 void (*)(int),这个函数的参数是int,返回值是void
  2. void (*)(int) 去掉函数名和函数参数,剩下的就是函数的返回类型。该signal函数的返回类型是 void ( * )(int)的函数指针。

这样写是不是挺不好理解的,我们可以使用typedef将复杂的类型简单化。
比如,将 int* 重命名为 int_p ,这样写

typedef int* int_p;

但是对于数组指针和函数指针稍微有点区别:新的名字必须在*的旁边
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int (*parr_t)[5]

将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void (*pf_t)();
pf_t signal(int, pf_t);  //signal函数就可以被这样简化

7. 函数指针数组

数组是⼀个存放相同类型数据的存储空间,而且我们已经学习了函数指针。

int Add(int x, int y)
{return x + y;
}int main()
{int (*p)(int, int) = Add;  //函数指针return 0;
}

那么如果要把多个函数(函数参数的类型、返回值的类型都应相同 )的地址存放起来,那函数指针数组应该如何定义呢?

int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{int (*p)(int, int) = Add;//函数指针int (*pArr[])(int, int) = { Add, Sub, Mul, Div };  //函数指针数组return 0;
}

我们都知道,一个数组,去掉数组名和 [ ] 剩下的就是数组元素的类型。例如 int arr[ ], int 就是数组元素的类型。
因此 int (* pArr[ ] )(int, int),去掉数组名和 [],剩下的int (*)(int ,int)就是这个数组元素的类型,很显然,这是数组元素的类型是函数指针类型。

函数指针数组的使用:
在这里插入图片描述

8. 转移表

当我们学习完函数指针数组以后,你是否也有过这样的疑问:我直接通过函数名调用函数不是更简单吗,干嘛还要放进数组中,然后再调用函数呢?请看下面的代码:
假设我们要实现一个计算器,一般写法是不是这样呢?

void menu()
{printf("****************************\n");printf("******1.Add       2.Sub*****\n");printf("******3.Mul       4.Div*****\n");printf("******0.exit           *****\n");printf("****************************\n");
}int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:\n");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择!\n");}} while (input);return 0;
}

在这里插入图片描述
因此,我们就可以利用转移表来解决代码的冗余问题,首先,我们要知道什么是转移表?

转移表就是用一个函数指针数组存储每一个自定义的函数指针,在调用自定义函数的时候,就可以通过数组下标访问 ----总结于《C和指针》

利用转移表解决问题:

int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;int (*pArr[])(int, int) = { NULL, Add, Sub, Mul ,Div };//                           0     1    2    3    4   使用NULL巧妙地与选择相对应do{menu();printf("请选择:\n");scanf("%d", &input);if (input == 0){printf("退出计算器\n");}else if (input >= 1 && input <= 4){printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pArr[input](x, y);printf("%d\n", ret);}else{printf("选择错误,请重新选择!\n");}} while (input);return 0;
}

这样是不是就很好地解决了代码地冗余问题,同时也方便了以后再增加新的功能,只需向函数指针数组中添加函数的地址即,改变以下判断的范围即可,无需再写一大串的case了。

9. 回调函数

  1. 回调函数是什么呢?
    回调函数就是⼀个通过函数指针调用的函数。
    如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的⼀方调用的,用于对该事件或条件进行响应

Calculate函数的参数是函数指针类型的

void Calculate(int (*pfunc)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pfunc(x, y);   //通过函数指针调用函数printf("%d\n", ret);
}int main()
{int input = 0;do{menu();printf("请选择:\n");scanf("%d", &input);switch (input){case 1:Calculate(Add);break;case 2:Calculate(Sub);break;case 3:Calculate(Mul);break;case 4:Calculate(Div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择!\n");}} while (input);return 0;
}

我们可以发现,当Calculate函数的参数是函数指针类型时,只要你给Calculate函数传递一个函数指针类型的变量,它都可以调用,这样看它的功能是不是强大了不少。

10. qsort函数的使用与模拟实现

  1. qsort函数是什么?
    qsort函数是可以排序任何类型数据的一个函数。
  2. qsort是如何设计的?
void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));

这个函数有四个参数,各参数的说明如下:

在这里插入图片描述

第四个参数有点特别,它是一个函数指针,指针指向的函数的功能是比较两个元素的大小,这个函数需要qsort函数的使用者自己实现,并且函数的返回值要符合qsort函数的要求

在这里插入图片描述
那么我们先来使用以下qsort函数吧:

struct Stu
{int age;char name[20];
};
//使用者自己实现两个元素的比较函数
int cmp(const void* p1, const void* p2)
{return   *((int*)p1) - *((int*)p2);//此处是将p1、p2变量强制转换为 整型指针变量 然后解引用
}int cmp_struct_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}void print1(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}void print2(struct Stu arr[],int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d %s\n", arr[i].age, arr[i].name);}printf("\n");
}int main()
{int arr1[] = { 2,1,8,5,6,3,4,9,7,0 };int sz1 = sizeof(arr1) / sizeof(arr1[0]);print1(arr1, sz1);qsort(arr1, sz1, sizeof(arr1[0]), cmp);print1(arr1, sz1);struct Stu arr2[] = { {18, "zhangsan"}, {38, "lisi"}, {25, "wangwu"} };int sz2 = sizeof(arr2) / sizeof(arr2[0]);print2(arr2, sz2);qsort(arr2, sz2, sizeof(arr2[0]), cmp_struct_name);print2(arr2, sz2);return 0;
}

在这里插入图片描述

接下来,我们就来模拟实现一个qsort函数,由于qsort的实现使用的是快速排序,我们在此就使用冒泡排序

//使用者自己实现两个元素的比较函数
int cmp(const void* p1, const void* p2)
{return   *((int*)p1) - *((int*)p2);//此处是将p1、p2变量强制转换为 整型指针变量 然后解引用
}int cmp_struct_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}int cmp_struct_age(const void* p1, const void* p2)
{return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
}//由于被交换的数据的类型不是固定的,但是数据类型的大小是知道的
//因此我们可以交换数据的每一个字节的数据
void _Swap(const void* p1, const void* p2, int sz)
{int i = 0;//交换数据的每一个字节for (i = 0; i < sz; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}void my_qsort(void* base, int num, int size, int (*compar)(const void*, const void*))
{int i = 0;for (i = 0; i < num - 1; i++){int j = 0;for (j = 0; j < num - 1 - i; j++){
//此处应该是传给compar()两个参数arr[j]与arr[j+1],让其进行比较
//但是怎么拿到要传的数呢?
//qsort函数只有这个数组首元素的地址,和数组元素类型的大小
//因此我可以让base指针加上 j个类型的大小 找到某个元素的首地址,具体比较多大内容的数据,看比较什么类型的数据,由使用者决定
//但是我得到的数组首元素的地址也是void* 类型的,所以我们可以将base转换位char*类型的指针,一次访问 j*size 大小if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0){//交换_Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
int main()
{//比较整型的数据int arr1[] = { 2,1,8,5,6,3,4,9,7,0 };int sz1 = sizeof(arr1) / sizeof(arr1[0]);printf("排序整型\n");print1(arr1, sz1);my_qsort(arr1, sz1, sizeof(arr1[0]), cmp);print1(arr1, sz1); //比较结构体类型的数据struct Stu arr2[] = { {38, "lisi"}, {18, "zhangsan"}, {25, "wangwu"} };int sz2 = sizeof(arr2) / sizeof(arr2[0]);printf("排序结构体型-按姓名\n");print2(arr2, sz2);my_qsort(arr2, sz2, sizeof(arr2[0]), cmp_struct_name);print2(arr2, sz2);printf("排序结构体型-按年龄\n");print2(arr2, sz2);my_qsort(arr2, sz2, sizeof(arr2[0]), cmp_struct_age);print2(arr2, sz2);return 0;
}

在这里插入图片描述
本次的分享就到这里啦~在这里插入图片描述

相关文章:

【C语言:深入理解指针二】

文章目录 1. 二级指针2. 指针数组3. 字符指针变量4. 数组指针变量5. 二维数组传参的本质6. 函数指针变量7. 函数指针数组8. 转移表9. 回调函数10. qsort函数的使用与模拟实现 1. 二级指针 我们知道&#xff0c;指针变量也是变量&#xff0c;它也有自己的地址&#xff0c;使用什…...

前端实现表格生成序号001、002、003自增

我们最终想要实现的效果如图&#xff0c;从后端获取数据之后&#xff0c;不使用data中的id&#xff0c;而是使用自己生成的按照顺序自增的序号id。 script <template><el-table :data"sticker" border style"width: 100%" id"stickerList&q…...

【Django-01】 视图函数和视图类

视图函数 作用详解视图函数的特点视图类实际开发怎么用一个无意义的demo 作用 用于返回给前端数据详解 def list(request):"""1.普通的视图函数 request是HttpRequest 函数2.且必须用request.GET|request.POST 指定方法是什么方法3.返回值不能用 rest_framewor…...

编译安装报错:configure: error: cannot guess build type; you must specify one

1、编译安装报错 configure: error: cannot guess build type; you must specify one 该报错信息翻过过来的意思是&#xff1a;无法猜测编译 操作系统类型,请指定一个 2、解决方法 在原本的编译安装语句后面加上一句&#xff1a; “--buildarm-linux ” &#xff0c;这句话…...

2311rust,到66版本更新

1.60.0稳定版 基于源码的代码覆盖率 rustc中已稳定支持基于LLVM的覆盖率检测.可用-Cinstrument-coverage重构代码,如: RUSTFLAGS"-C instrument-coverage" cargo build之后,运行生成的二进制文件,它在当前目录中生成一个default.profraw文件.环境变量可覆盖路径和…...

JOSEF约瑟 过电流继电器 JL15-300/11 触点形式一开一闭 板前接线

系列型号 JL15-1.5/11电流继电器JL15-2.5/11电流继电器 JL15-5/11电流继电器JL15-10/11电流继电器 JL15-15/11电流继电器JL15-20/11电流继电器 JL15-30/11电流继电器JL15-40/11电流继电器 JL15-60/11电流继电器JL15-80/11电流继电器 JL15-100/11电流继电器JL15-150/11电流继电…...

postman设置接口关联这样做,薪资直接涨3k

postman设置接口关联 在实际的接口测试中&#xff0c;后一个接口经常需要用到前一个接口返回的结果&#xff0c; 从而让后一个接口能正常执行&#xff0c;这个过程的实现称为关联。 在postman中实现关联操作的步骤如下&#xff1a; 1、利用postman获取上一个接口指定的返回值…...

Java常见的bug

Java是一种强类型、面向对象的编程语言,有一些常见的bug或错误类型,尽管具体的bug会因项目和代码的不同而有所差异。以下是一些Java开发中常见的bug类型: 空指针异常(NullPointerException): 尝试在一个空对象上调用方法或访问属性时会引发空指针异常。这通常发生在没有对…...

gitea仓库镜像同步至gitlab

1、参考文档&#xff1a;仓库镜像 | Gitea Documentation 2、错误一&#xff1a;账号密码错误问题 解决方法&#xff1a; 出现以上错误为第三步用户名&#xff08;Oauth2应用名称&#xff09;或者密码&#xff08;Gitlab个人访问令牌&#xff09;错误。 1&#xff09;如下图1…...

服务器不备案的影响

服务器不备案的影响 不备案&#xff0c;不能解析域名。 但凡你的域名绑定到的是国内地址&#xff0c;你不备案&#xff0c;这个域名解析未来就可能会失效。 &#xff08;你借用的其它网站的子域名情况除外&#xff0c;因为他们的网站本身主域名有可能已经备案。&#xff09; …...

5 个适用于 Linux 的开源日志监控和管理工具

当Linux等操作系统运行时&#xff0c;会发生许多事件和在后台运行的进程&#xff0c;以实现系统资源的高效可靠的使用。这些事件可能发生在系统软件中&#xff0c;例如 init 或 systemd 进程或用户应用程序&#xff0c;例如 Apache、MySQL、FTP 等。 为了了解系统和不同应用程序…...

树莓派镜像安装 + 设置 + 镜像批量化操作 - 自动化烧写工具 (四)

简介 当需要大批量使用树莓派时, SD Card烧录过程中的重复和繁杂操作需要被工具给取代, AT Disk Imager这就出现了;软件介绍 实现监控读卡器&#xff0c;当SD Card接入读卡器时自动格式化、 烧写设定镜像、并自动软弹出设备;目前可设定参数: 1) 镜像文件&#xff0c; 烧录的镜…...

Redis 性能管理 主从复制与哨兵模式

目录 redis性能管理 内存碎片率 如何清理内存 面试题 Redis雪崩 Redis集群大面积故障 面试&#xff1a;Redis的缓存击穿 Redis的缓存穿透 Redis的集群高可用方案 redis的主从复制 哨兵模式 redis性能管理 redis的数据缓存在内存当中 info memory #在redis数据库中查…...

volatile 详解

目录 一. 前言 二. 可见性 2.1. 可见性概述 2.2. 内存屏障 2.3. 代码实例 三. 不保证原子性 3.1. 原子性概述 3.2. 如何解决 volatile 的原子性问题呢&#xff1f; 四. 禁止指令重排 4.1. volatile 的 happens-before 关系 4.2. 代码实例 五. volatile 应用场景 5…...

Flink Operator 使用指南 之 Flink Operator安装

介绍 Flink Kubernetes Operator 充当控制平面来管理 Apache Flink 应用程序的完整部署生命周期。尽管 Flink 的Native Kubernetes 集成已经允许用户在运行的 Kubernetes(k8s) 集群上直接部署 Flink 应用程序,但自定义资源和Operator Pattern 也已成为 Kubernetes 原生部署体…...

类与对象(上篇)

前言 在之前我们学的C入门主要是为现在学习类与对象打基础&#xff0c;今天我们才算真正开始学习C了。因为类与对象的知识点比较多&#xff0c;所以我们将它分为三部分讲解&#xff0c;今天我们学习类与对象的上篇。 一、面向过程和面向对象的初步认识 1、面向过程 面向过程顾…...

使用SpringBoot集成MyBatis对管理员的查询操作

增删改查中的查询操作&#xff0c;对所有的普通管理员进行查询操作。 效果展示&#xff1a; 不仅可以在打开页面时进行对管理员的自动查询操作&#xff0c;还可以在输入框进行查询。 首先是前端向后端发送POST请求&#xff0c;后端接收到请求&#xff0c;如果是有参数传到后端…...

数据报文去哪儿了

背景 今天遇到一个诡异的现象&#xff0c;当接口附加一个IP时&#xff0c;主IP业务正常&#xff0c;附加IP死活不行&#xff0c;tcpdump抓包确可以正常抓到到业务的报文&#xff0c;但是在PREROUTING raw添加规则确没有命中&#xff0c;说明报文没有到netfilter框架内&#xff…...

Mysql中join on中的like使用

1、使用mysql中的函数CONCAT(str1,str2,…) 返回结果为连接参数产生的字符串。如有任何一个参数为NULL &#xff0c;则返回值为 NULL。 SELECT * FROM Table1 INNER JOIN Table2 ON Table1.col LIKE CONCAT(%, Table2.col, %) 2、放弃使用join语句 SELECT * FROM Table1, T…...

微信运营神器:从群发到批量添加,让你的微信营销更轻松

在这个数字化时代&#xff0c;微信已经成为了我们生活中不可或缺的一部分。对于许多企业和个人来说&#xff0c;微信营销也是非常重要的一部分。但是&#xff0c;微信营销并不是一件容易的事情&#xff0c;需要花费大量的时间和精力。为了解决这个问题&#xff0c;今天我们将向…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008&#xff1a;是version产品的大版本。 R006&#xff1a;是release产品特性版本。 C009&#xff1a;是通用版 B0014&#xff1a;是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存&#xff1a;1GB 以上 硬盘&#xf…...