【C语言步行梯】一级指针、二级指针、指针数组等 | 指针详谈

在这里插入图片描述

🎯每日努力一点点,技术进步看得见
🏠专栏介绍:【C语言步行梯】专栏用于介绍C语言相关内容,每篇文章将通过图片+代码片段+网络相关题目的方式编写,欢迎订阅~~

文章目录

  • 什么是指针?
  • 指针的大小
  • 指针类型
  • 野指针
  • 指针运算
  • 指针和数组
  • 二级指针
  • 指针数组


什么是指针?

在我们的日常生活中,酒店为了方便找到某个房间,会给每个房间都进行编号。

计算机中,一个4GB的内存包含的字节数多如牛毛,为了方便查找到每个字节所在的位置。我们就需要给内存的每个位置编个号,这个编号就称为内存地址。
在这里插入图片描述
为了保存这个编号,我们就需要使用一个变量来存储它,也就是指针变量。这里要注意的是:指针=内存地址=编号。而我们日常说的定义一个“指针”,这里的指针指的是指针变量,这里的“指针”并不是内存地址的意思。

在计算机科学中,指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

例如:我们定义指针变量p,它存储值是0x00000001。这时使用p就能找到0x00000001这个内存单元。

在这里插入图片描述
指针变量用于存放内存单元的地址(编号),存放在指针变量中的值都被当成地址处理。下面来看一下如何定义一个指针变量↓↓↓

#include <stdio.h>
int main()
{int a = 10;//在内存中开辟空间int* p = &a;//在内存中开辟一个指针变量p,用它保存a的地址。return 0;
}

指针的大小

我们的计算机分为32位机器和64位机器。32位机器表示它有32根地址线(每个地址线可以产生0或者1),在为内存编制地址时使用了32个比特位,可以表示0到 2 32 − 1 2^{32}-1 2321的地址。同理,64位机器表示它有64根地址线,在为内存编制地址时使用了64个比特位,可以表示从0到 2 64 − 1 2^{64}-1 2641的地址。

每8个比特位是一个字节。在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节(32÷8=4)的空间来存储,所以一个指针变量的大小就应该是4个字节。那如果在64位机器上,如果有64个地址线(64÷8=8),那一个指针变量的大小是8个字节,才能存放一个地址。

总结
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。

指针类型

变量有不同的类型,整形,浮点型等。那指针有没有类型呢? 准确的说:有的。

指针的定义方式是: type_name + * 。 其实: char* 类型的指针是为了存放 char 类型变
量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。 int* 类型的指针是为了存放
int 类型变量的地址。

#include <stdio.h>
int main()
{int a = 10;int* pa = &a;short b = 20;short* pb = &b;char c = 'a';char* pc = &c;return 0;
}

那指针类型的意义是什么?

#include <stdio.h>
int main()
{int a = 10;int* pa = &a;printf("%p\n", pa);printf("%p\n", pa + 1);char c = 'c';char* pc = &c;printf("%p\n", pc);printf("%p\n", pc + 1);
}

在这里插入图片描述
我们知道,int类型占4个字节,char类型占1个字节。对于int类型的指针变量,如果+1后,存储的地址将向后移动4个字节;对于char类型的指针变量,如果+1后,存储的地址将向后移动1个字节。

所以,指针的类型决定了指针向前或者向后走一步有多大(距离)。

指针变量保存了某个变量的地址,那么,怎么使用指针变量间接访问指针变量所指向的变量呢?那就需要使用解引用操作符*(星号)。在指针变量的前面加上解引用操作符,就能访问指针变量所指向的变量。↓↓↓

#include <stdio.h>int main()
{int a = 10;int* pa = &a;printf("a = %d\n", *pa);*pa = 66;printf("a = %d\n", a);//a的值被pa间接修改
}

对于解引用操作符来说,它是根据指针变量的类型来确定一次性需要读取(操作)多少个字节的空间。对于char类型,解引用后需要操作1字节的空间;对于int类型,解引用后需要操作4字节的空间。

总结
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

野指针

野指针的概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

野指针的几种情况

  1. 指针变量未初始化
#include <stdio.h>
int main()
{int* p;printf("%d\n", *p);return 0;
}

★ps:指针变量没有初始化时,指针变量中保存的地址是不确定的。这时对该指针变量执行解引用操作可能引起程序崩溃。因为指针变量中的随机地址可能是其他程序所占有的地址,还有可能是其他情况。(等同于:不是我的东西,我没有权力操作)

  1. 指针越界访问
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int* p = arr;for(int i = 0; i < 100; i++){printf("%d\n", *p);p++;}return 0;
}

