C语言指针进阶

news2024/11/15 4:11:10

目录

0.指针初阶回顾:

1.字符指针

2.指针数组

3.数组指针

(1)数组指针的定义:

(2)数组名和&数组名

(3)数组指针的使用

(4)数组指针的数组:

4.数组传参和指针传参

一维数组传参:

二维数组传参:

一级指针传参:

二级指针传参:

5.函数指针

(1)函数的地址:

(2)函数指针的存放:

(3)使用存放函数指针的函数指针变量来调用函数:

(4)两个有趣的代码:

代码一:

代码二:

6.函数指针数组

(1)概念:

(2)函数指针数组的用途:转移表,减少代码的冗余性。

7.指向函数指针数组的指针(了解)

8.回调函数

(1)概念

qsort函数

9.指针和数组的面试题

(1)数组名的理解

(2)strlen函数的理解

(3)二维数组的数组名理解

(4)几个易错的题:

(5)sizeof的特点

10.指针的笔试题

笔试题1

笔试题2

笔试题3

笔试题4

笔试题5

笔试题6

笔试题7

笔试题8


0.指针初阶回顾:

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

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

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

  • 指针的运算

1.字符指针

字符指针 char*

#include <stdio.h>
​
int main()
{
    char str1[] = "hello bit";
    const char* str2 = "hello bit";
​
    printf("%s\n", str1);
    printf("%s\n", str2);
​
    return 0;
}

字符指针除了表示字符的指针,还可以表示字符串

const char* pstr = "hello bit.";
  • 字符串可以用字符数组和字符指针来表示。

  • 字符指针表示的字符串是常量字符串

  • 这里的const在有的编译器可以不加,有的编译器要求严格必须加上。

  • 字符指针表示字符串不是把整个字符串放入字符指针中,本质是把字符串“hello bit“的首字符h的地址放入字符指针pstr中

内存分区:

​​

栈,堆,数据段(全局变量和静态变量),代码段(可执行代码,常量)

2.指针数组

指针数组顾名思义,就是存放指针的数组。

int* arr1[10];//整形指针数组 
char *arr2[4];//一级字符指针数组 
char **arr3[5];//二级字符指针数组

3.数组指针

(1)数组指针的定义:

数组指针就是指向数组的指针。

int *p1[10]; 
int (*p2)[10];
  • [ ]操作符的优先级是大于 * 操作符的。

  • p1先和[ ]结合,是一个数组,数组的每个元素都是int*的指针。

  • p2先和*结合,说明是一个指针,指针指向的类型是int [10],说明p2是指向数组的指针,指向的数组是一个大小为10的整型数组。

  • p1是指针数组,p2是数组指针

(2)数组名和&数组名

arr和&arr的值是一样的,但是他们的意义是不一样的。

arr是数组首元素的地址,&arr是数组的地址,也就是数组指针

&arr+1跳过的就是整个数组的大小。

数组指针解引用,其实就是数组首元素的地址。(这句话非常重要,以后都要用)

(3)数组指针的使用

数组指针一般很少在一维数组中使用

比如这样使用:

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

我们会发现这样使用毫无意义,就是在脱kz放p!

其实数组指针一般在二维数组中应用的比较广泛:

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

注意:

  • 数组传参,传的其实都是指针,尽管形参的形式还是个数组,但其实是为了增强代码的可读性。

  • 数组名是数组首元素的地址,二维数组的首元素是二维数组第一行的一维数组所以二维数组的数组名是第一行一维数组的地址,也就是数组指针(这是重点哦)

  • arr[i][j]在底层上就是* (*(arr+i)+j):

解析 arr[i][j],即* (*(arr+i)+j):

arr是二维数组第一行数组的地址。

arr+i是二维数组第i行数组的地址。

*(arr+i):前面说数组指针解引用就是数组首元素的地址,那么*(arr+i)就是第i行数组首元素的地址。

*(arr+i)+j:第i行数组第j个元素的地址。

*(*(arr+i)+j):第i行数组第j个元素

(4)数组指针的数组:

int (*parr1)[10];//parr1是数组指针,数组是大小为10的整形数组

int (*parr2[10])[5];数组指针数组:

parr2[10] : parr2是数组,该数组里面有10个元素

int (*)[5] : 每个元素都是数组指针,指针指向的数组是大小为5的整形数组

4.数组传参和指针传参

一维数组传参:

普通数组传参:

void test(int arr[ ]);

