5.3 指针与地址之间的关系
在C语言中,我们所说的地址就是计算机中内存单元的编号,通过这个编号,我们可以访问相应的内存单元。就像学生宿舍一样,每个宿舍都有一个编号,通过编号可以找到相应的宿舍。在此之前已经讲过指针,它的本质就是地址,也就是一个地址编号,它指向一片连续的内存单元,就好比安排同一个班级的学生在相连的宿舍中,只需要知道安排宿舍的多少和安排的第一个宿舍号就可以逐一找出班级所有的学生。相信读者对和&已经不再陌生了,可以作为定义指针时的形式说明符和取出指针变量保存的地址所指向的内存单元的值。我们可以通过*结合地址的方式来访问内存单元中的数据并存入数据;而&是取地址运算符,通过它得到变量的地址。接下来通过下面的一段代码来演示如何通过地址取出该地址单元中的内容。
include<stdio.h>
void main()
{
int a;
a=9;
printf("a=%d\n",*(&a));
return;
}
运行结果:
a=9
在代码的开始部分,申请了一个int的存储单元,之后在该单元中放入一个整数9,为了演示上面所说的指针与地址间的关系,接下来通过&a取得变量a在内存中的编号,也就是地址,然后通过来得到地址单元中的内容。通过打印输出可以发现,取出的该单元的值就是在开始部分对该单元赋的值,所以可以通过结合地址的方式来访问内存单元。接下来通过下面的代码来了解指针变量与地址之间的关系。
include<stdio.h>
void main()
{
int a;
int*pa;
a=12;
pa=&a;
printf("指针变量pa的地址:%d\n\n",&pa);
printf("int型变量a的地址为:%d\n",&a);
printf("int型指针的pa的值为:%d\n\n",*(&pa));
printf("a的值为:%d\n",a);
printf("int型指针pa所指的内存单元的值为:%d\n",*pa);
return;
}
运行结果:
指针变量pa的地址:1245048
int型变量a的地址为:1245052
int型指针的pa的值为:1245052
a的值为:12
int型指针pa所指的内存单元的值为:12
可以通过图5-5演示上面的运行结果,指针变量同样有一个内存地址,将变量a的地址存放到int型指针pa中,即int指针pa指向变量a在内存中的存储单元,所以通过*pa来打印pa所指内存单元的值时得到的是变量a的值。
图 5-5 int型指针pa与变量a地址间的关系
使用不同类型的指针指向同一片内存单元的首地址,由于不同类型的指针所指向内存单元的长度不同,所以尽管它们指向同一个起始地址,但是其指向的内存单元的值并不相同。为了便于理解,我们在此给出一段代码加以分析。
include<stdio.h>
void main()
{
int a;
short*pa=NULL;
char*pb;
a=0x12345678;
pa=(short*)&a;
pb=(char*)&a;
printf("short类型占用的内存大小为:%d\n",sizeof(short));
printf("char类型占用的内存大小为:%d\n\n",sizeof(char));
printf("int型指针的pa的值为:%x\n",pa);
printf("int型指针的pb的值为:%x\n\n",pb);
printf("int型指针的pa所指向内存单元的值为:%x\n",*pa);
printf("int型指针的pb所指向内存单元的值为:%x\n",*pb);
return;
}
运行结果:
short类型占用的内存大小为:2
char类型占用的内存大小为:1
int型指针的pa的值为:12ff7c
int型指针的pb的值为:12ff7c
int型指针的pa所指向内存单元的值为:5678
int型指针的pb所指向内存单元的值为:78
可以通过图5-6来演示上面的运行结果,pa和pb都指向变量a在内存中的首地址,但是它们所指向的内存单元的值并不一样,这是因为定义的pa和pb属于不同的指针类型,由代码中的sizeof()操作符求得的short型和char型在内存中所占的大小可知,定义的short类型指针变量pa在内存中占有2字节的大小,输出的是从变量a的起始地址开始的2字节的内容,而char类型指针变量pb在内存中占有1字节的大小,输出的是从变量a的起始地址开始的1字节的内容。
图 5-6 不同类型指针间的内存结构
值得注意的是,定义的指针变量可以进行自加和自减运算,而地址的值是一个常量,不能进行自加和自减运算。
在使用指针变量进行运算时要特别留意运算的优先级,因为这样的错误在编译的过程中不会出现任何警告信息,这样会大大增加查错时间。接下来通过下面的一段代码加以分析。
include<stdio.h>
void main()
{
int a[9];
int*pa;
int i;
for(i=0;i<9;i++)
{
a[i]=i+1;
}
pa=a;
for(i=0;i<9;i++)
{
printf("a[%d]=%d\t",i,*pa++);
if((i+1)%3==0)
printf("\n");
}
return;
}
运行结果:
a[0]=1 a[1]=2 a[2]=3
a[3]=4 a[4]=5 a[5]=6
a[6]=7 a[7]=8 a[8]=9
在程序中定义了一个int型数组,其长度为9,分配在一片连续的内存区域中,同时定义了一个int型的指针,它指向数组a在内存中的首地址,然后通过指针pa间接地取出内存中数组元素的值。值得注意的是,执行pa++运算时先取出pa所指内存单元的值,然后将指针移动到下一个位置,切记不可将其写为(pa)++。这里暂且不讨论这种做法的对与错,先通过下面一段代码来一窥究竟。
include<stdio.h>
include<stdlib.h>
void main()
{
int a[4];
int*pa;
int*pb;
int i;
printf("\n通过数组a来直接打印其中的元素值\n");
for(i=0;i<4;i++)
{
a[i]=rand()%100;
printf("a[%d]=%d\t",i,a[i]);
}
pa=a;
printf("\n通过int型指针pa来间接打印数组a中的元素值\n");
for(i=0;i<4;i++)
{
printf("a[%d]=%d\t",i,*pa++);
}
pb=a;
printf("\n通过int型指针pb来间接打印数组a中的元素值\n");
for(i=0;i<4;i++)
{
printf("a[%d]=%d\t",i,(*pb)++);
}
return;
}
运行结果:
通过数组a来直接打印其中的元素值
a[0]=41 a[1]=67 a[2]=34 a[3]=0
通过int型指针pa来间接打印数组a中的元素值
a[0]=41 a[1]=67 a[2]=34 a[3]=0
通过int型指针pb来间接打印数组a中的元素值
a[0]=41 a[1]=42 a[2]=43 a[3]=44
从上面的运行结果发现,两种间接打印出来的数组元素的值并不相同。现在来分析一下,pb指向的是数组a的首地址,所以通过(pa)得到的是数组元素a[0],可以将(pb)++等价转换为a[0]++,从而轻易地发现出错的原因,使用(*pb)++这样的方式并不能够间接引用数组a中的元素。
在使用指针变量进行运算的时候,要清楚地知道参与运算后的指针变量的值是否改变,如果所使用的指针发生了变化,却还是按照之前的方式来引用,那么就会发生不可预测的错误,因此对于指针变量的值是否发生变化要有一个清楚的认识。看看下面的代码:
include<stdio.h>
include<stdlib.h>
void main()
{
int a[4];
int i;
int*pa;
int*pb;
for(i=0;i<4;i++)
{
a[i]=i*2;
}
printf("数组a的首地址为%d\n",a);
pa=a;
for(i=0;i<4;i++)
printf("a[%d]=%d\t",i,*(pa+i));
printf("\n通过*(pa+i)间接引用数组a中元素之后pa的值为:%d\n",pa);
pb=a;
for(i=0;pb<pa+4;)
printf("a[%d]=%d\t",i++,*pb++);
printf("\n通过*pb++间接引用数组a中元素之后pb的值为:%d\n",pb);
return;
}
运行结果:
a[0]=0 a[1]=2 a[2]=4 a[3]=6
通过*(pa+i)间接引用数组a中元素之后pa的值为:1245040
a[0]=0 a[1]=2 a[2]=4 a[3]=6
通过*pb++间接引用数组a中元素之后pb的值为:1245056
从上面的运行结果发现,采用两种引用方式之后指针变量的值并不相同,这是因为在前一种引用方式中始终没有移动指针变量的位置,pa始终指向数组元素a的首地址,所以在用pb引用数组中的元素时,在for循环中巧妙地利用pa变量建立一个是否执行循环体的判断条件。而pb指针变量进行自加运算相当于pb=pb+1,所以pb指针变量的值每次都在改变,使最终退出循环之后pb已经不再指向整型数组a在内存中的存储单元,这时如果使用printf打印它所指向的内存单元的数据,就会发现其中存储的是一些随机数。