C-指针

每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。

指针

指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

1
type *var-name;

type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。
用来声明指针的星号 * ,星号是用来指定一个变量是指针。

以下是有效的指针声明:

1
2
3
4
int    *ip;    /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。

不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

使用指针

使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */

ip = &var; /* 在指针变量中存储 var 的地址 */

printf("var 变量的地址: %p\n", &var );

/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );

/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );

return 0;
}

指针的算术运算

C 指针是一个用数值表示的地址。因此,可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-

总结:

  • 指针的每一次递增,它其实会指向下一个元素的存储单元。
  • 指针的每一次递减,它都会指向前一个元素的存储单元。
  • 指针在递增和递减时,跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。

实例:

递增指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

const int MAX = 3;

int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;

/* 指针中的数组地址 */
ptr = var;
for ( i = 0; i < MAX; i++)
{
printf("存储地址:var[%d] = %p\n", i, ptr );
printf("存储值:var[%d] = %d\n", i, *ptr );

/* 指向下一个位置 */
ptr++;
}
return 0;
}

递减一个指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

const int MAX = 3;

int main()
{
int var[] = {10, 100, 200};
int i, *ptr;

/* 指针中最后一个元素的地址 */
ptr = &var[MAX - 1];
for (i = MAX; i > 0; i--)
{
printf("存储地址:var[%d] = %p\n", i - 1, ptr);
printf("存储值:var[%d] = %d\n", i - 1, *ptr);

/* 指向下一个位置 */
ptr--;
}
return 0;
}

指针的比较

指针可以用关系运算符进行比较,如 ==< 和 >

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

const int MAX = 3;

int main()
{
int var[] = {10, 100, 200};
int i, *ptr;

/* 指针中第一个元素的地址 */
ptr = var;
i = 0;
while (ptr <= &var[MAX - 1])
{
printf("存储地址:var[%d] = %p\n", i, ptr);
printf("存储值:var[%d] = %d\n", i, *ptr);

/* 指向上一个位置 */
ptr++;
i++;
}
return 0;
}

NULL指针

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL。赋为 NULL 值的指针被称为空指针。

NULL 指针是一个定义在标准库中的值为零的常量。

请看下面的程序:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main ()
{
int *ptr = NULL;

printf("ptr 的地址是 %p\n", ptr );

return 0;
}

结果:

1
ptr 的地址是 0x0

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

如需检查一个空指针,您可以使用 if 语句,如下所示:

1
2
if(ptr)     /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */

NULL指针应用

  • 必须初始化指针,没有被初始化的指针被称为失控指针(野指针)。
  • free() 后指针必须赋值 NULL

编程坏习惯

  • 初始化指针不赋 NULL

初始化指针不赋 NULL,因为这样的指针会指向一片未知的区域,这样的指针不是空指针,但指向一片访问受限制的内存区域,你无法使用它,这样的情况下的指针,业界给了它一个形象的名字:“野指针”,而且难以调试,在许多编译器单步 debug 会出现奇怪的错误,但经常看见的 Segmentation Fault 这样的错误

  • free() 后指针不赋 NULL

为指针分配内存后,指针便可以指向一片合法可使用的内存,但使用 free() 释放那片内存时,指针依旧存放着那片内存的地址,也就是依旧指向那片内存,但这片内存已经释放,不可访问,这时若不小心使用了这个指针,便会内存错误,又是会有奇怪的 bug ,代码几百行多点就会难以调试,业界给这样的指针也有个统称:悬空指针,为了避免这种情况出现,一定要释放内存后,给指向这片内存的指针,都赋值为 NULL