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

【c语言——指针详解(3)】


文章目录

  • 一、字符指针变量
  • 二、数组指针变量
    • 1、 数组指针变量是什么?
    • 2、 数组指针变量怎么初始化
  • 三、⼆维数组传参的本质
  • 四、函数指针变量
    • 1、函数指针变量的创建
    • 2、函数指针变量的使⽤
    • 3、两段有趣的代码
      • 1)typedef 关键字
      • 2)typedef和define的区别
  • 五、函数指针数组
    • 1、函数指针数组的使用
  • 六、转移表


作者主页

一、字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;
⼀般使⽤:

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

在这里插入图片描述

还有⼀种使⽤⽅式如下:

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

代码 const char* pstr = “hello bit.”; 特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。

其实这是一个字符数组的知识点:

int main()
{const char* p = "hello world";//等同于char arr[]="hello world"; char* p=arr;差异在于数组是可以改变的,字符数组中的常量字符串是不能修改的printf("%c\n", *p); //是将字符串的首字符地址赋值给p//*p = 'q';//err 不加const也是常量字符串return 0;
}

在字符数组中的字符串都是常量字符串,是不能被解引用所修改的

字符数组的三种打印方式:

#include<string.h>
int main()
{const char* p = "hello world";printf("%s\n", p);//提供的是一个地址就可以打印printf("%s\n", "hello world");int len = strlen(p);int i = 0;for (i = 0; i < len; i++){printf("%c", *(p + i));}return 0;
}

在这里插入图片描述

int main()
{char arr[] = "abcdef";char* p = arr;printf("%s\n", arr);printf("%s\n", p);return 0;
}

在这里插入图片描述

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

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");elseprintf("str1 and str2 are not same\n");if (str3 == str4)//常量字符串是不能修改的,两个数组指的同一块空间printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

在这里插入图片描述

这⾥str3和str4指向的是同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当这两个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

二、数组指针变量

1、 数组指针变量是什么?

  • 字符指针 char* p 指向字符的指针,存放的是字符的地址。
  • 整型指针 int* p 指向整型的指针,存放的是整型的地址。
  • 数组指针 指向数组的指针,存放的是数组的地址。
    数组指针是一种指针变量,是存放数组地址的指针变量 。
    注:指针数组是一种数组,是存放指针的数组。
char ch = 'w';
char* pc = &ch;int n = 10;
int* p = &n;

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

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

下⾯代码哪个是数组指针变量?

int *p1[10];
int (*p2)[10];

思考⼀下:p1, p2分别是什么?
数组指针变量是:

int (*p)[10];

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

int main()
{int arr[10] = { 0 };int(*p)[10] = &arr;//取出的是数组的地址//p是数组指针,*说明是指针,p指向的是数组,数组10个元素,每个元素的类型是intreturn 0;
}

代码二:

int main()
{char arr[5];char(*p)[5] = &arr;//p是数组指针//char (*)[5] 是数组指针类型char* arr[5];char* (*p)[5] = &arr;return 0;
}

因此在这里就可以解释&arr-- > &arr + 1为什么跳过40个字节,因为int( * p)[10] = &arr,它的类型是int (*)[10],是40个字节的大小。

2、 数组指针变量怎么初始化

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

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

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

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

在这里插入图片描述
接下来再看两组代码,体会数组指针变量是如何使用的

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *(p + i));}return 0;
}

在这里插入图片描述

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int (*p)[10] = &arr;int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", (*p)[i]);//不能采用+1因为是代表的是整个数组,只能挨个访问//            (*&arr)[i]//            arr[i]}return 0;
}

在这里插入图片描述

三、⼆维数组传参的本质

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

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

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
如下图:
在这里插入图片描述
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。

二维数组的数组名就是第一行的地址,第一行是一个一维数组
int arr[3][5];
p就是数组指针-是指向一维数组的指针
int(*p)[5] = arr;

如下:

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]));//= *(*(p + i) + j)//+i是按照一行一行计算的}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;
}

在这里插入图片描述

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

四、函数指针变量

1、函数指针变量的创建

什么是函数指针变量呢?
根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

函数指针-指向的是函数-存放的是函数的地址

那么函数是否有地址呢?
我们做个测试:

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是函数指针变量,int是pf3指向函数的返回类型,int x inty是两个参数int (*) (int x, int y) //函数指针变量pf3的类型

2、函数指针变量的使⽤

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

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(2, 3));//两种写法是一样的效果printf("%d\n",Add(2,3));//直接调用函数return 0;
}

输出结果:
在这里插入图片描述

