C语言教程——指针进阶(1)

目录

前言

1、字符指针

2、指针数组

3、数组指针

3.1数组指针

3.2&数组名VS数组名

3.3数组指针的使用

4、数组参数、指针参数

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

 4.4二级指针传参

4.5总结

5、函数指针

5.1思考

总结


前言

我们在之前知道指针就是一个变量,用来存放地址,地址唯一标识一块内存空间,指针的大小是固定的,32位平台是4个字节,64位是8个字节,指针还是有类型的,指针的类型决定了指针的+ - 整数的步长,指针解引用操作的时候的权限。指针还是有运算的,指针加减整数,指针减去指针,指针的关系运算,忘了的可以去之前的文章复习一下。

C语言教程——指针初阶(1)-CSDN博客

接着我们进行指针的深入学习。


1、字符指针

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

一般都是这样的:

nt main()
{char a = 'w';char* b = &a;return 0;
} 

这里b就是一个字符指针变量。还有一种是这样的:

int main()
{const char* pc = "hello";printf("%s\n", pc);return 0;
}

给定一个常量字符串hello,这里创建了一个pc的指针变量,相当于指向字符串首字母,可以理解成字符数组,这里打印字符串知道起始位置就可以直到识别到\0,之后才会结束打印。

2、指针数组

指针数组是一个存放指针(地址)的数组。

int main()
{const char* arr[4] = { "abc","edf","ghi","mno" };//存放字符指针的数组int i = 0;for (i = 0; i < 4; i++){printf("%s\n", arr[i]);}return 0;
}

这里给定了四个元素的char类型的字符指针,通过字符数组来存上这些指针(地址),当我们用循环来访问每一个元素地址的时候,就访问了这些常量字符串。

我们也可以仿照二维数组,来用这个指针数组来实现一个二维数组:

int main()
{int arr1[4] = { 1,2,3,4 };int arr2[4] = { 2,3,4,5 };int arr3[4] = { 6,7,8,9 };int arr4[4] = { 0,0,0,0 };int* arr[4] = { arr1,arr2,arr3,arr4 };int i = 0;for (i = 0; i < 4; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);//或者 *(arr[i] + j );}printf("\n");}return 0;
}

使用指针数组的好处是可以灵活地管理内存,只需分配和释放指针所指向的对象的内存空间,而不需要对整个数组进行操作。此外,指针数组还可以方便地传递和操作指针,提高程序的效率。

注意,使用指针数组时需要注意指针的有效性,即确保指针指向的对象在使用时是有效的,否则可能导致程序出错。

3、数组指针

我们知道指针有字符指针,指向字符的指针(char*),整形指针,指向整形的指针(int*),浮点型指针,指向浮点型的指针(float*)(double*)。

3.1数组指针

顾名思义,数组指针就是存放数组地址的指针,指向数组的指针。

但数组指针怎么来写呢:

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

这里说明pa是一个指针,因为[ ] 的优先级要高于 * 号的优先级,所以要加上一个( )来保证p先和* 结合,这个指针指向一个有10个整形元素的数组,用来接收arr数组的地址。

3.2&数组名VS数组名

对于这两种数组的区分是啥?

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%p\n", arr);printf("%p\n", &arr[0]);printf("%p\n", &arr);return 0;
}

 

这里通过一个十个元素的数组来进行区分,我们发现,这里打印出来的地址都一样,因为都是数组的首地址。这里并不能看出来什么,于是我们将每一个后都加上一个1,如果有不一样的效果那么加上1后的效果也不一样。

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%p\n", arr);printf("%p\n", arr+1);printf("%p\n", &arr[0]);printf("%p\n", &arr[0]+1);printf("%p\n", &arr);printf("%p\n", &arr+1);return 0;
}

这里就可以看出来了,十六进制中,第一个和第二个出现的情况都一样,都是多了四个字节,也就是一个整形的大小,而第三个,相差了40个字节,也就是差了一组的大小。他们的类型也不一样,第一个和第二个是整形指针(int *),而第三个的类型是数组指针,也就是(int( * ) [10] )。

所以,数组名是数组首元素的地址,拿到的是第一个元素的地址,而取数组名,拿到的是数组的地址。数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义是不一样的。我们从上述代码就可以看出来。

3.3数组指针的使用

针对数组指针和数组名的了解,我们可以以简单的用数组指针来访问一个二维数组:

