C语言:指针详解(3)

目录

一、字符指针

二、数组指针

1.数组指针的定义

2.数组指针的初始化

3. 二维数组传参的本质

三、函数指针

1.函数指针的创建

2.函数指针的使用

3.有趣的代码(1)

4.有趣的代码(2)

四、typedef关键字

1.typedef的使用方法

2.typedef和#define的区别

五、函数指针数组

六、转移表

1.使用函数指针数组实现简易计算器

2.使用函数指针实现简易计算器


正文开始

一、字符指针

在了解了指针的类型后我们都知道有一种指针为字符指针char*。我们一般是这么使用字符指针的:

int main()

{

    char ch = 'w';

    char *pc = &ch;

    *pc = 'w';

    return 0;

}

在学习了const关键词后,还有这么一种使用方法:

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中。

那么简而言之上面代码的意思就是把一个常量字符串的首字符h的地址存放到指针变量pstr中。

接下来我们来看下面代码:

#include <stdio.h>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;
}

我们先来分析一下代码:首先我们先创建了两个字符数组str1和str2,随后又创建了两个字符串常量str3和str4。随后要分别判断两个字符数组str1和str2是否相等,两个字符串常量str3和str4是否相等。

在讲述数组的时候C语言:数组-CSDN博客我们就已经知道在创建数组的时候编译器会向内存中随机申请一块空间来存放这个数组,这也就意味着,无论是两个数组的大小相同还是内容相同,这两个数组的地址是永远不会重叠在一起的。那么在上述代码中,字符数组str1和str2就不相等。

再来分析str3和str4。我们先引入一个例子,假设我们在某次编写代码时创建了两个整型变量n1和n2,随后将两个变量都赋值为10。然后我们再判断这两个变量是否相等。显然是相等的。我们为这两个变量赋值后本质上我们可以将这两个变量视为常量了,即使是n1和n2在内存中的地址是不同的。同样的,字符串常量也是如此。字符串常量字符串常量,都是常量了,即便是它们的地址是不同的,它们在数值上也是相等的。

那么上述代码的输出结果就是:

实际上只是为了便于理解才有上述对整型变量比较的类比。这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域, 当几个指针指向同一个字符串的时候,它们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

二、数组指针

1.数组指针的定义

之前我们学习了指针数组。指针数组是一种数组,在其中存放的是地址(指针)。那么数组指针变量是指针变量?还是数组?数组指针,数组指针,即数组的指针,所以数组指针存放的是指针变量。在前面我们学习指针的时候接触到了我们目前为止最为频繁的两个指针变量:整型指针变量和浮点型指针变量。

整形指针变量:int* pint;//存放的是整形变量的地址,能够指向整形数据的指针。

浮点型指针变量:float* pf;//存放浮点型变量的地址,能够指向浮点型数据的指针。

那么数组指针应该是存放数组的地址,能够指向数组的指针变量。

那么下面代码哪个是数组指针变量?

int *p1[10];

int (*p2)[10];

数组p1是int*类型,存放的是整型指针,所以p1是指针数组。那么p2就是我们在这一小节重点介绍的数组指针。

数组指针变量的写法

int (*p)[10];

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指向一个数组的指针,叫做数组指针。这里要注意:[]的优先级是要高于*号的,所以必须加上()来保证p先和*结合。

2.数组指针的初始化

数组指针是用来存放数组地址的,那该怎么获得数组的地址呢?我们之前已经知道并理解了该如何获得一个数组的地址:C语言:指针详解(2)-CSDN博客。就是利用&数组名来获取整个数组的地址。

int arr[10] = {0};

&arr;//得到的就是数组的地址

如果要存放个数组的地址,就需要存放在数组指针中:

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

我们需要调试来观察一下数组的地址是否真正地被存放到了数组指针中:

观察到,数组指针p的值与&arr的值是相同的,这说明数组的地址的确被存放到了数组指针p当中。那么我们可以对数组指针有一个更深的认识:

有了对数组指针的理解,接下来我们来进一步剖析二维数组。

3. 二维数组传参的本质

在以前我们要让一个二维数组传参给一个函数的时候,我们通常是这样写的:

#include <stdio.h>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]。这也就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:

#include <stdio.h>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));}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;
}

其中的printf("%d ", *(*(p+i)+j));可能会有同学不太理解。我们在利用指针打印一维数组的时候是这么写的:

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

在这里我们知道p取的是数组arr的首元素地址,当我们建立一个循环让其加上i后再解引用就可以依次得到后续的元素,然后打印。

同样的,printf("%d ", *(*(p+i)+j));这行代码其实就是在打印二维数组的元素,也就是一维数组。其中i是行数,j是列数。我们一步步从里到外慢慢分析:

我们已经知道p是数组指针,而且在这里特指二维数组的首元素——即第一个(第一行)的一维数组。那么也就是说可以将p+i理解为指针p向后移动i个整型数组的长度即第i行一维数组。随后再对其解引用,得到的就是第i行一维数组

在*(p+i)的基础上再加上列数j,就表示在第i行的整型数组中向后移动j个整型元素的长度,即指向第i行第j列的整型元素的指针。

然后再对*(p+i)+j进行解引用,即*(*(p+i)+j),表示取出指针*(p+i)+j所指向的整型元素的值,即第i行第j列的整型元素。

综上所述,*(*(p+i)+j)就可以理解为取出二维数组中第i行第j列的元素值。

所以,我们在进行二维数组传参的时候,既可以写成数组的形式,也可以写成指针的形式。

三、函数指针

1.函数指针的创建

什么是函数指针呢?根据前面学习的内容,我们进行类比之后不难得出结论:函数指针变量就是用来存放函数地址的。我们可以通过函数指针来调用函数。既然有函数指针的存在,那么函数就一定存在地址:

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

结果(VS2022 x86环境下):

可以发现,我们在对test()函数进行取地址操作后的确输出了一个地址,这就说明函数确实是存在地址的。然后我们又直接打印了test()函数的函数名,输出的结果也是一个地址,而且和&test的结果是一样的,这说明函数名就是函数的地址

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

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写上或者省略都是可以的

函数指针类型解析:

2.函数指针的使用

假设我们引出一个加法函数Add(),我们在以前调用Add()函数时是这么写的:

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

在知道了函数指针后,我们或许可以通过函数指针来实现函数的调用。

首先我们肯定要先创建一个函数指针来指向Add()函数,我们为这个指针起一个名字,叫做pAdd,那么以函数指针的形式来表示的话就是:

int (*pAdd)(int x,int y) = &Add;

当然,我们知道函数名和数组名一样,其含义都是本身的地址。那么上行代码也可以写成:

int (*pAdd)(int x,int y) = Add;

当然,这里的形参变量其实也可以省略,只写出形参部分的类型:

int (*pAdd)(int x,int y) = &Add;

int (*pAdd)(int x,int y) = Add;

既然pAdd是一个指向Add()函数的指针,那么我们对其解引用应该就会得到Add()函数,那么我们就可以将以前调用函数的方法改写为解引用指针来调用函数:

printf("%d\n", (*pAdd)(2, 3));

需要注意的是,这里的(*pAdd)与我们在声明函数指针用到的(*pAdd)是不一样的。在这里的意思是对函数指针变量pAdd进行解引用而间接调用Add()函数,而在声明时(*pAdd)代表的含义是一个名为pAdd的函数指针。

无论是&函数名还是函数名都可以表示函数指针,相应的,指针pAdd的值是Add()函数的地址也就是:pAdd=&Add等价于pAdd=Add。也就是函数指针可以直接转化为函数名本身,那上述调用函数的语句也可以写成:

printf("%d\n", pAdd(2, 3));

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

结果:

3.有趣的代码(1)

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

我们在拿到这种代码的时候大多都是比较懵的,但是如果静下心来分析,一切都会非常清晰。

这里我们由里到外逐个分析。最里层的(void (*)())这个语句块,根据我们刚才了解的函数指针的相关概念,可以知道这是一个指向某个返回类型为void的函数没有形参没有命名的一个函数指针