int Add(int a, int b)
{return a + b;
}int main()
{int arr[8] = { 0 };int(*pa)[8] = &arr;//pa是数组指针变量int (*pf)(int, int) = &Add;//pf就是函数指针变量printf("%p\n", &Add);printf("%p\n", Add);return 0;
}

在这里插入图片描述

3、两段有趣的代码

代码1

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

可以这样理解:

int main()
{(*(void(*)())0)();//函数调用//1.将0强制类型转换为void(*)()类型的函数指针//2.调用0地址处放的这个函数// (int*)0--强制类型转换return 0;
}

代码2

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

这样理解:

函数声明
声明的函数的名字叫:signal
signal函数有两个参数,第一个参数的类型是int
第二个参数的类型是void(*)(int)的函数指针类型,该指针可以指向一个函数,指向的函数参数是int,返回类型是void
signal函数的返回类型是void(*)(int)的函数指针,该指针可以指向一个函数,指向的函数参数是int,返回类型是void
void(*(signal(int, void(*)(int)))(int));
//返回类型 名字        函数指针类型
可以看成void(*)(int)  signal(int, void(*)(int));
void(*)(int)就是它的返回类型Add(int, char);//函数声明

两段代码均出⾃:《C陷阱和缺陷》这本书

1)typedef 关键字

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:

typedef unsigned int uint;
//将unsigned int 重命名为uint
typedef unsigned int uint;
int main()
{unsigned int num1;uint num1;//unit等同于unsigned intreturn 0;
}

如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 pint,这样写:

typedef int* pint;
//typedef 对指针类型重命名
typedef int* pint;
int main()
{int* p1 = NULL;pint p2 = NULL;return 0;
}

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

typedef int(*parr_t)[5]; 
typedef int(*parr_t)[5];//parr_t等价于int(*)[5] , parr_t是个类型
int main()
{int arr[5] = { 0 };int(*p)[5] = &arr;//p是数组指针变量,p是变量的名字//int (*)[5]--数组指针类型parr_t p2 = &arr;//数组指针变量p2return 0;
}

函数指针类型的重命名也是⼀样的,⽐如,

void test(char* s)
{}typedef void(*pf_t)(char*);
int main()
{void (*pf)(char*) = test;//pf是函数指针变量,只是个名字,test是函数地址// 类型是void(*)(char*)pf_t pf2 = test;return 0;
}

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

void(*(signal(int, void(*)(int)))(int));
可以看成void(*)(int)  signal(int, void(*)(int));
//简化后的代码
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);

2)typedef和define的区别

既然这样那么typedef和define的作用好像相同,那么它们实际有什么区别呢?

  1. 作用域:
  • typedef 定义的类型别名具有作用域,可以在函数内部或全局范围内定义。
  • #define 定义的宏没有作用域限制,它们在定义后的所有代码中都有效,直到遇到 #undef 指令或文件结束。
  1. 多重定义:
  • 使用 typedef 时,如果在一个作用域内多次定义相同的类型别名,编译器会报错。
  • 使用 #define 时,如果多次定义相同的宏,后面的定义会覆盖前面的定义,这可能导致难以追踪的错误。
typedef int* ptr_t;
#define PTR_T int*//意思是PTR_T的内容是int*
int main()
{ptr_t p1;//p1是整型指针PTR_T p2;//p2是整型指针ptr_t p1, p2;//int* p1,p2;  p1,p2是整型指针PTR_T p3, p4;//int* p3,p4; p3是指针,p4是整型return 0;
}

实际上,由于 #define PTR_T int*,PTR_T p3, p4; 会被预处理为 int* p3, p4;。这意味着 p3 是一个指向 int 的指针,而 p4 是一个 int 类型的变量(不是指针)!这是因为逗号分隔的变量声明中,只有第一个变量会被 * 修饰。
正确的理解应该是:

ptr_t p1, p2; // int* p1; int* p2; p1 和 p2 都是整型指针  
PTR_T p3, p4; // int* p3; int p4; p3 是整型指针,p4 是整型变量

五、函数指针数组

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

int* arr[10];
//数组的每个元素是int*
整数数组:是存放整型的数组
字符数组:是存放字符的数组
指针数组:存放指针的数组
char* arr1[5];//字符指针数组
int* arr2[7];//整型指针数组

如果要把多个相同类型的函数指针存放在一个数组中,这个数组就是:函数指针数组
那函数指针的数组如何定义呢?

int (*parr1[3])();

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

