【C语言】深入理解指针(进阶篇)

一、数组名的理解

数组名就是地址,而且是数组首元素的地址。

任务:运行以下代码,看数组名是否是地址。

#include <stdio.h>
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };printf("&arr[0] = %p\n", &arr[0]);printf("arr     = %p\n", arr);return 0;
}

输出结果:

运行以上代码,我们发现数组名和数组首元素的地址打印的结果一模一样,数组名就是数组首元素(第一个元素)的地址

疑问:数组名如果就是首元素的地址,那么下面的代码证明理解?

#include <stdio.h>
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };printf("%d\n", sizeof(arr));return 0;
}

 输出结果:40,如果arr是数组首元素的地址,那么输出的应该是4/8才对。

其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名):sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名:这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

除此之外,任何地方使用数组名,数组名都表示首元素的地址。

要了解arr和&arr有什么区别,请看以下代码:

#include <stdio.h>
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };printf("&arr[0]   = %p\n", &arr);printf("&arr[0]+1 = %p\n", &arr+1);printf("arr       = %p\n", arr);printf("arr+1     = %p\n", arr+1);printf("&arr       = %p\n", &arr);printf("&arr+1     = %p\n", &arr+1);return 0;
}

 输出结果:

这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,这是因为&arr[0]和arr都是首元素的地址,+1就是跳过一个元素。

都是&arr和&arr+1相差40个字节,这里是因为&arr是整个数组的地址,+1操作就是跳过整个数组的。

到这里大家应该搞清楚数组名的意义了吧。

数组名是数组首元素的地址,但是有两个例外( sizeof(arr)和&arr )。

二、使用指针访问数组

有了前面的知识支持,再结合数组的特点,我们可以很方便的使用指针访问数组了。

#include <stdio.h>
int main()
{int arr[10] = { 0 };int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;int* p = arr;//输入for (i = 0; i < sz; i++){scanf("%d", p + i);//p+i 是地址}//输出for (i = 0; i < sz; i++){printf("%d ", *(p + i));//*(p+1) 解引用操作符(*),是数值}return 0;
}

 这个代码搞明白了,我们再试一下,如果我们再分析一下,数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的。那我们可以使用arr[i]访问数组元素,那p[i]是否也可以访问数组呢?

#include <stdio.h>
int main()
{int arr[10] = { 0 };int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;int* p = arr;//输入for (i = 0; i < sz; i++){scanf("%d", p + i);//scanf("%d", arr + i);//也可以写成这样}//输出for (i = 0; i < sz; i++){printf("%d ", p[i]);}return 0;
}

在第17行的地方,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]等价于*(p+i)

因为数组名arr和p是等价的,所以arr[i]等价于*(arr+i)。数组元素的访问在编译器的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

三、一维数组传参的本质

我们知道数组是可以传递给函数的,这里我们讨论一下数组传参的本质。

首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给一个函数后,在函数内部求数组的元素个数吗?