#include<stdio.h>void print(int(*p)[4], int a, int b)
{int i = 0;for (i = 0; i < a; i++){int k = 0;for (k = 0; k < b; k++){printf("%d ", (*(p + i))[k]);//等同于p[i][j]}printf("\n");}
}
int main()
{int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };print(arr, 3, 4);return 0;
}

取到地址后,就通过每行+第几列遍历来进行访问数据打印数据,最后打印出这个二维数组。

学过后可以看看下面的代码代表什么意思?

int(*arr[10])[5];

这里是arr这个数组的里面存着的是每个元素的类型是:int( * ) [5]的数组指针类型。arr3里每一个元素存着的就是一个五个int类型元素的数组的地址(也就是指向)。

4、数组参数、指针参数

4.1一维数组传参

void test(int arr[]){}
void test(int arr[10]){}
void test(int *arr){}
void test2(int *arr[20]){}
void test2(int **arr[20]) {};int main()
{int arr[10] = { 0 };int* arr1[20] = { 0 };test(arr);test2(arr1);return 0;
}

可以通过上述代码中的方式来传入一维数组的参数,其中二级指针是因为在主函数中要传入的是一级指针,所以传入的参数要用二级指针来获取这个一级指针的地址。

4.2二维数组传参

二维数组在传参数的时候也是一样的道理,大差不差:

int main()
{int arr[3][5] = { 0 };test(arr);
}

我们要把一个二维数组传入test这个函数中,可以这么做:

void test(int arr[3][5]) {}
void test(int arr[][5]){}

通过传入二维数组,但这里要注意,二维数组行可以省略,但是列不可以省略。

二维数组传参形参的部分可以写成指针吗?以下有几种形式,我们来进行分析:

void test(int *arr) {}

这样不可以,因为数组名表示首元素地址,而这里是一个二维数组,所以这里写成一个整形一维数组的地址肯定不行。

void test(int* arr[5]){}

这里传入的是一个数组但不是二维数组,可以存指针但又不是一个指针,这里传参要么是数组,要么是一个指针,所以这个完全不搭边。

void test(int(*arr)[5]){}

这个是传入的是一个数组指针,也就是传入的是第一行五个整形元素的数组的地址,而刚刚好二维数组一行是五个元素,所以这个可以。

void test(int **arr){}

二级指针是用来接收一级指针的地址的,这里需要传入的是第一行的地址,是一个数组的地址,不能用二级指针来作为参数传入。

4.3一级指针传参

void test(int *p)
{}

 这里传入可以直接传入一个一级指针或者整形变量的地址

int a=10;
int *p=&a;
test(&a);
test(p);

 这里也可以传入数组,因为传入的是首元素的地址

int arr[10];
test(arr);

 4.4二级指针传参

二级指针传参形参的部分就用二级指针来接收就可以。

void test(int **a)
{ }int main()
{int n=10;int *p=&n;int **pp=&p;test(pp);test(&p);return 0;
}

如果我们发现函数的参数是二级指针,完全就可以传入一个二级指针的变量,也可以传入一个一级指针的地址,也可以传入一个指针数组的数组名。

4.5总结

传过去的是一级指针,就用一级指针来接收就行,传过去的是二级指针,就用二级指针就行。

5、函数指针

函数指针,顾名思义,就是指向函数的指针。

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

这里写出一个加法的简单函数,通过函数指针来接收这个加法函数的地址,之后打印这个函数指针。&函数名和函数名都是函数的地址,所以取不取地址符号都可以。

函数指针的写法和数组指针非常非常的类型:

函数指针:

int ( *pc )( int ,  int )

函数返回类型+指针变量+(参数类型)

数组指针:

int (*pc)[ 10 ]

数组元素类型+指针变量+数组元素个数(可以不写但要写[ ] )

注意优先级括号问题

我们可不可以通过函数指针来调用函数呢?

( *pc )( 2 , 3);
pc( 2 , 3);

对pc解引用,也就是找到了函数,之后进行传参,这样就可以实现函数的调用。不加解引用也可以,因为pc就相当于Add。用第一行来写是为了方便理解,就是为了摆设,告诉这是一个指针解引用。如果要是写这个*,一定要加上括号。

5.1思考

接下来看两段代码:

( *(void(*)()) 0 ) ();

这段代码出自《C陷阱和缺陷》。将0强制类型转换成一个函数指针,这个函数的参数没有,返回类型为void,这里就是把0地址处的函数调用了一下,调用的时候后面传参,因为函数没有参数,所以直接加个括号无参。

该代码是一次函数的调用,调用0地址处的一个函数。

接下来再看一个:

void (*signal(int,void(*)(int)))(int)

该代码是一次函数的声明,声明的函数名字叫signal,signal函数的参数有两个,一个参数是int,第二个参数是一个函数指针类型,该函数指针指向的参数是int,返回类型是void,signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void。

可以把这个代码简化一下:

typedef void(*pc)(int)
pc signal(int,pc)

这里说一下,typedef重命名,只有对指针的时候才会把类型名放在 * 旁边,像之前的

typedef unsigned int unit;

 就正常写就可以。


总结

本篇文章讲了一些不同类型指针的使用和写法,指针在编程中是非常实用的工具,可以帮助我们更好地管理内存、构建和操作数据结构,提高程序的性能和效率。熟练地使用指针可以让我们编写出更加灵活、高效和可靠的程序。下一篇继续学习。

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

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

相关文章

[应用类App] 轮廓线 aia源码 UI界面精美,画布实现手柄摇杆

屏幕数量&#xff1a;10个&#xff0c;仅主界面近3000代码块&#xff0c;请自行研究参考。 实现了手柄摇杆功能&#xff0c;界面做的比较好。 下载地址&#xff1a;轮廓线 aia源码 UI界面精美&#xff0c;画布实现手柄摇杆 - .aia 案例源码 - 清泛IT社区&#xff0c;为创新赋能…

C++—9、如何在Microsoft Visual Studio中调试C++

本文通过实例操作来介绍 Visual Studio 调试器的功能。调试器在运行过程中可提供许多方法让你查看代码的情况。 你可以逐步浏览代码、查看变量中存储的值、设置对变量的监视以查看值何时改变、检查代码的执行路径、查看代码分支是否正在运行等等。本实例主要是设置断点及查看内…

SpringBoot项目实战(39)--Beetl网页HTML文件中静态图片及CSS、JS文件的引用和展示

使用Beetl开发网页时&#xff0c;在网页中使用的CSS、JS、图片等静态资源需要进行适当的配置才可以展示。大致的过程如下&#xff1a; &#xff08;1&#xff09;首先Spring Security框架需要允许js、css、图片资源免授权访问。 &#xff08;2&#xff09;网站开发时&#xff0…

GetMaterialApp组件的功能与用法

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性3. 示例代码4. 内容总结我们在上一章回中介绍了"Get包简介"相关的内容,本章回中将介绍GetMaterialApp组件.闲话休提,让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经介绍过GetMaterialApp组…

插入实体自增主键太长,mybatis-plaus自增主键

1、问题 spring-boot整合mybtais执行insert语句时&#xff0c;主键id为长文本数据。 2、分析问题 1)数据库主键是否自增 2&#xff09;数据库主键的种子值设置的多少 3、解决问题 1&#xff09;数据库主键设置的时自增 3&#xff09;种子值是1 所以排查是数据库的问题 4、继…

【嵌入式硬件】嵌入式显示屏接口

数字显示串行接口&#xff08;Digital Display Serial Interface&#xff09; SPI 不过多赘述。 I2C-bus interface 不过多赘述 MIPI DSI MIPI (Mobile Industry Processor Interface) Alliance, DSI (Display Serial Interface) 一般用于移动设备&#xff0c;下面是接口…

(STM32笔记)十二、DMA的基础知识与用法 第三部分

我用的是正点的STM32F103来进行学习&#xff0c;板子和教程是野火的指南者。 之后的这个系列笔记开头未标明的话&#xff0c;用的也是这个板子和教程。 DMA的基础知识与用法 三、DMA程序验证1、DMA 存储器到存储器模式实验&#xff08;1&#xff09;DMA结构体解释&#xff08;2…

MySQL 如何赶上 PostgreSQL 的势头?

原文地址 我与 MySQL 社区的前辈交谈时&#xff0c;经常遇到这个问题&#xff1a;「为什么 MySQL 这么棒&#xff0c;而且&#xff08;至少根据 DB-Engines 的计算&#xff09;仍然比 PostgreSQL 更流行&#xff1b;但它的地位在下降&#xff0c;PostgreSQL 却势不可挡地越来越…

完全二叉树的删除

&#xff08;1&#xff09;删除叶子节点 找到要删除的节点 targetNode找到要删除节点的父节点parent&#xff08;父节点是否存在&#xff09;要删除的节点是父节点的左子树还是右子树如果是左子树&#xff0c;则parent.leftnull;如果是右子树则parent.rightnull。 &#xff08;…

Docker入门之docker基本命令

Docker入门之docker基本命令 官方网站&#xff1a;https://www.docker.com/ 1. 拉取官方镜像并创建容器&#xff08;以redis为例&#xff09; 拉取官方镜像 docker pull redis# 如果不需要添加到自定义网络使用这个命令&#xff0c;如需要&#xff0c;直接看第二步 docker r…

玩转大语言模型——ollama导入huggingface下载的模型

ollama导入huggingface模型 前言gguf模型查找相关模型下载模型 导入Ollama配置参数文件导入模型查看导入情况 safetensfors模型下载模型下载llama.cpp配置环境并转换 前言 ollama在大语言模型的应用中十分的方便&#xff0c;但是也存在一定的问题&#xff0c;比如不能使用自己…

DFS之迭代加深、双向DFS、IDA*

迭代加深 迭代加深&#xff1a; 170. 加成序列 满足如下条件的序列 X X X&#xff08;序列中元素被标号为 1 、 2 、 3 … m 1、2、3…m 1、2、3…m&#xff09;被称为“加成序列”&#xff1a; X [ 1 ] 1 X[1]1 X[1]1 X [ m ] n X[m]n X[m]n X [ 1 ] < X [ 2 ] < …

Ansible之批量管理服务器

文章目录 背景第一步、安装第二步、配置免密登录2.1 生成密钥2.2 分发公钥2.3 测试无密连接 背景 Ansible是Python强大的服务器批量管理 第一步、安装 首先要拉取epel数据源&#xff0c;执行以下命令 yum -y install epel-release安装完毕如下所示。 使用 yum 命令安装 an…

【Linux网络编程】第二十二弹---深入理解 I/O 多路转接之 epoll:系统调用、工作原理、代码演示及应用场景

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、I/O 多路转接之 epoll 1.1、epoll 初识 1.2、epoll 的相关系统调用 1.2.1、epoll_create 1.2.2、epol…

双向列表的实现(C++)

一.实现思路 主要是一个空间存储一个数值&#xff0c;然后为了索引后面的数据单元和前面的数据单元&#xff0c;所以在每个空间里面还要存储前面和后面数据单元的指针&#xff0c;就形成了每个数据单元 后面就是要管理的是双向列表的头结点和尾节点&#xff0c;方便实现后面的头…

国产信创实践(国能磐石服务器操作系统CEOS +东方通TongHttpServer)

替换介绍&#xff1a; 国能磐石服务器操作系统CEOS 对标 Linux 服务器操作系统&#xff08;Ubuntu, CentOS&#xff09; 东方通TongHttpServer 对标 Nginx 负载均衡Web服务器 第一步&#xff1a; 服务器安装CEOS映像文件&#xff0c;可直接安装&#xff0c;本文采用使用VMware …

Linux——修改USB网卡设备节点名称

修改驱动&#xff1a; 测试&#xff1a; 参考资料&#xff1a; https://blog.csdn.net/ablexu2018/article/details/144868950

上手体验微软全新整合的王炸平台Fabric

体验确实不错&#xff0c;微软强大的生态能力。 把可视化&#xff0c;数仓&#xff0c;数据胡&#xff0c;数据工厂&#xff0c;机器学习&#xff0c;数据监控等技术都整合到一个平台了。所有数据全都存储在统一的one lake数据中心&#xff0c;消除数据孤岛问题。而且不同角色可…

浅析PCIe链路均衡技术原理与演进

在现代计算机硬件体系的持续演进中&#xff0c;PCIe技术始终扮演着核心角色&#xff0c;其作为连接 CPU 与各类周边设备的关键高速通信链路&#xff0c;不断推动着计算机性能边界的拓展。而 PCIe Link Equalization均衡技术&#xff0c;作为保障数据在高速传输过程中准确性与稳…

东京大学联合Adobe提出基于指令的图像编辑模型InstructMove,可通过观察视频中的动作来实现基于指令的图像编辑。

东京大学联合Adobe提出的InstructMove是一种基于指令的图像编辑模型&#xff0c;使用多模态 LLM 生成的指令对视频中的帧对进行训练。该模型擅长非刚性编辑&#xff0c;例如调整主体姿势、表情和改变视点&#xff0c;同时保持内容一致性。此外&#xff0c;该方法通过集成蒙版、…