指针是 C 语言的魅力所在,也是陷阱所在。
疑惑:取址符在声明和语句中
我在学习指针的时候,觉得声明 int *p = &i;
和语句 p = &i;
不一致。相信很多人也有同样的疑惑。
造成困惑的根源在于,根据使用上下文的不同,C 语言中的 *
号可以有多种含义。
在声明 int *p = &i;
中,*
号不是间接寻址运算符,其作用是告知编译器 p
是一个指向 int
类型变量的指针。
而在语句中出现时,*
号(作为一元运算符使用时)会执行间接寻址。
打印
任何指针使用转换说明符 %p
来显示。
数组
可以把指针指向数组元素。
|
|
通过在 p
上执行指针算术运算(或者地址算术运算)可以访问数组 a
的其他所有元素。C 语言支持 3 种格式的指针算术运算:
- 指针 + 整数
- 指针 - 整数
- 两个指针相减(只有在两个指针指向同一个数组时,把它们相减才有意义)
指向同一数组的指针可以用关系运算符进行比较。
虽然可以把数组名 int a[10]
用作指针,但是不能给数组名赋新的值。试图使数组名指向其他地方是错误的。
|
|
可以将 a
复制给一个指针变量,然后改变该指针变量。
|
|
指针与地址
指针通常是地址,但是指针不是真正的内存地址,CPU 必须把它和存储在专用寄存器中的段值结合起来,来表示“偏移量”。
const 与指针
当某个函数使用指针时,可能只是为了检查其中的某个值,而不改变它。可以用 const
保护参数。
|
|
这一用法表明 p
是指向“常整数”的指针。试图改变 *p
是编译器会检查的一种错误。
再来仔细学习一下 const
。
|
|
两种方式一样,a 的值无法修改,要让它一开始就拥有一个值
- 声明时初始化
int const a = 1;
- 在函数中声明为
const
的形参在函数被调用的时候会获得实参的值
然而 const
和 指针一起使用时——有两样东西有可能成为常量——指针变量和它指向的实体,示例(这里是《C和指针》里的实例,变量名取得非常有意义)
|
|
为了指明数组型形式参数不会被改变,可以在其声明中包含单词 const
:
|
|
主要是看 const
右边是什么。
指针作为返回值
可以作为函数的返回值。但是永远不要返回指向自动局部变量的指针:
|
|
一旦 f()
返回,变量 i
就不存在了,所以指向变量 i
的指针将是无效的。有的编译器会在这种情况下给出类似 “function returns address of local variable” 的警告。
动态存储分配
内存分配函数
为了动态地分配存储空间,需要调用三种内存分配函数的一种,这些函数都是声明在 <stdlib.h>
头中的。
malloc()
——分配内存块,但是不对内存进行初始化。calloc()
——分配内存块,并且对内存块进行清零。realloc()
——调整先前分配的内存块大小。
当为申请内存块而调用内存分配函数时,由于函数无法知道存储在内存块中的数据是什么类型的,所以它不能返回
int
类型、char
类型等普通类型的指针。取而代之的,函数会返回void *
类型的值,void *
类型的值是“通用”指针,本质上它只是内存地址。
空指针
当调用内存分配函数时,如果找不到满足需要的内存块,函数会返回空指针。把函数的返回值存储到指针变量中以后,需要判断该指针变量是否为空指针。如果为空,需要采取适当的动作。
所有非空指针都为真,只有空指针为假。
|
|
与 NULL
进行比较可读性更好
动态分配字符串
使用 malloc()
为字符串分配内存
函数原型:
|
|
size_t
表示某块内存的大小,取值范围是目标平台下最大可能的数组尺寸。
|
|
使用 calloc()
|
|
calloc()
函数会将内存初始化为 0(清除分配的内存)
|
|
使用 realloc()
|
|
要确定传递给 realloc 函数的指针来自于先前 malloc, calloc 或 realloc 的调用。否则,程序可能行为异常。
- 当扩展内存块时,
realloc()
不回对添加进内存块的字节进行初始化。 - 如果
realloc()
不能按要求扩大内存块,那么它会返回空指针,并且在原有内存块中的数据不会改变。 - 如果
realloc()
被调用时以 0 作为第二个实际参数,那么它会释放掉内存块。
一旦 realloc 函数返回,一定要对指向内存块的所有指针进行更新,因为 realloc 函数可能会使内存块移动到了其他地方。
释放存储空间
对于程序而言,不可再访问到的内存块被称为是垃圾 (garbage)。
free()
|
|
要确定传递给 realloc 函数的指针来自于先前 malloc, calloc 或 realloc 的调用。否则,程序可能行为异常。
悬空指针
虽然 free()
允许收回不再需要的内存,但是使用该函数会导致:悬空指针 (dangling pointer)。调用 free(p) 函数会释放 p
指向的内存块,但是不会改变 p
本身。如果忘记了 p
不再指向有效的内存块,修改其指向的内存是严重的错误,因为程序不再对此内存有任何控制权了。
|
|
试图访问或修改释放掉的内存块会导致未定义的行为。
悬空指针很难发现,因为及格指针可能指向相同的内存块。在释放内存块后,全部的指针都悬空了。
链表
运算符 .
的优先级高于运算符 *
运算符 right arrow selection ->
产生左值:
|
|
简单示例
|
|
删除示例
- 定位要删除的结点;(保留一个指向前一个结点的指针 previous,还有指向当前结点的指针 current)
- 改变前一个结点,从而使它“绕过”删除结点;
- 调用
free()
收回删除结点占用的内存空间。
|
|
删除结点的函数
|
|
指向函数的指针
一个很好的例子
快速排序的函数原型
|
|
对 inventory 进行排序。
|
|
qsort()
要求其形式参数类型为 void *
,但我们不能通过 void *
型的指针访问 part
结构成员,而是需要指向结构 part
的指针。
|
|
更加简明的实现:
|
|
通过移除 if
语句可以把函数 compareParts()
变得更短:
|
|