# 指针

指针是 C 语言最重要的概念之一，也是最难理解的概念之一。

## 简介

指针是什么？首先，它是一个值，这个值代表一个内存地址，因此指针相当于指向某个内存地址的路标。

字符`*`表示指针，通常跟在类型关键字的后面，表示指针指向的是什么类型的值。比如，`char*`表示一个指向字符的指针，`float*`表示一个指向`float`类型的值的指针。

```c
int* intPtr;
```

上面示例声明了一个变量`intPtr`，它是一个指针，指向的内存地址存放的是一个整数。

星号`*`可以放在变量名与类型关键字之间的任何地方，下面的写法都是有效的。

```c
int   *intPtr;
int * intPtr;
int*  intPtr;
```

本书使用星号紧跟在类型关键字后面的写法（即`int* intPtr;`），因为这样可以体现，指针变量就是一个普通变量，只不过它的值是内存地址而已。

这种写法有一个地方需要注意，如果同一行声明两个指针变量，那么需要写成下面这样。

```c
// 正确
int * foo, * bar;

// 错误
int* foo, bar;
```

上面示例中，第二行的执行结果是，`foo`是整数指针变量，而`bar`是整数变量，即`*`只对第一个变量生效。

一个指针指向的可能还是指针，这时就要用两个星号`**`表示。

```c
int** foo;
```

上面示例表示变量`foo`是一个指针，指向的还是一个指针，第二个指针指向的则是一个整数。

## * 运算符

`*`这个符号除了表示指针以外，还可以作为运算符，用来取出指针变量所指向的内存地址里面的值。

```c
void increment(int* p) {
  *p = *p + 1;
}
```

上面示例中，函数`increment()`的参数是一个整数指针`p`。函数体里面，`*p`就表示指针`p`所指向的那个值。对`*p`赋值，就表示改变指针所指向的那个地址里面的值。

上面函数的作用是将参数值加`1`。该函数没有返回值，因为传入的是地址，函数体内部对该地址包含的值的操作，会影响到函数外部，所以不需要返回值。事实上，函数内部通过指针，将值传到外部，是 C 语言的常用方法。

变量地址而不是变量值传入函数，还有一个好处。对于需要大量存储空间的大型变量，复制变量值传入函数，非常浪费时间和空间，不如传入指针来得高效。

## & 运算符

`&`运算符用来取出一个变量所在的内存地址。

```c
int x = 1;
printf("x's address is %p\n", &x);
```

上面示例中，`x`是一个整数变量，`&x`就是`x`的值所在的内存地址。`printf()`的`%p`是内存地址的占位符，可以打印出内存地址。

上一小节中，参数变量加`1`的函数，可以像下面这样使用。

```c
void increment(int* p) {
  *p = *p + 1;
}

int x = 1;
increment(&x);
printf("%d\n", x); // 2
```

上面示例中，调用`increment()`函数以后，变量`x`的值就增加了1，原因就在于传入函数的是变量`x`的地址`&x`。

`&`运算符与`*`运算符互为逆运算，下面的表达式总是成立。

```c
int i = 5;

if (i == *(&i)) // 正确
```

## 指针变量的初始化

声明指针变量之后，编译器会为指针变量本身分配一个内存空间，但是这个内存空间里面的值是随机的，也就是说，指针变量指向的值是随机的。这时一定不能去读写指针变量指向的地址，因为那个地址是随机地址，很可能会导致严重后果。

```c
int* p;
*p = 1; // 错误
```

上面的代码是错的，因为`p`指向的那个地址是随机的，向这个随机地址里面写入`1`，会导致意想不到的结果。

正确做法是指针变量声明后，必须先让它指向一个分配好的地址，然后再进行读写，这叫做指针变量的初始化。

```c
int* p;
int i;

p = &i;
*p = 13;
```

上面示例中，`p`是指针变量，声明这个变量后，`p`会指向一个随机的内存地址。这时要将它指向一个已经分配好的内存地址，上例就是再声明一个整数变量`i`，编译器会为`i`分配内存地址，然后让`p`指向`i`的内存地址（`p = &i;`）。完成初始化之后，就可以对`p`指向的内存地址进行赋值了（`*p = 13;`）。

为了防止读写未初始化的指针变量，可以养成习惯，将未初始化的指针变量设为`NULL`。

```c
int* p = NULL;
```

`NULL`在 C 语言中是一个常量，表示地址为`0`的内存空间，这个地址是无法使用的，读写该地址会报错。

## 指针的运算

指针本质上就是一个无符号整数，代表了内存地址。它可以进行运算，但是规则并不是整数运算的规则。

（1）指针与整数值的加减运算

指针与整数值的运算，表示指针的移动。

```c
short* j;
j = (short*)0x1234;
j = j + 1; // 0x1236
```

上面示例中，`j`是一个指针，指向内存地址`0x1234`。由于`0x1234`本身是整数类型（`int`），跟`j`的类型（`short*`）并不兼容，所以强制使用类型投射，将`0x1234`转成`short*`。你可能以为`j + 1`等于`0x1235`，但正确答案是`0x1236`。原因是`j + 1`表示指针向内存地址的高位移动一个单位，而一个单位的`short`类型占据两个字节的宽度，所以相当于向高位移动两个字节。同样的，`j - 1`得到的结果是`0x1232`。

指针移动的单位，与指针指向的数据类型有关。数据类型占据多少个字节，每单位就移动多少个字节。

（2）指针与指针的加法运算

指针只能与整数值进行加减运算，两个指针进行加法是非法的。

```c
unsigned short* j;
unsigned short* k;
x = j + k; // 非法
```

上面示例是两个指针相加，这是非法的。

（3）指针与指针的减法

相同类型的指针允许进行减法运算，返回它们之间的距离，即相隔多少个数据单位。

高位地址减去低位地址，返回的是正值；低位地址减去高位地址，返回的是负值。

这时，减法返回的值属于`ptrdiff_t`类型，这是一个带符号的整数类型别名，具体类型根据系统不同而不同。这个类型的原型定义在头文件`stddef.h`里面。

```c
short* j1;
short* j2;

j1 = (short*)0x1234;
j2 = (short*)0x1236;

ptrdiff_t dist = j2 - j1;
printf("%td\n", dist); // 1
```

上面示例中，`j1`和`j2`是两个指向 short 类型的指针，变量`dist`是它们之间的距离，类型为`ptrdiff_t`，值为`1`，因为相差2个字节正好存放一个 short 类型的值。

（4）指针与指针的比较运算

指针之间的比较运算，比较的是各自的内存地址哪一个更大，返回值是整数`1`（true）或`0`（false）。