保姆式教学--指针的进阶-创新互联

在这里插入图片描述

创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站设计、网站建设、外贸网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的平泉网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!

共和国的的建设者可要好好学好指针哦!

文章目录
  • 引入!
  • 1、 字符指针
  • 2、 指针数组
  • 3、 数组指针
    • 3.1 数组指针的定义
    • 3.2 &数组名VS数组名
    • 3.3 数组指针的使用
  • 4、 数组传参和指针传参
    • 4.1一维数组传参
    • 4.2二维数组传参
    • 4.3一级指针传参
    • 4.4二级指针传参
  • 5、 函数指针
  • 6、 函数指针数组
  • 7、 指向函数指针数组的指针
  • 8、 回调函数

引入!
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
5. 指针的运算。

在这里插入图片描述

1、 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:

int main()
{char ch = 'w';
  char *pc = &ch;
  *pc = 'w';
  return 0;
}

还有一种使用方式如下:

int main()
{const char* pstr = "hello bit.";//这里并不是把一个字符串放到pstr指针变量里,而是将字符串的首字符的地址放到了pstr中。
  printf("%s\n", pstr);
  return 0;
}

`直接上题目练习,在题目中深度了解字符指针

//输出什么?
#includeint main()
{char str1[] = "hello bit.";//————————1
  char str2[] = "hello bit.";//————————2
  const char *str3 = "hello bit.";//————————3
  const char *str4 = "hello bit.";//————————4
  if(str1 ==str2)
printf("str1 and str2 are same\n");
  else
printf("str1 and str2 are not same\n");
  
  if(str3 ==str4)
printf("str3 and str4 are same\n");
  else
printf("str3 and str4 are not same\n");
  
  return 0;
}

答案:
在这里插入图片描述

分析:1和2分别创建了一个全新的数组,再进行比较的时候,操作系统会对他们的本质,即首字符地址,进行比较,既然是两个数组,那么他们的首字符地址一定不同(双胞胎都有不同的基因),所以拿1和2进行比较的时候会输出“str1 and str2 are not same”
str3是一个字符指针,指针的本质是字符串首字符的地址,str3、str4两个指针指向的都是同一个字符串的首字符的地址,所以str3和str4是相同的。
如何理解?:一家人有一个共同的家,通过一家三口中的每一个人我们都能找到这个家。
所以此时会输出“str3 and str4 are same”

再来一题关于const的使用的重要性

int main()
{char* pstr= "hello bit.";
	*pstr = "666";
	printf("%s\n", pstr);
	return 0;
}

输出:在这里插入图片描述
为什么这里我们没有任何输出?
分析:字符串首字符的地址首先传给了pstr,程序继续进行,走到27行,pstr被更改了,但是指针pstr仍是“h”的地址,内容变为“666”,此时进行输出,操作系统无法判断出输出内容。
假设我们使用了const进行修饰:

int main()
{const char* pstr= "hello bit.";
	*pstr = "666";//————5
	printf("%s\n", pstr);
	return 0;
}

在这里插入图片描述

5处在编译器里会有提示,提示表达式左侧必须为可以修改的标量,所以,妈妈再也不用担心我的pstr会被改变了,在实际中大大增强了安全性、实用性。
结论:合理使用const进行限制可以减少某些bug的产生。

2、 指针数组
类比:
整型数组————存放整形的数组
字符数组————存放字符的数组
得出:
指针数组————存放指针(地址)的数组
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
const char *arr[4]={"EDG","AG","QG","AE"};//字符指针数组

在这里插入图片描述
模拟二重数组

#includeint arr1[4]={1,2,3,4};
int arr2[4]={2,3,4,5};
int arr3[4]={3,4,5,6};
int arr4[4]={4,5,6,7};
int *arr[4]={arr1,arr2,arr3,arr4};
for(int i=0;i<=4;i++)
{for(int j=0;j<=4;j++)
	{printf("%d",arr[i][j]);
		//printf("%d",*(arr[i]+j));
	}
}

在这里插入图片描述

3、 数组指针 3.1 数组指针的定义

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针

int*p; //整形指针
float*q;//浮点型指针
int(*b)[];//数组指针
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 &数组名VS数组名

对于下面的数组:

int arr[10]; 	

arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:

#includeint main()
{int arr[10] = {0};
  printf("%p\n", arr);
  printf("%p\n", &arr);
  return 0;
}

在这里插入图片描述
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:

#includeint main()
{int arr[10] = {0 };
printf("arr = %p\n", arr);
printf("arr = %p\n", arr+1);

printf("&arr= %p\n", arr[0]);
printf("arr+1 = %p\n", arr[0]+1);

printf("&arr+1= %p\n", &arr);
printf("&arr+1= %p\n", &arr+1);
return 0;
}

在这里插入图片描述

分析:

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义是不一样的.
事实上,&arr表示的是整个数组的地址,而arr仅仅表示数组首元素的地址,因为&arr表示整个数组的地址,所以才有&arr+1跳过整个数组的大小

3.3 数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
来样例:

#includevoid ErWei(int arr[2][3],int m,int n)
{for(int i=0;ifor(int j=0;j	printf("%d",arr[i][j]);
		}
		printf("\n")
	}
}
int main()
{int arr[2][3]={1,2,3,4,5,6};
	ErWei(arr,2,3);
  return 0;
}

在这里插入图片描述

4、 数组传参和指针传参 4.1一维数组传参
1:C语言中,当一维数组做函数参数时,编译器总是把它解析成一个指向其首元素的指针。
2:实际传递的数组大小与函数形参指定的数组大小没有关系。
3:数组传参本质上传递的是数组首元素的地址。
4:一维数组传参可以由:
	4.1  一维数组接受
	4.2  一级或二级指针接受
	4.3  指针数组接受
4.2二维数组传参
二维数组作为参数传递到函数有三种方式:
1:直接传递
2:指针传递,将二维数组的第一行传递
3:利用二级指针进行传递

如下形式都是允许的:

void test(int arr[3][5])
{}
void test(int arr[][5])
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算
void test(int (*arr)[5])
int main()
{int arr[3][5] = {0};
test(arr);
}
4.3一级指针传参

直接上题目:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

答案:
		1、(地址类)一级指针变量
		2、(地址类)一维数组数组名
		3、(地址类)整型变量
4.4二级指针传参

直接上题目
当函数的参数为二级指针的时候,可以接收什么参数?

答案:(本质上都是地址)
		1、二级指针变量
		2、数组名
		3、一级指针变量地址
5、 函数指针

先推理:
数组指针:指向数组的指针

int (*p)[10];

函数指针:指向函数的指针

int (*p)(void,void);//p就是一个存放函数地址的指针

再看代码:

#includevoid test()
{printf("hehe\n");
}
int main()
{printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

在这里插入图片描述
输出的是两个地址,这两个地址是 test 函数的地址。
结论:函数名和&函数名都是函数的地址
保存test的方式:

void (*pfun1)()=&test;

写一个简单的函数指针:

#includevoid Add(int x, int y)
{return x + y;
}
int main()
{int (*pf)(int,int) = &Add;
	//int (*pf)(int,int)=Add;
	int ret = (*pf)(2, 3);
	//int ret=pf(2,3);
	printf("%d", ret);
}

在这里插入图片描述
《C陷阱和缺陷》中的题目:大家做着玩吧

//分析代码题目:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

结束函数指针。

6、 函数指针数组

前面我们已经学习了函数指针

int(*p)(void,void);

数组是一个存放相同类型数据的存储空间,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组
在这里理解起来函数指针数组不会是困难的事情,我们先来瞧瞧函数指针数组的样子:

int (*p[10])(void,void)

p 先和 [] 结合,说明 p是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表
直接给兄弟们上例子:
未使用函数指针数组做转移表的计算器是非常臃肿的:

#includevoid menu()
{printf("***********************************\n");
		printf("************1.Add  2.Sub***********\n");
		printf("************3.Mul  4.Div***********\n");
		printf("************5.exit******************\n");
		printf("***********************************\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;

	do
	{int ret = 0;
		int x, y = 0;
		menu();
		printf("你要进行什么计算:");
		scanf_s("%d", &input);
		switch (input)
		{case 1:
		{	printf("请输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
		}
			break;

		case 2:
			printf("请输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("已退出");
		default:
			printf("请重新输入");
			break;
         }
	} while (input);
	return 0;
}

此时我们加上函数指针数组用作转移表:

#includevoid menu()
{printf("***********************************\n");
		printf("************1.Add  2.Sub***********\n");
		printf("************3.Mul  4.Div***********\n");
		printf("************5.exit******************\n");
		printf("***********************************\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 ret = 0;
	int x, y = 0;
	do
	{menu();
		printf("请选择功能:");
		scanf_s("%d", &input);
		if (input >= 0&&input<=4)
		{	printf("请输入操作数:");
			scanf_s("%d %d", &x, &y);
			int (*pf[5])(int, int) = {NULL,Add,Sub,Mul,Div };
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else
		{	printf("请重新输入......\n");
		}


	} while (input);
}

在这里插入图片描述
结论:代码2更加简洁,代码量更少,功能实现与1相同,而且2更让你更加便于后期增加功能。

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

听起来比较绕,但理解起来只需要在函数指针数组的基础上再加上一个指针,如下:

int(*p[10])(int,int);//函数中指针数组
int(*(*pp)[10])(int,int);//指向函数指针数组的指针

是不是很简单啊!

8、 回调函数

先来看看来自维基百科的对回调(Callback)的解析:
In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback.
再来看看来自Stack Overflow某位大神简洁明了的表述:
A “callback” is any function that is called by another function which takes the first function as a parameter。 也就是说,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。到此应该明白回调函数的定义了吧?

机制
⑴定义一个回调函数;
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理

实例:用回调函数改进基础版计算器

#includevoid menu()
{printf("***********************************\n");
		printf("************1.Add  2.Sub***********\n");
		printf("************3.Mul  4.Div***********\n");
		printf("************5.exit******************\n");
		printf("***********************************\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;
}
void calc (int(*pf)(int, int))
{int ret = 0;
	int x = 0;
	int y = 0;
	printf("请输入操作数:");
	scanf_s("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}
int main()
{int input = 0;

	do
	{menu();
		printf("你要进行什么计算:");
		scanf_s("%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("已退出");
		default:
			printf("请重新输入");
			break;
         }
	} while (input);
	return 0;
}

在这里插入图片描述
回调函数结束。

本期就讲到这里。下期见友友们!
在这里插入图片描述

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧

本文题目:保姆式教学--指针的进阶-创新互联
网页网址:https://www.cdcxhl.com/article42/cshchc.html

成都网站建设公司_创新互联,为您提供建站公司域名注册动态网站小程序开发全网营销推广网站内链

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

商城网站建设