p一开始指向数组的首地址,解引用后可以访问到第一个元素1,p++后,会向后跳转4个字节,也就是指向第二个元素的地址…一直执行++操作,直到p指向arr范围以外的地址时,由于后序地址不知道属于谁,随便访问并不安全,甚至会导致程序崩溃。

3.指针指向的空间已经被释放

#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int));//申请了一个int类型的空间free(p);//释放一个int类型的空间*p = 0;//errorreturn 0;
}

在释放了空间后,操作系统可能会将它分配给其他程序,此时访问并修改它,可能导致程序崩溃。

那如何规避野指针呢?

  1. 指针初始化
#include <stdio.h>
int main()
{int* p = NULL;//指针初始化int a;p = &a;return 0;
}
  1. 小心指针越界
  2. 指针指向空间释放即使置NULL
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int));//申请空间free(p);//释放空间p = NULL;//置为NULL
}
  1. 指针使用之前检查有效性
#include <stdio.h>
int main()
{int* p = (int*)malloc(sizeof(int));//申请空间有可能失败if(p == NULL){printf("申请失败\n");}else{printf("申请成功可以使用\n");*p = 100;printf("%d\n", *p);}return 0;
}

指针运算

指针+(-)整数
例如:对于整型指针p,对它+4。由于整型占4个字节的空间,对p+4,则p向后移动4个整型空间,即16字节。

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

例如:对于字符指针c,对它+4。由于字符型占1个字节的空间,对c+4,则c向后移动4个字符型空间,即4字节。

#include <stdio.h>
int main()
{char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};int* c = arr;printf("%p\n", c);printf("%p\n", c + 4);return 0;
}

指针-指针
一个指针变量减另一个指针变量,可以计算出两个指针变量之间的元素个数。

#include <stdio.h>
int main()
{int arr[5] = {1,2,3,4,5};int* p = arr;int* t = &arr[4];printf("%d\n", t - p);//结果是4return 0;
}

在这里插入图片描述

指针的关系运算
指针变量的比较,就是地址进行大小比较。下面代码中定义了一个包含5个元素的数组,数组是从低地址向高地址存储的。因此,使用下标为0的元素的地址与下标为4的元素的地址进行比较时,p1必然小于p2,故下面代码执行结果为I am Jammingpro

#include <stdio.h>
int main()
{int arr[5] = {1,2,3,4,5};//数组元素从小地址向大地址存储int* p1 = &arr[0];int* p2 = &arr[4];if(p1 < p2){printf("I am Jammingpro\n");}return 0;
}

在这里插入图片描述

★ps:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

指针和数组

数组名是什么?它是不是和指针有什么关系呢?我们来看一段代码↓↓↓

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

在这里插入图片描述
由上面代码的执行结果可以知道,数组名就是数组首元素的地址。我们可以将数组名理解成指向首元素的指针,但两者还是有区别的(例如:sizeof(arr)的大小是整个数组大大小,而不是指针的大小,说明指针和数组名不等划等号)。

我们可以使用(数组名+数组下标),通过对它解引用,就可以访问数组第i号下标的元素。

#include <stdio.h>
int main()
{int arr[3] = {1,2,3};int* p = arr;//存储数组首元素的地址for(int i = 0; i < 3; i++){printf("arr[%d] = %d, *(p + %d) = %d\n", i, arr[i], i, *(p + i));printf("&arr[%d] = %d, (p + %d) = %d\n", i, &arr[i], i, (p + i));}return 0;
}

在这里插入图片描述
★ps:这也就说明了,为什么数组是从下标0开始的。因为arr[i]的本质就是*(arr+i)。

二级指针

上面我们讨论都是用一个指针变量保存一个变量的地址。如果我们用指针变量pc指向字符变量c,再使用指针变量ppc指向指针变量pc的地址。像这种:使用一个指针变量保存另一个指针变量的地址的情形,称为二级指针。
在这里插入图片描述

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

上面我们介绍了解引用的操作,对于pc指针来说,对它进行解引用后就可以操作字符变量c。对于ppc指针两说,对它进行一次解引用操作后就可以操作pc指针;如果对它进行两次解引用操作就可以操作字符变量c。

指针数组

听到这个名字,让人有点疑惑。指针数组到底是指针还是数组?其实,指针数组就是一个用来保存指针的数组。

