指针核心知识:5篇系统梳理4

本系列将通过 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分支,用一行调用替代。
  • 扩展性强:新增运算只需在数组里加一个函数指针,不用改主逻辑。

由于单篇文章无法涵盖所有知识,其他内容请移步至本专栏其他文章指针梳理

相关推荐
AuroBreeze2 小时前
RISC-V: Minimal U-mode implementation
linux·c语言·c++·risc-v
0xDevNull2 小时前
Java 高频面试题
java·开发语言
GGbond--2 小时前
2026年最佳静态ISP代理:最佳提供商、功能和测评
java·服务器·人工智能·深度学习·接口隔离原则
Ronin3052 小时前
【Qt常用控件】多元素控件
开发语言·qt·常用控件·多元素控件
YXXY3132 小时前
二分查找算法
算法
爱玩亚索的程序员2 小时前
算法入门(一)Python基础(list、dict、set、tuple、for、enumerate、lambda、sorted)
python·算法·list
一叶落4382 小时前
LeetCode 134. 加油站(贪心算法详解 + C语言实现)
c语言·数据结构·算法·leetcode·贪心算法
say_fall2 小时前
位运算底层逻辑与解题应用绪论
c++·学习·算法·leetcode·职场和发展
测试_AI_一辰2 小时前
AI测试工程笔记:AI Agent评测体系设计(从数据集到质量验证)
人工智能·笔记·功能测试·自动化·ai编程