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

指针的深入讲解

本章重点:

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数

我们在指针的初阶的时候主要讲了:

1.指针就是变量,用来存放地址,地址唯一标识一块内存空间

2.指针的大小是固定4个字节/8个字节(32为平台/64位平台)

3.指针是有类型的,指针的类型决定了指针+-整数的步长,指针解引用操作的时候的权限。

4.指针的运算

这里我们来探讨指针更高级的主题

1.字符指针

 字符指针就是char*类型的指针

一般使用:

使用1:

使用2:

一般表达式都有两个属性,值属性和类型属性。

char* p="abcdef",是把字符串首元素的地址放到p中。用%s打印时,只需要提供一个起始地址即可。这里"abcdef"是一个常量字符串,不能被该,所以我们要加上const修饰

#include<stdio.h>//字符指针
int main()
{const char* p = "abcdef";//把字符串首元素的地址放到p中printf("%s", p);return 0;
}

要是想把字符串放进一个变量里面,需要创建一个数组

练习:分析下列代码及其结果产生的原因

首先p1,p2中存放的都是常量字符串,常量字符串不能被改,没必要存放多份,在只读数据区,相同的常量字符串只需要存一个,而p1,p2都是首元素a的地址,指向的是同一块内存空间,所以p1==p2。而使用相同的常量字符串来初始化数组时会开辟出不同的内存块,arr1和arr2是数组名,数组名表示首元素的地址,内存空间不同,所以首元素的地址不同。

2.指针数组

指针数组是一个存放指针的数组

int arr[10]是整型数组--用来存放整形数据的数组

char arr[10]是字符数组--用来存放字符型数据的数组

int *arr[10]是整形指针数组--用来存放整型指针类型数据的数组

char *arr[10]是字符型指针数组--用来存放字符型指针类型数据的数组

我们可以用一个指针数组来模拟一个二维数组(初阶讲过)

#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[] = { 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("%d ",parr[i][j]);}printf("\n");}return 0;
}

打印方法1:

parr[i]是找到下标为i的元素(整形指针类型)的地址,parr[0]-->arr1,就是arr1这个数组的首元素的地址,parr[1]-->arr2;parr[2]-->arr3;然后让你后我们要求第几个元素就再让这个地址加上几,求arr1[0]的地址就是arr1+0,arr1[1]的地址就是arr1+1,(指针与指针相减跳过的是元素的个数,所以指针+元素的个数,得到的就是下一个指针),我们在对地址进行解引用就可以得到再arr[i][j]处元素,从而模拟出一个二维数组。

打印方法2:

有两种理解

arr[i]-->*(p+i)-->*(arr+i)-->i[arr] 

所以*(arr[i]+j)-->arr[i][j]

还可以理解成arr[i]-->arr1,arr2,arr3,而访问元素通过下标来访问,就是arr1[0],访问数组1的第一个元素。

3.数组指针

[]的优先级高于*,所以要加上括号表示先结合)

整型指针--指向整形的指针int a=3; int* p=&a;指针变量p是int*类型的,指向的是元素是a,a是一个整形类型的数据,所以p是整形指针

数组指针--指向数组的指针int(*p)[10],p是一个数组指针,p可以指向一个数组,该数组有10个元素,每个元素是int类型的(指向一个有10个元素的整型数组)。

 这里p的类型是int(*)[10](数组指针类型),存放的是arr这个数组所有元素的地址(之前在数组章节说过arr表示的首元素地址,两种情况除外1.sizeof 2.&arr)

再举个例子: 

有个指针数组,char* arr[5]={0};

若要将这个指针数组整个数组的地址存放起来,需要用什么接收

char *(*p)[5],需要用一个数组指针接收,这个数组指针指向的内容是这个指针数组就是char*arr[5],这个数组指针的类型是char*(*)[5]。

在定义类型时int(*)[ ] ,一定要把[ ]里面的数加上,指明指向的数组有几个元素。

数组指针一般用在二维数组中。

对于一维数组:
我们如果想访问它的元素,或者通过他的地址改变它的元素,只需要存入首元素的地址,用一个指针变量存放即可,用数组指针存放整个数组的指针还会将问题复杂化。

#include<stdio.h>//数组指针
int main()
{int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };int* p = arr;int i = 0;for (i = 0;i < 10;i++){printf("%d ", *(p + i));}return 0;
}
#include<stdio.h>//数组指针
int main()
{int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };int (*p)[10] = &arr;int i = 0;for (i = 0;i < 10;i++){printf("%d ", *(*p + i));//解引用p就相当于通过p找到了arr这个数组,而arr又是数组名,数组名就是首元素的地址,再通过他在到底i个元素的地址,在解引用,才能访问到数组内容}//int a = 0;//int*p=&areturn 0;
}

对于二维数组

#include<stdio.h>//数组指针
void print_(int arr[][4], 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][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };print_(arr, 3, 4);return 0;
}

