type
status
date
slug
summary
tags
category
icon
password
1、指针的定义
在内存中,每个字节都有一个对应的编号,这个编号就是”地址“。如果定义一个变量,系统就会给这个变量分配内存,而这个内存是有一个地址值的。这个地址值就叫做指针,按照这个地址值读写变量值的方式叫做**“直接访问“;有字节访问当然就有“间接访问”,即将这个变量(这里暂时叫做
A
)的地址值用一个新类型的变量(这里暂时把他叫叫做B
)储存起来,用B
变量的值去找到A
变量,最后获取到内容。其中B
变量就称为指针变量**。指针与指针变量是两个概念,指针是一个变量的地址,而指针变量是用来存放指针的变量。
1.1、指针的声明
指针的定义和声明使用星号(*)运算符。下面是指针的定义语法:
在这里,<数据类型>表示指针所指向的数据类型,<指针变量名>是指针变量的标识符。
例如,以下是一个指向整数类型的指针的定义:
这里,ptr是一个指向整数的指针变量。
注意,指针的类型与它所指向的数据类型必须匹配。这是因为指针解引用时,它会根据自身的类型来读取相应大小的内存。
另外,还可以通过在指针变量名前添加const关键字,将指针声明为常量指针。常量指针指向的内存地址是固定的,不可更改。
例如,以下是一个指向整数的常量指针的定义:
这里,
ptr
是一个指向整数的常量指针,它指向的整数值是不可更改的。1.2、指针的空间大小
现如今大多数家用电脑都是64位架构的电脑,所以在大多数情况下,指针的的大小是8个字节。但是开发环境不同,受不同机器限制,指针会有不同的大小:
- 在32位体系结构上,指针的大小通常为4字节(32位),它可以表示内存中的地址范围为2^32个字节(4GB)。
- 在64位体系结构上,指针的大小通常为8字节(64位),它可以表示内存中的地址范围为2^64个字节(16EB)。
需要注意的是,指针的大小不仅取决于计算机体系结构,还取决于所使用的编程语言和编译器的实现。例如,某些编程语言中的指针可能具有固定的大小,无论计算机体系结构如何。
此外,指针的空间大小和指针所指向的数据类型无关。不论指针指向的是一个字节、整数、浮点数还是其他类型的数据,指针本身的大小都是固定的。
需要注意的是,在某些特定的情况下,指针的大小可能会有所不同。例如,嵌入式系统或某些特殊的编程环境中可能存在非标准的指针大小。在这种情况下,具体的系统文档或编译器文档会提供有关指针大小的详细信息。
2、指针的使用
2.1、指针的运算符
2.1.1、运算符介绍
指针在编程语言中有一些特定的运算符用于操作和处理指针。以下是常见的指针运算符:
- 取地址运算符(&):也称作引用运算符,用于获取变量的地址。例如,
&variable
将返回变量variable
的地址。
- 取值运算符(*):也称解引用运算符,用于访问指针所指向的存储位置上的值。例如,
ptr
将返回指针ptr
所指向的值。
- 指针赋值运算符(=):用于将一个指针的值赋给另一个指针变量。例如,
ptr1 = ptr2
将使ptr1
指向与ptr2
相同的地址;或者ptr1 = &ptr2
将使ptr2
的地址赋值给ptr1
。
- 指针加法运算符(+):用于将指针的值增加某个偏移量。例如,
ptr = ptr + 1
将使指针ptr
指向下一个相邻位置。
- 指针减法运算符(-):用于将指针的值减去某个偏移量。例如,
ptr = ptr - 1
将使指针ptr
指向上一个相邻位置。
- 指针比较运算符(==、!=、>、<、>=、<=):用于比较两个指针的值。这些运算符通常用于检查指针是否相等、指针的相对顺序等。
需要注意的是,指针运算通常是基于指针所指向的数据类型的大小。例如,对指针进行加法或减法时,会根据指针所指向的数据类型的大小进行偏移量的计算。此外,指针运算还受到指针的有效范围的限制,避免越界访问。
2.1.2、运算符的使用
这是引用运算符和解引用运算符的使用:
需要注意:
- 指针变量前的 ”*“ 表示该变量为指针型变量,例如:
此时指针的变量名为
pointer
,而不是*pointer
- 在进行指针定义时,我们必须做到类型对应,即
int
类型对应int
指针等。例如:
而以下说错误的赋值方式:
- 有时候我们会看到一个奇葩语句,
&*p
或者&p
,例如: - &p 分析:首先
p
间接访问到变量i
,然后&取地址变量i
,最后的结果还是&i 或 p
。 - &p 分析:首先
&p
取地址得到p
变量的地址,然后通过p
变量的地址间接访问到p
,最后的结果还是&i 或 p
。
在代码中甚至还会出现**p或者&&p等,可以根据上面的推理自己了解。
- 我们在使用指针声明时,最好遵守一个规范,把*和变量名写在一起,如
int *a
;当有多个变量时我们可以:int *a,*b,*c
。
3、指针的使用场景
从简单的来说,指针的使用场景主要分为两个,一个是传递,另一个是偏移。传递一般是在函数之间的调用;偏移一般用在数组的操作。
如下是一些使用场景,由ChatGPT生成:
- 动态内存分配:指针允许在运行时动态地分配内存。通过使用指针和相关的内存管理函数(如
malloc()
和free()
),可以在程序执行期间动态地分配和释放内存,以满足灵活的内存需求。
- 数组操作:指针与数组密切相关。C语言中的数组名实际上是指向数组首元素的指针。通过使用指针算术运算,可以遍历数组元素、进行数组操作和传递数组作为函数参数。
- 字符串处理:字符串在C语言中是以字符数组的形式表示的。通过使用指针,可以对字符串进行遍历、复制、连接和比较等操作。指针还可以用于字符串的动态分配和释放。
- 函数传递和返回:指针允许在函数之间传递和返回数据。通过传递指针作为函数参数,可以在函数内部直接修改传递的变量的值。指针还可以用于返回函数内部动态分配的内存。
- 数据结构:指针在构建各种数据结构时非常有用。例如,链表、树和图等数据结构通常使用指针来连接不同的节点或元素,实现灵活的数据结构操作。
- 处理硬件和底层操作:指针在与底层硬件交互、进行位操作和直接访问内存等方面非常有用。通过指针,可以直接读取和写入内存位置,实现对硬件设备的控制和底层操作。
3.1、指针的传递
在C语言中,可以通过指针来实现函数之间的参数传递。通过传递指针作为函数参数,可以在函数内部直接访问和修改指针所指向的变量,从而实现对原始数据的修改。这种方式称为指针传递或引用传递。例如:
还有传递指针的指针:
3.2、指针的偏移
指针的偏移大多用在数组之上,我们声明一个数组
int a[5] = {10,20,30,40,50}
,这时a
的值就是一个指针,而且他指向的是数组的第0个元素。我们可以通过*a
访问到元素10
,以此类推我们可以访问到 *a + 1, *a + 2, ···
,例如:在使用指针偏移时,我们不能使其访问越界,否则会造成访问到其他内存中的数据,导致代码运行过程中出错,同时传递的指针不能获取到数组的长度。原因与解决方案参考:
三、数组的访问越界和传递4、指针与动态内存申请
我们知道,在声明一个数组时,我们需要指定数组的长度。但是在实际开发当中,我们并不知道数组的长度是多少,需要一个变量作为数组的长度。
其实C语言中数组长度固定是因为他的数组变量是在栈空间中的,栈空间的大小在编译时时固定的,如果空间的大小不确定,我们就要使用堆空间。例如:
首先是
malloc
函数,在执行 malloc(size * sizeof(int))
时,我们申请了一块内存,但由于编译器不知道我们需要空间存放什么类型的数据,所以需要 (int *)malloc(size * sizeof(int))
来强制类型转换。同时,因为堆空间由我们自己管理,所以我们在使用完堆空间后,需要通过
free(ptr);
自己手动释放内存。5、栈空间与堆空间的差异
如果我们不自己手动释放内存,堆内存中的数据不会因为函数的栈空间释放而释放,导致留在内存中占用过大。例如:
- 作者:Rainvice
- 链接:https://rainvice.com/article/0f888f69-2782-47f2-bb46-e3ea380a5627
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。