【C语言】初阶指针详解
大家好,我是苏貝,本篇博客带大家了解C语言中令人头疼的指针,如果大家觉得我写的不错的话,可以给我一个赞👍吗,感谢❤️
使用的是VS2019编译器,默认为32位平台
文章目录
- ①指针是什么
- ②指针定义与&和*操作符
- 1.指针定义
- 2.&和*操作符
- ③指针类型
- ④ 野指针
- 1.野指针成因
- 2.如何规避野指针
- ⑤指针运算
- 1.指针 + - 整数 = 指针
- 2.指针 - 指针
- 3.指针的关系运算
- ⑥二级指针
- ⑦指针和数组
- ⑧指针数组
①指针是什么
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
单元编号 == 地址 ==C语言中也叫: 指针 - 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
内存:

指针变量:
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量
要知道
1.内存被划分为一个个的内存单元,每个内存单元的大小是一个字节
2.每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中被称为指针
3.每个内存单元都有唯一的地址来标识
4.地址要存储的话,存放在指针变量中
5.在32位机器上,地址的大小为4个字节,所以指针变量的大小也为4个字节
在64位机器上,地址的大小为8个字节,所以指针变量的大小也为8个字节
对第5点进行解释
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
每个地址有32个比特位,即32/8=4个字节
同理:对于64位的机器,有8个字节
那32和64位机器能编址多大空间呢?
由上方解释可知,32位机器就有2的32次方个地址。每个地址标识一个字节,那我们就可以给 (2^ 32Byte = 2^ 32/1024KB =2^ 32/1024/1024MB=2^32/1024/1024/1024GB = 4GB) 4G的空闲进行编址。
同理,64位机器就可以给 2^ 64Byte 的空闲进行编址。
②指针定义与&和*操作符
1.指针定义
这里我们在讨论一下:指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的。当有这样的代码:
(1) int* p;
(2) char* p;
(3) int** p;
(1)* 代表p是指针变量,int* 是指针变量p的类型,int 是p所指向的类型,即p所指向的是int类型的变量
(2)* 代表p是指针变量,char* 是指针变量p的类型,int 是p所指向的类型
(3)离p最近的 * 代表p是指针变量,int* * 是指针变量p的类型,int* 是p所指向的类型
2.&和*操作符
这里的&是取地址操作符,*是间接访问操作符
int a=10;
int* p=&a;
p=&a的意思是:用&操作符取出a的地址,放在指针变量p中,指针变量的类型是 a的类型 + 一个 * 号,例如:a是int类型的,p的类型为int * ; a是char类型的,p的类型为char * ……
int a=10;
int *p=&a;
*p=0;
*p=0;意思是通过 *间接访问操作符解引用找到p指向的那个变量并将之赋值为0(即将0赋值给a)*p==a
int main()
{int a = 10;//在内存中开辟一块空间存储aint* p = &a;//用&操作符取出a的地址,放在指针变量p中*p = 20;//p通过*解引用找到a并将之赋值为20,*p==aprintf("a=%d\n", a);return 0;
}
//a=20
③指针类型
先看下面代码的结果,正如我们上面所说的,在32位平台下地址的大小为4个字节,那既然大家字节数都一样,为什么还要区分char* ,int* 等类型呢?直接用一个笼统的类型如all*类型不可以吗?

答案是不可以。为什么呢?
因为指针类型是有意义的,指针类型决定了指针进行解引用操作时访问几个字节
示例1:
因为下图的p是整型指针,指向的是一个int类型的变量,所以解引用时访问4个字节,所以在进行*p=0操作后,0x11223344全部变成0


示例2:
将上图的int* p=&a;改为char* p=&a;
因为下图的p是 char* 指针,指向的是一个char类型的变量,所以解引用时访问1个字节,所以在进行*p=0操作后,0x11223344全部变成0x11223300

示例3:
指针类型决定了指针 +1 / -1跳过几个字节(与指针类型进行解引用操作时访问的字节数相同)

