指针的深入讲解

本章重点:

  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.回调函数

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

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

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

相关文章

LWIP协议:三次握手和四次挥手、TCP/IP模型

一、三次握手&#xff1a;是客户端与服务器建立连接的方式&#xff1b; 1、客户端发送建立TCP连接的请求。seq序列号是由发送端随机生成的&#xff0c;SYN字段置为1表示需要建立TCP连接。&#xff08;SYN1&#xff0c;seqx&#xff0c;x为随机生成数值&#xff09;&#xff1b;…

WEB开发: 全栈工程师起步 - Python Flask +SQLite的管理系统实现

一、前言 罗马不是一天建成的。 每个全栈工程师都是从HELLO WORLD 起步的。 之前我们分别用NODE.JS 、ASP.NET Core 这两个框架实现过基于WebServer的全栈工程师入门教程。 今天我们用更简单的来实现&#xff1a; Python。 我们将用Python来实现一个学生管理应用&#xff0…

WatchAlert - 开源多数据源告警引擎

概述 在现代 IT 环境中&#xff0c;监控和告警是确保系统稳定性和可靠性的关键环节。然而&#xff0c;随着业务规模的扩大和数据源的多样化&#xff0c;传统的单一数据源告警系统已经无法满足复杂的需求。为了解决这一问题&#xff0c;我开发了一个开源的多数据源告警引擎——…

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

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

Vue3 + Element-Plus + vue-draggable-plus 实现图片拖拽排序和图片上传到阿里云 OSS 父组件实现真正上传(最新保姆级)

Vue3 Element-Plus vue-draggable-plus 实现图片拖拽排序和图片上传到阿里云 OSS&#xff08;最新保姆级&#xff09;父组件实现真正上传 1、效果展示2、UploadImage.vue 组件封装3、相关请求封装4、SwiperConfig.vue 调用组件5、后端接口 1、效果展示 如果没有安装插件&…

容器化技术全面解析:Docker 与 Containerd 的深入解读

目录 Docker 简介 1. 什么是 Docker&#xff1f; 2. Docker 的核心组件 3. Docker 的主要功能 4. Docker 的优点 5. Docker 的使用场景 Containerd 简介 1. 什么是 Containerd&#xff1f; 2. Containerd 的核心特性 3. Containerd 的架构 4. Containerd 与 Docker 的…

LNMP+discuz论坛

0.准备 文章目录 0.准备1.nginx2.mysql2.1 mysql82.2 mysql5.7 3.php4.测试php访问mysql5.部署 Discuz6.其他 yum源&#xff1a; # 没有wget&#xff0c;用这个 # curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo[rootlocalhost ~]#…

Android Studio的笔记--BusyBox相关

BusyBox 相关 BusyBoxandroid上安装busybox和使用示例一、下载二、移动三、安装和设置环境变量四、使用 busybox源码下载和查看 BusyBox BUSYBOX BUSYBOX链接https://busybox.net/ 点击链接后如图 点击左边菜单栏的Get BusyBix中的Download Source 跳转到busybox 的下载源码…

LabVIEW与PLC点位控制及OPC通讯

在工业自动化中&#xff0c;PLC通过标准协议&#xff08;如Modbus、Ethernet/IP等&#xff09;与OPC Server进行数据交换&#xff0c;LabVIEW作为上位机通过OPC客户端读取PLC的数据并进行监控、控制与处理。通过这种方式&#xff0c;LabVIEW能够实现与PLC的实时通信&#xff0c…

C++ OpenGL学习笔记(1、Hello World空窗口程序)

终于抽出时间系统学习OpenGL 教程&#xff0c;同时也一步一步记录怎样利用openGL进行加速计算。 目录 1、环境准备1.1、库的下载1.2、库的选择及安装 2、OpenGL第一个项目&#xff0c;Hello World!2.1、新建hello world控制台项目2.2、配置openGL环境2.2.1 包含目录配置2.2.2 …

