【C语言】指针详解(3):函数指针及指针相关习题讲解


前言

文章先讲述函数指针变量以及函数指针数组的知识,再通过示例演示如何使用它们。在进行指针习题讲解之前,把做这些习题需要用到的知识串了一下,如:函数指针数组的特殊用法、sizeof和strlen的对比、数组名的理解和指针(地址)的大小等;在这些知识的基础上,对指针相关习题通过画图进行详细解析。


一、函数指针变量

1.函数指针变量的创建

什么是函数指针变量呢?
函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。

那么我们应该怎样拿到函数的地址呢?看以下示例:

#include <stdio.h>

void test()
{
	return;
}

int main()
{
	printf("test: %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

在这里插入图片描述
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的方式获得函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针非常类似。 如下:

(1)
void test()//省略函数名,剩下的就是这个函数的类型:void ()
{
   return;
}

void (*p1)() = &test;
void (*p2)()= test;//test和&test都是函数的地址,它们是完全等同的

(2)
int Add(int x, int y)//省略函数名,剩下的就是这个函数的类型:int (int x,int y)
{
   return x+y;
}

int(*p3)(int, int) = Add;
int(*p4)(int x, int y) = &Add;//x和y写上或者省略都是可以的

函数指针类型解析:

int     (*p3)    (int x, int y)
 |        |       ------------ 
 |        |            |
 |        |           p3指向函数的参数类型
 |       函数指针变量名
p3指向函数的返回类型

int (*) (int x, int y) //p3函数指针变量的类型

2.函数指针变量的使用

通过函数指针调用指针指向的函数:

#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*p3)(int, int) = Add;

	printf("%d\n", (*p3)(2, 3));
	printf("%d\n", p3(4, 5));//函数指针比较特殊,它可以不解引用,直接进行使用,使用效果和解引用使用是一样的
	return

在这里插入图片描述

二、函数指针数组

数组是一个存放相同类型数据的存储空间,类比之前学过的指针数组,比如:

int* arr[10];//省略arr[10],就能知道数组的每个元素是int*

char* arr[10];//数组的每个元素是char*

如果要把同类型函数(返回值类型和参数类型一致)的地址存到⼀个数组中,那这个数组就叫函数指针数组 ,那函数指针的数组如何定义呢?

int add(int a, int b)
{
 return a + b;
}

int sub(int a, int b)
{
 return a - b;
}

int mul(int a, int b)
{
 return a * b;
}
//以上三个函数的返回值类型和参数类型一致

int (*pa[3])(int,int) = {add,sub,mul};//函数指针数组的创建和初始化

pa 先和 [ ] 结合,说明 pa是数组, 数组中元素的类型是什么呢?
其实只要把pa[3]省略,剩下的就是数组中元素的类型,是 int (*)(int,int) 类型的函数指针。

三、转移表(函数指针数组的使用)

函数指针数组的用途:转移表(只是函数指针数组使用时的别名而已)

使用函数指针数组实现计算器的加减乘除运算:

#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
	return a - b;
}

int mul(int a, int b)
{
	return a * b;
}

int div(int a, int b)
{
	return a / b;
}

int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int , int ) = { 0, add, sub, mul, div }; //转移表
	do
	{
		printf("*************************\n");
		printf("****** 1:add 2:sub ******\n");
		printf("****** 3:mul 4:div ******\n");
		printf("****** 0:exit      ******\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = (p[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
			printf("退出计算器\n");
		else
			printf("输⼊有误\n");
	} while (input);
	return 0;
}

四、字符指针变量

在指针的类型中我们知道有一种指针类型为字符指针 char*
(1)它的一般使用:

#include <stdio.h>

int main()
{
	char ch = 'w';
	char* pc = &ch;
	printf("%c", *pc);
	return 0;
}

在这里插入图片描述
(2)它还有一种特殊的使用方式如下:

#include <stdio.h>

int main()
{
	char* ps = "hello bit.";//这里是把字符串"hello bit."⾸字符h的地址放到了ps中。
	printf("%c\n", *ps);
	printf("%s\n", ps);
	return 0;
}

在这里插入图片描述
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 ps 中。

五、sizeof和strlen的对比

sizeofstrlen
1. sizeof是操作符1. strlen是库函数,使用需要包含头文件 string.h
2. sizeof计算操作数所占内存的大小,单位是字节2 . srtlen是求字符串长度的,统计的是 \0 之前字符的个数
3. 不关注内存中存放什么数据3.关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

sizeof和strlen的使用如下:

