【C语言】指针的进阶篇,深入理解指针和数组,函数之间的关系

欢迎来CILMY23的博客喔,本期系列为【C语言】指针的进阶篇,深入理解指针和数组,函数之间的关系,图文讲解其他指针类型以及指针和数组,函数之间的关系,带大家更深刻理解指针,以及数组+指针,指针和函数的用法,感谢观看,支持的可以给个赞哇。

前言

在上一篇博客中,我们了解了strlen的模拟实现,以及冒泡排序,并且为了熟悉指针数组,我们还学习了用指针数组来模拟实现二维数组,本期博客将用其他指针类型来开篇,并学习指针和数组,函数之间的关系。 

目录

一、字符指针变量

 二、数组指针变量

三、二维数组传参

四、函数指针变量

五、typedef关键字

六、函数指针数组

七、转移表


一、字符指针变量

 什么是字符指针变量?

看下列代码:

#include<stdio.h>int main()
{char ch = 'c';char* pc = &ch;return 0;
}

上列代码中,pc就是字符指针变量,那如果后面的不是&ch,而是一个字符串又是如何存放在字符指针变量中的呢?

#include<stdio.h>int main()
{char* pc = "abcde";printf("%c ", *pc);return 0;
}

我们通过调试发现,最后的输出结果为a。所以字符串在存入指针变量的时候,是将首字符a的地址存入的。 

 接下来看以下代码:

#include<stdio.h>int main()
{char str1[] = "hello SILMY23";char str2[] = "hello SILMY23";const char* str3 = "hello SILMY23";const char* str4 = "hello SILMY23";if (str1 == str2)printf("same\n");elseprintf("%p\n%p\n",str1,str2);if (str3 == str4)printf("same\n");elseprintf("%p\n%p\n", str3, str4);return 0;
}

我们假设有两个字符数组,和两个用const修饰的字符指针变量,存放字符串hello SILMY23,我们看看在内存上它们是如何存放的?

运行结果如下:

 

我们发现在内存上,用const修饰的,它们是公用同一块空间的,也就是空间图如下所示:

 

用const修饰的常量字符串是不能被修改的,既然不能修改那么同样的内容就没有必要再开辟另外一个空间进行存放,所以两个str3和str4所指向的空间是相同的。 

 二、数组指针变量

我们在上一篇学习到,指针数组是用来存放指针的数组,那这一块数组指针,我们仍然采用类比的方式,字符指针,指向字符,是用来存放字符地址, 整型指针,指向整型,是用来存放整型地址,所以数组指针是指向数组,用来存放数组地址。例如:

int arr[10];
int (*p)[10] = &arr;

&arr拿到数组的地址。 p就是数组指针。

数组指针和指针数组

int* p[10];//指针数组
int (*p)[10];//数组指针

 字符数组指针:

char cha[8];
char (*pc)[8] = &cha;

所以数组指针类型:

int (*)[10]

char (*)[8] 

那数组指针如何初始化呢?就是用数组地址来初始化。

 那数组指针和指针数组的用法得分开

#include<stdio.h>int main()
{//指针数组int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p[] = { arr1 };int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[0][i]);}printf("\n");//数组指针int(*p1)[10] = &arr1;for (i = 0; i < 10; i++){printf("%d ", (*p)[i]);}return 0;
}

结果如下:

 

上述代码的分布图如下: 

总结:

指针数组是数组内的元素是一个个指针而数组指针是指向数组的指针

指针数组是数组,而数组指针就是个指针。

三、二维数组传参

我们在上一篇博客中,用指针数组模拟了二维数组,那二维数组传参的本质又是什么呢?

我们先来看二维数组的传参使用

#include<stdio.h>void print(int arr[2][5], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; j++){printf("%d ", arr[i][j]);}printf("\n");}
}int main()
{int arr1[2][5] = { 1,2,3,4,5,6,7,8,9,10 };print(arr1, 2, 5);return 0;
}

