指针数组?数组指针?函数指针?

  最近在学习C语言,着实遇见了让我非常头疼的问题,什么是指针数组?什么又是数组指针?函数指针又是个什么鬼?
  在学习这些之前,我们先来回顾一下指针的相关内容,指针是一个存放地址的变量,指针的大小是固定的,在32位机下是4个字节,在64位机下是8个字节,指针是有类型的,比如整形指针,字符型指针,指针的类型决定了指针的加减整数的步长,指针解引用操作时候的权限。接下来就让我们来深入学习一下指针吧。


(一)指针数组

  存放指针的数组,本质上是一个数组, 不同的类型代表存放的指针的类型。
例如

char* arr[10] //存放字符型指针的数组
int* arr[10] // 存放整型指针的数组

#include<stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* parr[3] = { &a,&b,&c};
	for (int i = 0;i < 3;i++)
	{
		printf("%d", *(parr[i]));
	}
	return 0;
}
#include<stdio.h>
int main()
{
	int arr1[3] = { 1,2,3 };
	int arr2[3] = { 2,3,4 };
	int arr3[3] = { 3,4,5 };
	int* parr[3] = { arr1,arr2,arr3 };
	for (int i = 0;i < 3;i++)
	{
		for (int j = 0;j < 3;j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

   在第一个例子中指针数组parr[3]在内存中开辟连续的三个空间,分别存放a,b,c的地址,打印的结果是a,b,c的值;在第二个例子中指针数组parr[3]存放的则是数组arr1,arr2,arr3的首元素地址,打印的结果是整个数组arr1,arr2,arr3的值,其中的parr[i][j]等价于*(parr[i]+j)。

#include<stdio.h>
int main()
{
  const char* parr[3] = { "abcdef","zhang","wang" };
  for (int i = 0;i < 3;i++)
  {
  	printf("%s ", parr[i]);
  }
  return 0;
}

  以此类推,此时的指针数组parr[3]存放的则是三个常量字符串首元素的地址,打印出来则是三个常量字符串。

(二)数组指针

   本质上是指针,指向数组的指针

int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int* p = arr;//arr是首元素的地址
	int(*parr)[10] = &arr;//&arr是整个数组的地址,存放在指针数组里
	return 0;
}

  整型的地址存放在整形指针中,字符型的地址存放在字符型指针中,同样,数组的地址存放在指针数组中。整型指针的类型是int*,整型指针数组的类型是int*[10],变量的类型就是在其基础上去掉相应的自己取的名称,那么整组指针的类型则是int(*)[10]。

数组名和&数组名之间的区别

  数组名是数组首元素的地址,&数组名是整个数组的地址

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

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

打印出来的结果下图所示,

   在图片中,我们发现,前三个的地址完全相同,但是加一后的地址则存在差别, 指针类型决定指针加一产生的效果,类型不同效果不同,arr是首元素的地址,类型是int*,arr[0]的类型是int,取地址之后变成int类型,&arr是整个数组的地址,类型是int()arr,所有指针加一,前两个分别都加4,而数组指针则加整个数组的大小,在这里就是40。

数组名是首元素地址的例外

  第一种情况:sizeof(数组名),这里的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节。
  第二种情况:&数组名,这里的数组名表示的是整个数组,而不是首元素的地址,&数组名则表示的是整个数组的地址。

数组指针的应用

  我们先来看一个反面的例子。

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

  或者Print函数写成这样。

void Print(int* arr, int sz)
{
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", *(arr+i));
	}
}

  这样一来通过传递地址从而找到数组中的各个元素。学习了数组指针后,我们想,是不是也可以传递整个数组的地址随后再打印出来呢?其实这种方法不是太好,我们接着来修改上面的例子。

Print(&arr,sz);
void Print(int (*parr)[10], int sz)
{
	for(int i = 0;i < sz;i++)
	{
		printf("%d\n",*(parr+i));//error
	}
	return 0;
}

  这样得到的结果却是这样的;

  前面我们知道指针类型决定指针加一产生的效果,类型不同效果不同,在这里parr加一,向前移动的是整个数组的步长,所以得到的结果不是期望的数组打印。
  将打印代码做修改

void Print(int (*parr)[10], int sz)
{
	for(int i = 0;i < sz;i++)
	{
		printf("%d\n", parr[0][i]);
		//或者写成printf("%d",*(parr+0)[i]);
		//或者写成printf("%d",(*parr)[i]);
		//在这里*(parr+0)就等价于 parr[0]指向的是第一行的元素。
	}
	return 0;
}

  得到正确的输出。数组指针的正确应用体现在二位数组的传参上

#include<stdio.h>
void Print(int arr[3][4], int i.int j)
{
	for (int i = 0;i < 3;i++)
	{
		for(int j = 0;j < 4;j++)
		{
		printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
	Print(arr,3,4);
	return 0;
}
void Print(int (*parr)[4], int i.int j)
{
	for (int i = 0;i < 3;i++)
	{
		for(int j = 0;j < 4;j++)
		{
		printf("%d ", *(*(parr+i)+j);
		}
		printf("\n");
	}
}

  通过数组指针传递二维数组的参数。

(三)数组传参和指针传参

一维数组传参

#include<stdio.h>
void Print(int arr[])
{
	//或者void Print(int arr[5])
	//或者void Print(int* parr)
	//
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	Print(arr);
	return 0;
}
#include<stdio.h>
void Print(int* arr[5])
{
	//或者void Print(int* arr[])
	//或者void Print(int** parr)传递的是首元素的地址,每个元素是一个整型指针
}
int main()
{
	int* arr[5] = { 0 };
	Print(arr);
	return 0;
}

二维数组传参

#include<stdio.h>
void Print(int arr[3][4])
{
	//或者void Print(int arr[][4])
	//这里不能写成arr[][],类似于二维数组的定义
	//或者void Print(int(* parr)[4])
}
int main()
{
	int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
	Print(arr);
	return 0;
}

  这里要注意arr传递的是二维数组首行的地址

一级指针传参

#include<stdio.h>
void Print(int* parr)
{

}
int main()
{
	int a=1;
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	Print(&a);
	Print(arr);
	Print(p);
	return 0;
}

二级指针传参`

#include<stdio.h>
void Print(int** parr)
{

}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* ch[5]={0};
	int* p = arr;
	int** ppa = &p;
	Print(&p);
	Print(ppa);
	Print(ch);//指针数组的首元素地址,即整型指针的地址。相当于二级指针
	return 0;
}

(四)函数指针

  函数指针,即存放函数地址的变量,这里要注意,在函数指针的概念下,函数名和&函数名没有任何区别,这两者是完全相同的。函数指针的定义如下

#include<stdio.h>
int Add(int x;int y)
{
	return x+y;
}
int main()
{
	int (*p)(int ,int ) = &Add;
	int (*p)(int ,int ) = Add;
	//()内声明参数类型
	//p是函数指针变量,用来存放函数指针地址
	return 0;
}

  去掉名称,即函数指针类型int (*) (int ,int),在定义之后,我们便可以通过函数指针来调用函数。

int main()
{
	int (*p)(int ,int ) = &Add;
	int (*p)(int ,int ) = Add;
	int ret=(*p)(3, 4);//通过函数指针来调用函数
	ret=Add( 1, 2);//直接调用函数
	ret=p( 4, 5);//这里的p和Add相等
	return 0;
}

函数指针数组

  即存放函数指针的数组,当函数指针类型相同时,可以创建函数指针数组来存放多个函数的地址。

#include<stdio.h>
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 (*p[4])(int ,int ) = {Add, Sub, Mul, Div};
	return 0;
}

指向函数指针数组的指针

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

函数回调

  通过函数指针再次调用函数。

#include<stdio.h>
int Add(int x,int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
void Cal(int (*p)(int, int),int a, int b)
{
	int ret = p(a, b);
	printf("%d", ret);
}
int main()
{
	int x, input, a, b;
	int (*p[3])(int, int) = { 0, Add, Sub };
	scanf("%d", &input);//输入选择值,选择1进行加法,2进行减法
	scanf("%d %d", &a, &b);//输入要进行运算的两个数字
	switch (input)
	{
	case 1:
		Cal(p[input],a,b);
		break;
	case 2:
		Cal(p[input],a,b);
		break;
	}
	return 0;
}

  函数回调的例子,也可以参考qsor函数在冒泡排序中的应用

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值