#include <stdio.h>
void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
int main()
{int arr[10] = { 0 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}

 输出结果:

 在函数内部求数组的元素个数,输出的结果是1,并没有正确获得元素个数。

这就要学习数组传参的本质了,第一节数组名的理解,我们知道:数组名就是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说数组传参本质上传递的是数组首元素的地址

 所以函数形参的部分理论上应该是用指针变量来接收首元素的地址。那么在函数内部写sizeof(arr)计算的是一个地址的大小(单位字节),而不是数组的大小(单位字节)。正是因为函数的参数部分本质是指针,所以在函数内部是没有办法求数组元素个数的

#include <stdio.h>
void test2(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
void test3(int *p)
{int sz3 = sizeof(p) / sizeof(p[0]);printf("sz3 = %d\n", sz3);
}
int main()
{int arr[10] = { 0 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test2(arr);test3(arr);return 0;
}

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

四、冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

方法一:

#include <stdio.h>
bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz-1; i++){int j = 0;for (j = 0; j < sz - 1 - i; j++){if (arr[j]>arr[j+1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}
int main()
{int arr[10] = { 3,1,7,5,8,9,0,2,4,6 };int sz = sizeof(arr) / sizeof(arr[0]);//冒泡排序bubble_sort(arr,sz);int i = 0;//打印输出for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}

方法二:优化

#include <stdio.h>void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{int i = 0;for (i = 0; i < sz - 1; i++){int flag = 1;//假设这⼀趟已经有序了int j = 0;for (j = 0; j < sz - i - 1; j++){if (arr[j] > arr[j + 1]){flag = 0;//发⽣交换就说明,⽆序int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}if (flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了break;}
}
int main()
{int arr[10] = { 3,1,7,5,8,9,0,2,4,6 };int sz = sizeof(arr) / sizeof(arr[0]);//冒泡排序bubble_sort(arr, sz);int i = 0;//打印输出for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}

五、二级指针

5.1 指针的定义

一级指针:是一个指针变量,指向一个普通变量,并保存该普通变量的地址

二级指针:是一个指针变量,指向一个一级指针,并保存该一级指针的地址

 二级指针是一个指向指针的指针变量。它存储了一个指针的地址,该指针又指向另一个变量的地址。

二级指针画图

 5.2 引入二级指针

#include <stdio.h>
int main()
{int a = 10;int b = 20;int* pa = &a;int** ppa = &pa;//一次解引用*ppa,此时类型int**ppa = &b;//二次解引用**ppa,此时类型int**ppa = 200;return 0;
}

逻辑关系如下:

a是一个int类型的变量,一级指针pa指向a,并保存a的地址。

二级指针变量ppa指向一级指针pa,并保存pa的地址

二级指针ppa解引用操作:

  • 一次解引用:*ppa的类型变成 int*(代表一级指针pa)间接改变了pa的指向,从a的地址变成了b的地址。
  • 二次解引用:ppa的类型变成了 int (代表变量b),此时ppa = 200;(等价于b=200)。

(1)下面举个例子:

#include <stdio.h>
int main()
{//普通变量int a1 = 1;int a2 = 1;int a3 = 1;//一级指针int* p1 = &a1;int* p2 = &a2;int* p3 = &a3;//二级指针int** s = &p1;
}

 (假设a1,a2.a3空间连续,p1,p2,p3空间连续)逻辑图如下:

表达式移动字节数/值的变化类型
s+1sizeof(int*)*1 =  4*1  =  4int**
*s+1sizeof(int)*1  =  4*1  =  4int*
**s+1a1+1  =  1+1  =  2int

分析:

  • s+1 表示二级指针s指向了p2,移动的字节数需要根据指向的数据的空间大小进行计算sizeof(int*)*1 = 4字节,此时s+1还是二级指针,所以类型是int*。
  • *s+1 先对s进行一次解引用为*s,相当于操控一级指针p1,然后*s+1,相当于p1指向了a2的地址,所以移动sizeof(int)*1 = 4字节,此时的类型为int*。
  • **s+1 先对s进行二次解引用为**s,相当于操控变量a1,然后a1加1,所以a1=2;a1的类型是int。

总结:在对二级指针变量s的移动时,s都会将已经保存的一级指针的类型进行解析步长(s+sizeof(p)*n);而一级指针*s(相当于p一级指针变量)会以保存的变量的类型进行解析步长(*s+sizeof(a)*n)。

 六、指针数组

指针数组是多个指针变量,以数组的形式存储在内存中,数组中的每个元素都是一个地址(指针),占有多个指针存储空间。指针数组即存放指针的数组

指针数组的声明方式为:数据类型 *数组名[数组长度]。

 6.1 指针数组模拟二维数组

#include <stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组名是数组首元素的地址,类型是int*,可以存放在parr数组中int* parr[3] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for(j = 0; j < 5; j++){printf("%d ", parr[i][j]);}printf("\n");return 0;}
}

parr数组的画图演示

 parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。

上述代码模拟出的二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的

七、数组指针变量

7.1 数组指针变量是什么

上一节我们学习了指针数组,指针数组是一种数组,数组中存放的是地址(指针)。

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

任务: int (*p)[5] = { },如何理解?

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

 这里()和[ ]优先级相同,根据结合律,从左到右运算

()里是*p,p先和*结合,先定义了指针,说明p是一个指针变量,然后指向的是一个大小为5个整数的数组。所以p是一个指针,指向一个数组,叫数组指针。

int (*p)[5]的画图

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

数组指针变量是依赖存放数组地址的,那么怎么获取数组的地址呢?这就要用到&数组名

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

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

 我们调试可以看到&arr和p的类型是完全一致的。

数组指针类型解析:

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

 7.3 二维数组传参的本质

有了数组指针的理解,我们来讲解一下二维数组传参的本质。

过去我们有一个二维数组,徐亚传参给一个函数的时候,我们是这样写的:

#include <stdio.h>
void test(int arr[3][5],int r,int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; 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} };test(arr, 3, 5);return 0;
}

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他写法吗?

这里我们再次理解一下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是一维数组。

arr数组

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

#include <stdio.h>
void test(int (*p)[5], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;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;
}

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

八、函数指针变量

8.1 函数指针变量的创建

函数指针即定义一个指向函数的指针变量。

 定义格式如下:

int (*p)(int x, int  y);  //注意:这里的括号不能掉

 这个函数的类型是有两个整型参数,返回值是整型。

思考:函数是否有地址?

让我们看看以下代码:

#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}

输出结果如下:

 观察发现,确实打印出来了地址,所以函数是有地址的,函数名技术函数的地址,当然也可以通过&函数名的方法获取函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针变量非常类似。如下:

void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以

 函数指针类型解析:

int (*pf3) (int x, int y)
|        |       ------------
|        |            |
|        |            pf3指向函数的参数类型和个数的交代
|        函数指针变量名
pf3指向函数的返回类型


int (*) (int x, int y)     //pf3函数指针变量的类型

 8.2 函数指针变量的使用

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

#include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int (*pf)(int, int) = Add;printf("%d\n", (*pf)(2, 3));printf("%d\n", pf(3,5));return 0;
}

输出结果:

九、函数指针数组

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

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

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

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

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

十、转移表

函数指针数组的用途:转移表

举例:计算机的一般实现

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输入操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

使用函数指针数组的实现 

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("输入操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输⼊有误\n");}} while (input);return 0;
}

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

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

