Introduction to pointers in C
指针pionter
是C
中最基础也是最重要的概念之一. 要理解指针, 首先我们需要理解不同数据类型或不同变量是怎样在计算机内存中存储的.
How to store a variable
Computer Momory 计算机内存
在我们谈论计算机程序执行的时候, 对于计算机内存大部分时候我们说的是随机存储器(RAM
).
假设下图每一格在内存中都代表1 byte
. 在一个典型的计算机系统中, 每个字节都有一个地址,
假设最底层的地址为0
, 从下至上地址增加.
声明一个变量
int a
例如我们声明一个整型变量int a;
, 那么当程序执行时, 计算机(或编译器)会为这个特定变量分配一些内存空间.
具体分配内存大小取决于数据类型和编译器. 在一个典型的计算机系统中:
变量类型 | 字节大小 |
---|---|
$int$ | $4\;bytes$ |
$char$ | $1\;byte$ |
$float$ | $4\;bytes$ |
因为a
是一个int
型变量, 因此计算机会为它分配$4\;bytes$内存空间. 我们可以假设计算机为a
分配内存地址范围: $204\sim 207$.
此外, 计算机有一个内部结构 — 一张查找表, 保存着变量a
的信息. 例如(不限于):
变量名 | 变量类型 | 变量起始地址 |
---|---|---|
$a$ | $int$ | $204$ |
具体如图所示:
char c
如果我们声明另一个变量, 例如我们声明char c;
. 与a
的情况相同, 当计算机看到这条声明时, 就会知道这是一个字符型变量, 需要$1\;byte$内存空间. 所以它会去找$1\;byte$大小的空闲内存空间, 本例中我们可以设空闲内存空间的地址是$209$. 同理, 在查找表中会多出一条有关c
的条目.
对变量操作
现在, 我们可以对变量进行操作, 比如说我们初始化a = 5;
. 当计算机看到这条语句时, 它会去查找表查找变量a
, 通过a
的条目机器可以知道: a
是一个整型变量, 地址为$204$. 所以它可以跑到$204$这个地址, 向以$204$为起始地址的$4$字节写入5
这个值.
实际上, 计算机中数值以2
进制存储, 不过为了便于理解这里直接用10
进制表示了.
如果后续还有对a
的操作, 例如a ++;
. 同样的, 计算机看到这条语句时: a
自增, 它会再次去查找表, 找到a
的地址, 然后修改地址中存放的值.
指针 pointer
那么在程序中, 我们能否知道一个变量的地址呢? 或者我们能否通过这些内存地址进行操作呢?
答案是可以, 我们可以在C
或者C++
程序中使用指针pointer
来实现上述操作.
- 指针: 一个变量, 存储另一个变量的地址.
pointer
: variable that stores address of another variable.
假设现在我们有一个int
类型的指针变量p
, p
可以存储a
的地址$204$. 这个时候通过对p
进行一些操作
我们可以访问a
.
因为p
也是一个变量, 需要一定的内存存储(具体大小与机器位有关, 一般我们都是使用64
位机器, 指针的大小是$64\;bit = 8Bytes$). 本例中假设p
的地址为$64$.
p
是一个变量, 它存储的地址是可以改变的. 也就是我们可以修改p
使其可以指向其他整型变量.
C
中的指针语法
C
中一个普通变量在声明时需要给出它的数据类型和变量名, 所以int a;
表明我们声明了一个
int
型变量a
; 如果我们想声明一个指针变量, 我们需要做的仅是在它的前面加一个*
:
int *p
—p
是一个指针变量, 指向一个整型. 也即p
是一个可以存放整型变量地址的变量.
为便于理解, 可以把(
*p
)看作一个整型变量;p
是一个指向整型的指针变量.
为了在p
中存储a
的地址, 我们需要使用语句:
p = &a;
当我们把&
放在一个变量前面, 可以得到对应变量的地址. 也即返回一个指向变量的指针.
在本例中执行上述语句后, p
存储a
的地址$204$.
指针与变量
假设a
的值为p
, 观察下述输出:
-
Print p
— $204$,p
存储的地址. -
Print &a
— $204$,&
返回a
的地址. -
Print &p
— $64$,p
本身也是一个变量, 它存储在地址为64
的内存中. -
Print *p
— 5, 当我们在指针前加上一个*
时, 我们会得到存储在指针指向地址的值.操作
*p
被称为解引用dereferencing
. 指针也可被称为对变量的引用, 解引用通过指针操作变量, 可以认为是引用的逆向操作.
实际上, 我们可以通过解引用修改指针指向变量的值. 例如执行语句*p = 8
, *p
表示地址位于p
的值
(value at p
), 这个过程和a = 8
是一样的, 不过计算机不用去查找表找到a
的地址, 而是通过p
的
值得到a
的地址. 此时我们执行Print a
, 会得到8
.
p
—address*p
—value at address p