1、函数指针数组的使用

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 (*pf1)(int, int) = Add;//存放函数Add的地址//int (*pf2)(int, int) = Sub;//int (*pf3)(int, int) = Mul;//int (*pf4)(int, int) = Div;int (*pfArr[4])(int, int) = { Add,Sub,Mul,Div };//pfArr就是函数指针数组int i = 0;for (i = 0; i < 4; i++){int ret = pfArr[i](8, 4);printf("%d\n", ret);}return 0;
}

在这里插入图片描述
以上打印的是根据四种运算法则以及两个参数8和4得出的结果。根据这种方式,我们是否可以通过函数指针数组实现一个计算器?

六、转移表

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

举例:计算器的⼀般实现:
想写一个计算机器:完成2个整数的运行。

  1. 加法
  2. 减法
  3. 乘法
  4. 除法
//代码一
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;
}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("请选择:");scanf("%d", &input);switch (input){case 1:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}

在上述代码的实现中,我们发现了两个问题:

  1. 代码冗余
  2. 如果扩展功能,代码也会大量增加

那我们可以考虑使用函数指针数组来实现:

//代码二:
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;
}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;//创建一个函数指针数组//转移表int (*pfArr[5])(int,int) = {NULL,Add,Sub,Mul,Div};//                           0    1   2   3   4do{menu();printf("请选择:");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("%d\n", ret);}else if (input == 0){printf("退出计算器\n");break;}else{printf("选择错误,重新选择\n");}} while (input);return 0;
}

我们可以发现,在代码一的主函数部分有大量的重复部分,所以我们可以换一种思路,将重复的部分再定义一个函数calc。