然后再看外层的左边:(*(void (*)())0)。这部分其实可以看成两个小部分:第一个部分就是(void (*))())0这部分。既然void (*)()是一个函数指针,那么用括号括起来后就代表它是一个类型。我们在之前已经知道了强制类型转换——(类型)C语言:操作符详解-CSDN博客。那么这一部分的操作就是将0给强制转换成函数指针类型。啊这时肯定就会有人问啦,0是一个整型啊,怎么能被转换成一个指针类型呢?怎么不行呢?强制类型转换中的强制是很强制的,无论你是啥类型都能被转换成我所需要的类型。况且0在这里的真正含义其实是空指针NULL,当然是可以被转换成另一种指针类型的。然后再加上左边的解引用操作符就不难理解了,就是对这个被强转后的指针进行解引用操作。

最后再加上外层的右边:(*(void (*)())0)()。我们已经知道了,函数指针可以直接写成函数名,它们俩是等价的。而且无论函数指针是否进行解引用,其表达的含义其实就是函数名。根据这一点我们就不难理解了,左边一坨再加上右边的一个小圆括号,其实就是在调用一个函数啊!

但是呢这行代码只是用来检验大家的学习成果,并没有实际的意义,如果尝试执行它可能会发生报错:

4.有趣的代码(2)

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

还是一样的,遇到类似这样的代码不要慌张,我们慢慢的将其从里到外拆开来看~

首先看最内层的:signal(int , void(*)(int))。我们可以很清晰地分析出这个语句要表达的含义:它其实就是代表一个有两个形参分别是int类型和函数指针类型(形参为int类型且无返回类型)名为signal的函数(返回类型我们暂且不说)。

理解了内层后呢外层其实就很好理解了:void (*signal(int , void(*)(int)))(int)。既然内层的本质实际上是一个函数,那我们不妨先将其简写为signal(即舍去形参部分)。那么整个语句就可以写成这样的:void (*signal)(int)。诶!我们就会发现,外层居然也是一个指向无返回类型的形参为int类型的函数名为signal的函数指针!那么到这里这行代码就分析完全了~

四、typedef关键字

1.typedef的使用方法

在C语言中提供了typedef关键字,由此我们可以依靠它来为某种类型取一个新的名字,进而可以将复杂的类型简单化。typedef的语法如下:

typedef 原始数据类型 新类型名称;

比如,如果你觉得unsigned int写起来不方便,想将其改写为uint,那么我们可以这样写:

typedef unsigned int uint;//将unsigned int重命名为uint

如果是指针类型,也可以对其进行重命名。比如将int*重命名为ptr_t:

typedef int* ptr_t;

但是需要注意的是对于数组指针函数指针稍微有点区别。比如我们有一个数组指针类型int(*)[5],我们需要将其重命名为parr_t,那可以这样写:

typedef int(*parr_t)[5];//新的类型名必须在*的右边

函数指针的重命名也是一样的。比如将void(*)(int)类型重命名为pf_t,就可以这样写:

typedef void(*pf_t)(int);//新的类型名必须在*的右边

用上typedef来简化上述的有趣的代码(2):

typedef void(*pf_t)(int);

pf_t signal(int, pf_t);

当然typedef也可以用来对结构体进行重命名,这在后续讲解结构体的时候会再次强调。同时,typedef重命名对未来学习数据结构的时候也是非常重要的一个语法,可以帮助我们简化代码,让代码的可读性更强。

2.typedef和#define的区别

尽管我们目前还没有遇到过利用#define来重命名类型的情况,其中遇到的名词也可能是陌生的,我们会在后续文章中进行解释。但是这里还是需要先记住typedef和#define对类型的重命名之间的区别:

①typedef与#define不同,typedef创建的符号名只受限于类型,不能用于值

②typedef由编译器解释,不是预处理器

③在其受限范围内,typedef比#define更灵活

这里再提一下typedef和#define在语法上的区别。

如果我们事先将char*类型用typedef重命名为STRING,在后续创建变量的时候是没有任何问题的。其中name变量和sign变量都是char*类型的:

但是,如果我们将typedef替换为#define,这种做法是未定义的:

如果我们写成这样:

我们发现,编译器并没有发生任何报错,但是这种做法真的和typedef的做法一样吗?

我们可以发现,name的类型是char*,而sign的类型居然是char。由此我们可以总结出:

①对于typedef char* STRING,当利用STRING创建变量时,例如变量sign和name,其效果就等价于char* sign,* name;

②对于#define STRING char*,当利用STRING创建变量时,例如变量sign和name,其效果就等价于char* sign,name;

五、函数指针数组

数组是一个存放相同类型数据的连续的数据结构。我们学习了指针数组:

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

如果把函数的地址存到一个数组中,那么这个数组就叫函数指针数组,其中每个元素都是指向若干个函数的指针。那函数指针数组该如何定义呢?定义函数指针数组的基本语法如下:

返回类型 (*数组名[数组大小])();

如果函数没有返回值,则返回类型为void;如果函数没有参数,则参数列表为空。例如:

int (*parr1[3])();

就是一个函数指针数组。

六、转移表

函数指针数组的定义意味着我们可以将若干个函数的地址存放在一个数组当中,因此我们调用函数的方式又多了一种——即访问数组的下标来进行调用函数。那么依此我们可以写出转移表这个对函数指针数组的应用实例。

比如我们要实现一个简易计算器,我们该如何实现呢?一个简易计算器,一般只含有加减乘除这四则运算,那我们在实现的时候就要将四个运算的函数写出来。随后就是写出一大串代码来供操作人员进行选择运算法则。这种用一般方法实现简易计算器应该是不难的,我们直接放代码:

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

但是大家有没有发现一个很明显的问题,就是在该代码中的switch-case语句中的子语句太过冗余:

就上述的4个小语句,就分别需要重复4遍,这会让代码显得非常冗余,使代码的观感和可读性变得很差。有问题就要分析,分析出来就要优化。既然这些代码变得非常冗余,我们可以将这个4行非常相似的代码放在一个函数当中,然后来进行调用使用。随后计算方面的问题就由我们刚刚了解到的函数指针数组来完成,当要完成相应的计算时,访问相应的数组元素即可完成计算。这里有两种方法进行优化,我们先讲述其中比较简单的一种。

1.使用函数指针数组实现简易计算器

第一种方法其实与传统实现简易计算器的代码大差不差,但是用到了函数指针数组进而才让这个代码显得不那么冗余。我们来分析一下,当我们创建好一个函数指针数组的时候,我们就需要把四则运算法则的相对应的函数的地址存入到这个数组当中,当然也包括0(用来退出计算器)。既然我们已经提前设计好了我们输入哪些值进行相应的运算,那我们也应该将相应的函数的标号与与之对应的数组下标对齐:

int (*p[5])(int x, int y) = { 0, add, sub, mul, div };//转移表

这就是对函数指针数组的一个应用。当我们需要进行加法运算的时候,我们就访问下标为1的元素,也就是间接地调用了add()函数;我们需要进行乘法运算的时候,我们就访问下标为3的元素,也就是间接地调用了mul()函数;当我们不再使用计算器的时候,我们就访问下标为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;
}

2.使用函数指针实现简易计算器

既然传统实现简易计算器的代码在switch-case语句中显得非常冗余,那我们就从根本上解决问题,我们将这些冗余的代码放在同一个函数体当中,这样只需要调用四次这个函数就可以实现计算器的功能,同时也减少了代码量。

我们不妨将这个函数命名为calc(),当我们转移到switch-case语句中应该是这样的(以add函数为例):

case 1:

    calc(add);
    break;

因为add是一个函数,所以calc函数的形参部分应该是函数指针,它负责打印和计算等一系列的操作,所以它的返回类型是void类型。那么calc()函数的基本结构就已经定下来了:

void calc(int (*pf)(int,int))

然后calc()函数首先要执行的就是输入输出,我们将之前冗余的部分写入,就可以将这冗余的部分变成公共的部分。当然,calc主要进行的还是计算这个操作,我们就要在其中创建三个临时变量进行计算,随后打印出所需的结果:

void calc(int (*pf)(int,int))

