若有一个变量专门用来存放另一个变量的地址,那么就称它为“指针变量”。也就是说,指针变量里面存放的是指针,即地址。
大家一定要区分“指针”和“指针变量”这两个概念。指针是一个地址,而指针变量是存放地址的变量。
习惯上我们也将“指针变量”简称为“指针”,但大家心里一定要明白这两个指针的区别。一个是真正的指针,它的本质是地址;而另一个是指针变量的简称。
为了表示指针变量和它所指向的变量之间的联系,在程序中用“*”表示“指向”。如果定义变量 i 为指针变量,那么 *i 就表示指针变量i里面存放的地址所指向的存储单元里面的数据。
C 语言规定所有变量在使用前必须先定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是专门用来存放地址的,所以必须将它定义为“指针类型”。
指针变量定义的一般形式为:
比如:
int *i;
float *j;
“*” 表示该变量的类型为指针类型。指针变量名为 i 和 j,而不是 *i 和 *j。
在定义指针变量时必须指定其基类型。指针变量的“基类型”用来指定该指针变量可以指向的变量的类型。比如“int*i;”表示 i 只可以指向 int 型变量;又比如“float*j;”表示 j 只可以指向 float 型变量。
换句话说,“基类型”就表示指针变量里面所存放的“变量的地址”所指向的变量可以是什么类型的。说得简单点就是:以“int*i;”为例,“*”表示这个变量是一个指针变量,而“int”表示这个变量只能存放 int 型变量的地址。
为什么叫基类型,而不直接叫类型?因为比如“int*i;”,其中 i 是变量名,i 变量的数据类型是“int*”型,即存放 int 变量地址的类型。“int”和“*”加起来才是变量i的类型,所以 int 称为基类型。
“int*i;”表示定义了一个指针变量 i,它可以指向 int 型变量的地址。但此时并没有给它初始化,即此时这个指针变量并未指向任何一个变量。此时的“*”只表示该变量是一个指针变量,至于具体指向哪一个变量要在程序中指定。这个就跟定义了“int j;”但并未给它赋初值一样。
因为不同类型的数据在内存中所占的字节数是不同的,比如 int 型数据占 4 字节,char 型数据占 1 字节。而每个字节都有一个地址,比如一个 int 型数据占 4 字节,就有 4 个地址。那么指针变量所指向的是这 4 个地址中的哪个地址呢?指向的是第一个地址,即指针变量里面保存的是它所指向的变量的第一个字节的地址,即首地址。因为通过所指向变量的首地址和该变量的类型就能知道该变量的所有信息。
指针变量也是变量,是变量就有地址,所以指针变量本身也是有地址的。只要定义了一个变量,程序在运行时系统就会为它分配内存空间。但指针变量又是存放地址的变量,所以这里有两个地址大家一定要弄清楚:一个是系统为指针变量分配的地址,即指针变量本身的地址;另一个是指针变量里面存放的另一个变量的地址。这两个地址一个是“指针变量的地址”,另一个是“指针变量的内容”。
地址也是可以进行运算的,我们后面会学到指针的运算和移动。比如“使指针向后移 1 个位置”或“使指针加 1”,这个 1 代表什么呢?这个 1 与指针变量的基类型是直接相关的。指针变量的基类型占几字节,这个 1 代表的就是几。比如指针变量指向一个 int 型变量,那么“使指针移动 1 个位置”就意味着移动 4 字节,“使指针加 1”就意味着使地址加 4。所以必须指定指针变量所指向的变量的类型,即指针变量的基类型。某种基类型的指针变量只能存放该种基类型变量的地址。
我们前面讲过两个指针变量相减的结果是一个常量,而不是指针型变量。如两个“int*”型的指针变量相减,结果是 int 型常量。此时要是把相减的结果赋给“int*”型就会报错。而且两个指针变量相减的结果是这两个地址之间元素的个数,而不是地址的个数。
比如说,两个“int*”型的指针变量相减,第一个指针变量里面存放的地址是 1245036,第二个指针变量里面存放的地址是 1245032,那么这两个地址相减的结果是几?是 1,而不是 4。因为 int 型变量占 4 字节,所以一个 int 元素就占 4 字节,两个地址之间相差 4 个地址,正好是一个 int 元素,所以结果就是 1。下面写一个程序验证一下:
# include <stdio.h>
int main (void)
{
int *p, *q;
int k; //k用来存放两个地址数相减的结果
int i = 3, j = 4;
p = &i;
q = &j;
k = p-q;
printf("p = %d\nq = %d\nk = %d\n", p, q, k);
return 0;
}
输出结果是:
p = 1245040
q = 1245036
k = 1