玩转C语言——深入理解指针

news2024/11/28 6:36:04

一、指针概念

        1.1 内存和地址

     在开始学习指针前,我们先来讲一个例子,假如你身处一栋楼中,你点了一份外卖,那么,外卖员如何能找到你?有两种方法。法一:直接一间一间找,这样做不仅消耗时间长,而且效率低。法二:把房间编号,然后告诉外卖员房间号即可。⽣活中,每个房间有了房间号,就能提⾼效率,能快速的找到房间。

        生活中如此,计算机中亦如此。计算机为了实现对内存的高效管理,是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。其中,每个内存单元,相当于⼀个房间,⼀ 个⼈字节空间⾥⾯能放8个⽐特位,就好⽐一个房间住八个人,每个⼈是⼀个⽐特位。 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的⻔牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。

        ⽣活中我们把⻔牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫:指针

        所以我们可以理解为: 内存单元的编号 == 地址 == 指针

        1.2 指针变量和地址

        理解了以上概念,我们再回到C语⾔,在C语⾔中创建变量其实就是向内存申请空间。

        那我们怎么拿到地址呢?那就不得不拿出一个操作符了——&(取地址操作符),它的作用便是拿出地址。

	int a = 4;
	printf("%p", &a);

        这样便可打印出a的地址。

        1.3 指针的类型

        那么,指针都有哪些类型呢?

        int *ptr; // 声明一个指向整数的指针

         char *ptr; // 声明一个指向字符的指针

         double *ptr; // 声明一个指向双精度浮点数的指针

        总之,和我们以前学习变量类型时差不多,那我们该如何判断指针类型呢?

        从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看各个指针的类型:
int*ptr;//指针的类型是int*
char*ptr;//指针的类型是char*
int**ptr;//指针的类型是int**
int(*ptr)[3];//指针的类型是int(*)[3]
int*(*ptr)[4];//指针的类型是int*(*)[4]
怎么样?找出指针的类型的方法是不是很简单?

 1.4 指针变量和解引⽤操作符(*) 

        那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要 存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。

	int a = 4;
	int* p = &a;

        指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

        我们把地址保存好以后,我们该如何使用呢?大家想象一下:我们如何把锁住门打开,答案很简单,使用钥匙即可。同理,我们如何使用地址,就要找出与之匹配的“钥匙”那就是叫解引⽤操作符(*)。

	int a = 4;
	int* p = &a;
	*p = 0;

        以上代码,使用了*操作符,什么意思呢?即*pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0。

 1.5 指针的大小

        初步了解了使用后,我们肯定要了解一下指针的大小,那指针的大小和什么有关呢?大家可以先猜测一下,我们随后解密。

#include<stdio.h>
int main()
{
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

        答案如下:

x64环境下输出结果

        

x86环境下输出结果

        总结:

        32位平台下地址是32个bit位,指针变量⼤⼩是4个字节

         64位平台下地址是64个bit位,指针变量⼤⼩是8个字节

         注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。 

1.6 指针变量类型的意义 

        调试以下代码,观察内存变化。

#include <stdio.h>
int main()
{
 int n = 0x11223344;
 int *pi = &n; 
 *pi = 0; 
 return 0;
}//代码一
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 char *pi = &n; 
 *pi = 0; 
 return 0;
}//代码二

        调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

        结论:

        指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。

1.7 野指针

        概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

        形成原因:

        1. 指针未初始化

#include <stdio.h>
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
 *p = 20;
 return 0;
}//此代码在VS上会报错

        2.指针越界访问

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	for (int i = 0; i < 11; i++)
	{
        //当指针指向的范围超出数组arr的范围时,p就是野指针
		*p = arr[i];
		p++;
	}
	return 0;
}

        3.指针指向的空间释放

#include<stdio.h>
int* test()
{
	int n = 10;
	return &n;
}
int main()
{
	int* p = test();
	return 0;
}

        那该如何避免指针变成野指针呢?

        1.指针初始化,明确指针指向哪里。如若不知道可以赋值为NULL(NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。)

        2.⼩⼼指针越界,不要越界访问。

        3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性。

        4.避免返回局部变量的地址。

        1.8 const修饰 

        变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作用。变量中可以这样使用,指针亦是如此。