#include <stdio.h>
#include <string.h>
//X86环境(32位平台)下运行
int main()
{
	char arr[] = "abc";
	printf("%zd\n", strlen(arr));

	printf("%zd\n", sizeof(arr));//arr是数组首元素地址,只要是地址(指针),它在固定环境下的大小就是固定的
	                             //X86环境下(32位平台下)地址大小固定4字节,X64环境下(64位平台下)地址大小固定8字节
	printf("%zd\n", sizeof(*arr));
	return 0;
}

在这里插入图片描述

六、指针相关习题解析

(1)相关知识补充一(详见指针详解(1)):

指针变量的大小和类型是无关的,指针变量的大小只取决于地址的大小。32位平台(X86环境)下地址是32个bit位,指针变量大小固定是4个字节;64位平台(X64环境)下地址是64个bit位,指针变量大小固定是8个字节。所以只要是指针类型的变量,在相同的平台下,大小都是相同的。

(2)相关知识补充二(详见指针详解(2)):

数组名是数组首元素(第一个元素)的地址,但是有两个例外:
(1)sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
(2)&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址属于不同类型的指针,使用时会有很大区别)
除此之外,任何地方使用数组名,数组名都表示数组首元素的地址。

例一以下例题的结果均在X86环境(32位平台)下运行得到):

char arr[] = "abcdef";//数组中内容{'a','b','c','d','e','f','\0'}
printf("%d\n", sizeof(arr));//sizeof中单独放数组名,计算的是整个数组的大小
printf("%d\n", sizeof(arr+0));//arr是数组首元素地址,arr+0还是数组首元素地址,只要是地址(指针)在X86环境下大小固定4字节
printf("%d\n", sizeof(*arr));//*arr就是数组首元素
printf("%d\n", sizeof(arr[0]));//等同于*(arr+0);等同于*arr
printf("%d\n", sizeof(&arr));//&数组名,取出的是整个数组的地址,但只要是地址(指针)在相同的平台下,大小都是相同的。
printf("%d\n", sizeof(&arr+1));//&arr是整个数组的地址,&arr+1是跳过整个数组后的那个地址,但也是地址,大小固定
printf("%d\n", sizeof(&arr[0]+1));//&arr[0]是数组首元素地址,&arr[0]+1是第二个元素的地址

在这里插入图片描述
在这里插入图片描述

例二

char arr[] = "abcdef";//数组中内容{'a','b','c','d','e','f','\0'}
printf("%d\n", strlen(arr));//arr是数组首元素地址,strlen函数从这个地址向后统计字符个数,只统计'\0'之前的字符个数,结果是6
printf("%d\n", strlen(arr+0));//arr+0还是数组首元素地址,strlen函数统计的结果是6
printf("%d\n", strlen(*arr));//*arr就是数组首元素'a','a'的ASCll码值是97,这里是把97作为地址传给了strlen函数,
                             //strlen函数会从97这个地址向后统计字符串长度,但是97作为地址的空间,不一定会属于当前程序,所以程序会报错
printf("%d\n", strlen(arr[0]));//arr[0]等同于*arr
printf("%d\n", strlen(&arr));//&arr是整个数组的地址,其实也是指向数组开始位置的地址,strlen函数从数组开始位置向后统计字符个数,结果是6
printf("%d\n", strlen(&arr+1));//&arr+1是跳过整个数组后的那个地址,strlen函数从这个地址向后统计字符个数得到的结果是随机值,
                               //因为这个地址往后的空间是未知的,不知道统计到第几个字符才能遇到'\0'
printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1是第二个元素的地址,strlen函数从这个地址向后统计字符个数得到的结果是5

例三

char *p = "abcdef";
printf("%d\n", sizeof(p));//p是一个字符指针,它存放着字符串首字符'a'的地址,指针就是地址,X86环境下大小固定4字节
printf("%d\n", sizeof(p+1));//字符指针+1跳过一个字符,p+1是第二个字符'b'的地址
printf("%d\n", sizeof(*p));//对首字符的地址解引用得到首字符'a',字符的大小为1字节
printf("%d\n", sizeof(p[0]));//等同于*(p+0);等同于*p
printf("%d\n", sizeof(&p));//取出字符指针p的地址
printf("%d\n", sizeof(&p+1));//&p+1跳过一个字符指针,指向字符指针p末尾的地址
printf("%d\n", sizeof(&p[0]+1));//&p[0]等同于&*p,&和*会相互抵消,所以&p[0]就是p,p+1是第二个字符'b'的地址