根据下边实际传入地址我们知道列是不可以省略的。需要将第一行的元素都传入函数。 

我们也可以用数组指针表示,二维数组的数组名表示的也是首元素的地址,但是二维数组的首元素是他第一行的元素。我们接受这个二维数组,就应该用一个数组指针接受。

#include<stdio.h>//数组指针
void print_(int (*p)[4], 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][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };print_(arr, 3, 4);return 0;
}

*(*(p + i) + j)的理解:存的是一行的元素的地址,(p+i)就相当于第i行的元素的地址(虽然数据是连续存储的,但是我们可以把发看成一个三行四列的)*(p+i)就是通过这个地址找到了第一行的元素,(而二维数组可以看成一维数组的数组,就是可以将指向的第一行看成arr[0]此时arr[0]是一个数组名,数组名又是这行元素的首元素的地址,所以,可以通过,首元素的地址+j,找到第j列的元素的地址,在对这个地址进行解引用,就得到了第i行,第j列元素的地址。

int(*p)[4];p的类型是:int(*)[4],p是指向一个整型数组的,数组5个元素 int[5],p+1,表示跳过5个int元素的数组。 

数组指针和指针数组:

 指针数组就是一个数组中放的元素都是由地址组成的数组,可以是&a,&b,&c,将几个元素的地址放在指针数组中,也可以是将几个数组的首元素的地址放在指针数组中,例如:模拟二维数组(我们就可以通过这个每个数组的首元素找到这个数组,在由数组名找到每一个元素,对他进行访问。

而数组指针是指向一个数组的指针,存放的是这个数组整个数组的地址,但我们一般不单于存放一个数组的地址,我们通常使用的是存放一个二维数组(数组名表示第一行元素的地址,将第一行元素传过去由一个数组指针接收,然后通过这个数组指针访问每一行的元素)

我们来分析下面代码的意思

int arr[5];

int *parr1[10];

int(*parr2)[5];

int(*parr3[5])[3];

arr是一个数组,存放5个整型元素

parr1是一个指针数组,存放指针变量的数组

parr2是一个数组指针,是一个指针,指向一个int [5]有五个元素组成的整型数组

parr3是一个存放数组指针的数组,首先在()里面parr3先和[]结合,构成一个数组,然后这个数组的类型是int(*)[3]是一个数组指针类型,表示一个数组里有5个元素,每个元素指向的都是一个有三个元素的数组。 

4.数组参数和指针参数

一维数组传参:

 void test1(int arr1[])//数组传参由数组接受,元素个数可以不写,传入的是首元素的地址
{ }
void test1(int arr1[10])
{ }
void test1(int *arr)//数组传参实际上传入的是首元素的地址,可以有指针变量接收
{ }
void test2(int*arr[])//指针数组由数组接受
{ }
void test2(int **arr)//指针数组是一个存放指针的数组,相当于存放的元素是指针变量,指针变量的地址应该由二级指针接收(二级指针是一个存放一级指针的指针)
{ }
int main()
{
    int arr1[10] = { 0 };
    int* arr2[20] = { 0 };
    test1(arr1);
    test2(arr2);
    return 0;
}

二维数组的传参: 

void test(int arr[3][4])//二维数组传参由一个二维数组接收
{ }
void test(int arr[][4])//行可以省略,列不能省略,传入的是第一行元素的地址,需要知道第一行有多少个元素
{ }
void test(int(*p)[4])//二维数组传参,实际上传的是第一行的地址,应该用一个数组指针接收一行的地址,还需要知道一行有多少个元素
{ }

int main()
{
    int arr[3][4];
    test(arr);
    return 0;
}

 一级指针传参:

void test(int *p)//一级指针传参由一级指针接收
{ }
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    int* p = arr;
    test(p);
    return 0;
}

如果函数的形式参数部分是一级指针:传入的可能是一级指针变量,可能是某个元素的地址(地址由指针接收),也可能是数组名(数组名就表示首元素的地址),不过还要注意一级指针的类型

二级指针传参:

void test(int** p)
{ }
int main()
{
    int n = 0;
    int* p = &n;
    int** pp = &p;
    test(pp);//二级指针传参由二级指针接收
    test(&p);//一级指针的地址传参有二级指针接收
    return 0;
}

 如果函数的形式参数是二级指针,传入的可能是二级指针变量,也可能是一级指针的地址,也可能是指针数组(指针数组是一个存放指针的数组,相当于存放的元素是指针变量,指针变量的地址应该由二级指针接收(二级指针是一个存放一级指针的指针))。

 5.函数指针

指向函数的指针就是函数指针

函数在创建时就有地址,和全局变量一样,函数在代码区,代码区是只读的,不能被修改。

&数组名,是取出数组的地址,同理&函数名是取出函数的地址,但是对于函数来说&函数名和函数名都是取出函数的地址(对于函数没有首元素一说)

定义一个函数指针有两种写法:

int ADD(int x, int y)
{
    return x + y;
}
int main()
{
    int (*pf)(int, int) = &ADD;
    int (*pf)(int, int) = ADD;
    return 0;
}

这里(*pf)表示pf是一个指针,指向的是(int,int)是一个函数,函数的返回值类型为int,所以pf是一个函数指针,指针类型是int(*)(int,int)。

若果我们定义一个整形指针的话,我们可以解引用这个指针变量,访问它指向的元素,并可以对这个元素进行修改,那我们也可以通过解引用访问这个函数,从而进行传参就是(*pf)(x,y)。这里我们也可以不解引用,访问函数就行传参,再定义函数指针的第二种写法将函数名赋给指针变量pf那么pf就相当于这个函数名,我们一般的函数调用是ADD(x,y),直接调用函数传参,那么同理,我们也可以写成pf(x,y)。但需要注意加*号的时候一定要带上括号,否则会先调用函数(就是利用第二种调用方法,调用过后在对这个值进行解引用,此时这个值是函数返回的一个int类型的数不能对他进行解引用)

所以利用函数指针调用函数也有两种写法:

#include<stdio.h>

//函数指针
int ADD(int x, int y)
{
    return x + y;
}
int main()
{
    //int (*pf)(int, int) = &ADD;
    int (*pf)(int, int) = ADD;
    int ret1 = pf(3, 4);
    int ret2 = (*pf)(3,4);
    printf("%d\n", ret1);
    printf("%d\n", ret2);

    return 0;
}

函数指针的初步应用:
 可以在将这个函数以函数指针的形式传递给另一个函数,在另一个函数使用这个函数时,就不需要再调用这个函数了。

#include<stdio.h>//函数指针的应用
int ADD(int x, int y)
{return x + y;
}
void calc(int(*p)(int, int))
{int a = 3;int b = 4;int ret = p(3, 4);printf("%d\n", ret);
}
int main()
{calc(ADD);return 0;
}

观察两段有趣的代码(来自于C陷阱和缺陷)

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

//void(*)(),是函数指针类型,(void(*)())0,是将int型的0,强制类型转换为函数指针类型,这个代码是一次函数调用,调用的是0作为地址处的函数,首先把0强制类型转换为:无参,返回值类型为void的函数的地址。在调用0地址处的这个函数。


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

//这是一个函数指针类型的函数声明,signal函数的的第一个参数类型为int,第二个参数的类型为 void(*)(int)函数指针类型,函数指针类型的函数声明的函数指针类型是一个指向函数的参数是int,指向函数的返回值类型void的函数指针。signal函数的返回值类型没写,也默认是void。

这里我们可以知道*p(int,int)是一个函数声明,先于函数结合,p函数返回值类型为void,(*p)(int,int)是一个函数指针,指向的函数的参数类型为int ,int型,指向函数的返回值类型也是void。()的优先级大于*。

我们将第二段代码简化一下:

//typedef unsigned int uint;//把unsigned int,定义为uint
typedef void(*pf_t)(int);//把void(*)(int)类型重命名为pf_t
int main()
{
    //void(*signal(int, void(*)(int)))(int);
    pf_t signal(int, pf_t);
    return 0;
}

进一步应用函数指针:实现简单加减乘除的计算器

初写代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>//函数指针的应用
void menu()
{printf("******************************************\n");printf("***********   1.add   2.sub    ***********\n");printf("***********   3.mul   4.div    ***********\n");printf("***********   0.exit           ***********\n");printf("******************************************\n");}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 input = 0;int x = 0;int y = 0;do{menu();printf("请输入你的选择->");scanf("%d", &input);printf("请输入两个整数->");scanf("%d %d", &x, &y);switch (input){case 1:printf("%d\n", add(x, y));break;case 2:printf("%d\n", sub(x, y));break;case 3:printf("%d\n", mul(x, y));break;case 4:printf("%d\n", div(x, y));break;case 0:printf("退出游戏\n");break;default:printf("输入错误请重新输入\n");break;}} while (input);return 0;
}