//代码三
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;
}void menu()
{printf("************************************\n");printf("******* 1.add      2.sub     *******\n");printf("******* 3.mul      4.div     *******\n");printf("*******       0.exit         *******\n");printf("************************************\n");
}void calc(int (*pf)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入2个数\n");scanf("%d %d", &x, &y);ret = pf(x, y);printf("%d\n", ret);
}int main()
{int input = 0;do{menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:calc(add);//add是地址,传递给函数指针calc,calc再去调用函数break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}

在这里对前面学过的知识做一个小总结:

一级指针  char* p;  int* p;
二级指针  char** pp = &p; int** pp = &p;
数组指针--指向的是数组  int arr[5];  int(*p)[5] = &arr;//&arr取的是数组arr的地址
函数指针--指向的是函数  
char* test(int n,char* s)
{ }
char* (*pf)(int, char*) = test;//pf是函数指针变量指针数组--数组里存放的都是指针
char* arr[5];
int* arr2[5];
double* arr3[9];
float* arr4[6];
函数指针数组
char* (*pfArr[4])(int, char*);拓展:指向函数指针数组的指针
char* (*(*p)[4])(int, char*) = &pfArr;//取出的是函数指针数组的地址
//p就是一个指向函数指针数组的指针

相关文章:

【c语言——指针详解(3)】

文章目录 一、字符指针变量二、数组指针变量1、 数组指针变量是什么&#xff1f;2、 数组指针变量怎么初始化 三、⼆维数组传参的本质四、函数指针变量1、函数指针变量的创建2、函数指针变量的使⽤3、两段有趣的代码1&#xff09;typedef 关键字2&#xff09;typedef和define的…...

QT系统学习篇(2)- Qt跨平台GUI原理机制

一、Qt工程管理 1、新建项目&#xff1a; 我们程序员新建项目对话框所有5类项目模板 Application: Qt的应用程序&#xff0c;包含Qt Quick和普通窗口程序。 Library: 它可以创建动态库、静态库、Qt Creator自身插件、Qt Quick扩展插件。 其他项目: 创建单元测试项目、子目录项…...

运用MinIO技术服务器实现文件上传——在Linux系统上安装和启动(一)

# MinIO 单机版环境搭建详解 ## 1. 简介 随着大数据时代的到来&#xff0c;数据存储的需求日益增大&#xff0c;如何有效地存储和管理大规模的非结构化数据成为许多企业和开发者面临的挑战。MinIO 作为一个高性能、分布式对象存储系统&#xff0c;致力于为用户提供简单、快速…...

Python技术深度探索:从基础到进阶的实践之旅(第一篇)

Python技术深度探索&#xff1a;从基础到进阶的实践之旅&#xff08;第一篇&#xff09; 在编程的世界里&#xff0c;Python以其简洁的语法、强大的库支持和广泛的应用领域&#xff0c;成为了无数开发者心中的“瑞士军刀”。无论是数据分析、机器学习、Web开发&#xff0c;还是…...

利士策分享,旅游是否要舟车劳顿才能尽兴?

利士策分享&#xff0c;旅游是否要舟车劳顿才能尽兴&#xff1f; 国庆假期&#xff0c;当夜幕降临&#xff0c;城市灯火阑珊&#xff0c;一场关于美食与等待的较量悄然上演。 李女士在北京天坛公园附近餐厅的等位经历——前方1053桌的壮观景象&#xff0c;不仅让人咋舌&#xf…...

C++入门——类的默认成员函数(取地址运算符重载)

文章目录 一、const成员函数二、取地址运算符重载总结 一、const成员函数 1.将const修饰的成员函数称之为const成员函数&#xff0c;const修饰成员函数放到成员函数参数列表的后⾯。2.const实际修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的任何成员进…...

学习记录:js算法(四十九):二叉树的层序遍历

文章目录 二叉树的层序遍历网上思路队列循环 总结 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 图一&#xff1a; 示例 1&#xff1a;如图一 输入&#xff1a;roo…...

【PCB工艺】表面贴装技术中常见错误

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 1、什么是SMT和SMD2、表面贴装技术的优势是什么&#xff1f;3、通孔和表面贴装技术之间的区别是什么&#xff1f;4、焊…...

3.使用条件语句编写存储过程(3/10)

引言 在现代数据库管理系统中&#xff0c;存储过程扮演着至关重要的角色。它们是一组为了执行特定任务而编写的SQL语句&#xff0c;这些语句被保存在数据库中&#xff0c;可以被重复调用。存储过程不仅可以提高数据库操作的效率&#xff0c;还可以增强数据的安全性和一致性。此…...

Effective C++中文版学习记录(三)

Effective C中文版学习记录&#xff08;三&#xff09; 章节三&#xff1a;资源管理 进度&#xff1a;17/55 文章目录 Effective C中文版学习记录&#xff08;三&#xff09;条款13、以对象管理资源条款14、在资源管理类中小心copying行为条款15、在资源管理类中提供对原始资…...

VBA学习(76):文件合并神器/代码

1.定义变量 Dim savePath As String Dim SaveFile As String Dim dataFolder As String Dim FileSystem As Object Dim folder As Object Dim FileExtn As String Dim t As Integer Dim blnCkb As Boolean 2.自定保存文件名、选择待合并文件所在文件夹 Private Sub CkbName_…...

非农就业数据超预期,美联储降息步伐或放缓?

KlipC报道&#xff1a;当地时间10月4日&#xff0c;美国劳工部发布了最新的非农就业数据。数据显示&#xff0c;9月非农就业人数增加25.4万人&#xff0c;远超市场预期。失业率为4.1%&#xff0c;比上月略降0.1个百分点。平均时薪环比增长0.4%&#xff0c;亦高于市场预期。此外…...

每日OJ题_牛客_乒乓球筐_哈希_C++_Java

目录 牛客_乒乓球筐_哈希 题目解析 C代码 Java代码 牛客_乒乓球筐_哈希 乒乓球筐__牛客网 (nowcoder.com) 描述&#xff1a; nowcoder有两盒&#xff08;A、B&#xff09;乒乓球&#xff0c;有红双喜的、有亚力亚的……现在他需要判别A盒是否包含了B盒中所有的种类&#…...

基于SpringBoot+Vue的酒店客房管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

检索增强思考 RAT(RAG+COT):提升 AI 推理能力的强大组合

在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;已经取得了显著的进展&#xff0c;能够生成类似人类的文本并回答各种问题。然而&#xff0c;它们在推理过程中仍面临一些挑战&#xff0c;例如缺乏对事实的准确把握以及难以处理复杂的多步骤问题。为了解决…...

python脚本实现Redis未授权访问漏洞利用

之前介绍过Redis未授权访问漏洞&#xff0c;本文使用python实现Redis未授权访问检测以及对应三种getshell。 1 测试环境准备 CentOS 7&#xff08;192.168.198.66/24&#xff09;&#xff1a;安装 Redis 服务器并用 root 权限开启服务&#xff0c;关闭保护模式&#xff1b;安…...

简单线性回归分析-基于R语言

本题中&#xff0c;在不含截距的简单线性回归中&#xff0c;用零假设对统计量进行假设检验。首先&#xff0c;我们使用下面方法生成预测变量x和响应变量y。 set.seed(1) x <- rnorm(100) y <- 2*xrnorm(100) &#xff08;a&#xff09;不含截距的线性回归模型构建。 &…...

上海理工大学《2023年+2019年867自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《上海理工大学867自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2023年真题 2019年真题 Part1&#xff1a;2023年2019年完整版真题 2023年真题 2019年…...

计算机网络面试题——第三篇

1. TCP超时重传机制是为了解决什么问题 因为TCP是一种面向连接的协议&#xff0c;需要保证数据可靠传输。而在数据传输过程中&#xff0c;由于网络阻塞、链路错误等原因&#xff0c;数据包可能会丢失或者延迟到达目的地。因此&#xff0c;若未在指定时间内收到对方的确认应答&…...

Elasticsearch 开放推理 API 增加了对 Google AI Studio 的支持

作者&#xff1a;来自 Elastic Jeff Vestal 我们很高兴地宣布 Elasticsearch 的开放推理 API 支持 Gemini 开发者 API。使用 Google AI Studio 时&#xff0c;开发者现在可以与 Elasticsearch 索引中的数据进行聊天、运行实验并使用 Google Cloud 的模型&#xff08;例如 Gemin…...

react-问卷星项目(7)

实战 React表单组件 入门 重点在于change的时候改变state的值&#xff0c;类似vue的双向数据绑定v-model&#xff0c;即数据更新的时候页面同步更新&#xff0c;页面数据更新时数据源也能获得最新的值&#xff0c;只是Vue中设置在data中的属性默认绑定&#xff0c;React中需…...

【git】main|REBASE 2/6

很久没合并代码合并出现冲突&#xff0c;自动进入了 main|REBASE 2/6 的提示: 【git】main|REBASE 2/6 It looks like you’ve encountered several merge conflicts after a git pull operation while a rebase is in progress. Here’s how you can resolve these conflict…...

51单片机的水质检测系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温度传感器ph传感器浑浊度传感器蓝牙继电器LED、按键和蜂鸣器等模块构成。适用于水质监测系统&#xff0c;含检测和调整水温、浑浊度、ph等相似项目。 可实现功能: 1、LCD1602实时显示水温、水体ph和浑浊度 2、温…...

【python面试宝典7】线程池,模块和包

目录标 题目37&#xff1a;解释一下线程池的工作原理。题目38&#xff1a;举例说明什么情况下会出现KeyError、TypeError、ValueError。题目39&#xff1a;说出下面代码的运行结果。题目40&#xff1a;如何读取大文件&#xff0c;例如内存只有4G&#xff0c;如何读取一个大小为…...

Android input系统原理二

1.inputmanager启动源码分析 在SystemServer.java中构造了 inputmanagerservice的对象&#xff0c;在其构造函数中&#xff0c;最重要的是这个nativeInit函数。 下面是核心代码 inputManager new InputManagerService(context);public InputManagerService(Context context)…...

Oracle登录报错-ORA-01017: invalid username/password;logon denied

接上文&#xff1a;Oracle创建用户报错-ORA-65096: invalid common user or role name 我以为 按照上文在PDB里创建了用户&#xff0c;我以为就可以用PLSQL远程连接了&#xff0c;远程服务器上也安装了对应版本的Oracle客户端&#xff0c;但是我想多了&#xff0c;客户只是新建…...

JavaScript 获取浏览器本地数据的4种方式

JavaScript 获取浏览器本地数据的方式 我们在做Web开发中&#xff0c;客户端存储机制对于在浏览器中持久化数据至关重要。这些机制允许开发者存储用户偏好设置、应用状态以及其他关键信息&#xff0c;从而增强用户体验。本文将介绍几种常用的JavaScript获取浏览器本地数据的方…...

77寸OLED透明触摸屏有哪些应用场景

说到77寸OLED透明触摸屏&#xff0c;那可真是市场营销中的一大亮点&#xff0c;应用场景多到数不清&#xff01;我这就给你细数几个热门的&#xff1a; 商业展示&#xff1a;这可是77寸OLED透明触摸屏的拿手好戏&#xff01;在高端零售店铺里&#xff0c;它可以作为陈列窗口&am…...

二分解题的奇技淫巧都有哪些,你还不会吗?

先说一下我为什么要写这篇文章。 “二分“ 查找 or ”二分“ 答案的思想大家想必都知道吧&#xff08;如果不懂&#xff0c;可以看一下我之前写的一篇文章&#xff09;。 二分求解 可是呢&#xff1f;思想都会&#xff0c;做题的时候&#xff0c;就懵圈了。 这个题竟然考的是…...

LeetCode-871 最低加油次数

重启力扣每日一题系列&#xff01; 因为过去两个月里掉粉掉的好严重&#xff0c;我想大抵是因为更新的频率不如上半年了&#xff0c;如果我重启了每日一题系列那岂不是至少是每日一更☝&#x1f913;&#xff1f; 也不是每天都更&#xff0c;我有两不更&#xff0c;特难的就不…...