C语言中不同类型指针的区别

一、字符指针

字符指针是指向字符或字符串的指针,例如 char*。我们都知道数组名存放的是首元素的地址,而指针同理存放的是首字符的地址。但不同的是,字符型数组在初始化时会开辟新的内存块,用相同的常量字符串初始化不同数组也会开辟出不同的内存块。而字符指针是指向一个地址,也就是说,不论是多少个字符指针,只要你指向的是同一个常量字符串,那么实际你指向的内存地址是不会发生变化的,因为作为常量的字符串有着它独一份不会改变的地址。

下面是一道非常经典的例题:

#include <stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	char *str3  = "hello world.";
	char *str4  = "hello world.";

	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;
}

结果为
在这里插入图片描述
由于str1str2是数组名,表示数组首元素的地址,而他们在初始化时开辟的是不同的内存块,所以地址不同;而str3str4是指针,指向的是首字符的地址,但作为字符串常量,它的地址是不变的,所以这两个字符指针所存放的地址是相同的。

二、指针数组和数组指针

指针数组是存放指针的数组。如int *arr[3] ,其中 [3] 的优先级高,确定它为一个数组,数组名为arr,类型为 int * 。

数组指针是指向数组的指针。如int (*p)[3], (*p)的优先级最高,确定它为一个指针,而指针指向的 [3] 是一个数组,数组类型为 int

刚开始我们可能不太好分清楚指针数组和数组指针,他们的名字很相似,不免会使人混乱。那么如何分清指针数组与数组指针呢?答案就是优先级。我们可以将其拆分开来,最先定义优先级高的,一般判断出最高的优先级时,我们就能初步判定出它到底是数组还是指针,由此便可得出它的类型。

&数组名和数组名的区分 int arr[10]

我们都知道数组名表示数组首元素的地址,这在前面的字符数组中也提到过。例如 arr 表示 arr[10] 这个数组的首元素地址,即arr[1] 的地址。但当我们用 %p 去分别打印 arr&arr 的地址时,会发现他们两个的地址是一样的。但他们的实际意义是相同的吗?我们可以通过下面一段代码来验证。

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr = %p\n", &arr);
	printf("arr + 1 = %p\n", arr + 1);
	printf("&arr + 1 = %p\n", &arr + 1);
	return 0;
}

这段代码分别打印了 arr&arrarr+1&arr+1 的地址。
结果为:
在这里插入图片描述

我们可以看到,当他们各自 +1 以后的结果不同,可知实际意义也有所不同。arrarr+1 的差值为4,是一个int类型的大小,也就是跳过了一个元素。&arr 则表示的是数组的地址。数组的地址 +1 ,跳了整个数组的大小,而该数组有10个元素,每个元素都是int类型,所以 &arr+1 相对于 &arr 的差值刚好为40。由此可知,&arr 的类型为数组指针,他可以等同于 *int (p)[10]

三、函数指针

函数指针是能够指向函数的指针,例如 void (*Fun) ()Fun 先和 * 结合,确定 Fun 为指针,而指针指向的是一个函数,指向的函数无参数,返回值类型为 void 。函数在调用时需要开辟栈帧,这时就会开辟出新的地址,而函数名就代表函数地址,函数指针则存储函数的地址。

在《c陷阱与缺陷》中,有这样两个代码:

(*(void (*)())0)();

这个代码看上去确实给人一种很混乱的感觉,毕竟有这么多的括号在内,但只要找到它的优先级,还是很好理解的。我们可以看到,其中中间的(void (*)())作为一部分整体是在0的前面,也就是对0进行一个类型的转换,将0强转为一个地址,(*)的优先级最高,表明它是一个指针,而指针指向的是一个函数(),函数的返回值为void。所以它转换后的地址的类型就是“指向返回值为void的函数的指针”。而整体就是调用0地址处的函数。

再来看另外一个代码:

void (*signal(int , void(*)(int)))(int);

同样是一段看上去比较复杂的代码,首先signal先与后面小括号结合,说明它是一个函数,它的参数是signal后面括号内的intvoid(*)(int)。而除了signal函数的其他部分表示的是函数的返回值,void(*) (int),是一个函数指针。所以这个代码实际上就表示的是一个signal函数,函数的参数为 (int , void(*)(int)),函数的返回值是一个函数指针void(*) (int)。当然这个函数有简化的方式:

typedef void(*pfun_t)(int);
pfun_t signal(int , pfun_t);

第一个语句通过 typedefpfun_t 定义为函数指针的类型,而第二个语句就十分明了,signal函数的参数为intpfun_t,返回值为 pfun_t

四、函数指针数组

我们知道了函数指针是指向函数的指针,那函数指针数组也就是存放函数指针的数组。
例如 int (*parr[10])()。首先它必须是一个数组,所以要先和中括号结合,拆分开来就是 parr[10],它的优先级最高确定它是一个数组。其次是 * ,表明指针,而指向的内容就是后面的函数 () ,数组类型为 int

函数指针数组最大的用途:转移表。

#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 Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
//以上为加减乘除四个函数
void menu()
{
	printf("1.Add\n2.Sub\n3.Mul\n4.Div\n");
}
int main()
{
	int input = 0;//input用来做不同计算函数的选项
	int a = 0;
	int b = 0;
	int(*Pfun[5])(int, int) = { 0, Add, Sub, Mul, Div };
	//第一个元素为0,使元素下标1234分别代表加减乘除函数
	do
	{
		menu();
		printf("请输入菜单选项: ");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			printf("%d\n", Pfun[input](a, b));
			//Pfun[input](a, b)直接调用下标为input的函数
		}
	} while (input);
	return 0;
}

由以上代码我们可以看到,使用转移表会很大程度上去节省我们调用函数的空间,只需要通过函数指针数组访问元素下标地址去调用,而不用一一去调用,能让代码避免更加冗余。

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

在理解了函数指针数组之后,那么指向函数指针数组的指针也不难掌握。首先它是一个指针,指针指向一个数组,数组中的元素是函数指针。其实只要将每一层由内到外逐层分析理解,就不难分辨。

例如上文的函数指针数组int (*parr[10])(),那么 我们再增加一个指向该数组的指针,也就是
int (*(*parr)[10])()这是一个指向存放十个函数指针元素数组的指针。

  • 12
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值