这个代码我们可以实现简单的加减乘删除,但如果我们输入的0,或者输入错误的时候他不会直接提醒我们输入错误,而是会继续让我们输入两个整数,这个时候我们就应该考虑一下如何改进这个代码,初步改进:我们可以将输入两个整数的算法放到计算器内部

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>//函数指针的应用
//模拟简单计算器
void menu()
{printf("******************************************\n");printf("***********   1.add   2.sub    ***********\n");printf("***********   3.mul   4.div    ***********\n");printf("***********   0.exit           ***********\n");printf("******************************************\n");}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 input = 0;int x = 0;int y = 0;do{menu();printf("请输入两个整数->");scanf("%d %d", &x, &y);switch (input){case 1:printf("请输入两个整数->");scanf("%d %d", &x, &y);printf("%d\n", add(x, y));break;case 2:printf("请输入两个整数->");scanf("%d %d", &x, &y);printf("%d\n", sub(x, y));break;case 3:printf("请输入两个整数->");scanf("%d %d", &x, &y);printf("%d\n", mul(x, y));break;case 4:printf("请输入两个整数->");scanf("%d %d", &x, &y);printf("%d\n", div(x, y));break;case 0:printf("退出游戏\n");break;default:printf("输入错误请重新输入\n");break;}} while (input);return 0;
}

