1
0
wiki/docs/开发/C/指针.md

187 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: 指针
title: 指针
sidebar_position: 7
data: 2022年3月30日
---
指针是 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`。你可能以为`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("%d\n", dist); // 1
```
上面示例中,`j1`和`j2`是两个指向 short 类型的指针,变量`dist`是它们之间的距离,类型为`ptrdiff_t`,值为`1`因为相差2个字节正好存放一个 short 类型的值。
4指针与指针的比较运算
指针之间的比较运算,比较的是各自的内存地址哪一个更大,返回值是整数`1`true或`0`false