假设我们需要用一个数组保存3个整型数组,由于数组的地址是int*类型的,我们可以定义一个int* arr[3]来存储这3个整型数组的地址。

在这里插入图片描述
下面演示如何使用该arr指针数组,访问其保存的各个数组的元素。↓↓↓

#include <stdio.h>
int main()
{int* arr[3] = {0};int numArr1[5] = {1,2,3,4,5,6};int numArr2[5] = {1,2,3,4,5,6};int numArr3[5] = {1,2,3,4,5,6};arr[0] = numArr1;arr[1] = numArr2;arr[2] = numArr3;for(int i = 0; i < 3; i++){for(int j = 0; j < 5; j++){printf("%d ", *((*arr[i]) + j));}printf("\n");}return 0;
}

★ps:*arr[i]获取的是第i个数组的地址,*((*arr[i]) + j)获取的是第i个数组第j个元素。

🚩这篇文章结束了~~
如果文章中出现了错误,欢迎私信或留言。(๑•̀ㅂ•́)و✧
有任何疑问请评论或私信哦~~o( ̄▽ ̄)ブ

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

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

相关文章

SpringMVC | SpringMVC中的“JSON数据交互“ 和“RESTful支持“

目录: 1.JSON 数据交互1.1 JSON概述1.2 JSON的“数据结构”对象结构数组结构 1.3 JSON的“数据转换” (JSON交互) 作者简介 &#xff1a;一只大皮卡丘&#xff0c;计算机专业学生&#xff0c;正在努力学习、努力敲代码中! 让我们一起继续努力学习&#xff01; 该文章参考学习教…

HUAWEI Pocket 2外屏实时查看App动态,小小窗口大便捷

当我们点外卖、等候飞机时&#xff0c;不少人习惯频繁点亮手机查看外卖配送进度、值机时间。 这时候&#xff0c;手机亮屏、解锁、打开对应App查看状态对于我们来说就显得非常繁琐。而华为Pocket 2结合HarmonyOS 4系统的实况窗功能&#xff0c;与常显外屏的搭配使用&#xff0…

微服务技术栈SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式(五):分布式搜索 ES-下

文章目录 一、数据聚合1.1 聚合种类1.2 DSL实现聚合1.3 RestAPI实现聚合1.4 演示&#xff1a;多条件聚合 二、自动补全2.1 拼音分词器2.2 自定义分词器2.3 DSL自动补全查询2.5 实现酒店搜索框自动补全2.5.1 修改酒店索引库数据结构2.5.2 RestAPI实现自动补全查询2.5.3 实战 三、…

CSS案例-2.简单版侧边栏练习

效果 知识点 标签显示模式 块级元素 block-level 常见元素:<h1>~<h6>、<p>、<div>、<ul>、<ol>、<li>等。 特点: 独占一行长度、宽度、边距都可以控制宽度默认是容器(父级宽度)的100%是一个容器及盒子,里面可以放行内或者…

Docker部署TeamCity来完成内部CI、CD流程

使用TeamCity来完成内部CI、CD流程 本篇教程主要讲解基于容器服务搭建TeamCity服务&#xff0c;并且完成内部项目的CI流程配置。至于完整的DevOps&#xff0c;我们后续独立探讨。 一个简单的CI、CD流程 以下分享一个简单的CI、CD流程&#xff08;仅供参考&#xff09;&#…

SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程…

DEYOv2: Rank Feature with Greedy Matchingfor End-to-End Object Detection

摘要 与前代类似&#xff0c; DEYOv2 采用渐进式推理方法 来加速模型训练并提高性能。该研究深入探讨了一对一匹配在优化器中的局限性&#xff0c;并提出了有效解决该问题的解决方案&#xff0c;如Rank 特征和贪婪匹配 。这种方法使DEYOv2的第三阶段能够最大限度地从第一和第二…

Pytorch环境下基于Transformer模型的滚动轴承故障诊断

注意力机制是深度学习中的重要技术之一&#xff0c;正日益受到重视关注。注意力机制作为一种信息贡献筛选的方法被提出&#xff0c;它可以帮助神经网络更多地关注与任务相关的特征&#xff0c;从而减少对任务贡献较小信息的影响。因此&#xff0c;利用注意机制可以提高神经网络…

ArcGIS巧思制作3D景观地图