二维数组的传参本质其实还是传了数组首元素的地址,传的是数组第一行第一列的地址,所以我们函数写形参还是可以写指针形式接收。

二维数组的每一行可以看作一个一维数组,这个一维数组可以看作是二维数组的一个元素,所以二维数组也可以认为是一维数组的数组,所以二维数组的首元素地址就是第一行的地址,也就是一个一维数组的地址。所以:

*(arr+i) == arr[i]

代码形参可以改造成这种,形参部分写成指向第一行的数组指针。而arr[i][j]==*(*(arr+i)+j)

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

四、函数指针变量

我们已经接触过了很多指针变量,整型指针,数组指针,字符指针,那现在要新认识一个函数指,那顾名思义,函数指针,就是用来存放函数的地址,那函数名是否就是地址呢?

我们看之前写下的代码:

#include<stdio.h>void print(int (*arr)[5], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; j++){printf("%d ", *(*(arr + i) + j));}printf("\n");}
}int main()
{int arr1[2][5] = { 1,2,3,4,5,6,7,8,9,10 };printf("%p ", print);printf("%p ", &print);return 0;
}

结果如下: 

 

我们发现,无论是函数名还是&函数名,都是函数的地址,而在数组当中,数组名和&数组名是不一样的。 

那现在要将print存入指针变量,在写法上还是类似数组指针的

那如何通过p调用print函数呢?

	(*p)(arr1, 2, 5);

首先是对p解引用获得函数地址,其次是传参。这样就可以通过函数指针调用函数了。

 但其实不写也是可以的。但写上(*)更容易理解。 

五、typedef关键字

typedef关键字是用来进行类型重命名的,可以将复杂的类型简单化:

//重命名数据类型
typedef unsigned int uint;
typedef double dl;
//重命名指针类型
typedef char* pcr;
typedef int* pint;
//重命名数组指针类型
typedef int(*parr_t)[5];
//重命名函数指针类型
typedef void(*pfun_t)(int);
//新的类型名必须在*的右边

六、函数指针数组

函数指针数组?说白了就是一个数组,这里面存放的都是函数指针的地址

在第四个知识点我们写了print函数,现在我们想把它放进一个函数指针数组里

void (*parr[1])(int, int, int) = { print };

函数指针类型是需要一样的,唯一不一样的是变量名我们给了一个数组来实现函数指针数组。 

七、转移表

函数指针数组的用途:转移表

普通四则计算器的实现:

#include <stdio.h>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;
}void menu()
{printf("***************************\n");printf("*****  1. add  2.sub  *****\n");printf("*****  3. mul  4.div  *****\n");printf("*****  0. exit        *****\n");printf("***************************\n");
}int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;int(*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div};do{menu();printf("请选择:>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入2个操作数:>");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("%d\n", ret);}else if(input == 0){printf("退出计算器\n");break;}else{printf("选择错误,请重新选择");}} while (input);return 0;
}

如果利用函数指针数组来简化代码,代码就会变得相对简短一些,增添一些功能,也只会从pfArr中添加。我们把这种用函数指针数组拿来做中间板的情况,就叫转移表。

感谢各位同伴的支持,本期指针进阶篇就讲解到这啦,如果你觉得写的不错的话,可以给个赞,若有不足,欢迎各位在评论区讨论。  

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

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

相关文章

【DDD】学习笔记-四色建模法

或许正是认识到彩色 UML 在建模过程的不足之处&#xff0c;ThoughtWorks 的徐昊才在彩色 UML 基础之上提出了自己的“四色建模法”。可考的四色建模法资料仅见于徐昊在 InfoQ 上发表的文章运用四色建模法进行领域分析。在这篇文章中&#xff0c;徐昊回答了建模活动的一个关键问…

2.16学习总结