{

    int x = 0,y = 0,z = 0;

    printf("输入操作数:");

    scanf("%d %d", &x, &y);
    z = pf(x, y);

    printf("ret = %d\n", ret);

}

我们在调用所需函数的时候,只需要写calc(函数名)即可,这样calc()就会通过传址调用这一手段来找到相对应的函数,然后在z = pf(x,y)这行代码中将pf替换成对应的函数,然后再进行计算。那么通过函数指针来优化简易计算器的代码如下:

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

通过函数指针这一方法实现计算器其实也涉及到了主调函数回调函数的相关概念,相关概念我会在下一篇文章中体现。


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

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

相关文章

前端性能优化知识梳理

1.重要性 当我们面试的时候&#xff0c;前端性能优化方面算是必考的知识点&#xff0c;但是工作中我们又很少会重点的对项目进行前端优化&#xff0c;它真的不重要吗&#xff1f; 如果我们可以将后端响应时间缩短一半&#xff0c;整体响应时间只能减少5%~10%。而如果关注前端…

imx6ull启动方式和镜像文件烧写

文章目录 前言一、BOOT启动方式1.串行下载2.内部BOOT模式 二、内部BOOT模式详细流程1.启动设备的选择2.镜像烧写 总结 前言 &#x1f4a6; I.MX6Ull 支持多种启动方式以及启动设备&#xff0c;比如可以从 SD/EMMC、NAND Flash、QSPI Flash等启动。用户可以根据实际情况&#x…

【web安全】-- 命令执行漏洞详解

本文将从原理开始介绍命令执行漏洞并附有三个实例来供各位客官学习 文章目录 一、什么是命令执行漏洞二、出现的原因三、有可能存在命令执行漏洞的函数&#xff08;php&#xff09;1、利用一些函数来实现命令执行2、直接执行系统命令的函数 四、命令拼接符号1、Windows2、linux…

QT:不同UI间数据,信号的交互

前言 接上文&#xff0c;手动绘制的矩形框毕竟还是在上位机的播放界面内&#xff0c;想要把数据发送给3559还是需要通过串口或者网口发送&#xff0c;没有部署在一个界面就需要不同UI间数据和信号进行交互了&#xff0c;数据还好说&#xff0c;全局变量都可以做到&#xff0c;信…

【c++】反向迭代器的探究实现

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 在list中我们实现了正向的迭代器&#xff0c;学习完优先级队列后&#xff0c;我们也对适配器模式有了一个深刻的理解&#xff0c;这篇文章基于这种模式下&#xff0c;实现各类容器的反向迭…

Docker-compose部署LTC同步节点

1、下载ltc程序包&#xff0c;litecoin下载地址 下载页 mkdir /data/docker-compose/ltc cd /data/docker-compose/ltc https://github.com/litecoin-project/litecoin/releases/download/v0.21.3/litecoin-0.21.3-x86_64-linux-gnu.tar.gz2、编写dockerfile和bitcoin.conf b…

我开始接单/兼职/搞副业/建设个人社区,为自己谋后路了。

我开始接单/兼职/搞副业/建设个人社区&#xff0c;为自己谋后路了。 简述 大家好&#xff0c;我是小荣&#xff0c;一个前端开发程序员。我最近开始在业余时间接私单了&#xff0c;也在想一些能够带来成长&#xff0c;收入的副业&#xff0c;主要也是为了自己谋后路&#xff…

C语言:数据结构(双向链表)

目录 1、双向链表的结构2、顺序表和双向链表的优缺点分析3、双向链表的实现 1、双向链表的结构 注意&#xff1a;这⾥的“带头“跟前面我们说的“头节点”是两个概念&#xff0c;实际前面的在单链表阶段称呼不严谨&#xff0c;但是为了更好的理解就直接称为单链表的头节点。 带…

SSH远程登录实操实验!

ssh远程登录协议&#xff1a;默认端口号22 以下实验7-2是服务端&#xff0c;7-1是客户端 服务器的相关信息&#xff1a; 服务名称&#xff1a;sshd 服务端主程序&#xff1a;/usr/sbin/sshd 服务端配置文件&#xff1a;/etc/ssh/sshd_config 客户端相关信息&#xff1a; …