void test(int arr[10]);

void test(int* arr);

指针数组传参:

void test(int* arr[ ]);

void test(int* arr[10]);

void test(int** arr);

二维数组传参:

void test(int arr[3][5]);

void test(int arr[ ][5]);

void test(int arr[ ][ ]); ​ 错误!!

void test(int (*arr)[5]); ​ 二维数组的数组名是数组指针

一级指针传参:

一级指针

变量的地址

普通数组(不是指针数组)的数组名

二级指针传参:

二级指针

一级指针变量的地址

指针数组的数组名

5.函数指针

(1)函数的地址:

int Add(int a, int b)
{
    return a + b;
}
​
int main()
{
    printf("%p\n", Add);
    printf("%p\n", &Add);
​
    return 0;
}
Add 和 &Add都是函数的地址

(2)函数指针的存放:

int (*pfun)(int , int) = Add(或者&Add);

pfun是函数指针变量:pfun先和*结合,说明pfun是指针。

int (*)(int,int)是函数指针类型

注意:语法上是不允许这么写的:int (*)(int,int)pfun;

(3)使用存放函数指针的函数指针变量来调用函数:

一般调用:int a = Add(3,5);

int b = (*pfun)(4,5); - - - > &Add

int c = pfun(5,5); - - - > Add

两种方式都是可以的。

(4)两个有趣的代码:

代码一:

(*((void (*) ())0))();
  1. void (*) ()函数指针类型

  2. ​ void (*)() ​)0 :将0强制类型转换成函数指针类型

  3. *((​ void (*)() ​)0 ) 对函数指针类型解引用,就是调用该函数。

  4. 调用函数:(*((​ void (*)() ​)0 ) )()

意思就是调用0地址处的函数,函数无参,返回值void

代码二:

void (*signal(int,void(*)(int)))(int);
  1. signal先和()结合,说明signal是函数名。

  2. signal函数的第一个参数是int,第二个参数是函数指针void(*)(int),该函数指针指向一个参数为int,返回类型为void的函数。

  3. signal函数的返回类型也是一个函数指针void(*)(int);​去掉函数名和参数,剩下的就是返回类型:void(*)(int)

  4. 这是一个signal函数的声明

代码二的简化:使用typedef:

这种形式容易理解,但是语法上不允许,会报错

简化:

注意:

对函数指针类型void(*)(int)重命名,在语法上也要这么写:typedef void(*pfun_t)(int),不能typedef void(*)(int)pfun_t

6.函数指针数组

(1)概念:

函数指针数组顾名思义就是一个指针数组。

前面学习了数组指针数组,也就是int (*parr2[10])[5];

那么函数指针数组就是这么定义的:

int (*pfArr[4]) (int, int);
  • 注意操作符的优先级,[ ]的优先级比*高,pfArr先和[ ]结合,说明pfArr是一个数组。

  • 补充:对于一个数组的声明,去掉数组名和[ ],剩下的就是数组元素的类型;对于一个函数声明,去掉函数名和参数,剩下的就是返回类型。

    pfArr是数组名,去掉这个数组名和[ ],剩下的int (*)(int, int)是一个函数指针类型,说明pfArr是函数指针的数组。

  • 对于int (*parr2[10])[5];parr先和[ ]结合,是一个数组;去掉数组名parr2和[ ]。剩下的是int ( * )[5],是一个数组指针,说明parr2是数组指针的数组。

(2)函数指针数组的用途:转移表,减少代码的冗余性。

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
​
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 x, y;
    int input = -1;
    int ret = 0;
​
    int (*pfArr[5])(int, int) = { NULL,add,sub,mul,div };//函数指针数组
    while (input)
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input >= 1 && input <= 4)
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = pfArr[input](x, y);
        }
        else
        {
            printf("输入错误!\n");
        }
        printf("ret=%d\n", ret);
    }
​
    return 0;
}

我们想要实现一个计算器,如果不使用函数指针数组,免不了要使用switch语句来分开调用四个计算的函数,这样的话每个case语句就会有重复的代码出现,这样就造成了代码的冗余,而函数指针数组实现的转移表可以解决这个缺点。

7.指向函数指针数组的指针(了解)

很明显这是个指针,该指针指向的是函数指针数组。

如何定义?

int ( * (*p)[5])(int ,int) = &pfArr;

p先和*结合,说明p是指针, *p后面接[5],说明是数组指针,数组的元素类型是int ( * ) (int ,int),是函数指针类型。

