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