相关文章

数据结构——算法的空间复杂度

【本节内容】 1.空间复杂度 2.常见空间复杂度 1.空间复杂度 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用额外存储空间大小的量度。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算…

动态规划课堂4-----子数组系列

目录 引入&#xff1a; 例题1&#xff1a;最大子数组和 例题2&#xff1a;环形子数组的最大和 例题3&#xff1a;乘积最大子数组 例题4&#xff1a;乘积为正数的最长子数组 总结&#xff1a; 结语&#xff1a; 引入&#xff1a; 在动态规划&#xff08;DP&#xff09;子…

java中移位<< >> <<< |数据类型转换

移位 x64转换二进制&#xff1a;100 0000 左移2位 &#xff1a; 1000 0000 0 对应十进制 i 256 >>右移 <<左移 >>无符号位右移 关于右移一位相当于整除2 数据类型及其转换 基本数据类型&#xff0c;数据类型范围 byte(-128~127)&#xff08;-2^7~2…

【开源】SpringBoot框架开发教学资源共享平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…

mysql如何开启手动提交事务

在mysql中&#xff0c;有一个变量autocommit&#xff0c;表示自动提交&#xff0c;默认为1&#xff0c;表示开启自动提交。通过以下命令查询 select autocommit;当autocommit为1时&#xff0c;任何一条sql语句都是一个事务&#xff0c;执行完由mysql自动提交。如果想自己决定什…

Java 抽象类和接口

登神长阶 第三阶 抽象类和接口 &#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340;&…

LVS负载均衡集群基础概念

目录 一、集群 1、集群概述 1.1 什么是集群 1.2 集群系统扩展方式 1.2.1 Scale UP&#xff08;纵向扩展&#xff09; 1.2.2 Scale OUT&#xff08;横向扩展&#xff09; 1.2.3 区别 1.3 分布式系统 1.4 分布式与集群 1.5 集群设计原则 1.6 集群设计实现 1.6.1 基础…

存储引擎的简介

简介&#xff1a; 1.在mysql存储引擎可以说就是指表的类型&#xff0c;可以称为表处理器&#xff0c;以表的形式存储。 2.他的功能就是接收上层传下来的指令&#xff0c;然后对表中的数据进行提取写入操作。 目的&#xff1a; 为了管理方便&#xff0c;我们把连接管理&#xf…

并查集(蓝桥杯 C++ 题目 代码 注解)

目录 介绍&#xff1a; 模板&#xff1a; 题目一&#xff08;合根植物&#xff09;&#xff1a; 代码&#xff1a; 题目二&#xff08;蓝桥幼儿园&#xff09;&#xff1a; 代码&#xff1a; 题目三&#xff08;小猪存钱罐&#xff09;&#xff1a; 代码&#xff1a; …