总结
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问1个字节,而 int* 的指针的解引用就能访问4个字节,double* 的指针解引用就只能访问8个字节……
④ 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.野指针成因
(1). 指针未初始化:
指针未初始化,默认为随机值
int main()
{int* p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}
(2).指针越界访问:
数组arr只有10个元素,但却循环了11次,循环第11次时,针指向的范围超出数组arr的范围时,p成为野指针
解释*p++ = i ;
++(后缀自增操作符)的优先级高于*(间接访问操作符),所以p先后置++,再与 * 结合,但由于后置++是先使用再自增,所以执行*p=i操作后再自增,所以数组首元素被赋值为0后p自增1,使得p指向数组的第二个元素(跳了4个字节,原因在上面指针类型)
int main()
{int arr[10] = { 0 };int* p = arr;//p指向arr的首元素int i = 0;for (i = 0; i <= 10; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*p++ = i;}return 0;
}
(3).指针指向的空间释放:
a是函数内的局部变量,在函数调用结束后,a所开辟的内存空间会被释放,即使在释放前将a的地址传了出去,但p接收地址的时候a所开辟的内存空间已被释放,此时再用* 对p进行解引用,即使找到了该地址也属于非法访问
int* text()
{int a = 10;return &a;
}int main()
{int* p = text();//p就是野指针printf("*p=%d", *p);return 0;
}
2.如何规避野指针
- 指针初始化(若一开始不知道指针该指向哪,就先指向NULL)
NULL就是0,当0作为地址时,用户程序是不能访问的

- 小心指针越界
- 指针指向的空间释放,那就使之指向NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
int main()
{int* p = NULL;//1//....int a = 10;p = &a;if (p != NULL)//5{*p = 20;}return 0;
}
⑤指针运算
1.指针 + - 整数 = 指针
int main()
{int arr[9] = { 1,2,3,4,5,6,7,8,9 };//使用指针打印数组的内容int* p = arr;//arr是首元素地址,所以p指向数组首元素int i = 0;for (i = 0; i < 9; i++){printf("%d ", *(p + i));//p指向数组首元素,即下标为0的元素//p+i指向数组下标为i的元素//*(p+i)是通过*解引用找到下标为i的元素//p+i 与p相比,跳过了i*sizeof(int)个字节}return 0;
}
//1 2 3 4 5 6 7 8 9
上面arr是首元素地址,指针变量p里面存的也是首元素地址,所以arr==p
因此arr+i == p+i, arr[i] == p[i](下标为i的元素的地址)
*(arr+i) == *(p+i) = arr[i]
下面表达式中,左边数组的这种形式在编译器处理时会转化为右边的表达式,又因为[ ] 和+ 一样只是一个操作符,所以i写在[ ]里面或外面都可以
arr[ i ] == *(arr+i) //当然,更建议写成 arr[ i ] 这种形式
i [ arr] == *(i+arr)

拓展:
数组名是数组首元素地址,除了以下2种情况:
(1)sizeof(数组名),此时的数组名代表整个数组,所以计算结果是整个数组的大小
(2)&数组名,此时的数组名也代表整个数组,取出的是整个数组的地址,返回的是数组首元素的地址,但绝不代表&数组名==arr(首元素地址),如下:
arr和&arr[0]等价,都是首元素地址,类型是int*,所以+1跳过4个字节
&arr是取出了整个数组的地址,但返回首元素地址,所以+1跳过整个数组即10* 4=40个字节
sizeof(arr)计算结果是整个数组的大小即10* 4=40个字节

2.指针 - 指针
指针-指针的前提:两个指针指向同一块区域,指针类型相同
指针-指针差值的绝对值是:指针和指针之间的元素个数


了解了这些之后,我们是不是又多了一种计算字符个数的功能的自定义函数my_strlen()实现的方法
点击链接了解:自定义实现strlen函数的3种方法
3.指针的关系运算
请看下面代码:
int main()
{int arr[5];int* vp = NULL;for (vp = &arr[5]; vp > &arr[0];){*--vp = 0;}return 0;
}

若不想一开始就越界,有人可能会修改成这样的代码:
int main()
{int arr[5];int* vp = NULL;for (vp = &arr[4]; vp >= &arr[0];vp--){*vp = 0;}return 0;
}

那这两种方法哪个更好呢?答案是第一种,第二种在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
⑥二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
存放在 二级指针 中

