指针的详细运用介绍(高阶)

目录

一:指针数组与数组指针

二:数组参数     指针参数

三:函数指针

函数指针数组:

回调函数

qsort库函数的介绍

结尾


一:指针数组与数组指针

指针数组  -   是数组   -是指一种存放指针的数组

数组指针  -   是指针  - 是指一种指向数组的指针

如图:

指针的详细运用介绍(高阶)_第1张图片

 我们由之前的学习可以知道数组名代表着首元素的地址

我们可以由以下代码来观察一下数组指针和首元素地址的区别

指针的详细运用介绍(高阶)_第2张图片

我们由代码可以发现arr数组的首元素地址,数组名代表的地址和整个数组的地址的值都是一样的,但事实真的如此么???

我们可以通过地址+1的方式可以发现情况与刚开始我们的设想不太一样(如图)

指针的详细运用介绍(高阶)_第3张图片

当数组名+1时,跳过了四个字节,同时也可以验证数组名代表首元素的地址的事情

而当整个数组的地址+1的时候,我们发现地址从A0到了C8中间跳过了40个字节,明显是越过了一个数组的内存量

最后一个与情况一类雷同,当此时,我们可以发现数组名和数组的地址虽然表达的值是一样的,但是意义大不相同,数组只表示首元素的地址,而数组的地址则代表整个数组的地址,+1会越过整个数组,但表达的值和首元素地址相同 

小提示:

数组名也有不代表首元素地址的时候:

1:当sizeof(arr)时,arr表示整个数组

2:&arr时,这里表示整个的数组

数组指针的使用:

如以下代码:

指针的详细运用介绍(高阶)_第4张图片

指针的详细运用介绍(高阶)_第5张图片

相信到这里,大家对数组指针和指针数组已经有了较为深刻的理解

二:数组参数     指针参数

当我们函数传参时,不可避免的会遇到一些数组传参,指针传参,因此掌握它非常有必要

我们来看以下代码:

指针的详细运用介绍(高阶)_第6张图片

 case1: 参数部分直接写数组,是否写数组大小对结果来说没有区别,因此case1与case2都是可以,且效果一样的,但是我们要知道,数组传参传递的是指针而非数组本身,只是形式写成数组罢了

case3:传递数组时,形参直接写成指针形式,完全正确

case4:同case1

case5:传递的也是指针,因此用二级指针来传递也是ok的

但当我们传参使用二维数组的时候,事情会有所不同,我们来看以下代码:

指针的详细运用介绍(高阶)_第7张图片

当此时,我们都可以看到vs报错,二维数组传参时,我们发现行可以省略,但列不行。

我们的系统可以不知道有多少行,但是必须知道一行由多少元素,这样才方便运算 

二维数组我们还有以下操作传参

指针的详细运用介绍(高阶)_第8张图片

 我们传递的是一维数组的地址,而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
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*));

  1. base:待排序数组的第一个元素
  2. num:待排序的元素个数
  3. size:数组内每个元素的大小,单位是字节
  4. 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*必须强制类型转化,然后再进行比较

指针的详细运用介绍(高阶)_第9张图片

结尾

希望本文内容会对大家有帮助,希望大家也给乐言来一个一键三连,以后乐言也会推进更多干活内热

你可能感兴趣的:(c语言,数据结构,c++,开发语言)