John Nelson 又制作了一个制图教程视频,我原以为只是一个简单的局部场景DEM夸张实现的3D地图。 不过细看以后…… 还就是比较简单的3D场景地图,操作不难,但是 John Nelson 就是天才。 为什么? 他使用 ArcGIS Pro,在普通的3D地图中,不仅仅是图层混合制作地形效果,还巧妙的…

51-32 CVPR’24 | 3DSFLabelling,通过伪自动标注增强 3D 场景流估计

24 年 2 月&#xff0c;鉴智机器人、剑桥大学和上海交通大学联合发布CVPR24工作&#xff0c;3DSFLabelling: Boosting 3D Scene Flow Estimation by Pseudo Auto-labelling。 提出 3D 场景自动标注新框架&#xff0c;将 3D 点云打包成具有不同运动属性的 Boxes&#xff0c;通过…

【光伏监控系统的相关产品有哪些】Acrel-1000DP分布式光伏监控系统

光伏发电系统是指无需通过热过程直接将光能转变为电能的发电系统。通常由光伏方阵、蓄电池组&#xff08;蓄电池控制器&#xff09;、逆变器、交流配电柜和太阳跟踪控制系统等设备组成。其特点是可靠性高、使用寿命长、不污染环境、能独立发电又能并网运行。 分布式光伏监控系…

高防服务器秒解是什么意思

高防服务器秒解是指高防服务器在遭受大规模的DDoS攻击时&#xff0c;能够迅速解决问题或应对攻击。DDoS攻击是指攻击者通过向目标服务器发送大量的请求&#xff0c;使服务器资源耗尽或无法正常响应其他合法用户的请求&#xff0c;从而导致服务不可用。高防服务器通过具备高性能…

upload-labs-pass01

1.安装好环境进入关卡&#xff08;记得打开小皮&#xff09; 2.可以看到第一关是要求上传图片&#xff0c;但是同时限制了图片类型&#xff0c;那么如果我们将木马写入图片&#xff0c;但是类型又不在白名单&#xff0c;就要想办法绕过 3.可以看到这里的要求是有check&#xff…

数据结构:栈「详解」

目录 一&#xff0c;栈的定义 二&#xff0c;栈的基本操作 1&#xff0c;顺序栈 1.1顺序栈的基本概念 1.2顺序栈的基本操作 2&#xff0c;链栈 2.1链栈的基本概念 2.2链栈的种类 2.3链栈的基本操作 三&#xff0c;栈的应用 1&#xff0c;函数递归调用 2&#xff0c;…

基于支持向量机(svm)的人脸识别

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 数据集加载与可视化 from sklearn.datasets import fetch_lfw_people faces fetch_lfw_people(min_faces_per_person60) # Check out sample…

REDHAWK——连接(续)

文章目录 前言一、突发 IO1、数据传输①、输入②、输出 2、突发信号相关信息 (SRI)3、多输出端口4、使用复数数据①、在 C 中转换复数数据 5、时间戳6、端口统计①、C 二、消息传递1、消息生产者①、创建一个消息生产者②、发送消息 2、消息消费者①、创建消息消费者②、注册接…

优化选址问题 | 基于节约算法求解考虑碳排放及带时间窗的物流选址问题附matlab代码

目录 问题代码问题 节约算法(Savings Algorithm)通常用于解决车辆路径问题(Vehicle Routing Problem, VRP),特别是当需要考虑如何有效地组织车辆的路线以最小化总行驶距离时。然而,当问题扩展到包括碳排放和带时间窗的物流选址问题时,算法需要相应的调整。 在这个扩展…

VSCode创建用户代码片段-案例demo

示例 - 在线生成代码片段 Vue3代码片段 {"vue3": {scope": "javascript,typescript,html,vue","prefix": "vue3","body": ["<template>","$1","</template>",""…

webpack5零基础入门-11处理html资源

1.目的 主要是为了自动引入打包后的js与css资源&#xff0c;避免手动引入 2.安装相关包 npm install --save-dev html-webpack-plugin 3.引入插件 const HtmlWebpackPlugin require(html-webpack-plugin); 4.添加插件&#xff08;通过new方法调用&#xff09; /**插件 *…

Coursera上Golang专项课程3:Concurrency in Go 学习笔记(完结)

Concurrency in Go 本文是 Concurrency in Go 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 Concurrency in GoMODULE 1: Why Use Concurrency?Learning Objectives M1.1.1 - Parallel ExecutionM1.1.2 - Von Neumann BottleneckM1.1.3 - Power W…