深入理解指针Part4——字符、数组与函数指针变量

1 字符指针变量

在指针类型中,字符指针 char* 除了指向单个字符,还有一种常见用法是指向字符串常量。

c 复制代码
int main()
{
	const char* pstr = "hello";
	printf("%s\n", pstr);
	return 0;
}

const char* pstr = "hello"; ,其本质是将字符串常量首字符 'h' 的地址存入指针 pstr。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。

2 数组指针变量

顾名思义,是指向数组的指针变量(区分指向数组首元素的指针)。

c 复制代码
//例如
int arr[10] = {0};
int (*p)[10] = &arr;

解释:p 先和 * 结合,说明 p 是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以 p 是一个指针,指向一个数组,叫数组指针。

这里要注意:[] 的优先级高于 * 号,所以必须加上 () 来保证 p 先和 * 结合。

3 二维数组传参的本质

有了数组指针的理解,我们就能够讲一下二维数组传参的本质了。

过去我们有一个二维数组的需要传参给一个函数的时候,我们是这样写的:

c 复制代码
#include <stdio.h>
void test(int a[3][5], int r, int c)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < r; i++)
    {
        for (j = 0; j < c; j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7} };
    test(arr, 3, 5);
    return 0;
}

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?

首先我们再次理解一下二维数组,二维数组其实可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。

所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5]。那就味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:

c 复制代码
#include <stdio.h>
void test(int (*p)[5], int r, int c)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < r; i++)
    {
        for (j = 0; j < c; j++)
        {
            printf("%d ", *(*(p + i) + j));
            //*(*(p + i) + j) == *(p[i] + j) == p[i][j]
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7} };
    test(arr, 3, 5);
    return 0;
}

详解 *(*(p + i) + j)

p + i:指针前进 i 行,每次跳过5个整数(一行的大小)

*(p + i):解引用得到第 i 行的首地址(类型从 int (*)[5] 变为 int*

*(p + i) + j:在第 i 行内,指针前进 j 个整数位置

*(*(p + i) + j):最终解引用得到第 i 行第 j 列的元素值

4 函数指针变量

4.1 函数指针变量的创建

类比一下之前学习的指针类型,函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。那么,先来尝试打印一下函数的地址。

c 复制代码
#include<stdio.h>
void test()
{
	printf("%d\n", 20);
}

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

通过以上代码,打印出了 test() 函数的地址。其实,函数名就是函数的地址 ,当然也可以通过 &函数名 的方式获得函数的地址。

如果要存储函数的地址,就需要创建函数指针变量。函数指针的写法与数组指针非常类似。

c 复制代码
#include<stdio.h>

void test()
{
	printf("hehe\n");
}
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	void (*pf1)() = &test;
	void (*pf2)() = test;
    
	int(*pf3) (int, int) = Add;
	int(*pf4) (int x, int y) = Add;//x和y可省略
	
    printf("%p\n", pf1);
	printf("%p\n", pf2);
	printf("%p\n", pf3);
	printf("%p\n", pf4);
	return 0;
}

int (*pf4) (int x, int y) = Add; 为例拆解一下:

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

4.2 函数指针变量的使用

c 复制代码
#include<stdio.h>

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

int main()
{
	int a = 3, b = 5;
	int(*pf3) (int, int) = Add;//Add == pf3,两者是等价的

	printf("%d + %d = %d\n", a, b, (*pf3)(a, b));//这里的*没有实际作用,只是为了方便理解
	printf("%d + %d = %d\n", a, b, pf3(a, b));
	printf("%d + %d = %d\n", a, b, Add(a, b));
	return 0;
}

4.3 typedef 关键字

typedef 是用来类型重命名的,可以将复杂的类型,简单化。

c 复制代码
typedef unsigned int uint;
typedef int* ptr_t;

//数组指针重命名,int(*)[5]
typedef int(*parr_t)[5];//新的类型名必须在*的右边
//函数指针重命名,void(*)(int)
typedef void(*pfun_t)(int);//新的类型名必须在*的右边

5 函数指针数组

数组是一个存放相同类型数据的存储空间。在Part3中已经见过一些简单的指针数组,例如:

int* arr[5] 声明了 arr 是一个包含5个元素的数组,每个元素都是一个 int*(整型指针)。

那把函数的地址存到一个数组中,这种数组就叫函数指针数组

c 复制代码
int (*parr1[3])();

parr1 先和 [] 结合,说明 parr1 是数组,数组的内容是 int (*)() 类型的函数指针。

6 转移表

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

举例:计算器的一般实现

c 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#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 = 0;
	int y = 0;
	int input = 0;
	int (*pf[10])(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 == 0)
		{
			printf("退出计算器!\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("输入x y:\n");
			scanf("%d%d", &x, &y);
			int r = pf[input](x, y);
			printf("ret = %d\n", r);
		}
		else
		{
			printf("输入有误!\n");
		}
	} while (input);
	return 0;
}
相关推荐
Postkarte不想说话3 小时前
FreeBSD配置Jails
后端
但求无bug3 小时前
Java中计算两个日期的相差时间
后端
小傅哥3 小时前
新项目完结,Ai Agent 智能体、拖拉拽编排!
前端·后端
廖广杰3 小时前
java虚拟机-如何通过GC日志判断晋升失败(Promotion Failed)
后端
自由的疯3 小时前
优雅的代码java
java·后端·面试
gensue3 小时前
【征文计划】深度解析Rokid UXR 2.0 SDK:Unity开发者的空间计算开发利器
后端
武子康3 小时前
大数据-124 - Flink State:Keyed State、Operator State KeyGroups 工作原理 案例解析
大数据·后端·flink
绝无仅有3 小时前
面试真实经历某商银行大厂Java问题和答案总结(一)
后端·面试·github
绝无仅有3 小时前
面试真实经历某商银行大厂Java问题和答案总结(二)
后端·面试·github