• c语言进阶: 指针的进阶(上)


    首先 在学习新知识之前 我们先来回顾下之前的学习的内容

    1 指针是个变量 用来存放地址 地址唯一标识的一块内存空间
    2 指针的大小是固定的4/8字节(32位平台/64位平台)
    3 指针有类型的 指针的类型决定了两点 一个是指针操作的权限以及±整数的步长
    4 指针的运算

    初识指针

    熟悉指针

    一. 字符指针

    1. 解析

    在指针的类型中我们知道有一种指针的类型位字符指针

    char *

    我们一般这么使用

    int main()
    {
    	char ch = 'w';
    	// ch里存放的字符是一个w
    	char* pc = &ch;
    	// 指针pc里面存放ch的地址
    	*pc = 'w';
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    还有一种使用方式如下

    int main()
    {
    	const char* ps = "hello world";
    	printf("%s\n", ps);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    这里我们存放的ps其实不是一个字符 而是一个字符串的首地址

    我们打印字符串的时候只需要把首地址传进去就可以打印出来一个完整的字符串

    要验证这个说法我们可以使用数组

    int main()
    {
    	char arr[] = "hello world";
    	// arr就是数组的首地址
    	printf("%s\n", arr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    我再画图给同学们解析一下

    在这里插入图片描述

    2. 题目巩固

    int main()
    {
    	char str1[] = "hello world";
    	char str2[] = "hello world";
    	const char* str3 = "hello world";
    	const char* str4 = "hello world";
    	if (str1==str2)
    	{
    		printf("same\n");
    	}
    	else
    	{
    		printf("not same\n");
    	}
    	printf("----------------------------\n");
    	if (str3==str4)
    	{
    		printf("same\n");
    	}
    	else
    	{
    		printf("not same\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    我们来分析下
    str1和str2是两个数组 相同的常量字符串去初始化 不同的数组会开辟不同的内存块 所以str1和str2不同
    str3和str4是指向同一个常量字符串 会储存在单独的一个内存区域 所以说str3和str4相同
    我们打印出来看看

    在这里插入图片描述

    二. 指针数组

    在指针章节中我们也学习了指针数组 指针数组是存放指针的数组

    这里我们再复习一下 下面的指针数组是什么意思?

    int *arr1[10]
    一个指针数组 里面存放的int类型的指针
    char *arr2[4]
    一个指针数组 里面存放的是char类型的指针
    char **arr3[5]
    一个二级指针数组 存放的二级指针

    三. 数组指针

    1. 数组指针的定义

    我们先来看两段代码

    int* p[10];//1
    int(*p)[10];//2
    
    • 1
    • 2

    因为1中的变量名先和p结合 所以说它是一个数组
    因为2中的解引用标识符先和p结合 所以说它是一个指针

    2. &数组名和数组名的区别

    还是一样 先看代码

    int arr[10];
    
    • 1

    对于这段代码来说 arr和&arr分别是啥?
    (其实这一段已经在我们前面的数组详解中已经介绍过了 我们再来复习下)

    我们都知道arr是数组名,数组名表示数组首元素的地址

    那么&arr数组名到底是啥?

    我们来看下面的一段代码:
    在这里插入图片描述我们可以发现地址是一样的

    我们再来看下面的代码

    在这里插入图片描述
    &arr步进的地址实际上是40

    arr步进的地址实际上是4

    3. 数组指针的使用

    老规矩 看代码

    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    	int(*p)[10] = &arr;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用

    在这里插入图片描述

    4. 总结

    我们来逐个分析下 下面代码的意思

    int arr[5]//1
    int *p[10]//2
    int (*p)[10]//3
    int (*p[10])[5]//4

    第一 是一个数组 数组里面有五个元素 每个元素都是int类型的

    第二 是一个数组 数组里面有十个元素 每个元素都是整型指针类型的

    第三 是一个指针 指针指向一个数组 数组里面有十个元素

    第四 是一个数组 数组里每个元素类型是数组指针 每个数组指针指向的数组有五个元素
    这个比较难理解 我画图给大家分析下

    在这里插入图片描述

    大概就是上图的结构

    四. 数组传参 指针传参

    1. 一维数组的传参

    看代码

    void test(int arr[])
    {}
    void test(int arr[10])
    {}
    void test(int *arr)
    {}
    void test2(int *arr[20])
    {}
    void test2(int **arr)
    {}
    
    int main()
    {
    	int arr[10] = { 0 };
    	int* arr2[20] = { 0 };
    	test(arr);
    	test2(arr2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们说test传参的时候 arr[10]都是可以传递进去的 因为传递数组本质上是传递一个地址
    这一点在我前面的博客有过介绍 大家感兴趣可以参考这两篇文章

    函数栈帧上

    函数栈帧下

    而对于test2来说 arr2传递是本质也是一个数组 数组里面存放的都是指针
    所以说*arr[20]是可以传递的
    对于最后一个二级指针来说 我们传递进去的是一个一级指针的数组 本质是把一级指针的地址传递进去 用二级指针来接受 当然没有问题

    2. 二维数组的传参

    看代码

    
    void test(int *arr)
    {}
    void test(int *arr[5])
    {}
    void test(int (*arr)[5])
    {}
    void test(int **arr)
    {}
    int main()
    {
    	int arr[3][5] = { 0 };
    	test(arr);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    分析

    第一个指针指向整型 而我们的二维数组的地址实际上表示一行的所有元素 所以显然不行
    第二个是一个数组 而我们要传递一个指针进去 显然不可以
    第三个是一个数组指针 每一个指针指向五个元素 和我们的数组相符 所以可以传递进去
    第四个是一个二级指针 指向的要是一级指针的地址 这里显然不行

    3. 一级指针的传参

    void print(int* p, int sz) 
    {
    	for (int i = 0; i < sz; i++)
    	{
    		printf("%d\n", *(p + i));
    	}
    }
    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
    	int* p = arr;
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	print(p, sz);
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    一级指针的传参其实没什么花里胡哨的 直接把指针传进去就可以了
    我们主要思考的是 当一个函数的参数部分作为一级指针的时候 函数能接收什么参数呢
    我们说 可以传递
    1 一维数组的数组名
    2 一维数组地址
    3 一级指针

    4. 二级指针的传参

    代码和一级指针差不多 大家可以模仿者写一下 传参其实也没有什么花里胡哨的东西
    还是一样的问题
    当一个函数的参数部分作为二级指针的时候函数能够接收什么参数呢

    1 一级指针的地址
    2 二级指针
    3 指针数组的数组名 (ex:int *p[5])

    因为此时的p是一个数组 数组里面五个元素都是一级指针

    当我们传递这个数组名的时候实际上是传递的首元素的地址 也就是说一级指针的地址

    当然可以被二级指针接收啦

    五. 函数指针

    首先 看代码

    void test()
    {
    	printf("hehe\n");
    }
    int main()
    {
    	printf("%p\n", test);
    	printf("%p\n", &test);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    我们可以发现 打印的两个地址是一模一样的

    我们我们应该怎么设计一个指针来存储函数的地址呢?

    void (*pfun1)();//1
    void* pfun2();//2
    
    • 1
    • 2

    我们想想看 到底是1还是2可以存储呢?

    答案是1

    这个问题的关键和数组知识是相同的 看变量先和谁结合 所以说1是个函数指针 可以存储函数的地址

    1. 函数指针的创建

    int (*p)(int x, int y) = Add;// 前面需要加参数 后面不需要
    
    • 1

    2. 函数指针的使用

    int sum = p(3, 5);//解引用操作符可用可不用 对于结果没有影响
    
    • 1

    完成运行结果

    在这里插入图片描述

    六. 函数指针数组

    我们都知道 数组是一个存放相同类型数据的存储空间 那我们已经学习了指针数组

    那么函数有没有对应的指针数组呢? 如果有那应该怎么定义呢?

    1. 函数指针数组的定义

    我们说 函数指针数组的定义 应该遵循以下格式

    int (*p[10])();
    
    • 1

    首先p和[]结合说明了它是一个数组
    我们再将它拿掉可以发现 数组的内容就是
    int (*)() 类型的函数指针。

    2. 函数指针数组的用途:转移表(以计算器为例)

    我们首先来写一段代码实现计算器的功能

    int add(int x, int y)
    {
    	return x + y;
    }
    
    int sub(int x, int y)
    {
    	return x - y;
    }
    
    int mul(int x, int y)
    {
    	return x * y;
    }
    
    int div(int x, int y)
    {
    	return x / y;
    }
    
    int main()
    {
    	int x, y;
    	int input = 1;
    	int ret = 0;
    	do
    	{
    		printf("***********************\n");
    		printf("1:add               2:sub\n");
    		printf("3:mul               4:div\n");
    		printf("***********************\n");
    		printf("请选择 ");
    		scanf("%d", &input);
    		switch (input)
    		{
    		case 1:
    			printf("请输入两个操作数\n");
    			scanf("%d %d", &x, &y);
    			ret = add(x, y);
    			printf("%d\n", ret);
    			break;
    		case 2:
    			printf("请输入两个操作数\n");
    			scanf("%d %d", &x, &y);
    			ret = sub(x, y);
    			printf("%d\n", ret);
    			break;
    		case 3:
    			printf("请输入两个操作数\n");
    			scanf("%d %d", &x, &y);
    			ret = mul(x, y);
    			printf("%d\n", ret);
    			break;
    		case 4:
    			printf("请输入两个操作数\n");
    			scanf("%d %d", &x, &y);
    			ret = div(x, y);
    			printf("%d\n", ret);
    			break;
    		default:
    			printf("输入错误\n");
    			break;
    		}
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    那么我们再使用函数指针来实现试试

    最重要的代码是这两行

    int (*p[5])(int x, int y) = { 0,add,sub,mul,div };
    
    • 1
    ret = (*p[input])(x, y);
    
    • 1

    整体代码如下:

    int add(int x, int y)
    {
    	return x + y;
    }
    
    int sub(int x, int y)
    {
    	return x - y;
    }
    
    int mul(int x, int y)
    {
    	return x * y;
    }
    
    int div(int x, int y)
    {
    	return x / y;
    }
    
    int main()
    {
    	int x, y;
    	int input = 1;
    	int ret = 0;
    	int (*p[5])(int x, int y) = { 0,add,sub,mul,div };
    	do
    	{
    		printf("***********************\n");
    		printf("1:add               2:sub\n");
    		printf("3:mul               4:div\n");
    		printf("***********************\n");
    		printf("请选择 ");
    		scanf("%d", &input);
    		if (input<=4 && input >=1)
    		{
    			printf("请输入操作数: \n");
    			scanf("%d %d", &x, &y);
    			ret = (*p[input])(x, y);
    			printf("%d\n", ret);
    		}
    		else
    		{
    			printf("输入有误\n");
    		}
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    7. 指向函数指针数组的指针

    这个稍微了解下就好

    
    void test(const char* str)
    {
    	printf("%s\n", str);
    }
    
    int main()
    {
    	void (*pfun)(const char*) = test;
    	// 一个函数指针
    	void (*pfunarr[5])(const char* str);
    	//函数指针数组
    	pfunarr[0] = test;
    	//数组的第一个元素是test
    	void (*(*ppfunarr)[5])(const char*) = &pfunarr;
    	//指向函数指针数组的pfunarr的指针ppfunarr
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    以上就是本篇博客的全部内容啦 由于博主才疏学浅 所以难免会出现纰漏 希望大佬们看到错误之后能够

    不吝赐教 在评论区或者私信指正 博主一定及时修正

    那么大家下期再见咯

  • 相关阅读:
    LeetCode每日两题01:移动零 (均1200道)方法:双指针
    【新品发布】原来做多用途(室内、室外)无人机开发还能用它——P450进阶版
    网规配置案例分析——国庆
    算法:(六)栈
    【QML】vscode安装QML格式化插件方法
    在 Android 上简单安全地登录——使用凭证管理器和密钥
    在MongoDB中,您可以通过以下步骤来创建账号密码,并限制其在特定数据库上的访问权限
    java毕业生设计学生选课咨询系统计算机源码+系统+mysql+调试部署+lw
    【自学笔记】网络安全——黑客技术
    PEG化金纳米棒,CAS:7440-57-5
  • 原文地址:https://blog.csdn.net/meihaoshy/article/details/126889254