每一个变量都有一个内存位置,每一个内存位置都定义了可使用 &
运算符访问的地址,它表示了在内存中的一个地址。
指针
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
1 | type *var-name; |
type
是指针的基类型,它必须是一个有效的 C 数据类型,var-name
是指针变量的名称。
用来声明指针的星号 *
,星号是用来指定一个变量是指针。
以下是有效的指针声明:
1 | int *ip; /* 一个整型的指针 */ |
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
使用指针
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 *
来返回位于操作数所指定地址的变量的值。
1 |
|
指针的算术运算
C 指针是一个用数值表示的地址。因此,可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-
。
总结:
- 指针的每一次递增,它其实会指向下一个元素的存储单元。
- 指针的每一次递减,它都会指向前一个元素的存储单元。
- 指针在递增和递减时,跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。
实例:
递增指针
1 |
|
递减一个指针
1 |
|
指针的比较
指针可以用关系运算符进行比较,如 ==
、< 和 >
。
1 |
|
NULL指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL
。赋为 NULL
值的指针被称为空指针。
NULL
指针是一个定义在标准库中的值为零的常量。
请看下面的程序:
1 |
|
结果:
1 | ptr 的地址是 0x0 |
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句,如下所示:
1 | if(ptr) /* 如果 p 非空,则完成 */ |
NULL指针应用
- 必须初始化指针,没有被初始化的指针被称为失控指针(野指针)。
- free() 后指针必须赋值
NULL
编程坏习惯
- 初始化指针不赋 NULL
初始化指针不赋 NULL
,因为这样的指针会指向一片未知的区域,这样的指针不是空指针,但指向一片访问受限制的内存区域,你无法使用它,这样的情况下的指针,业界给了它一个形象的名字:“野指针”,而且难以调试,在许多编译器单步 debug 会出现奇怪的错误,但经常看见的 Segmentation Fault
这样的错误
- free() 后指针不赋 NULL
为指针分配内存后,指针便可以指向一片合法可使用的内存,但使用 free()
释放那片内存时,指针依旧存放着那片内存的地址,也就是依旧指向那片内存,但这片内存已经释放,不可访问,这时若不小心使用了这个指针,便会内存错误,又是会有奇怪的 bug ,代码几百行多点就会难以调试,业界给这样的指针也有个统称:悬空指针
,为了避免这种情况出现,一定要释放内存后,给指向这片内存的指针,都赋值为 NULL
。