AWS的CISO:GenAI只是一个工具,不是万能钥匙

根据CrowdStrike的年度全球威胁报告,尽管研究人员预计人工智能将放大对防御者和攻击者的影响,但威胁参与者在其行动中使用人工智能的程度有限。该公司上个月在报告中表示:“在整个2023年,很少观察到GenAI支持恶意计算机网络运营的开发和/或执行。” 对于GenAI在网络安全中的…

《vtk9 book》 官方web版 第3章 - 计算机图形基础 (3 / 6)

3.8 演员几何 我们已经看到了光照属性如何控制演员的外观&#xff0c;以及相机如何结合变换矩阵将演员投影到图像平面上。剩下的是定义演员的几何形状&#xff0c;以及如何将其定位在世界坐标系中。 建模 计算机图形学研究中的一个重要主题是建模或表示物体的几何形状。…

贪心算法(蓝桥杯 C++ 题目 代表 注解)

介绍&#xff1a; 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每一步选择中都采取当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望最终能够得到全局最好或最优的结果的算法。它通常用来解决一些最优化问题&#xff0c;如最小生…

扩展CArray类,增加Contain函数

CArray不包含查找类的函数&#xff0c;使用不便。考虑扩展CArray类&#xff0c;增加Contain函数&#xff0c;通过回调函数暴露数组元素的比较方法&#xff0c;由外部定义。该方法相对重载数组元素的“”符号更加灵活&#xff0c;可以根据需要配置不同的回调函数进行比较 //类型…

电脑解锁后黑屏有鼠标--亲测!!不需要重装系统!!

问题&#xff1a;上周电脑黑屏&#xff0c;只有鼠标&#xff0c;鼠标还不能右键&#xff01;&#xff01; 中招&#xff1a;win10系统最新版火绒安全 &#xff0c;那你有概率获得开机黑屏套餐一份。 原因是&#xff1a;火绒把我们的explorer删除了导致黑屏&#xff0c;这个文…

Synthetic Temporal Anomaly Guided End-to-End Video Anomaly Detection 论文阅读

Synthetic Temporal Anomaly Guided End-to-End Video Anomaly Detection 论文阅读 Abstract1. Introduction2. Related Work3. Methodology3.1. Architecture3.1.1 Autoencoder3.1.2 Temporal Pseudo Anomaly Synthesizer 3.2. Training3.3. Anomaly Score 4. Experiments4.1.…

每日学习笔记:C++ STL 的Array

Array定义 Array模板有两个参数&#xff0c;一个是元素类型&#xff0c;一个是数组大小 Array初始化 Array的操作 Array当作C数组 Array的Tuple接口

接口自动化测试从入门到高级实战!

接口测试背景和必要性 接口测试是测试系统组件间接口&#xff08;API&#xff09;的一种测试&#xff0c;主要用于检测内部与外部系统、内部子系统之间的交互质量&#xff0c;其测试重点是检查数据交换、传递的准确性&#xff0c;控制和交互管理过程&#xff0c;以及系统间相互…

【Selenium】selenium介绍及工作原理

一、Selenium介绍 用于Web应用程序测试的工具&#xff0c;Selenium是开源并且免费的&#xff0c;覆盖IE、Chrome、FireFox、Safari等主流浏览器&#xff0c;通过在不同浏览器中运行自动化测试。支持Java、Python、Net、Perl等编程语言进行自动化测试脚本编写。 官网地址&…

伪分布Hadoop的安装与部署

1.实训目标 &#xff08;1&#xff09;熟悉掌握使用在Linux下安装JDK。 &#xff08;2&#xff09;熟悉掌握使用在Linux下安装Hadoop。 &#xff08;3&#xff09;熟悉掌握使用配置SSH免密登录。 2.实训环境与软件 环境 版本 说明 Windows 10系统 64位 操作电脑配置 …

ChatGPT 串接到 Discord - 团队协作好助理

ChatGPT 串接到 Discord - 团队协作好助理 ChatGPT 是由 OpenAI 开发的一个强大的语言模型&#xff0c;本篇文章教你如何串接 Discord Bot &#xff0c;协助团队在工作上更加高效并促进沟通与协作。使 ChatGPT 发挥出最大的功效&#xff0c;进一步提升工作效率和团队协作能力。…