但是这样写,显而易见有很多重复的步骤,所以我们可以进一步改进:用一个函数来分装这一个过程,在这里面调用加法器,减法器……,根据传入函数的不同,我们可以在一个函数里面进行不同的运算。要是直接在函数内部调用add,一次只能调用一个函数,本质上还是需要重复这个步骤,但是我么可以由函数指针接受不同的函数完成每一步的调用,大大减少了代码重复率。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>//函数指针的应用
//模拟简单计算器
void menu()
{printf("******************************************\n");printf("***********   1.add   2.sub    ***********\n");printf("***********   3.mul   4.div    ***********\n");printf("***********   0.exit           ***********\n");printf("******************************************\n");}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 calc(int(*p)(int,int))
{int x = 0;int y = 0;printf("请输入两个整数->");scanf("%d %d", &x, &y);int ret = p(x, y);printf("%d\n",ret);
}
int main()
{int input = 0;do{menu();printf("请输入你的选择->");scanf("%d", &input);switch (input){case 1:calc(add);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;
}

这样我们就大大简化了计算机设计的步骤。 

6.函数指针数组

把函数的指针存到一个数组中,那这个数组就叫函数指针数组。

函数指针数组的定义:

//函数指针数组
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(*pf)(int, int) = add;
    int(*arr[4])(int, int) = { add,sub,mul,div };
}

 函数指针数组的调用:

int main()
{
    int(*pf)(int, int) = add;
    int(*arr[4])(int, int) = { add,sub,mul,div };
    int i = 0;
    for (i = 0;i < 4;i++)
    {
        int ret = arr[i](3, 4);//调用函数指针时可以解引用也可以不解引用
        printf("%d\n", ret);
    }
}

 由函数指针数组,我们还可以进一步把上面模拟简单计算器的实现,用函数指针数组的思想进行调用

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
函数指针数组的应用
模拟简单计算器
void menu()
{printf("******************************************\n");printf("***********   1.add   2.sub    ***********\n");printf("***********   3.mul   4.div    ***********\n");printf("***********   0.exit           ***********\n");printf("******************************************\n");}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(*arr[4])(int, int) = { add,sub,mul,div };
int calc()
{int(*arr[])(int, int) = { 0,add,sub,mul,div };}
int main()
{int input = 0;int x = 0;int y = 0;int(*arr[])(int, int) = { 0,add,sub,mul,div };do{menu();printf("请输入你的选择->");scanf("%d", &input);if (input == 0){printf("退出游戏");}else if (input >= 1 && input <= 4){printf("请输入两个整数->");scanf("%d %d", &x, &y);int ret = arr[input](x, y);//通过数组下标访问,找到这个函数printf("%d\n", ret);}else{printf("输入错误,请重新输入\n");}} while (input);return 0;
}

这种方法简化了修改代码,要是想要加别的计算,直接放到数组里面,在改变一下条件的范围即可。在数组首位补0,可以领输入的数直接跳转到需要的函数的位置,进而对函数进行调用。 

7.指向函数指针数组的指针

int(*(*p)[10])();;

//首先p先和*结合是一个指针变量,然后这个指针指向一个数组,所以是数组指针,这个数组有是函数指针类型int(*)()的数组,所以这是一个指向函数指针数组的指针。

8.回调函数

回调函数就是一个通过函数指针调用的函数,若果你把函数指针或者地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数(上面我们用函数指针和函数指针数组模拟简单计算器时都用到了回调函数)回调函数不是由该函数的实现方直接调用的,而是在特定的时间或者条件发生时由另外一方调用的,用于对该事件或者条件进行响应。

相关文章:

指针的深入讲解

本章重点&#xff1a; 字符指针数组指针指针数组数组传参和指针传参函数指针函数指针数组指向函数指针数组的指针回调函数 我们在指针的初阶的时候主要讲了&#xff1a; 1.指针就是变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间 2.指针的大小是固定4个…...

王佩丰24节Excel学习笔记——第十二讲:match + index

【以 Excel2010 系列学习&#xff0c;用 Office LTSC 专业增强版 2021 实践】 【本章小技巧】 vlookup与match&#xff0c;index 相结合使用match,index 结合&#xff0c;快速取得引用的值扩展功能&#xff0c;使用match/index函数&#xff0c;结合照相机工具获取照片 一、回顾…...

概率论得学习和整理28:用EXCEL画折线图,X轴数据也被当成曲线的解决办法

目录 1 折线图和散点图&#xff0c;对数据的处理差别 1.1 EXCEL画图的一些默认设置 1.2 多于2列的数据&#xff0c;也是如此 2 如果我们非要以第1列数据为X轴&#xff0c;做一个折线图呢&#xff1f;也能 2.1 首先&#xff0c;把第1列&#xff0c;想当成X轴的数据&#xf…...

387. 字符串中的第一个唯一字符

1&#xff0c;题目 给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返回它的索引 。如果不存在&#xff0c;则返回 -1 。 2&#xff0c;代码 class Solution { public:int firstUniqChar(string s) {//记数排序int coutArr[26] {0};//统计字符出现…...

Oracle RAC最佳实践-优化私网连接

在 Oracle RAC 环境中&#xff0c;私网&#xff08;Interconnect&#xff09; 是节点之间通信和数据传输的关键部分。一直有个误解&#xff0c;认为私网&#xff08;心跳网&#xff09;只要能通随便什么交换机都可以,甚至有直连的&#xff0c;实际上私网的性能至关重要&#xf…...

[bug] StarRocks borker load意向之外的bug

意向之外&#xff0c;又清理之中 背景&#xff1a; StarRocks各方面碾压相同类型的数据库&#xff0c;最近我们要从生成HIVE导历史数据&#xff08;ORC格式&#xff09;到StarRocks&#xff0c;前期小测一下&#xff0c;在测试是没问题&#xff0c;上生产先导2个月的数据&…...

游戏AI实现-寻路算法(Dijkstra)

戴克斯特拉算法&#xff08;英语&#xff1a;Dijkstras algorithm&#xff09;&#xff0c;又称迪杰斯特拉算法、Dijkstra算法&#xff0c;是由荷兰计算机科学家艾兹赫尔戴克斯特拉在1956年发现的算法。 算法过程&#xff1a; 1.首先设置开始节点的成本值为0&#xff0c;并将…...

9 OOM和JVM退出。OOM后JVM一定会退出吗?

首先我们把两个概念讲清楚 OOM是线程在申请堆内存&#xff0c;发现堆内存空间不足时候抛出的异常。 JVM退出的条件如下&#xff1a; java虚拟机在没有守护线程的时候会退出。守护线程是启动JVM的线程&#xff0c;服务于用户线程。 我们简单说下守护线程的功能: 1.日志的记录…...

Linux 端口操作

安装netstat yum -y install net-tools 检测端口占用 netstat -npl | grep "端口" 安装lsof lsof yum -y install lsof 检测端口占用 lsof -i :端口号 安装nc yum -y install nc 查看对方端口是否开放 nc -vz 对方ip 对方端口 安装telnet telnet yum -y in…...

【USB-HID】“自动化键盘“ - 模拟键盘输入

目录 【USB-HID】"自动化键盘" - 模拟键盘输入1. 前言2. 模拟键盘2.1 STM32CubeMX 配置2.2 修改代码配置2.3 发送按键信息 3. 接收主机Setup数据3.1 获取PC下发的数据 4. 总结 【USB-HID】“自动化键盘” - 模拟键盘输入 1. 前言 对于模拟键盘的实现&#xff0c;网…...

基于Spring Boot+Vue 的高校运动会管理系统

目录 1 绪论1.1研究背景1.2 研究意义1.3 相关开发技术简介1.3.1 Vue.js1.3.2 Spring Boot1.3.3 MySQL 2 系统分析2.1 需求分析2.1.1 功能需求2.1.2 非功能需求 2.2 系统可行性分析2.2.1 经济可行性2.2.2 技术可行性2.2.3 操作可行性 3 系统概要设计系统功能描述业务流程分析 4 …...

Linux应用程序中终止进程的几种方法

目录 1、正常退出进程的方法 1.1、exit(int status) 函数 1.2、_exit(int status) 函数 1.3、_Exit(int status) 函数 2、异常退出进程的方法 3、何时使用这些方法&#xff1f; 在 Linux 应用程序中&#xff0c;终止进程的方式有多种&#xff0c;通常取决于进程是否需要进…...

电脑文档损坏:原因剖析和修复方法

在使用电脑的过程中&#xff0c;许多用户可能会遇到文档突然提示损坏、无法打开的情况。这种情况的发生往往让人感到困惑&#xff0c;特别是当并未进行任何明显错误操作时。以下是一些常见的原因以及应对方法。 一、文档损坏的常见原因 1、非人为的异常操作&#xff1a; 在编…...

了解ARM的千兆以太网——RK3588

1. 简介 本文并不重点讲解调试内容&#xff0c;重点了解以太网在ARM设计中的框架以及在设备树以及驱动的一个整体框架。了解作为一个驱动开发人员当拿到一款未开发过的ARM板卡应该怎么去把网卡配置使用起来。 2. 基础知识介绍 在嵌入式ARM中实现以太网的解决方案通常有以下两种…...

【Nginx-4】Nginx负载均衡策略详解

在现代Web应用中&#xff0c;随着用户访问量的增加&#xff0c;单台服务器往往难以承受巨大的流量压力。为了解决这一问题&#xff0c;负载均衡技术应运而生。Nginx作为一款高性能的Web服务器和反向代理服务器&#xff0c;提供了多种负载均衡策略&#xff0c;能够有效地将请求分…...

低级计算机网络知识总结

1 应用层 1.1 HTTP(TCP) 浏览器访问WWW服务器过程&#xff1a;首先进行域名解析&#xff0c;然后通过TCP向服务器发送连接请求 HTTP本身是无连接&#xff0c;无状态的。无状态特性使服务器能够支持大量的并发HTTP请求。实际应用中&#xff0c;通常使用Cookie加数据库跟踪用户…...

linux sysrq的使用举例

在menuconfig中选择m和 *的区别&#xff1a; *: 模块驱动编译到内核中&#xff0c;启动时自动加载 M:标识作为内核模块编译 空格:表示该功能不编译到内核中&#xff0c;即新的内核将不支持该功能。 m&#xff1a;模块会被编译&#xff0c;但是不会被编译到内核中&#xff0c;只…...

数字IC后端设计实现篇之TSMC 12nm TCD cell(Dummy TCD Cell)应该怎么加?

TSMC 12nm A72项目我们需要按照foundary的要求提前在floorplan阶段加好TCD Cell。这个cell是用来做工艺校准的。这个dummy TCD Cell也可以等后续Calibre 插dummy自动插。但咱们项目要求提前在floorplan阶段就先预先规划好位置。 TSCM12nm 1P9M的metal stack结构图如下图所示。…...

Oracle 适配 OpenGauss 数据库差异语法汇总

背景 国产化进程中&#xff0c;需要将某项目的数据库从 Oracle 转为 OpenGauss &#xff0c;项目初期也是规划了适配不同数据库的&#xff0c;MyBatis 配置加载路径设计的是根据数据库类型加载指定文件夹的 xml 文件。 后面由于固定了数据库类型为 Oracle 后&#xff0c;只写…...

【记录】Django解决与VUE跨域问题

1 梗概 这里记录Django与VUE的跨域问题解决方法&#xff0c;主要修改内容是在 Django 中。当然其他的前端项目 Django 也可以这样处理。 2 安装辅助包 pip install django-cors-headers3 配置 settings.py INSTALLED_APPS [ # ... corsheaders, # ... ] 为了响应…...

Yolov10本地部署,torch找不到GPU问题解决

在本地部署跑Yolov10的模型.具体分为以下几步,也是踩了一些坑: 1.YoloV10 代码拉取 2.安装CUDA 1.查看CUDA支持版本 2.下载安装CUDA 3.下载CUDNN 3.创建python虚拟环境 Anaconda下载安装 虚拟环境安装配置 4.运行 1.yoloV10代码拉取 源码地址: GitHub - THU-MIG/yolov10: YOLO…...

el-upload 上传文件 入参格式为form-data格式,入参字段为code、name、type、file(文件)的形式,如何实现?

el-upload 是 Element UI 中用于文件上传的组件。如果你需要上传文件并将其封装为 form-data 格式&#xff0c;并且包含字段如 code、name、type 和 file&#xff0c;你可以通过自定义 before-upload 或 action 进行处理。 1. el-upload 的基本用法 Element UI 的 el-upload …...

VUE组件插槽使用示例,弹窗样式

在Vue.js中&#xff0c;插槽&#xff08;slots&#xff09;是一种非常强大的功能&#xff0c;它允许你在父组件中向子组件传递内容。插槽主要有三种类型&#xff1a;默认插槽、具名插槽和作用域插槽。下面是一些示例来展示如何使用这些插槽。 默认插槽 默认插槽是最简单的插槽…...

ARM嵌入式学习--第八天(PWM)

PWM -PWM介绍 PWM&#xff08;pulse Width Modulation&#xff09;简称脉宽调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术&#xff0c;广泛应用在测量&#xff0c;通信&#xff0c;工控等方面 PWM的频率 是指在1秒钟内&#xff0c;信号从…...

新能源汽车大屏可视化第三次数据存储

任务&#xff1a; 将数据存放到temp.csv 链接&#xff1a; 1.排行页面 https://www.dongchedi.com/sales 2.参数页面 https://www.dongchedi.com/auto/params-carIds-x-9824 完善打印&#xff1a; 1. [{‘series_id’: 5952, ‘series_name’: ‘海鸥’, ‘image’: ‘https://…...

linux 替换yum源镜像

1. 备份源镜像 sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak 2. 下载国内镜像阿里云 如果没有wget可以用curl 代替 sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 清华大学 sudo wget -…...

SAP:如何修改已释放的请求

SAP:如何修改已释放的请求 QQ出了一个新功能&#xff0c;把10年前的旧日志推给自己。这个10年前的日志&#xff0c;是用户反映在SE10中把请求释放后发现漏了内容&#xff0c;想修改已释放的请求。经调查写了一个小程序&#xff0c;实现用户的需求。 *&-------------------…...

js的?. 和??和||有什么区别

let a 0; let b null; let c Hello;console.log(a ?? default); // 0 console.log(b ?? default); // "default" console.log(c ?? default); // "Hello"console.log(a || default); // "default" (因为 0 是假值) console.log(b |…...

clickhouse 分布式表创建、增加、更新、删除、查询

创建分布式表 --先创建本地表 设置自动过期时间3天 CREATE TABLE IF NOT EXISTS ck_database.ck_databaseon cluster default(cluster name) (table_id String COMMENT id,item_id String COMMENT 业务id,desc Int64 COMMENT 描述,time DateTime DEFAULT now() COMMENT 数据…...

推送本地仓库到远程git仓库

目录 推送本地仓库到远程git仓库1.1修改本地仓库用户名1.2 push 命令1.3远程分支查看 推送本地仓库到远程git仓库 删除之前的仓库中的所有内容&#xff0c;从新建库&#xff0c;同时创建一个 A.txt 文件 清空原有的远程仓库内容&#xff0c;重新创建一个新的仓库&#xff0c;…...

LSTM长短期记忆网络

LSTM&#xff08;长短期记忆网络&#xff09;数学原理 LSTM&#xff08;Long Short-Term Memory&#xff09;是一种特殊的递归神经网络&#xff08;RNN&#xff09;&#xff0c;解决了标准RNN中存在的梯度消失&#xff08;Vanishing Gradient&#xff09; 和**梯度爆炸&#x…...

ABAP SQL 取日期+时间最新的一条数据

我们在系统对接的时候&#xff0c;外部系统可能会推送多个数据给到我们。 我们 SAP 系统的表数据中日期和时间是作为主键的&#xff0c;那么如果通过 ABAP SQL 取到最新日期的最新时间呢。 解决方案&#xff1a; 方式 1&#xff1a;SELECT MAX 可以通过两个 SELECT MAX 来取…...

SAST静态应用安全测试常见的编码规则

行业优先级难易度标准标准名称数量 军工12易GJB 5369:2005GJB_5369&#xff08;国家军用标准航天型号软件C语言可靠性编程规范&#xff09;138军工行业最早的C语言编码标准&#xff0c;强制性4易GJB 8114:2013GJB_8114&#xff08;国家军用标准C/C语言可靠性编程规范&#xff…...

AI相关专业名词汇总解释

1.SFT Supervised fine-tuning&#xff0c;“有监督微调”意味着使用有标签的数据来调整一个已预训练好的语言模型&#xff08;LLM&#xff09;&#xff0c;使其更适应某一特定任务。通常LLM的预训练是无监督的&#xff0c;但微调过程往往是有监督的。 详解&#xff1a;https:/…...

【C语言】指针数组和数组指针

前言 指针数组和数组指针是C语言中经常混淆的两个概念&#xff0c;虽然他们的名字相似&#xff0c;但其含义却完全不同。 指针数组 指针数组本质是一个数组&#xff0c;特点是数组中的元素均为指针&#xff0c;其定义形式为&#xff1a; 数据类型 *指针名[长度] 例如 int *…...

联邦学习中:公共物品属性的一般定义

在经济学和相关领域中,公共物品属性具有特定的含义,在论文中与联邦学习数据交易等情境相关联时,其意义如下: 公共物品属性的一般定义 非排他性 公共物品一旦被提供,很难或不可能排除其他人使用。例如,路灯照亮了街道,一个人使用路灯照明并不会阻止其他人同时使用,无法…...

前端的Python应用指南(一):快速构建 Web 服务器 - Flask vs Node.js 对比

随着前端开发技术的不断发展&#xff0c;前端开发者的技术栈也在不断扩展。如今&#xff0c;前端开发者不仅要掌握 HTML、CSS、JavaScript&#xff0c;还要掌握后端技术&#xff0c;成为全栈开发者。而在后端技术的选择上&#xff0c;Python 和 Node.js 是两种非常流行的选择。…...

典型案例 | 旧PC新蜕变!东北师范大学依托麒麟信安云“旧物焕新生”

东北师范大学始建于1946年&#xff0c;坐落于吉林省长春市&#xff0c;是中国共产党在东北地区创建的第一所综合性大学。作为国家“双一流”建设高校&#xff0c;学校高度重视教学改革和科技创新&#xff0c;校园信息化建设工作始终走在前列。基于麒麟信安云&#xff0c;东北师…...

【UE5】pmx导入UE5,套动作。(防止“气球人”现象。

参考视频&#xff1a;UE5Animation 16: MMD模型與動作導入 (繁中自動字幕) 问题所在&#xff1a; 做法记录&#xff08;自用&#xff09; 1.导入pmx&#xff0c;删除这两个。 2.转换给blender&#xff0c;清理节点。 3.导出时&#xff0c;内嵌贴图&#xff0c;选“复制”。 …...

ROS+PX4+Gazebo仿真环境配置全流程解析

上一期文章介绍了我们即将发布的仿真平台&#xff0c;并提到后续需要在Ubuntu系统上进行PX4软件在环仿真。本期文章将为大家详细介绍如何配置Ubuntu环境以及安装ROS和PX4仿真环境。具体配置包括&#xff1a;Ubuntu 20.04 ROS Noetic PX4 Python3。 需要注意的是&#xff0c…...

STM32F103单片机HAL库串口通信卡死问题解决方法

在上篇文章 STM32F103单片机使用STM32CubeMX创建IAR串口工程 中分享了使用cubeMX直接生成串口代码的方法&#xff0c;在测试的过程中无意间发现&#xff0c;串口会出现卡死的问题。 当串口一次性发送十几个数据的时候&#xff0c;串口感觉像卡死了一样&#xff0c;不再接收数据…...

基于微信小程序的电影院订票选座系统ssm+论文源码调试讲解

第2章 开发环境与技术 本章节对开发基于微信小程序的电影院订票选座系统需要搭建的开发环境&#xff0c;还有基于微信小程序的电影院订票选座系统开发中使用的编程技术等进行阐述。 2.1 Java语言 Java语言是当今为止依然在编程语言行业具有生命力的常青树之一。Java语言最原始…...

解决新安装CentOS 7系统mirrorlist.centos.org can‘t resolve问题

原因 mirrorlist.centos.org yum源用不了 解决办法就是 # cd /etc/yum.repos.d/ # mv CentOS-Base.repo CentOS-Base.repo_bak # vim CentOS-Base.repoCentOS系统操作 # mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/*.repo_bak # curl -o /etc/yum.repos.d/CentOS-Linux-Ba…...

分布式系统架构3:服务容错

这是小卷对分布式系统架构学习的第3篇文章&#xff0c;虽然知道大家都不喜欢看纯技术文章&#xff0c;写了也没多少阅读量&#xff0c;但是个人要成长的话&#xff0c;还是需要往深一点的技术上去探索的 1.为什么需要容错 分布式系统的本质是不可靠的&#xff0c;一个大的服务…...

鸿蒙项目云捐助第十四讲云函数的初步使用

鸿蒙项目云捐助第十四讲云函数的初步使用 在开发项目的过程中&#xff0c;云端充分利用已成为一种驱势。云监控&#xff0c;云运维&#xff0c;云开发&#xff0c;云办公等等软件层出不穷&#xff0c;本地软件云端化也成为一种潮流。在这股大潮中&#xff0c;华为云也是独树一…...

【5G】5G的主要架构选项

最初&#xff0c;在3GPP讨论中考虑了所有可能的聚合和核心网络组合&#xff0c;共有八个架构选项。以下重点介绍option2、3、4和7。 1. 独立组网 (Standalone, SA) 架构选项 2 &#xff1a;Standalone architecture with 5G-core 特点&#xff1a; 5G核心网&#xff08;5GC, …...

【Laravel】端口问题导致菜单打不开

以下是修改 Laravel 应用程序的端口配置&#xff0c; 修改环境变量 APP_URL 来实现 app/Providers/AppServiceProvider.php <?phpnamespace App\Providers;use Illuminate\Events\Dispatcher; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\URL…...

网络安全等级保护系统定级流程与示例

一、定级流程 安全保护等级初步确定为第二级及以上的等级保护对象&#xff0c;其运营使用单位应当依据《网络安全等级保护定级指南》进行初步定级、专家评审、主管部门审批、公安机关备案审查&#xff0c;最终确定其安全保护等级。 二、定级方法 等级保护对象的级别由两个定级…...

项目练习:若依-ruoyi系统的部署与运行(前后端分离版)

文章目录 一、我的环境二、代码下载三、数据库配置四、项目配置文件修改五、启动运行六、验证 一、我的环境 jdk&#xff1a;8 MySQL&#xff1a;5.7 Redis&#xff1a; nodejs&#xff1a;v16.13.2 npm&#xff1a;8.1.2 vue&#xff1a;5.0.8 开发工具 idea Navicat for MyS…...

【UE5 C++课程系列笔记】10——动态单播/多播的基本使用

目录 概念 申明动态委托 一、DECLARE_DYNAMIC_DELEGATE 二、DECLARE_DYNAMIC_MULTICAST_DELEGATE 绑定动态委托 一、BindDynamic 二、AddDynamic 三、RemoveDynamic 执行动态委托 ​一、Execute 二、ExecuteIfBound 三、IsBound 四、Broadcast 动态单播使用示…...