【C语言进阶】指针与数组、转移表详解

news/2024/4/23 19:42:53/文章来源:https://blog.csdn.net/weixin_64916311/article/details/129213705

前言

大家好我是程序猿爱打拳,我们在学习完指针的基本概念后知道了指针就是地址,我们可以通过这个地址并对它进行解引用从而改变一些数据。但只学习指针的基础是完全不够的,因此学习完指针的基础后我们可以学习关于指针的进阶,其中包括指针数组、数组指针、函数指针等。这篇文章的末尾也有模拟实现计算器源码及讲解。

目录

1.字符指针

2.指针数组

3.数组指针

3.1&数组名和数组名

3.2数组指针的定义

3.3数组指针的使用

4.数组参数、指针参数

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

5.函数指针

6.函数指针数组

6.1函数指针数组定义

7.实现计算器

7.1使用switch实现

7.2使用转移表实现


1.字符指针

经过学习指针的基础后,我们知道了有一种指针类型为字符指针char*。一般这样写代码:

#include<stdio.h>int main()
{char ch = 'a';char *p = &ch;*p = 'b';printf("%c\n", ch);return 0;
}

以上代码最终输出的值为b,对指针p进解引用并赋新值从而改变了ch的值我们不难理解。还有一种写代码方式:

#include<stdio.h>int main()
{const char* p = "Hello World";printf("%s\n", p);return 0;
}

输出结果

以上代码,我们把Hello World的首字符地址赋值给了指针p并不是把整个Hello World赋值给了指针p,因此在输出的时候是从H开始依次往后面输出的。当然我们在指针初阶学过以""初始化一个字符串的时候,字符串末尾会默认生成'\0'(结束标识符)。


 在理解以上程序后,我们来看一组代码:

#include<stdio.h>int main()
{char str1[] = "Hello World.";char str2[] = "Hello World.";const char* str3 = "Hello World.";const char* str4 = "Hello World.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

输出结果:

以上代码,可能有些朋友认为str1不是和str2一模一样吗,为啥输出else后面的结果呢。其实是这样的。

str1和str2没有被const修饰的话是分别在内存中占不同的空间,str3和str4两个字符串都被const修饰了因此占用的空间是一致的。所以str1不等于str2,str3等于str4。


2.指针数组

在指针基础知识中我们学到了指针数组是存放指针的数组。如以下代码:

	int* arr1[3];//整型指针的数组char *arr2[4];//一级字符指针的数组int **arr3[5];//二级字符指针的数组

我们拿整型指针的数组来举例:

#include<stdio.h>int main()
{int arr1[2] = { 1,2 };int arr2[2] = { 3,4 };int arr3[2] = { 5,6 };int* arr4[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 2; j++){printf("%d", arr4[i][j]);}}return 0;
}

输出结果:

以上代码中int *arry4[3]就是一个存放整型指针的数组,它的每一个元素都存放的是一个地址,这些地址分别是arr1,arr2,arr3的数组名也就是第一个元素的地址。通过这些个地址就能依次访问到这个地址及这个地址以后的内容,如通过arr1的地址访问到了1和2。


3.数组指针

数组指针是什么呢,指针还是数组?其实它是指针。我们在指针初阶知道了整型指针可以这样定义:int * p;说了了p指向的是一个整型。浮点型指针可以这样定义:float * p;说明了p指向的是一个单精度浮点型。


3.1&数组名和数组名

我们在数组学习的时候已经知道了数组名就是数组的首元素地址。那么&数组名到底是什么呢?我们来看一组代码:

#include<stdio.h>int main()
{int arr[2] = { 3,4 };printf("arr    = %p\n", arr);printf("&arr   = %p\n", &arr);printf("arr+1  = %p\n", arr+1);printf("&arr+1 = %p\n", &arr+1);return 0;
}

输出结果:

以上代码我们可以看到,数组名&数组名还是有很大差异的。前两个结果看不太出,后两个代码我们可以看出单个的数组名+1也就是arr(首元素)+1只是增长了4,而&数组名+1也就是&arr+1却增长了8。我们知道整型是占四个字节而arr数组里面刚好有两个整型数字,因此我们得到的结论是&数组名是&整个数组的地址。 