python的输入输出(爽文,备忘,查询,友好)

Python中的输入输出主要涉及到输入函数和输出函数。 输出函数&#xff1a;print() print() 函数用于将信息输出到屏幕上。它可以输出字符串、变量的值&#xff0c;以及其他各种数据类型。 name "Alice" age 30 print("姓名:", name, "年龄:&quo…

ip ssl证书无限端口网站

IP SSL证书是由CA认证机构颁发的一种特殊数字证书。大部分SSL数字证书都需要用户使用域名进行申请&#xff0c;想要对公网IP地址加密实现https访问就需要申请IP SSL证书。IP SSL证书采用了强大的加密算法&#xff0c;可以有效地防止数据在传输过程中被窃取或篡改&#xff0c;具…

[C语言]典型例题:小蚂蚁爬橡皮筋、买汽水问题、导致单词块、菱形打印……

1、小蚂蚁爬橡皮筋问题 假设橡皮筋长4m&#xff0c;小蚂蚁从一端爬向另一端每天爬1m&#xff0c;且每爬了1m&#xff0c;橡皮筋会立马拉伸4m&#xff0c;在理想条件下&#xff0c;小蚂蚁需要爬多少天可以到达橡皮筋的另一端&#xff1f; 不仔细想&#xff0c;我们很可能认为小蚂…

Scikit-Learn回归树

Scikit-Learn回归树 1、决策树1.1、什么是决策树1.2、决策树学习的步骤1.3、决策树算法 1、决策树 决策树&#xff08;DTs&#xff09;是一种用于回归和分类的有监督学习方法。通常&#xff0c;决策树用于分类问题&#xff1b;当决策树用于回归问题时&#xff0c;称为回归树。回…

【C++】:日期类的实现 -- 日期计算器

前言 1.日期类是一种十分经典的类型。对于C的初学者&#xff0c;它能够帮助我们融会贯通许多C的基础知识&#xff0c;它涉及许多的基础语法&#xff0c;比如引用&#xff0c;函数重载&#xff0c;传值/传参返回&#xff0c;构造函数&#xff0c;运算符重载&#xff0c;const成…

【Python小练】求斐波那契数列第n个数

题目 输出斐波那契数列第n个数。 分析 首先我们要知道&#xff0c;斐波那契数列&#xff0c;这个数列从第三位开始等于前两个数的和&#xff0c;要知道数列第n个数&#xff08;n>2&#xff09;&#xff0c;就要知道其前两相的值&#xff0c;着就需要用到递归了。来看一下吧…

C语言实验-循环结构和选择结构

一&#xff1a; 求和:1(14)(149)(14916)…(14916…n2)? 其中n的值由键盘输入&#xff1b; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int sum 0;int n 0;printf("请输入一个整数");scanf("%d", &n);for (int i 0; i &l…

Apache中如何配置 ws 接口

Apache中如何配置 wss 接口 在Apache中配置WebSockets的支持&#xff0c;你需要使用mod_proxy_wstunnel模块&#xff0c;该模块是Apache的一个代理模块&#xff0c;它允许你代理WebSocket请求。 以下是配置步骤的简要说明和示例&#xff1a; 确保你的Apache服务器安装了mod_…

【最大公约数 排序】2344. 使数组可以被整除的最少删除次数

本文涉及知识点 最大公约数 排序 LeetCode2344. 使数组可以被整除的最少删除次数 给你两个正整数数组 nums 和 numsDivide 。你可以从 nums 中删除任意数目的元素。 请你返回使 nums 中 最小 元素可以整除 numsDivide 中所有元素的 最少 删除次数。如果无法得到这样的元素&a…

【高质量】2024五一数学建模C题保奖思路+代码(后续会更新)

你的点赞收藏是我后续更新的最大动力&#xff01; 一定要点击文末的卡片&#xff0c;那是获取资料的入口&#xff01; 你是否在寻找数学建模比赛的突破点&#xff1f; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024 年五一数学建模&#xff08;C题&#xff09;…

1700java进销存管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 java web进销存管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为sqlser…