本系列将通过 5 篇文章系统梳理指针核心知识,欢迎关注专栏指针梳理,获取完整内容
1.字符指针变量
字符指针变量的类型是char*
代码举例:
cpp
//字符指针使用举例
#include <stdio.h>
int main()
{
char a = 'w';
char* ch = &a;
*ch = 'z';
printf("%c\n", a);
return 0;
}
易错点------>思考:以下代码是将''hello world''放到指针变量pstr里了吗?
cpp
#include <stdio.h>
int main()
{
char* pstr = "hello world";
printf("%s\n", pstr);
return 0;
}
初学者容易误解这段代码,以为 pstr 存储了整个字符串内容,但实际上它只保存了字符串首字符的地址。
2.《剑指offer》的一道字符串笔试题
cpp
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
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;
}

拆解:
++str1和str2++
- char str1[ ]="hello bit."会在栈内存中开辟一块独立空间,把字符串内容拷贝进去;
- char str2[ ]="hello bit."会再开辟另一块独立空间,把字符串内容拷贝进去;
- 即使内容完全一样,它们是两个不同的数组 ,所以str1和str2指向的地址不同,用==比较时,比较的是指针地址,因此结果为false
++str3和str4++
- "hello bit."是字符串常量 ,C/C++会把它放在只读数据段(常量区);
- 编译器会做常量折叠优化:相同内容的字符串常量,只会存一份,节省空间;
- const char* str3和const char* str4都指向这个唯一的常量字符串,所以地址相同,用==比较时,地址相等,因此结果为true
3.数组指针变量
定义
数组指针变量:存放的是数组的地址,能够指向数组的指针变量
提问:下面哪个是数组指针变量?
cpp
int* p1[10];
int(*p2)[10];
答案是第二个
[ ]的优先级要高于*号,所以必须加上()来保证p先和*结合
cpp
int(*p2)[10];
解释:p2先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
数组指针变量的初始化
cpp
int arr[10] = { 0 };
int(*p)[10] = &arr;
数组指针类型解析:
cpp
int (*p) [10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
4.二维数组传参的本质
二维数组本质上是由多个一维数组组成的数组结构。具体来说,二维数组的每个元素本身就是一个一维数组 。因此,二维数组的首元素实际上就是它的第一行数据,这个首元素本身就是一个完整的一维数组。

因此,依据数组名代表数组首元素地址这一规则,二维数组名实际上表示第一行的地址,即一维数组的地址。以前文示例为例,第一行的一维数组类型为int[5],因此第一行地址的类型应为数组指针类型int(*)[5]。这意味着二维数组传参本质上是地址传递,传递的是第一行这个一维数组的地址。
5.函数指针变量
函数指针变量是用来存放函数地址的,未来通过地址能够调用函数
提问:函数是否有地址呢?
cpp
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}

函数是有地址的,函数名就是函数的地址,当然也可以通过&函数名的方式获得函数的地址。
这里两种取函数地址的方式没有区别,不像数组
函数指针类型解析
cpp
int (*pf3) (int x, int y)
| | ------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型
举例使用:
cpp
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
6.typedef关键字
typedef是用来类型重命名的,可以将复杂的类型,简单化
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:
cpp
typedef unsigned int uint;
//将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:
cpp
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
cpp
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
7.函数指针数组
要把函数的地址存到一个数组中,那这个数组就叫函数指针数组
cpp
int (*parr1[3])(int,int);
parr1 先和 [ ] 结合,说明parr1是数组,数组里的元素是int (*)(int,int) 类型的函数指针
8.函数指针数组的应用------转移表
简易计算器的一般实现:
cpp
#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;
}
void menu()
{
printf("---------------------------------\n");
printf("------- 1.加法 2.减法 ----------\n");
printf("------- 3.乘法 4.除法 ----------\n");
printf("---------- 0.退出 --------------\n");
printf("---------------------------------\n");
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使用函数指针数组实现:
cpp
#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;
}
void menu()
{
printf("---------------------------------\n");
printf("------- 1.加法 2.减法 ----------\n");
printf("------- 3.乘法 4.除法 ----------\n");
printf("---------- 0.退出 --------------\n");
printf("---------------------------------\n");
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
menu();
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;
}
switch---case版本痛点:
- 代码高度重复:每个case里都要写「输入操作数→调用函数→打印结果」的模板代码。
- 扩展性差:如果要新增「取模、幂运算」,就要多写一个case,代码越来越长
而函数指针数组的版本更好
cpp
// 声明一个数组,每个元素是「指向返回 int、参数为两个 int 的函数」的指针
int (*p[5])(int x, int y) = {0, add, sub, mul, div};
用数组下标直接调用函数
cpp
// input 是用户选择的 1~4,直接作为数组下标
ret = (*p[input])(x, y);
// 也可以写成 ret = p[input](x, y);
优势:
- 代码极简:去掉了所有case分支,用一行调用替代。
- 扩展性强:新增运算只需在数组里加一个函数指针,不用改主逻辑。
完
由于单篇文章无法涵盖所有知识,其他内容请移步至本专栏其他文章指针梳理