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

在这里插入图片描述

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

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

文章目录
  • 引入!
  • 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元起,快前往官网查看详情吧


文章名称:保姆式教学--指针的进阶-创新互联
标题路径:http://scyanting.com/article/cshchc.html