*pp 通过对pp中的地址进行解引用,这样找到的是 p , *pp 其实访问的就是 p
**pp 先通过 *pp 找到 p ,然后对 p 进行解引用操作: *p ,那找到的是 a
int main()
{int a = 10;int* p = NULL;int** pp = NULL;*pp = &a;//等价于 p = &b;**pp = 30;//等价于*p = 30;//等价于a = 30;return 0;
}
⑦指针和数组
指针就是指针,指针变量就是一个变量,存放的是地址,指针变量的大小取决与32位平台还是64位平台
数组就是数组,可以存放一组相同类型的数,数组的大小取决于元素的类型和个数
数组的数组名是首元素地址,通过指针可以访问一个数组的每一个元素
int main()
{int arr[5] = { 1,2,3,4,5 };int* p = arr;//p=arr//arr[i]=p[i]=*(arr+i)=*(p+i)都是下标为i的元素int i = 0;for (i = 0; i < 5; i++){printf("%d ", p[i]);//arr[i],*(arr+i),*(p+i)}return 0;
}
//1 2 3 4 5
⑧指针数组
指针数组是指针还是数组?我们知道,整型数组是数组,存放的是int 类型的元素;字符数组是数组,存放的是char类型的元素……
指针数组是数组,是存放指针的数组
例如:int* arr[5];
arr是数组名,5代表数组有5个元素,int* 代表每个元素都是一个整型指针
好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️
相关文章:
【C语言】初阶指针详解
大家好,我是苏貝,本篇博客带大家了解C语言中令人头疼的指针,如果大家觉得我写的不错的话,可以给我一个赞👍吗,感谢❤️ 使用的是VS2019编译器,默认为32位平台 文章目录 ①指针是什么②指针定义与…...
ElasticSearch:项目实战(1)
es环境搭建参考:ElasticSearch:环境搭建步骤_Success___的博客-CSDN博客 需求: 用户输入关键可搜索文章列表 关键词高亮显示 文章列表展示与home展示一样,当用户点击某一篇文章,可查看文章详情 思路: …...
React 实现文件分片上传和下载
React 实现文件分片上传和下载 在开发中,文件的上传和下载是常见的需求。然而,当面对大型文件时,直接的上传和下载方式可能会遇到一些问题,比如网络传输不稳定、文件过大导致传输时间过长等等。为了解决这些问题,我们…...
2023.8.13
atcoder_abc\AtCoder Beginner Contest 310\E_NAND_repeatedly //题意:给定一个n长度的01串,计算f(l,r)(l<r,l在1~n,r在1~n)的和,f的计算(ai,a(i1))运算,有0就为1,11为0 //若f(l,r)1,则f(l,r-1)为0或sr为0,即只取决于上一位的情况和当前位ÿ…...
kvm not all arguments converted during string
kylin virt-manager 远程镜像制作问题记录(not all arguments ) 项目场景: 服务器端安装的OS版本:Kylin-Server-10-SP1-Release-Build20-20210518-arm64-2021-05-18 客户端安装的OS版本:Kylin-Server-10-SP1-Release-Build20-20210518-x86_…...
JVM 基础
巩固基础,砥砺前行 。 只有不断重复,才能做到超越自己。 能坚持把简单的事情做到极致,也是不容易的。 JVM 类加载机制 JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化&am…...
智谷星图赵俊:让人才和区块链产业“双向奔赴”丨对话MVP
区块链产业需要什么样的人才?赵俊很有发言权。 赵俊是北京智谷星图科技有限公司的技术总监,也是FISCO BCOS官方认证讲师。他2017年接触区块链,随后选择人才培育领域深耕。“为区块链行业引进更多人才这件事很有价值,跟我的职业理…...
C# Equals()方法报错:NullReferenceException was unhandled
下面是一个C# Equals()方法的例子,执行时报错了 static void Main(string[] args) {string name "sandeep";string myName null;Console.WriteLine(" operator result is {0}", name myName);Console.WriteLine("Equals method result…...
Linux下C语言调用libcurl库获取天气预报信息
一、概述 当前文章介绍如何在Linux(Ubuntu)下使用C语言调用libcurl库获取天气预报的方法。通过HTTP GET请求访问百度天气API,并解析返回的JSON数据,可以获取指定城市未来7天的天气预报信息。 二、设计思路 【1】使用libcurl库进…...
“深入解析JVM:Java虚拟机原理和内部结构“
标题:深入解析JVM:Java虚拟机原理和内部结构 摘要:本文将深入解析JVM(Java虚拟机)的原理和内部结构。我们将从JVM的基础概念开始,逐步介绍其组成部分,包括类加载器、运行时数据区、字节码解释器…...
Arrays.asList() 返回的list不能add,remove
一.Arrays.asList() 返回的list不能add,remove Arrays.asList()返回的是List,而且是一个定长的List,所以不能转换为ArrayList,只能转换为AbstractList 原因在于asList()方法返回的是某个数组的列表形式,返回的列表只是数组的另一个视图,而数组本身并没…...
命令执行漏洞
1、命令执行漏洞 1.1、简介 Django是用Python开发的一个免费开源的Web结构,几乎包括了Web使用方方面面,能够用于快速建立高性能、文雅的网站,Diango提供了许多网站后台开发常常用到的模块,使开发者可以专注于业务部分。 1.2、漏…...
Hive 中 sort by 和 order by 的区别
文章目录 数据量大小区别作用范围 在 Hive 中, SORT BY 和 ORDER BY 都用于对查询结果进行排序,但它们在实现方式和适用场景上有一些区别。 数据量大小区别 SORT BY: SORT BY 用于在 Hive 中对查询结果进行排序,它的主要特点是在…...
网络资源利用最大化:爬虫带宽优化解决方案
大家好,作为一名专业的爬虫程序员,我们都知道在爬取大量数据的过程中,网络带宽是一个十分宝贵的资源。如果我们不合理地利用网络带宽,可能会导致爬虫任务的效率低下或者不稳定。今天,我将和大家分享一些优化爬虫带宽利…...
STDF - 基于 Svelte 和 Tailwind CSS 打造的移动 web UI 组件库,Svelte 生态里不可多得的优秀项目
Svelte 是一个新兴的前端框架,组件库不多,今天介绍一款 Svelte 移动端的组件库。 关于 STDF STDF 是一个移动端的 UI 组件库,主要用来开发移动端 web 应用。和我之前介绍的很多 Vue 组件库不一样,STDF 是基于近来新晋 js 框架 S…...
C语言一些有趣的冷门知识
文章目录 概要1.访问数组元素的方法运行结果 2.中括号的特殊用法运行结果 3.大括号的特殊用法运行结果 4.sizeof的用法运行结果 5.渐进运算符运行结果 小结 概要 本文章只是介绍一些有趣的C语言知识,纯属娱乐。这里所有的演示代码我是使用的编译器是Visual Studio …...
Oracle数据库审计
1.什么是审计 审计是用来监控和记录用户的数据库操作的 2.审计级别 语句审计权限审计对象审计 3.查看审计功能是否开启: show parameter audit;相关参数: audit_file_destOS中审计信息存放位置audit_sys_operations默认值为FALSE,即不审…...
Node.js新手在哪儿找小项目练手?
前言 可以参考一下下面的nodejs相关的项目,希望对你的学习有所帮助,废话少说,让我们直接进入正题>> 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件,具有快速、可扩展、易于使用和灵活的特点。它支持多种数…...
全国各城市-货物进出口总额和利用外资-外商直接投资额实际使用额(1999-2020年)
最新数据显示,全国各城市外商直接投资额实际使用额在过去一年中呈现了稳步增长的趋势。这一数据为研究者提供了对中国外商投资活动的全面了解,并对未来投资趋势和政策制定提供了重要参考。 首先,这一数据反映了中国各城市作为外商投资的热门目…...
CentOS 7查看磁盘空间
CentOS如何查看硬盘大小 CentOS是一种基于Linux的操作系统,主要用于服务器端应用。在服务器管理中,硬盘大小是一个非常重要的指标,查看硬盘大小可以帮助系统管理员有效地管理硬盘空间和避免硬盘满了的情况。 方法一:使用df命令 …...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

