C【指针】详解(上)

目录

[一. 指针是什么](#一. 指针是什么)

[💭 内存 :](#💭 内存 :)

[💬 问题:](#💬 问题:)

[⚡ 如何理解编址(拓展):](#⚡ 如何理解编址(拓展):)

[👉 指针 :](#👉 指针 :)

[📌 指针大小 :](#📌 指针大小 :)

[⭐ 总结 :](#⭐ 总结 :)

[二. 指针变量 和 指针类型](#二. 指针变量 和 指针类型)

[📏 指针变量:](#📏 指针变量:)

[🪐 指针变量和地址](#🪐 指针变量和地址)

[🚩 指针变量 ptr :](#🚩 指针变量 ptr :)

[🌎 指针类型:](#🌎 指针类型:)

[💫 指针类型的意义](#💫 指针类型的意义)

[⭐ 结论:](#⭐ 结论:)

[⭐ 总结 :](#⭐ 总结 :)

三、指针运算

[🌊 指针 + - 整数](#🌊 指针 + - 整数)

[⭐ 结论:](#⭐ 结论:)

[❓ 问题:](#❓ 问题:)

[🌱 指针 - 指针](#🌱 指针 - 指针)

[⭐ 结论:](#⭐ 结论:)

[📚 应用场景:](#📚 应用场景:)

[🌴 指针的关系运算](#🌴 指针的关系运算)

[⭐ 总结 :​编辑](#⭐ 总结 :编辑)

[四、const 修饰的指针](#四、const 修饰的指针)

[🛸 const 在 * 左边 :](#🛸 const 在 * 左边 :)

[🛸 const 在 * 右边 :](#🛸 const 在 * 右边 :)

[⭐ 结论 :](#⭐ 结论 :)

五、野指针

[🌌 野指针成因:](#🌌 野指针成因:)

[1. 指针未初始化](#1. 指针未初始化)

[2. 指针越界访问](#2. 指针越界访问)

[3. 指针指向的空间释放](#3. 指针指向的空间释放)

[💡 如何避免野指针](#💡 如何避免野指针)

[1. 指针初始化](#1. 指针初始化)

[2. 小心指针越界](#2. 小心指针越界)

[3. 指针指向空间释放,及时置NULL,指针使用之前检查有效性](#3. 指针指向空间释放,及时置NULL,指针使用之前检查有效性)

[4. 避免返回局部变量的地址](#4. 避免返回局部变量的地址)

[5. 指针使用之前检查有效性](#5. 指针使用之前检查有效性)

[⭐ 总结 :](#⭐ 总结 :)

避免野指针:

六、指针和数组

[🧩 数组名的理解](#🧩 数组名的理解)

七、二级指针

八、指针数组


一. 指针是什么

💭 内存 :

要理解++指针++的概念,首先需要理解 内存和指针 的关系。

🔨 内存:

内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。所以为了有效的使用内存 ,就把内存划分成一个个小的内存单元 ,每个内存单元的大小是1个字节。 为了能够有效的访问 内存的每个单元 ,就给内存单元进行了编号 ,这些编号被称为该内存单元的地址。


💻 电脑分为 32位 和 64位 两种操作系统:

**64位:**同理 。


📋 注 :

32位 和 64位 :

这是根据 CPU 内的寄存器字长 来确定的 - 计算机内部数据都是二进制来呈现的

✦ 32 位的计算机 CPU 一次最多能处理 32 位的二进制数据

✦64 位的计算机 CPU 一次最多能处理 64 位的二进制数据


💬 问题:

1

1. 内存是怎么编号的?

因为有地址线,地址线通电时会产生电信号并转化为数字信号,对应的二进制序列就形成了地址。把地址作为编号分配给每一个内存单元,那么就会形成内存单元对应的地址(编号)。

1

2. 一个这样的内存单元是多大空间?

一个字节给一个对应的地址是比较合适的。

对于 32位 的机器,有 32根地址线,产生 2的32次方 个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32 / 1024KB == 2^32 / 1024 / 1024MB == 2^32 / 1024 / 1024 / 1024GB == 4GB) 4GB 的空间进行编址。那么,2的32次方 个地址可以管理 4个G 的空间。

所以,一个内存单元是一个字节,然后分配地址的,一个字节给一个编号


⚡ 如何理解编址(拓展):

地址总线传递过去就是地址信号,有了地址,就可以找到内存单元,拿到数据之后,再通过数据总线传回 CPU 内寄存器。

所以从硬件的角度来讲,这些地址是不需要存起来的,只要产生了一个地址,就可以通过地址很好的去访问内存单元


🚀 我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是 8GB / 16GB / 32GB 等,那么,如何高效的管理内存空间呢

其实也是把内存划分为⼀个个的内存单元 ,每个内存单元的大小取 1 个字节

💬 问题:

3. 当去访问一个内存空间的时候?

通过地址线产生一个地址信息,这时候把地址信息传递给内存,在内存上就可以找到该地址对应的数据,数据再通过数据总线传入CPU内寄存器。


到这里我们就可以明白:

👉 指针 :

指针是 内存中一个最小单元的编号 ,也就是 地址

平时说的指针,通常指的是 指针变量 ,是 用来存放内存地址的变量


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


📌 指针大小 :

一个比特位可以存储一个二进制数值,即 1 或 0

那么:

注意:

指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。


⭐ 总结 :

相信看到这里,你对指针已经有了更深入的理解了~

那么,即然有了指针,该如何存放呢?指针有没有自己的类型呢?


二. 指针变量 和 指针类型

📏 指针变量:

首先,我们需要先来了解一下:

🪐 指针变量和地址

变量 a 对应的四个字节,这四个字节分别对应的地址:

可以看到:一个字节对应一个地址

如何得到 a 的地址 ?

虽然整型变量占用 4 个字节,但只要知道第一个字节的地址,就能依次访问到 4 个字节的数据

|---------|--------------|
| & | 取地址操作符 |
| &a | 取出 a 的地址 |

🚩 指针变量 ptr :

因为 prt 是 存放指针的变量 所以叫 指针变量

🌠 ptr 的类型是 int*
int* 该如何理解?

指针变量就是用来存放地址的,存在指针变量中的值,会被当成地址来使用


🌎 指针类型:

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

有的。

指针大小都一样 ,4 或 8 个字节(无论什么类型)但是划分为不同的类型。

( NULL 本质上是 0 )

%zu 是为 sizeof 准备的格式字符,打印用 %u 最合适( %zu 最准确 )

这里可以看到,指针的定义方式 是:type *( 类型* )

其实:

char* 类型的指针是为了存放 char 类型变量的地址。

short* 类型的指针是为了存放 short 类型变量的地址。

int* 类型的指针是为了存放 int 类型变量的地址。


💫 指针类型的意义

没改之前 a 的值 :

改之后 :把四个字节都改掉了

用 int* 的指针把四个字节都改了

改用另一种指针类型:

改之前:

改之后:

仅修改了一个字节

因为这里是 char* 的指针 访问的时候只访问了一个字节

所以,只更改了一个字节的值

而刚刚是 int* 的指针可以访问到四个字节,所以可以全部更改

⭐ 结论:

指针变量的类型 决定了 指针在被解引用时 能访问几个字节(指针的权限)

如果是int*的指针,解引用访问4个字节

如果是char*的指针,解引用访问1个字节

( 其他类型同理 )


🌵 问题 :

最后打印的结果是 ?
📕 解析:

解引用 4 次,一次两个字节,刚好把前八个字节置为 0 d

指针 p 的类型为 short* 类型 ,因此 p 每次只能访问两个字节

for 循环对数组中内容进行修改时,一次访问的是:

arr[0] 的低两个字节,arr[0] 的高两个字节,arr[1] 的低两个字节,arr[1] 的高两个字节

故最后打印:0 0 3 4 5


⭐ 总结 :


三、指针运算


🌊 指针 + - 整数

分别加了 4个字节 1个字节

结论:

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

char* 的指针 加一 跳过一个字节,加二 跳过两个字节......

int* 的指针 加一 跳过四个字节,加二 跳过八个字节.....

( 其他类型同理 )

❓ 问题:

int* 和 float* 解引用都是访问 4 个字节,int* 和 float* 是不是就可以通用?

可以看到,在内存中是不一样的

float* 的指针认为指向内存中的数据是浮点数(int* 同理),浮点数和整数在内存中本质上存储方式是有所差异的

所以,即使 int* 和 float* 解引用都是访问 4 个字节,int* 和 float* 也不可以通用


🌱 指针 - 指针

前提:两个指针指向同一块空间

两个地址相减:

结论:

指针 - 指针 = 指针与指针之间 的 元素个数


📚 应用场景:

求字符串长度

\0 的地址 - 首元素地址 = 元素个数


🌴 指针的关系运算

指针比较大小 -- 地址比较大小

📝 规定:

允许 指向数组元素的指针 与 指向数组末尾之后的内存位置的指针 进行比较,但不允许与 指向数组起始位置之前 的内存位置的指针进行比较

p 可以和 p2 比较,不可以和 p1 比较


⭐ 总结 :


四、const 修饰的指针

🛸 const 在 * 左边 :

当 const 放在 * 左边时,*p 无法修改, p 可以被修改

🛸 const 在 * 右边 :

当 const 放在 * 右边时,*p 可以被修改,p 无法被修改

⭐ 结论 :

1

· const 修饰指针变量

1

  1. const 放在 * 的左边修饰 的是 指针指向的内容 ,保证 指针指向的内容不能通过指针来改变, 但是 指针变量本身的内容可变

1

  1. const 放在 * 的右边修饰 的是 指针变量本身 ,保证 指针变量的内容不能修改 ,但是 指针指向的内容,可以通过指针改变

1


五、野指针

定义:指针指向的位置是不可知的,可能是随机的、错误的或超出有效范围的

🌌 野指针成因:

1. 指针未初始化

局部变量不初始化,编译器会给它赋值一个随机值

2. 指针越界访问

下标范围在 [ 0~9 ]

3. 指针指向的空间释放

p 为野指针

当 fun 函数调用结束(fun 函数栈帧空间销毁),a 变量的空间销毁(还给操作系统,这块空间不属于我们了,不能使用了)

而p还保留着这块空间的地址,能够找到这块地址,但是不能使用访问


💡 如何避免野指针

1. 指针初始化

当明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值 NULL

( NULL 是C语⾔中定义的⼀个标识符常量,本质是 0,0 也是地址,但这个地址是⽆法使⽤的,读写该地址会报错)

对于 NULL 空指针(0 地址)是不能访问的,因为 *p 没有指向有效空间

正确的使用方式:

如果 p 不等于空,说明它指向有效的空间,就可以解引用它

所以 在初始化时 :给出明确地址 或者 赋值为空(空指针)


2. 小心指针越界

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问

当指针指向的范围超出 arr 的范围,p 就是野指针

所以,在不使用 p 之后,先置为空,下次使⽤的时候,判断 p 不为 NULL 的时候再使⽤


**3. 指针指向空间释放,及时置NULL,**指针使用之前检查有效性

当指针变量指向⼀块区域的时候,可以通过指针访问该区域,当后期不再使用这个指针的时候,就把该指针置为 NULL

默认规则:只要是 NULL 指针就不去访问,同时使用指针之前可以判断指针是否为 NULL


4. 避免返回局部变量的地址

arr 数组进入函数创建,出了函数销毁。当 p 再访问这块空间时,这块空间已经不属于当前程序了(局部变量在栈区,当返回一个局部变量的时候就是返回一块栈上空间的地址)


5. 指针使用之前检查有效性

a的空间确实是销毁了(空间销毁,这块空间不属于当前程序,没有这块空间的使用权限)

当通过p所指向的非法地址,找到a的空间时,如果这块空间没有被使用,没有被覆盖,里面放的就是10,但这个值可能会发生变化

首先调用 fun,内存中会创建 fun 的函数栈帧,调用完成之后 fun 函数栈帧销毁,这块空间就会空出来,接着调用 printf 函数时,又会创建 printf 的函数栈帧,就会把上一次的函数栈帧覆盖了,10 就会改变


⭐ 总结 :

避免野指针:


附:

释放内存空间后,将指针置为NULL 是一个良好的编程习惯

当释放一个指针所指向的内存空间时,该指针仍然保留着指向已释放内存的地址。如果在后续的代码中尝试使用这个指针,可能会导致 悬空指针的问题,即:访问已经释放的内存块,引发不可预测的行为和错误。

为了避免悬空指针问题,可以在释放内存空间后立即将指针置为NULL,这样任何后续对该指针的使用都可以被及时检测到。

当你需要检查指针是否有效或重新分配内存时,检查指针是否为NULL可以提供一个简单可靠的方式。


六、指针和数组

🧩 数组名的理解

可以看到,数组名 和 数组首元素地址一样

💡 结论:

数组名 就是 数组首元素地址

🌟 例外:

除此之外,遇到的 所有的数组名 都是 数组首元素的地址

那么,也可以这样写:

用指针存放数组数组首元素地址:

可以看到地址完全相同

所以 p + i 计算的是数组 arr 下标为 i 的地址,那么,就可以直接通过指针来访问数组

同理,打印元素:


七、二级指针

指针变量本质上也是一种变量,而所有变量都拥有自己的地址

那么,指针变量的地址该如何存储呢 ?

这就是 二级指针

n 的地址存放在 一级指针 p 中,p 的地址存放在 二级指针 pp 中

💬 通过 pp 想找到 n

要 解引用 两次 :

第一次 解引用 pp 可以找到 p

第二次 再对 p 解引用 找到 n

二级指针变量是用来存放一级指针变量的地址的


八、指针数组

存放指针的数组

int* arr[3];

arr 是一个指针数组,每个元素的类型是指针

i 用来访问 parr 数组中的元素

j 用来访问 arr 数组中的元素

原始二维数组 :

两 者 等 价

知道这一点后

🎈 为什么数组下标从 0 开始 ?

0

数组名 表示 数组首元素的地址

当我们访问arr[i] 时,编译器视角是:*(arr + i) ,而 下标本质是:相对于首元素的地址偏移量,首元素的偏移量为 0

🧲 所以:

arr[0] 就是 *(arr + 0) ,即首元素本身

如果数组下标从 1 开始,那么 arr[1] 就是:*(arr + 1),会指向后一个位置

所以,数组下标从 0 开始使得 下标与偏移量保持一致


上篇(完)感谢浏览~~(努力写出更高质量的文章ing...)

努力更新.........坚持学习,大家加油,早日成为技术大佬 hh,有什么问题和建议都可以留言或私信找我交流,大家一起进步 ~~~

相关推荐
martian6653 小时前
深入解析C++驱动开发实战:优化高效稳定的驱动应用
开发语言·c++·驱动开发
mit6.8243 小时前
固定中间
算法
老马啸西风3 小时前
成熟企业级技术平台 MVE-010-跳板机 / 堡垒机(Jump Server / Bastion Host)
人工智能·深度学习·算法·职场和发展
FMRbpm3 小时前
用队列实现栈
数据结构·c++·新手入门
fei_sun3 小时前
【数据结构】2021年真题
数据结构
立志成为大牛的小牛3 小时前
数据结构——五十九、冒泡排序(王道408)
数据结构·学习·程序人生·考研·算法
s09071363 小时前
下视多波束声呐进行测绘作业注意事项
算法·海洋测绘·下视多波束
wangjialelele4 小时前
git工作原理、个人使用到多人协作开发与git FLOW模型
c语言·c++·git·团队开发·个人开发