3.2数组指针的定义

我们可以这样写:int (*p)[10];解释:首先*先和p结合说明p是一个指针,其次int 和[10]结合。因此p指向的是一个有10个整型元素的数组。在3.1中我们知道了,单个的数组名只是数组的首元素地址,而&数组名得到是整个数组的地址,因此我们在初始化的时候应该这样:int (*p)[2]={&arr1,&arr2};以上为两个地址样式。

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


3.3数组指针的使用

上面我们说到了,数组指针代表着指针指向的是数组,那么数组指针中存放的就是数组的地址了。比如:

#include<stdio.h>void print_arr1(int(*arr1)[4],int x,int y)
{for (int i = 0; i < x; i++){for (int j = 0; j < y; j++){printf("%d ", arr[i][j]);}}
}void print_arr2(int arr2[3][4], int x, int y)
{for (int i = 0; i < x; i++){for (int j = 0; j < y; j++){printf("%d ", arr2[i][j]);}}
}int main()
{int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };print_arr1(arr, 3, 4);printf("\n");print_arr2(arr, 3, 4);return 0;
}

输出结果:

以上代码展示了数组指针的用法,在函数print_arry1(arr,3,4)中数组名arr表示首元素的地址。也就是二维数组的第一行地址。所以int (*arr1)[4]接受的arr其实是第一行的地址,可能有的朋友就有疑问了那为啥[]里面不是3而是4。因为二维数组的每一行有四个元素,因此[]里面是4。


4.数组参数、指针参数

我们在写代码的时候难免会把数组或者指针传给函数,那么函数里面的参数该如何设计呢?下面我们来看四种情况。


4.1一维数组传参

#include<stdio.h>void test1(int arr[])//函数1
{}
void test1(int arr[10])//函数2
{}
void test1(int* arr)//函数3
{}
void test2(int* arr[20])//函数4
{}
void test2(int** arr)//函数5
{}
int main()
{int arr1[10] = { 0 };int arr2[20] = { 0 };test1(arr1);test2(arr2);return 0;
}

函数1,没问题,数组传参过去,函数未指定大小的数组接收,可行。

函数2,没问题,数组传参过去,函数指定了大小的数组接收,可行。

函数3,没问题,数组传参过去,函数中未指定大小的指针来接受,可行。

函数4,没问题,数组传参过去,函数中指定大小的指针来接收,可行。

函数5,没问题,数组传参过去,函数中未指定大小指针来接收,可行。


4.2二维数组传参

#include<stdio.h>void tset(int arr[3][5])//函数1
{}
void test(int arr[][])//函数2
{}
void test(int arr[][5])//函数3
{}
void test(int* arr)//函数4
{}
void test(int(*arr)[5])//函数5
{}
void test(int** arr)//函数6
{}
int main()
{int arr[3][5] = { 0 };test(arr);return 0;
}

函数1,没问题,二维数组传参,函数中二维数组接收。

函数2,有问题,二维数组传参,函数中二维数组不能省略列数。因为对一个二维数组来说可以不知道有多少行,但不能不知道有多少列。这样才能方便计算。

函数3,没问题,二维数组传参,可以省略行数。

函数4,有问题,二维数组传参,函数中用一级指针来接收不可行。

函数5,没问题,二维数组传参,函数中用数组指针来接受,在外面3.3中有讲解到。

函数6,有问题,二维数组传参,函数中用二级指针来接收不不可行,因为二级指针接收的是一级指针,而二维数组传过去的参数是第一个元素也就是第一行的地址。


4.3一级指针传参

我们直接来看一组代码:

#include<stdio.h>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,10};int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);print(p, sz);return 0;
}

以上代码,中print函数就是一级指针的接收。在main函数中我们把arr的地址给了指针p,因此print(p,sz)传参过去的就是arr的首元素地址arr数组元素的个数。因此print函数可以通过首元素地址依次访问到该数组结束。

注意:sizeof操作符和&符号对数组名进行操纵时,此时的数组名代表的是整个数组。


4.4二级指针传参