#include<stdio.h>
void test1()
{
	int n = 10;
	int m = 20;
	int* p = &n;
	*p = 20;//ok?
	p = &m; //ok?
}
void test2()
{
	int n = 10;
	int m = 20;
	const int* p = &n;
	*p = 20;//ok?
	p = &m; //ok?
}
void test3()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20;//ok?
	p = &m; //ok?
}
void test4()
{
	int n = 10;
	int m = 20;
	const int*const p = &n;
	*p = 20;//ok?
	p = &m; //ok?
}
int main()
{
	test1();//无const修饰
	test2();//const在*的左边
	test3();//const在*的右边
	test4();//const在*的两边
	return 0;
}

        大家可自行在VS上验证,观察是否会报错。

        这里直接说结论:

        const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。

        const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。

        大家可以这样理解:假如你带你女朋友去逛街,突然想吃凉皮,你要是想守住你的钱袋子,就在左边加const,这样你的钱袋子就保住了,但是你的女朋友可能会想,连个凉皮都不给我,我要换男朋友,然后你就没女朋友了。所幸刚刚是做梦,但是不幸的是,你女朋友现在就是要吃凉皮,为了避免上一次情况的出现,把*放入了const右边,给她了一碗凉皮,这样她就不会换男朋友了。就在你为余额清零心疼不已时,你又醒了,原来又是一个梦,你女朋友又要吃凉皮,你重生归来,顿时恼怒,结合前两世方法,在const两边都加*,这样你既不用花钱,有能保住女朋友,这才是重生文男主。(仅当帮助记忆,无其他不良引导)

        1.9 二级指针

        指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?

        答案是:二级指针。

#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	int** pa = p;
	return 0;
}

        对于⼆级指针的运算有:

        *pa 通过对pa中的地址进⾏解引⽤,这样找到的是 p , *pa 其实访问的就是 p 。

        **pa 先通过 *pa 找到 p ,然后对 p 进⾏解引⽤操作: *pa ,那找到的是 a 。

        我觉得可以把多级指针想象成多级导数或者剥洋葱,逐渐抽丝剥茧,最后寻到本质。

 二、指针运算 

        指针的基本运算有三种,分别是:

  •          指针+- 整数
  •          指针-指针
  •          指针的关系运算

2.1 指针+- 整数 

        因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。

int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

2.2 指针-指针 

int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;
}
int main()
{
	printf("%d\n", my_strlen("abcd"));
	return 0;
}

2.3 指针的关系运算

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]);
	while (p < arr + sz)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

三、指针与数组

        3.1 数组名理解

        在学习函数时,我们要传数组时,总是写的是数组名。当你看到这时,或许会好奇数组名是什么?用以下代码来演示:

int main()
{
		int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
		printf("&arr[0] = %p\n", &arr[0]);
		printf("arr = %p\n", arr);
		return 0;
	return 0;
}

        运行结果如下:

        我们发现它们的地址一样,所以我们得出如下结论:

        数组名是地址并且是数组首元素的地址。

        这里强调两个例外:

• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节

• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的) 

        那么,arr和&arr有什么区别呢?那可以试试如下代码:

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

         这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是 ⾸元素的地址,+1就是跳过⼀个元素。 但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。

        3.2使⽤指针访问数组

         有了前⾯知识的⽀持,再结合数组的特点,我们就可以很⽅便的使⽤指针访问数组了。

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

        这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可 以访问数组呢?

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

         我们可以得出:arr[i]等价于*(arr+i)。

         3.3 一维数组传参本质

        我们在学习函数时,也传递过数组。那么,我们到底传过去的是什么呢?

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

        结果如下:

        这就要学习数组传参的本质了,说本质上数组传参本质上传递的是数组⾸元素的地址。

        所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函 数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

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

        3.4 指针数组 

        指针数组是指针还是数组? 我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?是存放指针的数组。

        指针数组的每个元素都是⽤来存放地址(指针)的。

        指针数组的每个元素是地址,⼜可以指向⼀块区域。

       3.5 指针数组模拟⼆维数组

        

        我们知道 ,二维数组在内存里是连续存放的。

        pa[i]是访问pa数组的元素,pa[i]找到的数组元素指向了整型⼀维数组,pa[i][j]就是整型⼀维数 组中的元素。

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 6,7,8,9,10 };
	int arr3[] = { 11,12,13,14,15 };
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在pa数组中
	int* pa[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", pa[i][j]);
		}
		printf("\n");
	}
	return 0;
}

     3.6 数组指针变量

        前面我们已经学习过了指针数组,指针数组是把指针放到一个数组中,那么,数组指针变量是指针变量?还是数组?

        答案是:指针变量。

        我们已经熟悉:

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

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

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

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

        大家可以先猜测一下p1和p2分别是什么?

        数组指针变量

int (*p)[10];

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

        那问题来了,那该这么初始化?数组指针本质是什么?答案是指针。指针是用来干什么的?答案是存放地址?那数组指针是不是可以理解为:存放数组的地址。所以:如果要存放个数组的地址,就得存放在数组指针变量中。

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

        通过调试,我们发现&arr与p的类型一致,证明我们的结论是正确的。

        数组指针类型解析:

int(*p)[10] = &arr;
|   |   |
|   |   |
|   |   p指向数组的元素个数
|   p是数组指针变量名
p指向的数组的元素类型

 3.7 二维数组传参本质

        学习到这里我们就可以解释二维数组传参本质。当时我们代码如下:

​
#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 6,7,8,9,10 };
	int arr3[] = { 11,12,13,14,15 };
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在pa数组中
	int* pa[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", pa[i][j]);
		}
		printf("\n");
	}
	return 0;
}

​

        这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?

         ⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。 

        所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀ 维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

#include<stdio.h>
void test(int *p[5],int a,int b)
{
	for (int i = 0; i < a; i++)
	{
		for (int j = 0; j < b; j++)
		{
			printf("%d", *((*p+i)+j));
		}
	}
}
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;
}

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

四、指针与函数

        4.1 函数指针变量的创建

        什么是函数指针呢?通过前面的学习我们不难知道。函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

        那我们不禁要问?函数真的有地址吗?我们来代码验证一下:

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

        确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅ 式获得函数的地址。

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

#include<stdio.h>
void test()
{
	;
}
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 3;
	int y = 4;
	test();
	int ret = add(x, y);
	void(*p)() = test;
	int(*p1)(int, int) = add;//里面有无x,y都一样
	return 0;
}

        函数指针类型解析:

int (*pf3) (int x, int y)
|	  | ------------
|	  |		|
|	  |		pf3指向函数的参数类型和个数的交代
|	  函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型

        4.2 函数指针变量的使⽤

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

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

        4.3 typedef关键字

        typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

        像unsigned int 你觉得写得麻烦,你就可以把它定义成unit。

typedef unsigned int unit;

        如果是指针类型,能否重命名呢?其实也是可以的。

typedef int* pt_t;

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

typedef int(*parr_t)[5];//parr_t必须放入*右边

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

​
typedef void(*pf_t)(int);//parr_t必须放入*右边

        4.4 函数指针数组

        数组是⼀个存放相同类型数据的存储空间,那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?如下:

int (*p[10])();

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

五、转移表 

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

        举例:计算器的⼀般实现:

        在没学习函数指针数组之前:

#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");
	printf("请选择:");
}
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 x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入要计算的两个数:");
			scanf("%d%d", &x, &y);
			ret =add(x,y);
			printf("ret = %d", ret);
			break;
		case 2:
			printf("请输入要计算的两个数:");
			scanf("%d%d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d", ret);
			break;
		case 3:
			printf("请输入要计算的两个数:");
			scanf("%d%d", &x, &y);
			ret = mul(x, y);
			printf("ret =%d", ret);
			break;
		case 4:
			printf("请输入要计算的两个数:");
			scanf("%d%d", &x, &y);
			ret = div(x, y);
			printf("ret = %d", ret);
			break;
		default:
			printf("选择错误,重新选择");
			break;
		}
	} while (input);
	return 0;
}

        我们可以看出,不仅非常繁琐,还涉及许多不必要的重复,这是我们就可以来优化一下:

#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");
	printf("请选择:");
}
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 x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	int (*p[5])(x, y) = { 0,add,sub,mul,div };
	do
	{
		menu();
		scanf("%d", &input);
		scanf("%d%d", &x, &y);
		if (input > 0 && input < 5)
		{
			printf("%d", (p[input])(x, y));
		}
	} while (input);
	return 0;
}

        我们可以看到更加的简洁,这就是函数指针数组的好处。

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

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

相关文章

数据结构(三)复杂度的深层次剖析

之前发布了数据结构&#xff08;一&#xff09;&#xff0c;很多同学反响不够清晰&#xff0c;那今天就发一篇对复杂度专题的博客&#xff0c;希望对大家理解复杂度提供一些帮助。 时间复杂度 我们先来一个理解一个复杂度&#xff0c;二分查找的复杂度&#xff08;之前写过二…

JSONP 实现跨域请求案例

后端使用 express 搭建&#xff0c;案例代码如下&#xff1a; const express require(express)const app express() const PORT 3000app.get(/data, (req, res) > {const jsonData {name: Alan,age: 666,city: GD}const callback req.query.callback // 获取前端中的回…

野火ESP8266模块开发-基于Arduino IDE

一、野火ESP8266模块介绍 ESP8266 拥有高性能无线 SOC&#xff0c;给移动平台设计师带来福音&#xff0c;它以最低成本提供最大实用性&#xff0c;为 WiFi 功能嵌入其他系统提供无限可能。ESP8266 是一个完整且自成体系的 WiFi 网络解决方案&#xff0c;能够独立运行&#xff0…

网络工程师之路由交换技术篇

网络工程师之路由交换技术篇 路由交换之技术篇ARPICMPBPDUIPv6IP编址MAC其他技术点参考 以下均为个人笔记&#xff0c;摘录到csdn做备份 路由交换之技术篇 ARP Operation Code指定了ARP报文的类型&#xff0c; 包括ARP request 和ARP reply&#xff1b;取值为1或者2 &#x…

