指针的概念
我们知道,计算机中所有的数据都必须放在内存中,为了正确访问这些数据,我们必须为每个字节编上号码,每个字节的编号是唯一的,根据编号可以准确的找到某个内存。
我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始一次增加,以十六进制表示,C 语言输出十六进制形式为 %#X
。
一切都是地址
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。
定义指针变量
数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。
在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址。
定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*,格式为:
datatype *name;
或者
datatype *name = value;
*
表示这是一个指针变量,datatype 表示该指针变量所指向的数据的类型。例如:
int *p1;
p1 是一个指向 int 类型数据的指针变量,至于 p1 究竟指向哪一份数据,应该由赋予它的值决定。再如:
int a = 100, b = 200;
int *p_a = &a;
p_a = &b;
p_a 需要的一个地址,a 前面必须要加取地址符 &,否则是不对的。
*
是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带 *
。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上 *
,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带 *
,给指针变量赋值时不能带 *
。
p_a 的类型是 int*
, 而不是 int
。
通过指针变量取得数据
指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:
*pointer;
这里的 * 称为指针运算符,用来取得某个地址上的数据,请看下面的例子:
#include <stdio.h>
int main(){
int a = 15;
int *p = &a;
printf("%d, %d\n", a, *p); //两种方式都可以输出a的值
return 0;
}
运行结果:
15, 15
假设 a 的地址是 0X1000,p 指向 a 后,p 本身的值也会变为 0X1000,*p 表示获取地址 0X1000 上的数据,也即变量 a 的值。从运行结果看,*p 和 a 是等价的。
上节我们说过,CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过 *p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”。
假设变量 a、p 的地址分别为 0X1000、0XF0A0,程序被编译和链接后,a、p 被替换成相应的地址。使用 *p 的话,要先通过地址 0XF0A0 取得变量 p 本身的值,这个值是变量 a 的地址,然后再通过这个值取得变量 a 的数据,前后共有两次运算;而使用 a 的话,可以通过地址 0X1000 直接取得它的数据,只需要一步运算。
也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。