#include<stdio.h>void test1(int** ptr1)
{printf("num= %d\n", **ptr1);
}void test2(int** ptr2)
{printf("num= %d\n", **ptr2);
}int main()
{int num = 10;int* p = &num;int** pp = &p;test1(pp);test2(&p);return 0;
}

输出结果:

以上代码,为二级指针的接收。二级指针的接收可以是一级指针的地址也可以是二级指针的地址。但无论是接收那个一种,解引用必须要解引用两次。

我们可以这样理解:p指针里面存放的是num,pp指针里面存放的是p。


5.函数指针

首先我们来看一组代码:

#include<stdio.h>int Add(int x, int y)
{return x + y;
}int main()
{printf("%p\n", &Add);return 0;
}

输出结果:

我们发现函数也是有地址的,因此我们可以把函数的地址存起来形成一个函数指针!

以上个代码为例:

#include<stdio.h>int Add(int x, int y)
{return x + y;
}int main()
{int (*p)(int, int) = Add;return 0;
}

以上代码为例,我们可以这样存放函数的地址,首先我们要用一个指针p来接受,指针p的类型跟函数的返回类型一致(Add返回类型为int,因此p的类型为int),其次指针p后面紧接着要说明函数的参数类型(Add参数为两个int,因此p后面要说明参数类型为int,int),最后把函数的地址赋值给指针p。

注意:

1.函数名等同于&函数名,如Add=&Add

2.函数指针中指针的类型根据函数的返回类型来定

3.函数指针后面要说明函数的参数类型


函数指针怎么用呢?还是根据以上代码进行修改:

#include<stdio.h>int Add(int x, int y)
{return x + y;
}int main()
{int (*p)(int, int) = Add;int sum = (*p)(4, 6);printf("sum=%d\n", sum);return 0;
}

输出结果:

 

我们已经知道了,函数指针如何去赋值。用法也并不难,只是把对应的数据放在函数指针后面的()里面即可实现功能。 


6.函数指针数组

函数指针数组的作用是:转移表,转移表是什么呢?

我们在写代码的时候,会遇到使用switch语句的情况。当我们使用switch来编写代码的时候,会发现得使用成千甚至上万条代码。但经过转移表的使用,代码的篇幅将会大大减少。


6.1函数指针数组定义

我们在前几节学到了指针数组的用法,如:char* arr[10]存放的是字符指针,此时arr数组的每个元素为char*int* arr[10]存放的是整型指针,此时arr数组的每个元素为int*

那我们可不可以把函数指针存放在数组里面呢?是可以的!所以函数指针数组是存放函数指针的数组。它的定义方法如下:

我们在定义函数指针数组的时候,需要要在函数指针的基础上加上一个[]。使得函数指针变为函数指针数组。[]里面为函数指针的个数。以下代码演示了如何在函数指针基础上改变方法。

#include<stdio.h>int Add(int x, int y)
{}int main()
{int (*p)(int, int) = Add;//这是一个函数指针int (*p[5])(int, int) = { Add };//这是一个函数指针数组return 0;
}

以上代码中,要注意的是:

1.函数指针数组定义时只是比函数指针多了一个[],[]的个数代表着函数指针的个数。

2.函数指针数组在赋值的时候只能是地址。

3.函数名等同于&函数名。


7.实现计算器

我们在认识道具函数指针数组的含义以及定义方式后,我们可以用转移表的方式来实现计算器。


7.1使用switch实现

#include<stdio.h>void menu()
{printf("************************\n");printf("*    1.Plu  2.Sub      *\n");printf("*    3.Mul  4.Div      *\n");printf("*    0.Exit            *\n");printf("************************\n");
}int Plu(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 input = 0;int x = 0;int y = 0;int key = 0;do{menu();printf("请输入你的选项:>");scanf("%d", &input);switch (input){case 0:printf("你已退出程序!");break;case 1:printf("请输入两个整数:>");scanf("%d %d", &x, &y);key = Plu(x, y);printf("两数之和为:%d\n",key);break;case 2:printf("请输入两个整数:>");scanf("%d %d", &x, &y);key = Sub(x, y);printf("两数之差为:%d\n", key);break;case 3:printf("请输入两个整数:>");scanf("%d %d", &x, &y);key = Mul(x, y);printf("两数之积为:%d\n",key );break;case 4:printf("请输入两个整数:>");scanf("%d %d", &x, &y);key = Div(x, y);printf("两数之商为:%d\n",key);break;default:printf("请输入正确的选项!\n");break;}} while (input);return 0;
}