在这里插入图片描述
在这里插入图片描述

例四

char *p = "abcdef";//数组中内容{'a','b','c','d','e','f','\0'}
printf("%d\n", strlen(p));//p是一个字符指针,存放着字符串首字符'a'的地址,strlen函数从这个地址向后统计字符个数得到的结果是6
printf("%d\n", strlen(p+1));//p+1是第二个字符'b'的地址,strlen函数从这个地址向后统计字符个数得到的结果是5
printf("%d\n", strlen(*p));//*p就是字符串首字符'a','a'的ASCll码值是97,这里是把97作为地址传给了strlen函数,
                           //strlen函数会从97这个地址向后统计字符串长度,但是97作为地址的空间,不一定会属于当前程序,所以程序会报错
printf("%d\n", strlen(p[0]));//等同于*(p+0);等同于*p
printf("%d\n", strlen(&p));//取出字符指针p的地址,strlen函数从这个地址向后统计字符个数得到的结果是随机值,
                           //因为这个地址往后的空间是未知的,我们实际并不知道p中存放的地址编号是多少,不知道统计到第几个字符才能遇到'\0'
printf("%d\n", strlen(&p+1));//&p+1是指向字符指针p末尾的地址,strlen函数从这个地址向后统计字符个数得到的结果是随机值,
                             //因为这个地址往后的空间是未知的,不知道统计到第几个字符才能遇到'\0'
printf("%d\n", strlen(&p[0]+1));//&p[0]+1等同于p+1,p+1是第二个字符'b'的地址,strlen函数从这个地址向后统计字符个数得到的结果是5

(3)相关知识补充三(详见指针详解(2)):

二维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么二维数组的首元素就是第一行,是个⼀维数组。所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。

例如:int a[3][4] = {0};
 二维数组的数组名:a //二维数组的数组名表示数组首元素地址;此处二维数组可看成有三个元素(三个一维数组):第一行,第二行,第三行的一维数组;
                    //所以数组首元素是第一行,是个⼀维数组;二维数组的数组名表示的就是第一行的地址,是一维数组的地址。
把二维数组的每一行看成一个一维数组:
                   第一行的数组名: a[0] //表示第一行首元素地址
                   第二行的数组名: a[1] //表示第二行首元素地址
                   第三行的数组名: a[2] //表示第三行首元素地址                            

例五(二维数组):

int a[3][4] = {0};
printf("%d\n",sizeof(a));//sizeof中单独放二维数组的数组名,计算的是整个二维数组的大小
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));//sizeof中单独放第一行的数组名,计算的是整个第一行(一维数组)的大小
printf("%d\n",sizeof(a[0]+1));//a[0]表示第一行这个一维数组的首元素地址,所以a[0]+1是第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));//*(a[0]+1)是第一行第二个元素
printf("%d\n",sizeof(a+1));//二维数组的数组名a表示的是第一行的地址,a+1表示第二行的地址
printf("%d\n",sizeof(*(a+1)));//等同于a[1],sizeof中单独放第二行的数组名,计算的是整个第二行(一维数组)的大小
printf("%d\n",sizeof(&a[0]+1));//a[0]是第一行的数组名,&a[0]取出的是第一行的地址,&a[0]+1就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));//&a[0]等同于&*a,&和*会相互抵消,&a[0]就是a
printf("%d\n",sizeof(*a));//*a等同于*(a+0),也就是a[0]
printf("%d\n",sizeof(a[3]));//a[3]表示第四行的数组名,实际并不存在,但是sizeof仍然能通过它的类型计算出大小,
                            //因为sizeof不会真的去访问括号里的内容,sizeof后面跟着的表达式都是不会真实计算的,它只根据类型计算大小

在这里插入图片描述
例六

#include <stdio.h>
//&a取出的是整个数组的地址,+1跳过整个数组;其他int*的指针(地址)+1跳过一个int类型数据
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* p = (int*)(&a + 1);
	printf("%d %d\n", *(a + 1), *(p - 1));
	return 0;
}

在这里插入图片描述
在这里插入图片描述
例七

#include <stdio.h>

int main()
{
	char* a[] = { "work","at","alibaba" };//a数组中的每一个char*元素存放的都是字符串首字符地址
	char** pa = a;//把数组中第一个char*元素的地址赋给pa
	pa++;//pa+1跳过一个char*元素,pa+1是数组中第二个char*元素的地址
	printf("%s\n", *pa);
	return 0;
}

在这里插入图片描述
在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值