8.回调函数

(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;
}
​
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("请输入两个操作数:");
    scanf("%d %d", &x, &y);
    ret = pf(x, y);
    printf("ret = %d\n", ret);
}
​
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 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;
}
 

qsort函数

C语言库中也有一个回调函数,那就是qsort函数。

  • 所谓qsort就是quick sort,他的底层是快速排序。并且qsort排序适用于任何数据类型的排序

  • 我们发现qsort函数的参数很有意思,首先void*无类型的指针决定了这个函数适用于任何类型数据的排序,其次第四个参数明显是一个函数指针,参数也都是void *,这是用来比较两个数据的,无类型的指针也说明可以用于任何数据类型的比较。

  • num是数据个数,size是数据的大小(单位字节)。

  • 这个compar函数是需要用户自己去实现的,然后让qsort函数调用comp函数的指针。

  • 对于void*指针,他可以接受任何类型的指针,但是这种指针是不能进行解引用操作的,也不能进行指针运算。

‍qsort函数的使用

下面展示qsort函数的使用:

void printArr(int arr[], int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
​
//int类型数据排序的比较函数
int comp_int(void* p1, void* p2)
{
    return *(int*)p1 - *(int*)p2;
}
​
//int类型数据排序
void test1()
{
    int arr[10] = { 1,3,5,7,9,2,4,6,8,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), comp_int);
    printArr(arr, sz);
}
​
typedef struct Stu
{
    char name[20];
    int age;
}Stu;
​
//struct类型数据排序的比较函数
//按年龄排序
int comp_StuByAge(void* p1, void* p2)
{
    return ((Stu*)p1)->age - ((Stu*)p2)->age;
}
​
//按姓名排序
int comp_StuByName(void* p1, void* p2)
{
    return strcmp(((Stu*)p1)->name, ((Stu*)p1)->name);
}
​
//struct类型数据排序
void test2()
{
    Stu arr[3] = { {"Jack",18},{"Tom",22},{"Lucy",15} };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), comp_StuByAge);
​
}
​
int main()
{
    //test1();
    test2();
​
    return 0;
}

‍qsort函数的实现

下面我们通过冒泡排序模拟实现一下qsort排序:

//int类型数据排序的比较函数
int comp_int(void* p1, void* p2)
{
    return *(int*)p1 - *(int*)p2;
}
​
typedef struct Stu
{
    char name[20];
    int age;
}Stu;
​
//struct类型数据排序的比较函数
//按年龄排序
int comp_StuByAge(void* p1, void* p2)
{
    return ((Stu*)p1)->age - ((Stu*)p2)->age;
}
​
//按姓名排序
int comp_StuByName(void* p1, void* p2)
{
    return strcmp(((Stu*)p1)->name, ((Stu*)p1)->name);
}
​
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素,一个一个字节的交换
{
    int i = 0;
    char tmp = 0;
    for (i = 0; i < size; i++)
    {
        tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
​
void bubble_sort(void* base, //base存放的是待排序数组中第一个元素的地址,void*说明可以对任何数据类型进行排序
                int size, //排序数据元素的个数
                int width, //数据的大小
                int (*cmp)(void* , void*))//比较函数的指针,比较函数用户自己实现。参数类型是void*,说明可以比较任何数据类型
{
    int  i = 0;
    //趟数
    for (i = 0; i < size - 1; i++)
    {
        int j = 0;
        //一趟内部比较的对数
        for (j = 0; j < size - 1 - i; j++)
        {
            //假设需要升序cmp返回>0,交换
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)//两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
            {
                //交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}

注意:

  • 通过cmp函数指针变量调用比较函数时,参数为什么要用char*?

因为char正好一个字节,(char* )base + j * width和(char*)base + (j + 1) * width 正好可以定位到arr[j],arr[j+1]的地址。

  • swap函数的参数为什么是char*?

char* 正好一个字节,可以用char*来一字节一字节的交换。

9.指针和数组的面试题


(1)数组名的理解

数组名是数组首元素的地址

但是有2个例外:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。sizeof里面必须要只有一个数组名,其他的都不行。

  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

除此之外所有的数组名都表示数组首元素的地址。包括sizeof(arr + 0)和sizeof(arr)也不一样。sizeof(arr + 0)里的arr表示数组首元素地址。


(2)strlen函数的理解

strlen函数接收的是指针参数,参数类型是const char* ,表示一个字符的指针,通过寻找‘\0’来计算字符串长度。


(3)二维数组的数组名理解

两种思维理解

例如二维数组int arr[3][4]:

  • 二维数组的数组名arr是第一行一维数组的指针,即数组指针,而数组指针解引用就是数组名,*arr是第一行一维数组的数组名,*(arr+i)就是第i行一维数组的数组名。

  • 另一种思维理解:二维数组是一维数组的数组,对于arr[i][j],arr[i]可以看成第i个一维数组的数组名而arr[i]即*(arr+i)。


(4)几个易错的题:

int a[]={1,2,3,4};
pritnf("%d\n",sizeof(*&a));

sizeof(*&a)就是sizeof(a),里面只有一个数组名,表示整个数组,大小是16;


int a[3][4]={0};
pritnf("%d\n",sizeof(a[0]));

两种思维理解(针对a[i]):

  • a是二维数组的数组名,就是第一行数组的指针,a[i]就是*(a + i), ​ a + i就是第i行数组的指针,​ *(a + i)就是第i行数组的数组名(数组指针解引用就是数组名,sizeof里面只有一个数组名,就表示整个一维数组(值为16)。

  • 二维数组是一维数组的数组,对于arri,arr[i]可以看成第i个一维数组的数组名sizeof里面只有一个数组名,就表示整个一维数组(值为16)。


(5)sizeof的特点

sizeof只关注类型

int a[3][4]={0};
ritnf("%d\n",sizeof(a[3]));

这里其实并不会实际去访问第四行(第四行越界)。

又比如:

int a = 7;
short b = 4;
printf("%d\n",sizeof(s = a + 2));

这里输出的是2(short类型的大小)

10.指针的笔试题

笔试题1


int main()
{
    int a[5] = { 1,2,3,4,5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));
​
    return 0;
}

&a:数组a的地址

答案:2和5

笔试题2


struct Test//20字节
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p;
//假设p的值为0x100000,求如下表达式:
​
int main()
{
    printf("%p\n", p + 0x1);
    printf("%p\n", (unsigned long)p + 0x1);
    printf("%p\n", (unsigned int*)p + 0x1);
​
    return 0;
}

注意:

  • 指针+/-整数:根据指针类型决定加多少字节。

  • 整数+整数:直接加整数个字节。

  • 十六进制的0x1就是数字1;

  • 内存中的每个内存单元都是1个字节,每个内存单元都有一个编号,就是地址,地址加1相当于跨一个字节

答案:

1.0x100014(加20)

2.0x100001(加1)

3.0x100004(加4)

笔试题3


int main()
{
    int a[4] = { 1,2,3,4 };
    int *ptr1 = (int*)(&a + 1);
    int *ptr2 = (int*)((int)a + 1);
    printf("%x,%x", ptr1[-1], *ptr2);
​
    return 0;
}

注意:

  • %x是以16进制打印。

  • ptr1是根据数组指针类型的来加1,加多少字节由数组大小决定。

  • ptr2转化成int再加1,直接加1个字节。

答案:4;02000000

笔试题4


int main()
{
    int a[3][2] = { (0,1),(2,3),(4,5) };
    int* p = a[0];
    printf("%d", p[0]);
​
    return 0;
}

注意:

  • 这里的二维数组是不完全初始化,因为是括号()而不是花括号{ },()里面的表达式是逗号表达式。所以数组元素是1 3 5 0 0 0。

  • p是第一行数组的数组名,也就是1的地址。

答案:1

笔试题5


int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
​
    return 0;
}

注意:

  • 二维数组在内存中是连续存放的。

  • 指针-指针,返回的是两个指针间的元素个数。

  • %p和%x的区别:两个都是打印十六进制数,%p一般打印地址。

  • 数据在内存中存放的都是补码。%d是以十进制打印。

答案:FFFFFFFFFFFFFFFC,-4

笔试题6


int main()
{
    int aa[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
​
    return 0;
}

注意:

ptr2:aa是二维数组的数组名,表示第一行一维数组的地址,aa+1就是第二行一维数组的地址,*(aa+1)就是第二行一维数组的数组名,也就是6的地址。

答案:10,5

笔试题7


int main()
{
    char* a[] = { "work","at","alibaba" };
    char** pa = a;
    pa++;
    printf("%s\n", *pa);
​
    return 0;
}

答案:at

笔试题8


int main()
{
    char* c[] = { "ENTER","NEW","POINT","FIRST" };
    char** cp[] = { c + 3,c + 2,c + 1,c };
    char*** cpp = cp;
    printf("%s\n", **++cpp);
    printf("%s\n", *--*++cpp + 3);
    printf("%s\n", *cpp[-2] + 3);
    printf("%s\n", cpp[-1][-1] + 1);
​
    return 0;
}

注意:

  • 运算符优先级:++/-- > ​ *(解引用操作符) > +/-(加减)。

  • 数组名是数组首元素的地址

  • **++cpp:cpp是c+3的地址,++后就是c+2的地址,*++cpp就是c+2,c+2是第三个char *的地址,** ++cpp就是 *(c+2),就是第三的char *,指向字符串“POINT”的首字符P。

  • *--*++cpp + 3:cpp是c+2的地址,++后再 *就是c+1,再--再 * 就是第一个char *,指向字符串“ENTER”的首字符E,再+3就指向了E。

  • *cpp[-2] + 3: ​转换成**(cpp-2)+3,cpp是c+1的地址,-2后再*就是c+3,再 *后就是第四个char *,指向字符串“FIRST”的首字符F,再+3就指向了S。

  • cpp[-1][-1] + 1转换成*( *(cpp-1)-1)+1,cpp是c+1的地址经过分析,*( *(cpp-1)-1)指向字符串NEW”**的首字符N,再+1指向E。

答案:POINT ER ST EW

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用均值漂移来量化带宽分类数据

均值漂移概念 均值漂移的基本概念&#xff1a;沿着密度上升方向寻找聚簇点&#xff0c;其计算过程如下&#xff1a; 1 均值漂移算法首先找到一个中心点center&#xff08;随机选择&#xff09;&#xff0c;然后根据半径划分一个范围 把这个范围内的点输入簇x的标记个数加1 2 在…

GPT和爬虫有什么区别?如何利用爬虫实现GPT功能

GPT&#xff08;Generative Pre-trained Transformer&#xff09;和爬虫是两个完全不同的概念和技术。GPT是一种基于Transformer模型的自然语言处理模型&#xff0c;用于生成文本&#xff0c;而爬虫是一种用于从互联网上收集数据的技术。 GPT是由OpenAI开发的一种深度学习模型&…

以太网之ARP协议(八)

一、概要 在网络通信中主要以IP为主机标识进行数据通信的&#xff0c;但实际的数据链路层传输以MAC地址为数据传输的节点地址。那设备之间又是如何通过IP地址确认对应主机的MAC地址的&#xff1f;这就是ARP协议的工作。 ARP是一种以目标IP地址为线索&#xff0c;用来定位下一个…

300PLC转以太网模块plc300以太网通信模块

摘要 工业通讯的发展已经迅速到了一个令人咋舌的地步&#xff0c;以太网通讯已经成为了工业通讯的主流。而今天&#xff0c;我们要介绍的是一款以太网通讯处理器——捷米特ETH-S7300-JM01&#xff0c;它不仅成熟、稳定&#xff0c;而且价格优惠&#xff0c;为工业以太网通讯领域…

Qt对地震数据(文件格式*.Segd)实现将时域数据转频域数据

文件格式以segd为例&#xff0c;其他地震文件格式同理。 时域数据 时域数据通俗点讲就是我在某个时间段记录的一个值&#xff0c;然后经过一段时间后&#xff0c;产生的一组数据就是时域数据。 频域数据 频域数据是指信号在频率域上的表示&#xff0c;即信号的频率特性。频…

PowerShell快速ssh

文件 ~/.ssh/config 内容 Host masterHostName 192.168.10.154User root访问 $ ssh master 效果 进阶 配置秘钥 待续。。。

Transform、GameObject、Rigidbody

文章目录 零、初衷和溯源一、Transform类二、GameObject类三、Rigidbody类 零、初衷和溯源 这三个类的API官方文档&#xff0c;有些杂乱——本可以把它们分门别类的整理好&#xff0c;结果却是凌乱的堆在一起&#xff0c;令人恼火。   之所以把它仨放一起总结&#xff0c;是因…

【数据挖掘】时间序列教程【十】

5.4 通用卡尔曼滤波 上一节中描述的状态空间模型作为观测方程的更一般的公式 和状态方程 这里是一个p1 向量

simulink stateFlow流程图

基础 修改分支优先级 使用matlab workspace变量 例题 输出数组输入数组的平方 for循环 使用脚本的数值 实现数组索引

2021 RoboCom 世界机器人开发者大赛-本科组(初赛)

编程题得分&#xff1a;100 总分&#xff1a;100 7-1 懂的都懂 (20分) 众所周知&#xff0c;在互联网上有很多话是不好直接说出来的&#xff0c;不过一些模糊的图片仍然能让网友看懂你在说什么。然而对这种言论依然一定要出重拳&#xff0c;所以请你实现一个简单的匹配算法。 …

图像分类论文阅读

该论文通过结合VGG-19和VIT模型,实现乳腺超声图像的分类Breast Ultrasound Images Dataset | Kaggle PyTorch VGG19复现代码 # VGG19.py import torch import torch.nn as nnclass Conv(nn.Module):def __init__(self, in_channels, out_channels, kernel_size=1, stride=1,…

面向 “大模型” 的未来服务架构设计

文章目录 大模型热潮大模型落地服务设计 or 重构未来的服务架构微服务化分层化 大模型应用架构架构设计图架构 Demo 实现 小结附录 大模型热潮 今年的互联网赛道中 “顶流” 非大模型莫属。 科技部新一代人工智能发展研究中心 5 月底发布的《中国人工智能大模型地图研究报告》显…

极米H6 4K光学变焦版 打造全新4K观影体验

随着时代的发展&#xff0c;拥有家用智能投影的家庭也越来越多了。当下许多家用投影仪都搭载了智能系统&#xff0c;内含大量片源。另外&#xff0c;投影仪还可以通过不同接口接入不同的设备&#xff0c;如游戏机、电脑、U盘等。只需要一台投影便可以实现在线追剧、听歌、游戏、…

ZER-10-EP-L350C-224叠加式比例溢流阀放大器

ZER-06-EP-L105C-224、ZER-06-EP-L210C-224、ZER-06-EP-L350C-224、ZER-10-EP-L105C-224、ZER-10-EP-L210C-224、ZER-10-EP-L350C-224叠加式比例溢流阀由主阀体和溢流阀、阀插件、佰业比例放大器组成&#xff0c;标准叠加安装接口&#xff0c;与换向阀等配合使用&#xff0c;便…

【ELK企业级日志分析系统】安装与部署ELK详解

ELK 企业级日志分析系统 1.ELK概述1.1 ELK简介1.2 可以添加的其它组件1.3 filebeat结合logstash带来好处1.4 为什么要使用ELK?1.5 完整日志系统基本特征 2.ELK的工作原理3.部署ELK集群3.1 安装Elasticsearch3.2 安装Elasticsearch-head插件3.3 安装ELK Logstash3.4 安装ELK Ki…

HO49-二叉树的最近公共祖先

leetcode原题链接&#xff1a;二叉树的最近公共祖先 题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的…

Linux中的GO更新与冲突

发行版&#xff1a;CentOS GO更新 在使用go get安装某模块时&#xff0c;报错&#xff1a; ../../pkg/mod/golang.org/x/sysv0.5.0/unix/syscall.go:83:16: undefined: unsafe.Slice ../../pkg/mod/golang.org/x/sysv0.5.0/unix/syscall_linux.go:2271:9: undefined: unsafe.…

Verilog学习笔记5:简单的加法器

代码&#xff1a; //加法器 timescale 1ns/10ps module add( a, b, y ); input a; input b; output[1:0] y;assign yab;endmodule //testbench module add_tb; reg a; reg b; wire[1:0] y;add add(.a(a),.b(b),.y(y));initial begina<0;b<0;#10 a<1;b<0;#10 a&l…

SpringMVC 中的视图如何渲染模型数据

SpringMVC 中的视图如何渲染模型数据 SpringMVC 是一个基于 Spring 框架的 Web 框架&#xff0c;它提供了一种方便的方式来处理 HTTP 请求和响应。在 SpringMVC 中&#xff0c;视图是用来渲染模型数据的组件&#xff0c;它们负责将模型数据转换为 HTML、JSON、XML 等格式的响应…

风沙文字粒子制作

目录 一、导入素材 二、合成介绍 三、文字合成新建和制作 四、文字动画合成新建和制作 五、风沙文字消失合成的新建和制作 六、风沙文字消失合成制作完成 七、制作风沙文字出现合成 风沙文字粒子效果如下&#xff1a; AE模板风沙文字粒子宣传片片头片尾 一、导入素材 打…