效果展示:

如果我们使用switch语句来实现这样一个简易的计算器我们会发现,每当我要添加一个功能的时候。都需要增加一个case语句,比如我要增加一个&运算,我得再加上一个case语句。因此我们可以使用函数指针数组(转移表)来实现,会简易很多。


7.2使用转移表实现

#include<stdio.h>int Plu(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 input = 1;int x = 0;int y = 0;int key = 0;int (*p[5])(int x, int y) = { 0,Plu,Sub,Mul,Div };while (input){printf("************************\n");printf("****  1.Plu  2.Sub  ****\n");printf("****  3.Mul  4.Div  ****\n");printf("****  0.Exit        ****\n");printf("************************\n");printf("请输入你的选项:>");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("请输入两个整数:>");scanf("%d %d",&x,&y);key = (*p[input])(x, y);printf("得到的结果为:%d\n", key);}else{if (input != 0){printf("请输入正确的选项!\n");}else{printf("您已退出程序!");break;}}}return 0;
}

效果显示:

以上代码如果我们想要增加程序的功能,只需要添加函数、增加菜单栏内容、if语句的判断条件即可。


以上就是本篇博客的内容,感谢你的阅读

 Never Give Up

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74642.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络高频知识点(一)

目录 一、http状态码 二、浏览器怎么数据缓存 三、强缓存与协商缓存 1、强缓存 2、协商缓存 四、简单请求与复杂请求 五、PUT 请求类型 六、GET请求类型 七、GET 和 POST 的区别 八、跨域 1、什么时候会跨域 2、解决方式 九、计算机网络的七层协议与五层协议分别指…

线上研讨会报名 | Perforce、中手游、星思半导体专家邀您一起畅聊如何通过数字资产管理与版本控制赋能大规模研发

全球领先的数字资产管理与DevSecOps工具厂商Perforce联合中国授权合作伙伴龙智举办的Perforce on Tour网络研讨会将于2月28日下午2:00举行。 本次研讨会以“赋能‘大’研发&#xff0c;助力‘快’交付”为主题&#xff0c;龙智董事长何明、Perforce高级顾问Robert Cowham&…

Spring中的FactoryBean 和 BeanFactory、BeanPostProcessor 和BeanFactoryPostProcessor解析

文章目录FactoryBean 和 BeanFactory后置处理器BeanPostProcessor 和 BeanFactoryPostProcessorBeanPostProcessorBeanFactoryPostProcessorFactoryBean 和 BeanFactory BeanFactory接⼝是容器的顶级接⼝&#xff0c;定义了容器的⼀些基础⾏为&#xff0c;负责⽣产和管理Bean的…

2.26selenium常用api

一.等待1.线程强制等待Thread.sleep()2.隐式等待driver.manage().timeouts().implicitlyWait(Duration.ofSeconds())3.显式等待WebDriverWait foo new WebDriverWait(driver,Duration.ofSeconds(10));foo.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector()…

Spring Boot整合Kaptcha实现验证码功能

目录一、前言1.Kaptcha 简介2.Kaptcha 详细配置表二、实现1.整合kaptcha&#xff0c;创建kaptcha的工具类1.1 添加依赖1.2 创建KaptchaConfig工具类2 编写接口&#xff0c;在接口中使用 kaptcha 工具类来生成验证码图片&#xff08;验证码信息&#xff09;并返回3 登录时从sess…

特斯拉4D雷达方案首次曝光!高阶智驾市场比拼安全冗余

随着L2级智能驾驶进入普及阶段&#xff0c;L3/L4级赛道正在成为各家车企的下一个竞争焦点。背后的最大难题&#xff0c;就是如何在成本可控的前提下&#xff0c;保证足够的安全。 高工智能汽车研究院监测数据显示&#xff0c;2022年度中国市场&#xff08;不含进出口&#xff…