系统移植——Linux 内核顶层 Makefile 详解

一、概述 Linux Kernel网上下载的版本很多NXP等有自己对应的版本。需要从网上直接下载就可以。 二、Linux内核初次编译 编译内核之前需要先在 ubuntu 上安装 lzop 库 sudo apt-get install lzop 在 Ubuntu 中 新 建 名 为 “ alientek_linux ” 的 文 件夹 &#xff0c; …

ubuntu16.04ros-用海龟机器人仿真循线系统

下载安装sudo apt-get install ros-kinetic-turtlebot ros-kinetic-turtlebot-apps ros-kinetic-turtlebot-interactions ros-kinetic-turtlebot-simulator ros-kinetic-kobuki-ftdi sudo apt-get install ros-kinetic-rocon-*echo "source /opt/ros/kinetic/setup.bash…

Connection lease request time out 问题分析

Connection lease request time out 问题分析 问题背景 使用apache的HttpClient&#xff0c;我们知道可以通过setConnectionRequestTimeout()配置从连接池获取链接的超时时间&#xff0c;而Connection lease request time out正是从连接池获取链接超时的报错&#xff0c;这通常…

【文档搜索引擎】在内存中构造出索引结构(上)

文章目录 主要思路正排索引和倒排索引的表示1. 正排索引查询文档详细信息2. 倒排索引中查找关联词3. 新增文档正排索引倒排索引实现词频统计 主要思路 通过 Index 类&#xff0c;在内存中构造出索引结构。这个类要提供的方法&#xff1a; 给定一个 docId&#xff0c;在正排索…

单节点calico性能优化

在单节点上部署calicov3273后&#xff0c;发现资源占用 修改calico以下配置是资源消耗降低 1、因为是单节点&#xff0c;没有跨节点pod网段组网需要&#xff0c;禁用overlay方式网络(ipip&#xff0c;vxlan),使用route方式网络 配置calico-node的环境变量 CALICO_IPV4POOL_I…

tryhackme-Pre Security-HTTP in Detail(HTTP的详细内容)

任务一&#xff1a;What is HTTP(S)?&#xff08;什么是http&#xff08;s&#xff09;&#xff09; 1.What is HTTP? (HyperText Transfer Protocol)&#xff08;什么是 HTTP&#xff1f;&#xff08;超文本传输协议&#xff09;&#xff09; http是你查看网站的时候遵循的…

Javascript面试手撕常见题目(回顾一)

1.JS查找文章中出现频率最高的单词? 要在JavaScript中查找文章中出现频率最高的单词&#xff0c;你可以按照以下步骤进行操作&#xff1a; 将文章转换为小写&#xff1a;这可以确保单词的比较是大小写不敏感的。移除标点符号&#xff1a;标点符号会干扰单词的计数。将文章拆…

算法-Z-order算法

1、学习背景 激光雷达点云是无序的&#xff0c;Transformer只能对有序的数据进行处理&#xff0c;为了将Transformer用在点云处理中&#xff0c;需要将无序的点云转换成有序的数据&#xff0c;另外&#xff0c;由于Transformer会用到局部注意力机制&#xff0c;所以将无序的数据…

ElasticSearch 数据聚合与运算

1、数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现数据的统计、分析和运算。实现这些统计功能的比数据库的 SQL 要方便的多&#xff0c;而且查询速度非常快&#xff0c;可以实现近实时搜索效果。 注意&#xff1a; 参加聚合的字段必须是 keywor…

三、使用langchain搭建RAG:金融问答机器人--检索增强生成

经过前面2节数据准备后&#xff0c;现在来构建检索 加载向量数据库 from langchain.vectorstores import Chroma from langchain_huggingface import HuggingFaceEmbeddings import os# 定义 Embeddings embeddings HuggingFaceEmbeddings(model_name"m3e-base")#…