计算机毕业设计-基于深度学习的验证码识别方法设计与实现

概要 验证码是一个系统区分人类与非人类行为的有效方式。验证码识别技术能够使计算机程序输入正确的验证码&#xff0c;伪装成人类用户进入目标系统。另一方面&#xff0c;系统方面需要考虑破解验证码识别技术&#xff0c;修补验证方式的可能漏洞&#xff0c;使之能够更有效地区…

返回a除以b的余数 operator.mod(a, b) 返回a的b次幂 operator.pow(a, b)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 返回a除以b的余数 operator.mod(a, b) 返回a的b次幂 operator.pow(a, b) [太阳]选择题 请问以下程序中operator.pow(2, 3)的执行结果是&#xff1a; import operator print("【执行】ope…

springboot284基于HTML5的问卷调查系统的设计与实现

问卷调查系统的设计与实现 摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;问卷信息因为其管理内容繁杂&#xff0c;管理数量繁多导…

【vue核心技术实战精讲】1.1 Vue开篇介绍 + 1.2 Vue的起步 和 插值表达式

文章目录 准备开始适应人群vue 框架学习路线一、vue 基础1、历史介绍2、前端框架与库的区别? 二、vue的起步 和 插值表达式Stage 1&#xff1a;下载包&#xff0c;并放入项目中Stage 2&#xff1a;编码 准备开始 适应人群 有一定的HTML/CSS/JavaScriptES6基础开发人员 vue …

深入解析MySQL数据库锁机制

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

基于springboot+vue的光影视频网站

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

【机器学习智能硬件开发全解】(九)—— 政安晨:通过ARM-Linux掌握基本技能【C语言程序的预处理过程】

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 机器学习智能硬件开发全解 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; C语言程序的预处理过程是在编译阶段之前进行的&#x…

基础-笔试题6

1、tcp/udp是属于哪一层&#xff1f;tcp/udp有何优缺点&#xff1f; tcp /udp属于运输层 TCP 服务提供了数据流传输、可靠性、有效流控制、全双工操作和多路复用技术等。 与 TCP 不同&#xff0c; UDP 并不提供对 IP 协议的可靠机制、流控制以及错误恢复功能等。由于 UDP 比较…

[Qt学习笔记]Qt鼠标事件mouseMoveEvent实时获取图像的坐标和像素值

目录 1、介绍2、效果展示3、实现过程3.1 图像的加载和显示3.2 设置鼠标跟踪事件激活3.3 实现代码 4、源码展示 1、介绍 上一篇介绍了使用OpenCV的setMouseCallback回调函数实现获取鼠标点击点的图像坐标和像素值&#xff0c;本篇使用鼠标事件mouseMoveEvent函数来实现实时获取…

vue3+element Plus form 作为子组件,从父组件如何赋值?

刚开始接触vue3时&#xff0c;碰到一个很low的问题&#xff0c;将form作为子组件&#xff0c;在页面中给form表单项输入内容&#xff0c;输入框不显示值&#xff0c;知道问题出在哪&#xff0c;但因为vue3组合式api不熟悉&#xff0c;不知从哪下手... 效果图&#xff1a; 父组…

如何在iOS系统抓取log

前言&#xff1a;因为作者目前工作领域和苹果智能家居有关&#xff0c;然后发现一些bug其实是apple sdk原生code的问题&#xff0c;所以需要给apple提radar单&#xff0c;就需要抓ios端Log充当证据给apple看&#xff0c;其实ios抓log非常简单&#xff0c;大家感兴趣可以学习下哦…

Python条件语句深度解析:从基础到应用的全面指南

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! ​ 目录 &#x1f4d8; 一、引言 &#x1f4dd; 二、…

【Flutter】导航组件 NavigationRail 的用法简介

​​Material Design 3 定义了三种导航模式&#xff0c;其用法和对应的 Flutter 组件如下所示&#xff1a; MD3 导航Flutter 组件用途Navigation barBottomNavigationBar小型屏&#xff08;宽度小于640&#xff09;Navigation drawerDrawer大型屏&#xff08;宽度大于960&…

Java基础 学习笔记四

标识符 标识符是在源代码中程序员自己有权利命名的单词标识符可以标识 变量名&#xff0c;方法名&#xff0c;类名 标识符命名规则 标识符只能由数字&#xff0c;字符&#xff08;java支持所有国家语言&#xff09;&#xff0c;_&#xff0c; $ 组成&#xff0c;不能含有其他…

基于树莓派实现 --- 智能家居

最效果展示 演示视频链接&#xff1a;基于树莓派实现的智能家居_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Tr421n7BM/?spm_id_from333.999.0.0 &#xff08;PS&#xff1a;房屋模型的搭建是靠纸板箱和淘宝买的家居模型&#xff0c;户型参考了留学时短租的公寓~&a…

软考高级:软件测试阶段概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…