1.邮递员送信&#xff08;dijkstra 不只是从起到到目标点&#xff0c;还要走回去&#xff09; 2.炸铁路(并查集) 3.统计方形&#xff08;数据加强版&#xff09;&#xff08;排列组合&#xff09; 4.滑雪&#xff08;记忆化&#xff09; 5.小车问题&#xff08;数学问题&#x…

MySQL容器的数据挂载

挂载本地目录或文件 可以发现&#xff0c;数据卷的目录结构较深&#xff0c;如果我们去操作数据卷目录会不太方便。在很多情况下&#xff0c;我们会直接将容器目录与宿主机指定目录挂载。挂载语法与数据卷类似&#xff1a; # 挂载本地目录 -v 本地目录:容器内目录 # 挂载本地…

VitePress-16- 配置- head 的配置网页icon与插入一个script标签

作用说明 head 配置项&#xff0c;可以在页面 HTML 的 <head> 标签中呈现的其他元素。 用户添加的标签在结束 head 标签之前呈现&#xff0c;在 VitePress 标签之后。说白了&#xff0c;就是自定义一些 head 标签中的元素&#xff0c;例如 &#xff1a;页面的icon等。 由…

ssm的网上招聘系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; ssm的网上招聘系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMv…

【研究生复试】计算机软件工程人工智能研究生复试——资料整理(速记版)——计算机网络

1、JAVA 2、计算机网络 3、计算机体系结构 4、数据库 5、计算机租场原理 6、软件工程 7、大数据 8、英文 自我介绍 2. 计算机网络 1. TCP如何解决丢包和乱序&#xff1f; 序列号&#xff1a;TCP所传送的每段数据都有标有序列号&#xff0c;避免乱序问题发送端确认应答、超时…

【Linux】进程的初步认识

进程的初步认识 基本概念描述进程task_struct-PCB的一种task_stuct内容分类 查看进程通过系统调用获取进程标识符 基本概念 要了解进程&#xff0c;首先我们要知道两点 我们可以同时启动多个程序&#xff0c;也就意味着我们可以将多个.exe文件加载到内存操作系统如何去管理这些…

【c++】析构函数

1.特征 析构函数是特殊的成员函数&#xff0c;其特征如下&#xff1a; 1.析构函数名是在类名前加上字符~。 2.无参数无返回值类型。 3.一个类只能有一个析构函数。若未显式定义&#xff0c;系统会自动生成默认的析构函数。注意&#xff1a;析构函数不能重载。 4.对象生命周…

Mybatis——Javaweb进阶学习(五)

目录 一、Mybatis快速入门1.创建Springboot工程&#xff0c;数据库表user&#xff0c;实体类User2.引入Mybaties相关依赖3.编写Sql语句 二、lombok1.基本概念2.使用方法 三、基础操作1.环境准备a.数据库准备b.创建员工实体类Emp数据类型对比命名对比 c.Mapper接口创建 2.删除操…

c语言操作符(下)

目录 ​编辑 逗号表达式 下标访问[] 函数调⽤() sizeof 结构成员访问操作符 结构体 结构体声明 直接访问 .成员名 间接访问 结构体指针->成员名 逗号表达式 exp1, exp2, exp3, …expN 运算规则&#xff1a;从左向右依次执⾏。整个表达式的结果是最后⼀个表达…

SQL的1999语法

目录 交叉连接 实现交叉连接 自然连接 实现自然连接&#xff08;实际上就是内连接&#xff09; ON和USING 使用自然连接时要求两张表的字段名称相同&#xff0c;但是如果不相同或者两张表中有两组字段是重名,这时就要利用 ON 子句指定关联条件&#xff0c;利用 USING 子句…

qml之Control类型布局讲解,padding属性和Inset属性细讲

1、Control布局图 2、如何理解&#xff1f; *padding和*Inset参数如何理解呢&#xff1f; //main.qml import QtQuick 2.0 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 1.4 import QtQml 2.12ApplicationWindow {id: windowvisible: …