Nginx搭建域名访问(负载均衡到网关)

1.修改Nginx总配置 配置上游服务器 多个网关换行 2.修改Nginx服务的配置 直接代理到上游服务&#xff08;网关&#xff09; 页面给Nginx发送请求&#xff0c;带Host&#xff0c;但是Nginx给网关转的时候会丢掉Host&#xff0c;所以要单独配上 3.修改网关微服务配置 安装ho…

追梦之旅【数据结构篇】——详解C语言实现二叉树

详解C语言实现二叉树~&#x1f60e;前言&#x1f64c;什么是二叉树&#xff1f;二叉树的性质总结&#xff1a;整体实现内容分析&#x1f49e;1.头文件的编写&#xff1a;&#x1f64c;2.功能文件的编写&#xff1a;&#x1f64c;1&#xff09;前序遍历的数值来创建树——递归函…

mysql索引分析之二

mysql索引分析之一 mysql索引分析之二 mysql索引分析之二1 mysql的索引类型2 Explain执行计划2.1 执行计划之 id 属性2.1.1 id 的属性相同表示加载表的顺序是从上到下2.1.2 id 值越大&#xff0c;优先级越高2.1.3 id 有相同&#xff0c;也有不同&#xff0c;同时存在2.2 执行计…

嵌入式系统硬件设计与实践(第一步下载eda软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 现实生活中&#xff0c;我们经常发现有的人定了很多的目标&#xff0c;但是到最后一个都没有实现。这听上去有点奇怪&#xff0c;但确实是实实在在…

【华为OD机试模拟题】用 C++ 实现 - 能力组队(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明能力组队题目输入输出示例一输入输出说明示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 O…

Word处理控件Aspose.Words功能演示:使用 C++ 在 Word (DOC/DOCX) 中添加或删除水印

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

Python实现贝叶斯优化器(Bayes_opt)优化LightGBM分类模型(LGBMClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景贝叶斯优化器(BayesianOptimization) 是一种黑盒子优化器&#xff0c;用来寻找最优参数。贝叶斯优化器是基…

第50天|LeetCode739. 每日温度、LeetCode496. 下一个更大元素 I

1.题目链接&#xff1a;739. 每日温度 题目描述&#xff1a; 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&a…

使用docker pull 跨系统架构拉取镜像

使用docker pull 跨系统架构拉取镜像使用docker pull 跨系统架构拉取镜像docker hub上找到相应的镜像在个人电脑中的执行拉取镜像命令&#xff1a;执行查看镜像命令&#xff1a;执行检查镜像命令&#xff1a;执行保存镜像命令&#xff1a;使用docker pull 跨系统架构拉取镜像 …

断点续传实现

断点续传 1、 什么是断点续传 通常视频文件都比较大&#xff0c;所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制&#xff0c;但是客户的网络环境质量、电脑硬件环境等参差不齐&#xff0c;如果一个大文件快上传完了网断了没有上…

高频面试题|RabbitMQ如何防止消息的重复消费?

一. 前言最近有很多小伙伴开始找工作&#xff0c;在面试时&#xff0c;面试官经常会问我们这样一个题目&#xff1a;RabbitMQ如何防止重复消费?有很多小伙伴这个时候都在想&#xff0c;消息怎么还会重复消费呢???.......所以他们在面试后就跑来问壹哥&#xff0c;针对这个比…

Python实现GWO智能灰狼优化算法优化循环神经网络回归模型(LSTM回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景灰狼优化算法(GWO)&#xff0c;由澳大利亚格里菲斯大学学者 Mirjalili 等人于2014年提出来的一种群智能优…

针对面试官的盘问-如何回答职场中的一些问题

(点击即可收听)初入职场,面对面试官的提问,如何回答01你为什么从上家公司离职?个人成长不足,不符合自己的预期&#xff08;关系到个人竞争力,希望找到一份更有挑战,个人提升更大的工作&#xff09;,切忌与面试官倒苦水,说前公司老板的不是业务发展缓慢,上升空间有限(有些不符合…

力扣-换座位

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;626. 换座位二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …