理解C语言指针
当我们声明一个变量或常量时,计算机系统会为这个变量或常量分配存储单元,变量的数据存储到被分配的存储单元内,对变量的赋值和取值操作都是针对存储单元的操作。
C编译器是如何通过变量找到与其对应的存储单元呢?实际上变量是被分配单元内存地址的名称,对开发者来说,记住一个变量名称要比记住用十六进制表示的内存单元地址更加简单,因此变量是内存单元地址的名称表示,开发者可以通过变量名来访问内存单元中的数据。下图给出了变量、数据和内存地址之间的关系。

我们知道“&”运算符是获取变量的内存地址,下面做一个实验,连续声明三个整型变量a、b、c,然后使用“&”运算符取出变量a、b、c的内存地址并输出到控制台。
#include <stdio.h>
void main()
{
int a = 20,b=10,c=0;
printf("变量a的内存地址:%d\n",&a);
printf("变量b的内存地址:%d\n",&b);
printf("变量c的内存地址:%d\n",&c);
}
输出结果如下:

由于内存对齐的原因,实际每个int占用了12个字节的存储空间,内存对齐主要是为了快速访问内存的数据。从输出结果图可以看出,变量a、b、c指向的存储空间连续占用了36个字节的内存。
C语言把变量的内存地址作为一种可处理的数据,称为指针值,以内存地址为值的变量称为指针变量,简称指针(pointer)。使用指针变量可以保存变量等程序对象的地址,通过它们就可以访问和处理变量等有关对象。
例如:a是一个整数类型的变量,a的值为0x20,变量a的内存地址为0xaffe,变量ptr存储变量a的内存地址0xaffe,则称ptr为指针变量,通过ptr可以直接存取变量a的值。

指针变量ptr名称前面带有“*”符号,表示这是一个指针变量,它存储的是其它变量或程序对象的内存地址。由于指针变量存储了其它变量或程序对象的地址,因此通过指针变量可以访问或修改它们的内容。
和其它变量相同,在声明指针变量时,也需要指定一个数据类型,即存储该数据类型变量的地址。例如前面的指针变量ptr只能存储int数据类型变量的地址,而不能指向其它数据类型的变量。
在C语言中,声明指针变量的语法为:
datatype *pointerName;
其中,datatype是数据类型,pointerName是指针变量的名称。符号“*”不是pointerName的一部分,它只是告诉编译器这是一个指针变量,用于存储datatype类型变量的地址。
例如:
// 声明一个存储整型变量地址的指针变量prt
int *prt;
// 声明一个存储浮点型变量地址的指针变量fprt
float *prt
// 可以在一行语句中声明多个同类型的指针变量
int *p,*ptr;
声明指针变量后,需要初始化和赋值后才能使用,否则会出现意想不到的错误,因为指针变量可以直接对内存进行操作。当声明指针变量时,若暂时不能确定指针变量存储的内存地址,可以将指针变量设置为null:
int *ptr = NULL;
NULL是C语言的一个宏定义,宏定义类似符号常量的定义,都是使用define关键字来定义。NULL表示一个0指针,即不存储任何内存地址的指针。
指针的操作
指针变量是一个特殊的变量,它用于存储其它变量或程序对象的内存地址。声明指针变量后,开发者可以使用“&”运算符取出其它变量的内存地址,赋值给指针变量。
例如:
int a = 20;
int *p;
p = &a;
代码段声明了整型变量a和指针变量p,整型变量a初始化为20,指针变量p在声明时没有初始化,当前p的值为随机的内存地址,p在初始化之前是不能被使用的,否则会出现意想不到的错误。代码段的第3条语句使用“&”运算符取出变量a的内存地址,赋值给指针变量p,此时指针变量p的值为变量a的内存地址。下图描述了指针变量和变量a的关系。

“&”运算符用于取出变量或程序对象的内存地址,它是一个单目运算符,返回操作数的内存地址,可以赋值给类型合适的指针。
指针变量p被赋值后,它存储了变量a的内存地址,当前p和变量a都指向了同一内存地址,下面的赋值语句是效果相同的代码:
int temp;
temp = a;
temp = *p;
变量a赋值给temp,temp的值为20,我们重点看第2条赋值语句,*p是取出指针变量p指向的内存地址中保存的数据,因为p保存了变量a的内存地址,因此相当于把变量a再次赋值给了temp。
“*”运算符也是单目运算符,用于获取给定地址(指针变量保存的内存地址)中保存的数据。“&”和“*”运算符与其它单目运算符的优先级相同,自右向左结合。
使用“*”运算符也可以把数据保存到指针变量指向的内存地址。例如:
*p = 20;
指针变量p指向的内存地址保存的数据为20.
例1: 指针变量的间接赋值
编程要求:声明一个整数变量x,一个整数类型的指针变量p,取出变量x的地址赋值给指针变量p,将整数20保存到p指向的内存地址,输出变量a的值。
程序清单 sample.c
ude<stdio.h>
void main()
{
int x;
// 变量x的内存地址赋值给指针变量p
int *p = &x;
// 整数20保存到p指向的内存地址
// *p = 20 等价于 x = 20
*p = 20;
// 输出x的值
printf("x=%d",x);
}
案例1演示了指针变量间接给变量x赋值的作用,指针变量p的值为变量x的内存地址,*p = 20语句将整数20赋值给p指向的内存地址,p指向的内存地址即为变量x的内存地址,因此变量x被赋值为20。
例2:指针变量参与运算
编程要求:分别声明浮点变量x和y并初始化,声明两个浮点类型的指针变量px和py,变量x的地址赋值给px,变量y的地址赋值给py,使用px和py计算变量x和y的乘积,乘积结果赋值一变量,并输出该变量的结果。
程序清单 sample.c
#incl#include<stdio.h>
void main()
{
float x=3.1,y=2.6;
// 变量x和y的内存地址赋值给指针变量px和py
float *px = &x,*py=&y;
// 计算*px和*px的乘积
float result = *px * *py;
// 输出x的值
printf("x*y=%.2f",result);
}
案例2演示了指针变量取值和参与运算的使用方法,*px是取出px指向内存地址的数据,因为px指向变量x的内存地址,因此*px取出的数据为3.1,同理*py取出的数据为2.6.
小结:当一个指针变量和变量A的内存地址绑定后,这个指针变量实际就是变量A的一个引用,对指针变量的赋值和取值操作都是针对变量A进行的。
指针的运算
指针变量可以进行赋值运算、加减算术运算和关系运算。下图描述了C语言指针的赋值和加减算术运算。

赋值运算
指针变量可以把其值赋值给指向相同类型的另一个指针变量。如指针变量A赋值给指针变量B后,指针变量A和B会指向同一个内存地址。
指针变量赋值代码段:
int a,*p1,*p2;
p1 = &a;
p2 = p1;
变量和指针变量可以在同一行语句中声明,指针变量需要在名称前面加“*”。指针变量p1的值为变量a的内存地址,p2 = p1赋值语句将p1的值赋值给p2,此时p1和p2都指向变量a的内存地址。
加减算术运算
指针变量存储的是其它变量或程序对象的内存地址,内存地址实际上是一个整数。下面的代码段输出了变量a的内存地址。
float a=3.1f;
printf("变量x的内存地址=%d\n",&a);
输出结果:

既然内存地址是一个整数,指针变量存储的是内存地址,指针变量自然就支持整数的算术元素和关系运算。指针变量使用的算术运算符主要是加法、减法、自增和自减运算符。
例3:指针变量的加减算术运算
程序清单 sample.c
#include <stdio.h>
void main()
{
int a=30,b=20;
int* pa = &a;
printf("变量a的内存地址=%d\n",&a);
printf("变量b的内存地址=%d\n",&b);
printf("*pa=%d\n",*pa);
// 指针变量的值做加3运算
pa=pa-3;
// 输出指针变量指向内存地址的数据
printf("*pa=%d",*pa);
}
例3演示了指针变量的加法运算,语句pa=pa-3将指针变量pa存储的内存地址减去12个字节,再赋值给pa。赋值给pa是赋值给pa保存的内存地址,而不是pa本身的内存地址。有同学可能会问,运算是减去3,怎么会是减去12个字节呢?指针变量的算术运算单位不是字节,而是指针指向的数据类型所占用的存储空间,pa指针是int类型的指针,int类型在32位操作系统中占4个字节。
pa-3的目的是让指针变量pa指向变量b的内存地址,在《理解C语言的指针》一节谈到了C编译器会为变量a和b分配连续的存储空间,由于字节对齐的缘故,实际分配到变量a和b的存储空间为12个字节,而不是4个字节,但指针的运算是按照数据数据类型占用的存储空间来计算的。
例3的输出结果:

从输出结果可以看出,变量a和变量b的内存地址相差12个字节,变量a的内存地址减去12个字节正好是变量b的内存地址,因此最后一条printf语句正确输出了变量b的值。
试想一下,如果对指针变量做pa=pa-2运算,pa不会指向变量b的内存地址,而是指向一个我们无法预知的内存地址,最后一条printf语句会输出错误的数值。
指针变量还支持关系运算符,使用关系运算符,可以判断两个指针变量内存地址的关系。
例4:指针变量的关系运算
程序清单 sample.c
#include<stdio.h>
void main()
{
int *ptr1,*ptr2;
int value = 10;
// 变量value的地址赋值给ptr1
ptr1 = &value;
// value做加1操作
value = value+1;
// 变量value的地址赋值给ptr2
ptr2 = &value;
// 判断两个指针变量的值是否相等
if( ptr1 == ptr2 )
{
printf("\n两个指针指向同一个地址\n");
}
else
{
printf("\n两个指针指向不同的地址\n");
}
}
例4演示了指针变量的关系运算,指针变量ptr1和ptr2分别被赋值为变量value的内存地址,然后判断两个指针的值是否相等,由于ptr1和ptr2都指向变量value的内存地址,因此ptr1的值和ptr2的值相等。