C语言指针.docx
《C语言指针.docx》由会员分享,可在线阅读,更多相关《C语言指针.docx(38页珍藏版)》请在冰豆网上搜索。
C语言指针
第八章 指针
本章主要内容:
指针与地址 1课时
指针变量的使用 1课时
指针与数组 2课时
指针在函数中的使用 2课时
指针和字符串 1课时
指针数组与多级指针 1课时
指向函数的指针 1课时
重点、难点:
指针概念、指针与数组、函数、字符串的关系
上机试验:
4课时
第一节指针与地址
内存储器为程序的存放和执行提供场所,而内存储器中存储单元的组织是按照线性结构来实现。
为了能够正确的访问这些单元,必须为每个单元进行编号,每个存储单元的编号称为地址。
程序运行之后,系统根据存储对象的类型在内存中分配相应的存储空间。
在TurboC系统中,为char类型对象分配1byte,为int类型对象分配2byte,为float类型对象分配4byte,为double类型对象分配8byte。
除了char类型之外,其它类型的对象都要分配多个字节。
由于为它们所分配的空间都是连续的,因此用连续分配的第一个单元的地址作为该对象的内存地址。
例8.1 int a;
float b;
char c;
double d;
假设它们的空间分配如图8.1,他们的地址分别为:
3000,3002,3020,2040。
为了将存储单元的地址与变量之间的关系有一个清晰的认识,我们来看一个形象的例子:
假设办公楼2楼每个办公室都有一个门牌号,201为教务处处长办公室,202为教务处实验科办公室,203为教务处教学科办公室,……。
这些房间就相当于存储单元,而房间号相当于地址。
处长办公室,实验科办公室,教学科办公室,……就相当于变量名。
因而变量对应存储单元,而变量名是给存储单元取得符号名。
可见存储单元对应于一个地址,又对应一个变量名。
因此,对存储单元的访问,既可以通过变量名来实现(前面所介绍的一般是采用这种方式),又可以通过地址来访问。
这正是C语言有别于其他高级语言的特点之一。
而存储对象的地址又称为指向该对象的指针。
因此指针实际上是内存单元的地址码。
指针类型与其他数据类型一样,也有相应的变量——指针变量。
指针变量是用来存放其他对象的内存地址。
如:
int a;
int*p=&a;
p为指针变量,用来存放变量a对应的内存单元的地址。
对于a的存单元,既可以用变量名a访问,也可以用指针p来实现访问:
*p
此时*p和a的值一样,即p中存放a的地址,而*p代表a。
注意:
● int*p=&a;中“*”相当于一特殊类型说明符,说明p是指针型变量。
在定义p的同时,将系统为a所分配单元的地址赋给p(而不是给*p),在程序的其他地方只能用p=&a;
● 要区分指针与指针变量的概念:
指针是一地址码,为数据对象所分存储空间的首地址;而指针变量是一个数据对象,需要分配存储单元,其中存放的是地址码(即指针),并且只能存放地址,不能存放其他数据。
第二节指针变量的使用
一、指针变量的定义及赋值
指针变量与其他变量一样,都是变量,都需要通过定义来分配存储单元,然后才能使用。
当然也可以在定义的同时进行初始化。
指针变量定义的一般形式为:
基类型* 指针变量名;
例如:
int*p;
表示定义一个指针变量p。
定义中的“*”表示该变量为一个指针变量而不是其他类型的变量,而前面的基类型,不是指针变量p所存放的类型,而是p的值所指向变量的数据类型,如上例中,p所存放的指针所指向的类型为int。
注意:
在定义中的“*”虽和前面所介绍的类型标识符有同样的功能,但只对一个变量名有效,如果要定义多个指针变量,需要多个“*”。
如:
int*p1,p2,*p3;
表示定义了两个指针变量p1和p3,而p2为一整型变量。
在定义指针变量的同时,也可以初始化,其初值为基类型变量的地址。
如:
inta;
int *pa=&a;
在定义指针变量pa的同时将整型变量a的地址赋值给它。
注意:
✍ 要求在赋初值时,初值对应的变量必须先定义。
上例可以写成:
inta,*pa=&a;
但不能写成:
int*pa=&a,a;
✍ 要求初值对应变量的类型要与基类型相同。
即一个指针变量只能指向同一种类型的变量。
如:
int a;
float*p=&a;
这种初始化是错误的。
✍ 对指针变量初始化时,只能给他赋地址,而不能是其他类型的数据。
虽然地址是存储单元的地址码,是整数,但一般不能将整数作为初值。
因为程序中变量的地址只能由编译程序分配而不能人为指定。
如:
int *p=1000;/*错误赋值*/
但可以将0赋值给指针变量,表示该指针未指向任何对象,为空指针。
如:
float*fp=0;/*表示fp未指向任何float对象*/
与其他变量一样,指针变量除了初始化赋值之外,还可以在定义之后给它赋值,让它指向一个对象。
如:
int a,*p;
p=&a;
二、 指针的类型
对于不同的数据类型,其对象在内存中要占用不同数目的存储单元,并且其运行方法也完全不同。
如在TurboC下,int类型数据占2byte,float类型数据占4byte,他们的运算一种是整型运算,一种是浮点运算。
但对于指针变量来说,不管它指向的是什么类型的对象,他们的值都是地址,因而都占有相同数目的单元,在TurboC为2byte。
通过指针变量可以访问所指向的对象,进行操作与运算。
由于不同类型的数据占用的单元数不一样,进行的运算也不相同,因而也要对指向它们的指针变量进行区别,即指针变量也有类型。
指针变量的类型就是它所指向对象的类型。
如:
doube d=3.5;
double*dp=&d;/*dp为一个double类型的指针变量*/
一个指针变量要有相应类型,并且只能指向同一类型,如dp只能指向double类型的变量,而不能指向其他类型的变量。
注意:
要区分开指针变量的值和指针变量所指向变量的值。
如上例中,指针变量dp的值为变量d的地址,所指向变量的值为d的值,即为3.5。
三、 指针运算符
C语言提供了两种指针运算符:
取地址运算符(&)和引用目标运算符(*)。
1.取地址运算符(&)
取地址运算符是单目运算符,其结合性为从右→左,其功能是取变量的地址,使用格式为:
&左值
左值是具有内存单元的数据,如变量、数组元素及结构变量和联合中的数据成员都是左值,可以用“&”运算符取得它们的地址,因而&左值是一个指针表达式,可以用它来作为指针变量的值。
注意:
要区分开取地址运算符&与双目运算符&(按位与)。
2.引用目标运算符(*)
与取地址运算符“&”相对应的是引用目标运算符“*”。
该运算符也是单目运算符,结合性为从右→左,这种运算符只能作用在指针表达式的数据上。
其使用格式为:
*指针表达式
功能:
“*”运算符形成的表达式表示指针表达式所指向的对象,通过它可以完成对该对象的访问:
读和写。
例8.2 分析下面程序运行结果。
main( )
{
int n,*pi;
double d,*pd;
pi=&n;
pd=&d;
printf("pleaseinputnandd:
");
scanf("%d%lf",pi,pd);
printf("n=%d,*pi=%d\n",n,*pi);
printf("d=%f,*pd=%f\n", d,*pd);
}
程序运行结果为:
pleaseinputnandd:
23 76.8↙
n=23,*pi=23
d=76.800000,*pd=76.800000
通过scanf()函数中使用pi、pd实现对指向对象的赋值。
在printf()函数中通过*pi、*pd实现对变量n和d的输出(即变量的读操作)。
也可以在一个语句中同实现对指向对象的读和写操作,如:
int n=10,*pi=&n;
(*p)++;
首先通过*p取出变量n的值作为表达式的值,然后再将该值加1之后,又赋值给变量n。
注意:
✍ 在对指针变量使用“*”运算符时,要求指针变量已经指向了一个确定的对象。
也即对指针变量必须先赋值,再使用“*”运算符。
✍
(*p)++和*p++之间的关系:
(*p)++表示对p所指向的对象加1修改,p仍指向原来的对象;而*p++表示先通过p去访问所指向的对象,再让p指向下一个对象,它等价于*(p++);
✍ 几个等价关系:
假设有int a, *p=&a;则有如下等价关系:
① *p与a等价,等价于*&a;
② p与&a等价,等价于&*p;
例8.3 分析下面程序的运行结果:
main( )
{
int a=10, b=20;
int*pa,*pb,*p;
pa=&a,pb=&b;
printf("a=%d,b=%d,*pa=%d,*pb=%d\n",a,b,*pa,*pb);
p=pa; pa=pb;pb=p;
printf("a=%d,b=%d,*pa=%d,*pb=%d\n",a,b,*pa,*pb);
}
运行结果为:
a=10,b=20,*pa=10,*pb=20
a=10,b=20,*pa=20,*pb=10
指针值的变化过程如图8.2。
说明:
图中的“
”表示指针值所指向的对象,“ ”表示存储单元之间值的拷贝,本章所有图形中的这两种箭头均表示相同意思。
例8.4 分析下面程序的运行结果。
main( )
{
int a=10,b=20,t;
int*pa,*pb;
pa=&a,pb=&b;
printf("a=%d,b=%d,*pa=%d,*pb=%d\n", a, b,*pa,*pb);
t=*pa,*pa=*pb,*pb=t;
printf("a=%d,b=%d,*pa=%d,*pb=%d\n", a, b,*pa,*pb);
}
运行结果为:
a=10,b=20,*pa=10,*pb=20
a=20,b=10,*pa=20,*pb=10
通过指针交换所指对象的过程如图8.3。
四、指针常量
同其他类型一样,有变量就有常量。
在C语言中,指针常量有:
数组名、函数名、字符串常量等。
数组名代表该数组在内存中分配的存储区的首地址,指向数组中的第一个元素,实际上就是指针类型的数据。
由于数组分配空间之后面,直到其生命期结束之前不会重新分配空间,因而数组名为指针常量。
函数代码存放在代码区,在程序的整个运行期,它不会改变,而函数名正好代表函数在代码区的首地址,因而函数名也是指针常量。
如:
int a[100],*p;
a就代表数组的首地址,指向第一个元素a[0],而p为一般的指针变量。
对于数组名a和一般的指针变量p它们的区别:
✍ a是指针常量,不能修改它的值,它总是代表数组的首地址;而p为变量,可以重新赋值,即让它指向不同的对象。
✍ a的意义是代表一个能存放100个int类型的存储区;而p在赋值之前,未指向任何有意义的单元。
第三节 指针与数组
在C语言中指针与数组的关系非常密切。
一、 指针与数组名之间的关系
前面已经介绍过数组名代表整个数组空间的首地址,指向的是数组中的第一个元素,而指针也是地址,因此数组名与指针都具有完全相同的数据类型,这使得他们的运算都具有通用性。
对数组元素的访问,是通过数组名利用下标运算符“[]”来实现访问,即:
数组名[下标]
可以转化为:
地址[整数]
也即:
(指针表达式)[整数]
例8.5 分析下面程序的运行结果。
#define N 5
main( )
{
int a[N],*p,i;
for(i=0;i a[i]=i+10;
p=a;
for(i=0;i printf("%5d",a[i]);
printf("\n");
for(i=0;i printf("%5d",p[i]);
}
运行结果为:
10 11 12 13 14
10 11 12 13 14
由上例可知:
a[i]等价于p[i],说明指针变量也可以用下标方式来访问对应单元中的值。
二、 定义指向数组元素的指针变量
定义指向数组元素的指针变量与指向简单变量的指针变量完全一样。
首先定义与数组元素类型一致的指针变量,然后再将数组元素的地址赋给指针变量,最后通过指针变量来引用数组元素。
例8.6 分析下面程序的运行结果。
#define N 5
main( )
{
inta[N],*p,i;
for(i=0;i {
p=&a[i];
*p=i*2;
}
for(i=0;i {
p=&a[i];
printf("%5d",*p);
}
}
运行结果为:
0 2 4 6 8
通过指针引用数组元素实现了对数组元素的读和写操作。
三、 指针的运算
指针是一种特殊的类型,它是以指针变量所持有的地址作为运算对象进行运算。
除了前面介绍的“*”和“&”可以使用,还有指针的加、减、比较等运算,而这些运算主要与数组操作相关。
1.指针与整数的加减运算
一个指针可以加上或减去一个整数,包括加1、减1。
前面介绍的数组元素访问方式为:
a[i],C语言中,实际上是先将a[i]转换成*(a+i)的形式然后再去求值。
这表明a+i是数组中第i+1个元素的地址,i为数组元素a[i]相对于a[0]的偏移量。
一个地址加减上一个整数其结果仍为地址,并且加减的单位不是以字节为单位,而是以指向的数据类型所占用的字节数为单位。
如int指针,以2byte为单位,double指针,以8byte为单位。
因此,p+n表示的实际地址为(假设p指针的基类型为type):
p+n*sizeof(type)
例8.7 分析下面程序运行结果。
main( )
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p=a;
printf("ais:
%X,a+3is:
%X\n",a,a+3);
printf("pis:
%X,p+3is:
%X\n",p,p+3);
printf("*ais:
%d,*(a+3)is:
%d\n",*a,*(a+3));
printf("*pis:
%d,*(p+3)is:
%d\n",*p,*(p+3));
printf("p[0]is:
%d,p[3]is:
%d\n",p[0],p[3]);
}
程序运行结果为:
ais:
FFC8,a+3is:
FFCE (结果有可能不相同)
pis:
FFC8,p+3is:
FFCE
*ais:
1,*(a+3)is:
4
*pis:
1,*(p+3)is:
4
p[0]is:
1,p[3]is:
4
由运行结果可见:
a+i等价于p+i,同时还有:
*(a+i)等价于*(p+i),并且等价于a[i]和p[i]。
对p++、p--也是以基类型占用的存储单元进行,同时要修改p本身的值,让p指向后一个或前一个数据。
2. 指针之间的减运算
一般是同类型指针之间进行减运算。
对同类型的指针p、q,p-q表示p与q所指对象之间的元素个数。
例8.8 分析下面程序运行结果。
main( )
{
double d[5];
double *p,*q;
p=d, q=d+4;
printf("p:
%X,q:
%X,q-p:
%d\n",p,q,q-p);
}
运行结果:
p:
FFB0,q:
FFD0,q-p:
4
由结果可知:
两指针相减并不是两指针值之差,而是两指针所指对象之间的元素个数,所以实际值为指向type类型的两指针p和q之间的的差为:
(q-p)/sizeof(type)
3.指针之间的关系运算
指针之间的关系运算主要用在同类型的指针之间,用来表示两指针所指对象之间的前后关系。
可以使用所有的关系运算符。
例8.9 将数组中的元素按位置颠倒。
分析:
可由两个指针p和q分别指向数组的两端,p和q分别向中间移动,同时交换它们所指向的值,停止交换的条件是:
p≥q。
(8-3.1)中的
符号表示交换,p=a,q=a+N-1对应赋值复句,下面部分对应一while型循环语句,循环头为:
while(p程序如下:
#define N 10
main( )
{
int a[N]={1,2,3,4,5,6,7,8,9,10};
int *p,*q,t;
p=a,q=a+N-1;
while(p {
t=*p;*p++=*q;*q--=t;
}
p=a,q=a+N;
printf("theitemsof array are:
\n");
while(p printf("%5d",*p++);
}
运行结果为:
the itemsofarrayare:
10 9 8 7 6 5 4 3 2 1
注意:
✍ (*p)++和*p++之间的关系:
(*p)++表示对p所指向的对象加1修改,p仍指向原来的对象;*p++根据运算符的优先级和结合性,先计算表达式p++的值,其值为p原来所指向变量的地址,然后p指向下一个单元,最后再“*”运算符作用于表达式p++的值,取出p原来所指向的对象的值。
它们的区别为:
(*p)++改变的是p所指向对象的值,整个表达式的值为p所指向对象值加1;而*p++改变的是指针p的值,整个表达式的值为p原来所指对象的值。
✍ *(p++)、*++p和*p++:
都要修改p的值;实际上*(p++)与*p++是等价的,都是先取出p所指对象的值,再修改p的值;而*++p先修改p的值,再取出p当前所指向的对象的值。
为了增加可读性,建议使用*(p++)和*(++p)。
实际上(8-3.1)是等价于:
而
对应一个for型循环语句:
for(i=0;i t=a[i],a[i]=a[N-i-1],a[N-i-1]=t;
加上说明和输入输出则可以编出另一程序,这里程序略。
四、 指针与一维数组
假设有:
int a[N], *p=&a[0], *q=&a[N-1];
用指针来引用一维数组的元素,主要有以下方法:
✍ 偏移量法:
用指针加上偏移量,形如*(p+i)或p[i],表示数组a的第i个元素a[i];
✍ 修改指针变量p或q的值,让他们依次指向数组中的每一个元素,一般形式为:
*p++,*q--。
✍ 将数组名用作指针,利用偏移量法:
*(a+i)或a[i]。
例8.10 求字符串的长度。
分析:
若p存放字符串的首地址,q存放字符串结束字符的地址,则字符串的长度为:
len=q-p;
程序如下:
main( )
{
char c[10]="abcde";
char*p=c, *q=p;
int len;
while(*q!
='\0')
q++;
len=q-p;
printf("thelenof string is:
%d\n", len);
}
运行结果为:
thelenof string is:
5
例8.11 将数组中的元素先从中间向前输出前半部分的元素,然后再从中间向后输出后半部分的元素。
分析:
首先要确定数组中间位置的元素下标,假设数组的长度为N,则可定中间元素的下标为(注意数组的下标从0开始):
,再分两步完成输出操作。
程序如下:
#define N 10
main( )
{
int a[N]={1,2,3,4,5,6,7,8,9,10};
int*p,*q, m, i;
m=(N-1)/2;
p=a;/*利用指针偏移量方式*/
i=m;
while(i>=0)
{
printf("%5d",*(p+i));
i--;
}
i=m+1;
while(i {
printf("%5d",*(p+i));
i++;
}
printf("\n");
}
程序运行结果为:
5 4 3 2 1 6 7 8 9 10
思考:
该程序是通过指针偏移量来实现的,请问如何通过指针下标方式、指针增量方式、数组名偏移量方式实现?
注意:
在用指针变量引用数组元素时,一定要注意是否越界(即超出数组的范围)。
五、 指针与二维数组
通过指针来引用二维数组中的元素要比处理一维数组复杂得多。
前面已经介绍二维数组在内存中的存放方式:
由于内存单元的组织是以线性结构组织的,因此二维数组也是以线形方式组织,并且以行序为主序进行处理。
虽然他与一维数组的存放形式一样