指针.docx
《指针.docx》由会员分享,可在线阅读,更多相关《指针.docx(23页珍藏版)》请在冰豆网上搜索。
指针
第八章指针
指针是C语言中一个重要的概念,是C语言的精华所在。
正确灵活地运用好指针,可以设计出简洁、紧凑、高效的C程序,能够处理各种较复杂的问题,这对设计系统软件是十分必要的。
学习C语言,必须掌握指针,否则等于没有学习C语言。
指针的概念比较复杂,应用类型教多,使用非常灵活,因此初学时应十分注意。
学习指针主要要点是:
a)正确理解指针的概念
b)掌握使用指针的方法
8.1指针的概念
一.变量的地址
要清楚指针的概念,首先必须弄清楚数据在内存中是如何存储和读取的。
计算机存储器的存储单元是以字节为单位进行线性编址(庞大的存储空间如何管理),每一个存储单元为一个字节,对应有一个地址(相当于宿舍的房间号)。
存储器的地址从0开始编号,顺序地每隔一个单元地址加1。
C语言一个程序的指令及数据都存放在存储器内,即在地址所标志的单元存放(相当宿舍各房间住学生一样)。
数据所占用存储空间取决于机器种类和数据类型,如在16位机器中,int型数据占用2个字节,float型数据占用4个字节,double型数据占用8个字节,char型数据占用一个字节。
请注意区别一个内存单元的地址和内存单元的内容是两个不同的概念。
在C中定义的变量,编译时系统根据该变量的
类型分配相应的内存单元,如:
inti,j;floatx;系统
分配给变量i两个存储单元,假设地址为1000、1001。
变量j分配1002、1003两个单元,变量x分配1004、
1005、1006、1007四个单元。
此时,i,j和x变量的
地址分别为1000、1002和1004,即变量的地址是指
该变量所占用存储单元的第一个单元的地址。
当程序执行时,对变量的存取操作是通过该变量对应的内
存地址来实现的,内存中没有i,j,x这些变量名,变量名是高级语言的产物。
如:
执行i=j+3;是找到j的地址1002,然后从1002开始的两个字节中读出数据(即j的值),与3相加后,送往地址为1000开始的两个字节的存储单元中(即存放i的值)。
二.变量的存取方式
以上已述,变量的存取是通过地址进行的。
根据该地址得到的方式(寻址方式),有以下基本两种:
1.直接存取方式
直接存取方式是按变量的地址存取变量的值,或称为直接访问方式,如:
scanf(“%d”,&x);y=x+10;
将从键盘上输入的值,送到变量x所对应地址的存储单元。
2.间接存取方式
可以通过间接访问方式对变量的值进行存取,该方法的思路是:
将一个变量的地址存放在另一个特殊变量中,这样就建立了这个变量与另一个特殊变量的联系,如定义一个特殊变量i_p(它被分配两个字节),它用来存放变量i的地址。
可通过语句i_p=&i;将i的地址存放在变量i_p中,此时i_p的值为1000,即变量i所在占用单元的起始地址。
要存取变量i的值,可先找到存放i的地址单元(3000,3001),从中取出i的地址(1000),然后到1000、1001字节单元取i值。
对一个变量存取单元的访问,可采用直接访问和间接访问两种形式,以打开一个抽屉为例。
三.指针与指针变量
从间接存取方式可知,变量i_p和变量i之间存在一种联系,即通过变量i_p可知道变量i的地址,找到变量i的内存单元,变量i_p保存对象变量i的地址。
一个变量用于存放其他变量的地址,这种变量被称为指针变量。
一个变量的地址称为该变量的指针。
“指针”和“指针变量”是两个不同的概念,指针变量本身也是一种变量,它用来存放指针值,即存放对象变量的地址。
而指针是地址,它是一个常量。
指针变量的值是指针。
例如:
变量的地址是1000,可以说变量i的指针是1000,而不能说变量i的指针变量是1000,而应说指针变量i_p的值是1000。
指针变量的值只允许取整数值,合法指针变量的值应当是非0值。
若指针变量的值为0,即值为NULL,表示指针变量所指向的对象不存在。
8.2指针变量的定义和引用
一.指针变量的定义
C语言规定,对每一个变量都必须先定义后使用。
指针变量也遵循这一规则,指针变量不同于其他类型的变量,必须将它定义为“指针类型”,定义的一般形式为:
类型标识符*变量标识符
例:
int*p1,*p2;float*p3;
上面定义了三个指针变量,p1,p2是指向整型变量的指针变量,p3是指向实型变量的指针变量。
注意:
1)标识符前面的“*”表示该变量为指针变量,它的含义是“指向……的指针”,这是指针变量特殊的定义标志。
但指针变量名是p1,p2,p3,不是*p1,*p2,*p3,这与一般变量的定义不同。
2)一个指针变量只能指向同一类型的变量。
因此必须规定指针变量所指向变量的类型。
3)在定义指针变量时,可以对其进行初始化,如:
intx,y;int*px=&x,*py=&y;。
这样初始化必须先定义变量x,y。
二.指针运算
在C语言中,指针有两个专门的运算符:
(单目运算)
&——取地址运算符
*——指针运算符(间接访问运算符)
1.取地址运算
在指针变量定义中对指针的类型作了说明,但指针与其指向的对象还未建立联系。
运算符“&”可用来实现给出运算对象的地址,其一般格式为:
<指针变量>=&<对象变量>;
如:
pa=&a;将变量a的地址赋给指针变量pa。
需指出,取地址运算符“&”只能作用于变量或数组元素,而不能作用于表达式、常量、寄存器。
2.取内容运算
将指针变量所指向变量的内容取出,可用运算符“*”来实现,其一般格式为:
*<指针变量>
作用是代表指针变量所指向的对象变量的值。
如:
intx=10,y;int*px;
px=&x;
y=*px;
printf(“%d,%d”,y,*px);
取内容运算是使用指针的基本手段,它与取地址运算互为逆运算,即“*”与“&”的作用相反。
“&”作用返回操作数的地址,“*”作用返回这个地址中变量的值。
3.算术运算
指针变量可以进行有限的算术运算,如增量、减量、加减运算。
a)指针的增量、减量运算的含义与整型变量的运算不完全相同,如p为指针变量,则p++表示使指向其基本类型的下一个元素的地址,即p的值增加n,这个n称为比例因子,它等于对象变量在内存中存放的长度,p—则表示使其指向前一个元素的地址。
b)两个指针可以进行减法运算,如两个指针指向同一个数组元素时,这两个指针相减的结果为两者之间的元素个数,利用此方法可测出字符串的长度。
c)指针变量与整型变量可以进行加减运算,若p是指向整型数组的起始地址,则p+n表示数组中的第n个元素的地址,即&array[n]。
但在具体应用中,n值是有一定限制的,应保证加减后指针在数组的存储空间内。
在C中,不允许两个指针相加、相乘、相除、移位等运算。
三.指针变量的使用
指针变量的使用应注意:
a)指针变量只能存放对象变量的地址。
b)运算符“&”和“*”的含义。
c)指针的指向问题,指针变量的值应该是有意义的,不能对不确定的指针进行操作。
例:
输入a和b两个整数,按大到小顺序输出。
main()
{int*p1,*p2,*p,a,b;
scanf(“%d,%d”,&a,&b);
p1=&a;p2=&b;
if(*p1<*p2)
{p=p1;p1=p2;p2=p;}
printf(“%d,%d”,*p1,*p2);
}
例中数据的交换是指针变量p1和p1的值,而a和b并未交换。
四.指针变量作为函数参数
在函数调用过程中,函数的参数可以是指针类型,它的作用是将一个变量的地址传送到另一个函数中。
例:
对输入两个整数大到小顺序输出
swap(p1,p2)main()
int*p1,*p2;{inta,b,*pa,*pb;
{inttemp;scanf(“%d,%d”,&a,&b);
temp=*p1;pa=&a;pb=&b;
*p1=*p2;if(a
*p2=p;printf(“%d,%d,”a,b);
}}
说明:
①指针变量作为函数参数在函数调用时,实参将它的值传给形参,形参的指针变量与实参的指针变量是指向同一个变量。
因此函数中形参指针变量所指向对象发生改变,等同于主调函数中实参指针变量所指向对象的改变。
如上例中,函数swap中*p1和*p2值的交换,等同main函数中a和b的值交换。
②在函数调用中,指针变量作为函数参数的主要用途是,函数可以返回多个变化的值(这是传地址法的特点)。
因为在被调函数中可以改变实参指针变量所指向变量的值。
③不能通过改变指针形参的值来使指针实参的值也改变,因为指针变量作为函数参数传递,也是单向的“值传递”方式,函数返回后,形参的指针变量已消失。
8.3数组的指针
数组是类型相同的一组变量的有序集合,这些变量在内存中按下标顺序连续存放,每一个变量都有相应的地址,而指针的概念与此紧密相连,指针和数组自然联系在一起。
数组的指针是数组的起始地址,数组元素的指针是数组元素的地址。
一般说,能由数组下标完成的操作,也能用指针来实现。
而且后者比前者产生的目标代码占用的空间更小,运行速度更快。
在实际应用中,指针所指向的对象变量很少是基本类型,大多数是构造类型,而数组是构造类型的一种。
一.向数组元素的指针变量
指向数组的指针是能够指向数组中任一个元素的指针,这种指针变量应当说明为数组元素的类型,指向数组元素的指针变量的定义与前面相同。
如:
intarray[20];
int*pa;
可以使pa指向数组array中的任何一个元素,如:
pa=&array[2];
通常情况下,首先是使指针变量与数组的起始地址建立关连,而C语言中数组名代表数组的首地址。
使指针指向数组中首元素,可以用更简洁的方式:
pa=array;等价于pa=&array[0];
在定义指针变量时可以赋给初值,如:
int*pa=array;
二.通过指针引用数组元素
设pa为指针变量,并已赋数组的一个地址,指向某个数组元素,则指针变量可参加代表数组元素的运算操作,如:
a)赋值:
*pa=28;
b)参加运算:
x=*pa+6;
c)自增或自减:
pa++;pa--;
在操作时,指针和数组通常有三种对应方式:
一种是数组表示法(下标),一种是指针表示法,还有一种是位移量表示法(以数组名为首地址)。
三种方式之间的对应关系如下:
(数组a中第k个元素)
数组inta[]指针int*pa位移
内容:
a[k]*(pa+k)*(a+k)
地址:
&a[k]pa+ka+k
若:
int*pa=a;则:
pa+k和a+k就是a[k]的地址,*(pa+k)和*(a+k)是数组元素a[k]。
下面用不同的方式对数组元素进行引用:
例:
有10个整型数据,利用数组输出全部元素。
方式1:
(下标法)方式2:
(位移法)
main()main()
{inta[10],k;{inta[10],k;
for(k=0;k<10;k++)for(k=0;k<10;k++)
scanf(“%d”,&a[k]);scanf(“%d”,a+k);
for(k=0;k<10;k++)for(k=0;k<10;k++)
printf(“%4d”,a[k]);printf(“%4d”,*(a+k));
}}
方式3:
(指针法)
main()
{inta[10],k,*pa;
pa=a;
for(k=0;k<10;k++)
scanf(“%d”,pa+k);
for(pa=a;pa<(a+10);pa++)
printf(“%4d”,*pa);
}
上述第一和第二种方法执行效率是相同的,C编译程序总是将a[k]转换为*(a+k)处理,这种方法要计算元素的地址(变址计算),故用的时间较多。
而第三种方法用指针变量直接指向数组元素,不必每次都重新计算地址,故效率较高。
在数组操作时,使用指针变量应注意:
①指针变量可以使自身的值改变,如:
pa=a、pa++,因为pa是变量。
而数组名是常量,如:
a++p、a=&a、a=pa都是非法的操作。
②要注意指针变量的当前值,这是初学者容易出现的问题。
如:
main()
{inta[10],k,*pa;
pa=a;
for(k=0;k<10;k++)
scanf(“%d”,pa++);
for(k=0;k<10;k++)/*在之前增加语句pa=a;*/
{printf(“%4d”,*pa);pa++;}
}
结果是不正确的,问题是指针变量的当前值在输入循环时已经改变,它指向数组的末尾,因此在printf语句执行时,pa的初值不是数组第一个元素的地址。
可在第二个循环前使pa=a。
③指向数组元素的指针变量的值必须是有意义的,由于C对数组下标超界不作检查,因此若指针变量中的地址超出定义数组的大小范围,将会得出一个随机数。
④注意指针变量的运算,设:
pa=a;
pa++和pa—表示指针变量指向下一个元素和上一个元素
*pa++是先得到pa指向变量的值,然后使pa=pa+1
*(++pa)表示得到a[1]的值(pa的初值为数组首地址)
(*pa)++表示pa所指向的元素值加1,即a[0]++
指针变量运算十分灵活,如不注意,很容易出错。
三.函数调用时数组数据的传递
前面已经讲过,数组名代表数组的起始地址,可以作为函数的形参和实参。
学习了指针后,指针变量和数组可以建立关联,指针变量也可以作为函数的形参和实参,它们都是传地址方式。
这样数组在函数调用时,形参和实参的对应关系有以下四种情况:
1)形参和实参都用数组名,如:
main()fn(x,n)
{inta[10];intx[],n;
……{
fn(a,10);……
……}
}
2)实参用数组名,形参用指针变量,如:
main()fn(pa,n)
{inta[10];int*pa,n;
……{
fn(a,10);……
……}
}
函数调用时,将数组a的起始地址传给指针变量pa,相当于pa=&a[0]。
在函数fn中可以通过pa值的改变,实现对数组a中的任一个元素进行操作。
3)实参和形参都用指针变量,如:
main()fn(p,n)
{inta[10];int*pa=a;int*p,n;
……{
fn(pa,10);……
}
}
调用时,pa的值传给p,p的初始值是&a[0]。
通过改变p的值可对数组a的任一个元素进行操作。
注意函数返回后,指针变量pa的值并未改变。
4)实参为指针变量,形参为数组名,如:
main()fn(x,n)
{inta[10];int*pa=a;intx[],n;
……{
fn(pa,10);……
……}
}
在调用时,将pa的值传给形参数组名x,使x获得a数组的首地址,即x数组与a数组共同一段存储单元。
例1:
有20个整数,找出最大数和最小数,用函数和指针完成操作。
分析:
函数要返回两个值,只有通过指针变量来实现。
设置两个指针变量,作为函数的传递参数。
main()voidfind(p,p1,p2,n)
{voidfind();int*p,*p1,*p2,n;
staticintx[20]={23,47,11,……};{intk;
intmax,min,*pmax,*pmin;*p1=*p2=*p;
pmax=&max;for(k=1;kpmin=&min;{if(*p1<*p)*p1=*p;
find(x,pmax,pmin,20);if(*p2>*p)*p2=*p;
printf(“max=%d\n”,*pmax);p++;
printf(“min=%d\n”,*pmin);}
}}
例2:
有n个整数,使前面各数顺序向后移m个位置,最后m个数变成最前面m个数。
用函数和指针实现。
设m=3,n=8
原数组:
m=3
10
20
30
40
50
60
70
80
调整后:
60
70
80
10
20
30
40
50
分析:
数组中元素的移动,可借助于一个中间变量,其操作步骤为:
1)将数组的最后一个元素移到中间变量。
2)数组的n-1个元素依次向后移动,共移n-1次。
3)将中间变量的数据送到数组的第一元素。
程序结构应该是双重循环,外循环由m决定,内循环由n-1决定。
关键是用指针实现操作。
main()voidmove(p,k,l)
{voidmove();int*p,k,l;
staticinta[8]={10,20,……};{inti,j,temp;
intn=8,m,k;for(i=0;iprintf(“inputmovenumberm=”);{p=p+k-1;temp=*p;
scanf(“%d”,&m);for(j=0;jmove(a,n,m);{*p=*(p-1);p--;}
for(k=0;k<8;k++)*p=temp;
printf(“%5d”,a[k]);}
}}
课堂练习:
有30个实验数据,找出大于平均数的所有数据组成一个数组。
用函数和指针操作。
四.指向二维数组的指针和指针变量
二维数组的指针是在一维数组指针上的扩充,但二维数组的指针要复杂一些,关键是正确理解二维数组的性质。
1.二维数组的地址
理解二维数组的地址是掌握二维数组指针的基础。
以数组a[3][4]为例,它有三行四列,定义为:
staticinta[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
可理解为:
a是一个数组名,它包含三个元素,即a[0]、,a[1],a[2]。
而每个元素又是一个一维数组,它又包含4个元素,见下图表示:
aa[0]1357
a[1]9111315
a[2]17192123
按照C语言的规定,数组名代表数组的首地址,因此a代表整个二维数组的首地址,也是第0行的首地址,而a+1代表第1行的首地址,a+2代表第2行的首地址。
同理,a[0]、a[1]、a[2]也是一维数组名,它们分别代表各行中第0列元素的地址,即a[0]是&a[0][0],a[1]是&a[1][0],a[2]是&a[2][0]。
应注意:
a、a[0]、a[1]、a[2]不是实际的变量,它是数组地址的一种表示方法。
讨论几点:
①在二维数组中某个元素的地址如何表示,可用a[i]+j来表示。
这里是将a[i]作为一维数组名,因而可归结为一维数组某个元素的地址表示法,如:
a[0][2]的地址是&a[0][2]。
②在二维数组中地址的表示非常灵活。
如:
a+i与*(a+i)等价,a[i]+j与*(a+i)+j等价,注意到a、a[i]都是数组名,它们不是一个实际变量,所以谈不上它的内容,*(a+i)就是a[i],这是二维数组中地址的一种特殊表示。
因此*(a+i)并不是a+i单元的内容,因为取内容运算*必须有一个确定的变量。
③在二维数组中用地址法表示某个元素a[i][j]的值,可采用如下方式:
*(*(a+i)+j)。
因为*(a+i)+j是a[i][j]的地址。
2.二维数组指针
可用指针变量指向二维数组及其元素,在二维数组有两种方式的指针变量,即如下:
a)指向数组元素的指针变量
若p定义为指向数组元素的指针变量,则每次p加1,指针移向下一个元素(类似一维数组)。
它的定义和指向为:
inta[3][4];int*p;p=a[0];
利用该指针变量对数组中某个元素a[i][j]操作时,应先算出该元素在数组中的相对位置,计算公式为:
i*m+jm为二维数组的列数,则*(p+i*m+j)为元素a[i][j]的值。
b)指向由m个数组成的一维数组的指针变量
若p是指向数组行元素组成的一维数组的指针变量,设p先指向a[0],则p+1是指向a[1]。
p的增值以一维数组的长度为单位。
定义和指向为:
inta[3][4];int(*p)[4];p=a;
定义中,(*p)[4]表示p是一个指针变量,它指向包含4个元素的一维数组,即*p有4个元素。
而p是行指针,它只能指向包含4个元素的一维数组,而不能指向数组中的第j个元素。
若要用p来表示a[i][j]的地址,则应写成*(p+i)+j,而*(*(p+i)+j)则是元素a[i][j]的值。
应注意此时地址不能写成(p+i)+j,因为该形式是第i+j行的首地址。
而*(p+i)+j中,i是以一维数组的长度为单位来改变行地址,括弧外的j已不是与p的指向采用相同长度单位,而是以列元素的长度为单位。
3.二维数组的指针变量作函数参数
在函数调用时,二维数组的指针变量也可作函数参数,传递数组数据。
有两种方法:
a)用指向数组元素的指针变量
b)用指向一维叔祖的行指针变量
举例:
有一个4*4的矩阵,根据输入行,找出该行的最大元素。
(用函数和指针)
main()intmax(p,k)
{intmax();int(*p)[4],k;
staticinta[4][4]={12,36,……};{intmax,j;
intn,m;max=*(*(p+k)+0);
scanf(“%d”,&n);for(j=1;j<4;j++)
m=max(a,n);if(max<*(*(p+k)+j))
printf(“max=%d”,m);max=*(*(p+k)+j);
}return(max);}
8.4字符串的指针和指向字符串的指针变量
在字符串的操作中可使用指针,其基本方法同数值型数组类似。
一.符串指针变量
字符串的操作前面已学过用字符数组来实现,这里可用指针变量来实现,即定义一个指向字符数据的指针变量,它可处理单个字符,也可处理字符串。
如:
staticcharx[]=”CLanguage”;
char*px=x;
printf(“%s”,px+2);/*结果为:
Language*/
在C语言中,也可以不定义字符数组,而仅定义一个字符指针变量,用指针变量指向字符串的字符。
如:
char*px=”CLanguage”;也可写成:
char*px;px=”CLanguage”;
这里未定义字符数组,而仅定义一个字符指针变量px,C中对字符串常量是按字符数组处理的,在定义指针变量中,将字符串的首地址赋给指针变量px。
注意px不是一个字符串变量。
类似数值型数组,在字符串的操作中,可用指针变量指向串中的某一个字符,如:
px+k表示串中第k个字符的地址,而*(px+k)表示串中第k个字符。
二.字符串指针作函数参数
在函数调用时,可以采用地址传递方法,将字符数组名或指向字符串的指针变量作为参数,实现字符串的传递。
同数值型数组的函数调用类似,有四种传递方式:
实参数组名数组名指针变量指针变量
形参数组名指针变量指针变量数组名
三.字符指针变量与字符数组的区别
用字符指