指针的详细运用介绍(高阶)
目录
一:指针数组与数组指针
二:数组参数 指针参数
三:函数指针
函数指针数组:
回调函数
qsort库函数的介绍
结尾
一:指针数组与数组指针
指针数组 - 是数组 -是指一种存放指针的数组
数组指针 - 是指针 - 是指一种指向数组的指针
如图:
我们由之前的学习可以知道数组名代表着首元素的地址
我们可以由以下代码来观察一下数组指针和首元素地址的区别
我们由代码可以发现arr数组的首元素地址,数组名代表的地址和整个数组的地址的值都是一样的,但事实真的如此么???
我们可以通过地址+1的方式可以发现情况与刚开始我们的设想不太一样(如图)
当数组名+1时,跳过了四个字节,同时也可以验证数组名代表首元素的地址的事情
而当整个数组的地址+1的时候,我们发现地址从A0到了C8中间跳过了40个字节,明显是越过了一个数组的内存量
最后一个与情况一类雷同,当此时,我们可以发现数组名和数组的地址虽然表达的值是一样的,但是意义大不相同,数组只表示首元素的地址,而数组的地址则代表整个数组的地址,+1会越过整个数组,但表达的值和首元素地址相同
小提示:
数组名也有不代表首元素地址的时候:
1:当sizeof(arr)时,arr表示整个数组
2:&arr时,这里表示整个的数组
数组指针的使用:
如以下代码:
相信到这里,大家对数组指针和指针数组已经有了较为深刻的理解
二:数组参数 指针参数
当我们函数传参时,不可避免的会遇到一些数组传参,指针传参,因此掌握它非常有必要
我们来看以下代码:
case1: 参数部分直接写数组,是否写数组大小对结果来说没有区别,因此case1与case2都是可以,且效果一样的,但是我们要知道,数组传参传递的是指针而非数组本身,只是形式写成数组罢了
case3:传递数组时,形参直接写成指针形式,完全正确
case4:同case1
case5:传递的也是指针,因此用二级指针来传递也是ok的
但当我们传参使用二维数组的时候,事情会有所不同,我们来看以下代码:
当此时,我们都可以看到vs报错,二维数组传参时,我们发现行可以省略,但列不行。
我们的系统可以不知道有多少行,但是必须知道一行由多少元素,这样才方便运算
二维数组我们还有以下操作传参
我们传递的是一维数组的地址,而case1给我们的是整形指针,不行!
case2中,传递的指针数组,显然不对
case3中,传递的是数组指针,是符合语法的
case4,传递的是二级指针,而事实上是一级指针,所以不行
二维数组传参,参数可以是指针,可以是数组,如果是数组,列不能省略,如果是指针,传过去的是第一行的地址,形参应该是数组指针
思考:当函数的参数部分是一级指针时,能传接收什么参数呢
int a;
int arr[10];
int*p1=&a;
print(p1);
print(&a);
print(arr);
我们可以传递一个整形的地址,或者一个整形指针,或者数组名,他们本质上都是地址
思考2:当参数为二级指针是时,可以接受什么参数?
test(二级指针变量)
test(一级指针变量的地址)
test(指针数组)
三:函数指针
而函数指针又是什么?
我们可以结果类比得出:
整形指针指向的是一个整形的地址
字符指针指向的是一个字符变量的地址
数组指针指向的是一个数组的地址
而顾名思义,函数指针指向的是一个函数的地址!!!
函数名和取地址函数名都是函数的地址!!!
我们仍然可以通过取地址函数名得出函数地址,具体看代码:
int Add(int x, int y)
{
return x+y;
}
(*pf)(int x, int y)=Add
int ret =(*pf)(3,5);
当此时我们定义了一个变量ret来接受pf的引用,以此来调用函数add
当然我们也可以这样调用
int ret = pf(3,5);
与上述方法调用函数是一样的
注意:*在此处对于编译器来说可有可无,只是为了方便理解,加不加都是一样的!!!!
我们来做几个小练习:
练习1:
(*(void(*)()0)();
当我们第一次碰到这个代码,想必都会不寒而栗,而当我们真正理解其中的规律之后我们不难看出结果,我们首先先把前面的提取处理
*(void(*)()0)
void(*)()这是一个函数指针类型,写在地址0的前面说明,要将0强制类型转化成以一个函数指针之后,再进行解引用之后调用这个函数
总结:1.首先先将0地址处放一个函数,没有参数,返回值为void
2。然后再调用函数
当然此处的前面的星号可以去掉,这是在上述问题中我们提到过的
练习2:
void(*signal(int,void(*)(int))(int)
我们可以先发现siganl函数有两个参数,一个是int,另一个也是参数为int,返回值为void的函数指针,而singal函数的返回类型也是一个函数指针,该函数指针指向的是int类型,返回值为void
总结:1:singal是一个函数声明,函数的名字是signal,
2:该函数第一个第一个参数是int,第二个也是函数指针,参数是int,返回值是void
3:siganl函数的返回类型是void,参数是int
函数指针数组:
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
当我们想建造一个数组存储函数的地址,只需要在在函数指针后加上一个[]即可
例如:
int (*pf[2])(int,int)={Add,Sub};
当我们把【2】去掉时,剩下的函数类型就是一个函数指针,所以这是一个函数指针数组
小练习:用函数指针数组写一个小计算器
当我们不使用函数指针时,会发现一切都显得那么冗余,那么没有必要,重复的过程要持续输入好多次,这显然是没有必要的,而当我们使用函数指针数组时,更加清晰明了,而且对于后期的加入新的计算方式,也很有利,具体,看代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mut(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("***请选择你的选项***\n");
printf("***1:Add*********\n");
printf("***2:Sub*********\n");
printf("***3:Mut*********\n");
printf("***4:Div*********\n");
printf("***0:EXIT********\n");
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int key = 0;
int (*pfarr[5])(int, int) = { NULL,Add,Sub,Mut,Div };
do
{
menu();
printf("请输入你的选项");
scanf_s("%d", &input);
if (0 == input)
{
printf("推出程序");
break;
}
else if (0 <= input && input <= 4)
{
printf("请输入两个数");
scanf_s("%d %d", &x, &y);
key = pfarr[input](x, y);
printf("%d", key);
}
else
{
printf("无效输入");
}
} while (input);
return 0;
}
函数指针数组指针
那我们了解了函数指针数组之后,指向函数指针数组的指针又该如何定义,如何使用?
听我慢慢道来
看起来很复杂,但是实际上我们只需要对函数指针数组的结构进行改造就行
int(*parr[4])(int,int)={Add,Sub,NULL,Mut};
实际上我们只需要对其中的数组名取地址就行,代码如下:
int (*(*pparr)[4])(int,int)=&parr;
其实还可以继续嵌套,例如:指向函数指针数组指针数组的指针,函数指针数组指针数组指针数组.......但是没有实际使用意义。
回调函数
这是一个通过函数指针调用的函数,如果你把函数指针地址作为参数传给另外一个函数时,当这个指针用来调用函数时,则被称为:回调函数。其本质意义就是一个函数的嵌套使用
注意:回调函数不是由实现方直接调用的函数,而是在特定情况由另外一方调用,对于该特定情况进行响应。
例如下列代码:
case 1:
calc(Add);
qsort库函数的介绍
qsort用来排序的一个库函数(底层是快速排序的方法)
qsort相较于冒泡排序来说好处诸多,首先他是一个库函数,可以直接使用,其次他可以排序任意类型的数据,而冒泡排序只能操作整形数据
qsort
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
- base:待排序数组的第一个元素
- num:待排序的元素个数
- size:数组内每个元素的大小,单位是字节
- compar:指向一个函数的函数指针,该函数可以比较两个函数的大小
使用qsort时,必须提供两个元素的比较方法,因为我们并不知道要接受什么类型的数据进行比较,所以此处我们使用void*
qsort必须包含头文件:stdlib.h
qsort默认排序是升序
下列代码表示qsort的使用例子:
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
该库函数的使用明显使用了多次回调,理解qsort有助于我们理解回调
再来一个例子:如何排序结构体?
代码如下:
当此时,void*必须强制类型转化,然后再进行比较
结尾
希望本文内容会对大家有帮助,希望大家也给乐言来一个一键三连,以后乐言也会推进更多干活内热