资源描述
单击此处编辑母版标题样式,*,*,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,第十章 指针,莆田学院C语言程序设计精品课程组,2005年5月 制作,C语言程序设计,目录,一、指针概述,二、指针与数组,三、指针与函数,作业,一、指针概述,1地址(,address,),P201,如果在程序中定义了一个“实体”(变量、数组、函数,),编译时系统就要给这些实体分配内存单元。,分配规则:,存储类型,register,auto,atatic,extern,决定存储区域(动态存储区/静态存储区/寄存器组),数据类型,char,int,float,double等,决定占用的内存长度(字节数)和存储方式(ASCII码、补码等),什么是内存单元“地址”?,内存单元是以字节为单位,每个字节都有一个编号(即“地址”)。如果将内存比作一个旅馆,内存单元就好比“床位”,而实体则好比“旅客”。这些“旅客”(实体)中,有单人型(,char,)、夫妇型(,int,)、家庭型(,float,long,double,等),还有团体型(,数组,等)。每个“实体”占用的内存单元是不同的。如:,char a;int b;float c;int d3;int max( ),内存单元与地址,main(),char a;,int b;,float c;,int d3;,int max( );,地址,通常我们关心的不是各个内存单元的具体地址值,而是每个实体的“起始地址”。,如何表示实体地址?,实体地址表示法1:,直接访问(实体名),普通变量,a,b,c,&a,&b,&c,数组,d3,d,(,数组名),&,d0,,,&d1,,,&d2,对二维数组,可用单下标法表示每行首地址。,如 对,char x34,,,可用,x0,、,x1,、,x2,分别表示其第,一、二、三行的首地址。,函数,max( ),max,(,函数名),“入口地址”,&,取地址运算符(适用于普通变量或数组元素),实体地址表示法2:,间接访问(指针),适合于地址运算(加减等),2指针(,pointer,),实体地址的一种表示法(便于编程处理)。,指针是一种特殊的数据类型存放的是某个实体的地址值。,变量的“指针”,变量在内存单元的占用的地址(首地址),那为什么不就叫“地址”呢?!,实际上我们在C程序中用到的并不是代表地址的“指针”,而是另有所指啊!,3指针变量,P202,存放“指针”(地址值)的特殊变量。,定义方法:,类型标识符,*,变量名,如,int,*,a; char,*,b; float,*,c;,通常在C语言中,所谓“指针”就是指“指针变量”。,从现在开始,我们所说的“指针”除非另加说明,否则均表示“指针变量”。,是不是说地址有”整型,”,”,字符型,”,”,实型,”,之分?,为什么要使用指针变量?,C程序中访问(读写)变量有两种方式:,直接访问,(按名单预留的座位入座),利用实体名访问变量。访问变量的过程,变量(实体)名,定义时分配的地址,变量值,好比“先坐再买票”看电影:来一个观众,分配一个空位给他去坐,并且还要在纸上记一个某人坐在哪里。这种方式对用户来说很方便(“直接就座”),但对系统来说,“找某人”就极不方便(间接:查名字座号)。,间接访问,(先买票,后按号入座),把变量地址先存放在“指针”中,再通过“指针”访问变量。,好比先买票(,票,指针,,,座号,地址,),再,“按号入座”,看电影。这种方式对用户来说属于“间接就座”,便对系统查找来说就很直接,且便于处理。尤其对于数组(团体),可通过指针简单自加或自减,对整个数组进行处理。,习惯用语:,若指针变量p存放了变量a的地址,我们称“p指向a”。,指针变量不要,谈,”指”,色,变,指针是C语言学习中的一大难点。,难难在概念。,main(),int a,*,p1,*,p2=,a=100;,p1=p2;,*,p1=,*,p2;,学了半天,我还是一头雾水,首先搞定,*,p,请看以下变量声明语句,int a,*p1;,char b,*p2;,a,b,普通变量(存放某个数值或字符),p1,p2,指针变量(存放某个实体的地址),如果是,int *a,p1;,char *b,p2;,变量声明时,如果变量名前带,*,号,表示该变量是个指针变量,注意不同的,*p,以下程序中哪些语句是错误的?,main(),int a,*p;,a=3;,p=3; /*或者 p=a;*/,p=,*p=a; /*或者*p=3*/,讨论:,程序中引用变量时,,对指针变量p,,不带,*,号引用表示?,带,*,号引用表示?,两个特殊的运算符,&变量名,取该变量的地址,* 指针变量名,取该地址处存放的值,有关,*p,的小结,变量声明时,,*p,表示定义了一个用来存放变量地址而非数据(数值、字符等)的指针变量。,程序中引用时,,*p,表示取指针变量,p,所指变量的值。,原来就这么简单!,main(),int a,*p1,*p2=,a=100;,p1=p2;,*p1=*p2;,*,p,并非就这么简单,*,p的含义与p所指的对象有关,如果p被定义成指向普通变量的指针变量,则,*,p,代表该变量的值。,如,int *p,a=5; p=,则,*,p,代表变量a的值(,5,)。,如果p被定义成指向某个数组的指针变量,则,*,p,代表该数组中某个元素的值。,如,int *p, a3=1,2,3; p=a;,则,*,p,代表数组a中某个,元素,。,看看两个例子,#include ,main(),int a,b=10,*p;,system(cls);,p=,a=*p+3;,printf(a=%d,b=%dn,a,b);,main(),int *p,a12=1,2,3,4,5;,clrscr();,p=a;,for ( ;*p5;p+),printf(%d,*p);,结果:a=13,b=10,结果:1234,p指向字符数组时的,*,p,如果p被定义成指向某个字符数组或某个字符串的指针变量,则*p代表某个字符。,如,int *p, a3=”abcd”; p=a;,*p,代表,a,中的某个字符,main(),char *p,a12=abcde;,p=a;,for (;*p;p+),printf(%c,*p);,main(),char *p;,p=abcde;,for (;*p;p+),printf(%c,*p);,如果直接用,a,进行循环,行不行?,在for语句中用*p控制循环,是否适用于数值数组?,&,与,*,组合使用时,若 int a, *p;,p=,则 ,*&a = a = *p,妙!&和,*,可以互相“抵消”。,小考一下,如何?,以下程序的运行结果是什么?,main(),float x,y;,int *p;,x=3.14;,p=,y=*p;,printf(y=%fn,y);,结果:,怎么会这样?,把int *p改为float *p后,结果正确:y=3.140000,指针变量能参加运算吗?,指针变量和其他变量一样,可以在各种表达式中参加运算。,但指针变量和普通变量不同,只能进行以下三种运算:,赋值运算,算术运算,指针比较,指针变量的赋值运算,指针变量初始化,变量声明时赋值,main( ),int a=5,*p=,printf(“%d,%d,%dn”,p,*p,a),指针变量一般赋值,程序处理时赋值,结果:2000,5,5,main(),int x;,int *p1,*p2;,p1=,p2=p1;,printf(%p,p2);,%p,以16进制显示指针,典型错误,指针变量定义后,未指向具体存储单元(实体地址)就使用,此时指针变量所指单元是任意的, 是个”,危险指针,”。,【,例一】,若有定义,char *p,ch;,则不能正确赋值的语句组是 :,A) p=&ch,scanf(%c,p);,B) *p=getchar();,p=,C) p=,*p=getchar();,【,例二】,若有定义,char *a,b30;,则以下各语句正确的是 :,A) a=”abcde”;,B)b=”abcde”;,C) scanf(“%s”,a);,D) scanf(“%s”,b);,X,X,字符串是一种特殊的实体,存放在内存用户区的常量区。,为什么未指向实体的指针是“,危险指针,”?,“危险指针”?不要耸人听闻好不好!,一个指针未指向任何实体就被使用,属于,”,内存盗用,”,!因为该指针将随意指向内存中某一单元,轻则误取或破坏其他实体的值,重则破坏操作系统的工作。,一个指针变量被声明后,在没有被赋予某个实体地址之前,如果使用它,不仅可能破坏你的程序,而且可能导致计算机操作系统崩溃,出现灾难性的错误。因为它可能指向内存的任何部分。,空指针,P256,空指针:,int *p;,p=NULL;,NULL,是什么?,在中,定义,#define NULL 0,所以,p=NULL;,相当于,p=0;,小姐,我把0号办公室分配给你,院长办公室给我?哼,空头人情!,内存使用常识:,任何C程序的变量在内存中的地址均由操作系统自动分配,不能由编程者通过赋值指定。,p=NULL,表示p不指向任何变量。,内存的低端只供由操作系统使用(相当于政府机关,普通百姓不能使用)。,讨论:以下程序中的,*p1,*p2,#include ,main(),int *p1=NULL,*p2;,clrscr();,*p1=100;,*p2=200;,printf(%d,%dn,*p1,*p2);,*p1有确定地址,但未指向任何变量,*p2无确定地址,是“危险指针”,在指针p指向某个实体的地址之前,不可对*p进行赋值。否则可能发生意想不到的错误(p随便指向某个单元)。,指针变量的算术运算,指针只有两种算术运算加、减,p+5 p+ p-1 p,-,注意加减运算是以实体为单位而不是以字节为单位。,此外,两个指针变量可以相减。即:如果两个指针变量指向同一数组时,两个指针变量值之差是两个指针之间的元素个数。参见P256。,但两个指针变量相加并无实际意义。,以下程序哪个语句执行时会出错?,#include ,main(),int a10,*p1=a;,clrscr();,a+;,p1+;,X,指针的逻辑比较,P256,指针变量指向同一个对象(如数组)的不同单元地址时,才可以进行比较。地址在前者为小。,任何指针变量或地址都可以与NULL作相等或不相等的比较。如,if(p=NULL),#include ,#include ,fun(char *w,int n), char t,*s1,*s2;,s1=w;,s2=w+n-1;,while (s1s2), t=*s1+;,*s1=*s2-;,*s2=t;,main(), char *p;,p=1234567;,fun(p,strlen(p);,puts(p);,结果:,1711717,【注意】对*s1+,因*与+同级,且自右至左结合,所以等价于*(s1+),执行时是先做*s1,后做s1+ 。,P215,*,p,:,多重指针,(指向指针的指针),P251,对于,int,*,p;,定义一个二级指针(指向指针的指针),存放某个,指针变量的地址,:等效于,int,*,(,*,p),在引用时,,*,p,是p间接指向的对象的地址。,*,p,是p间接指向的对象的值。,晕,!,用一个*声明一个变量p,int a=5,*p=,使用时,p带*是a的值(“直接取值”)。,用两个*声明一个变量p,int a=5,*p,*p1;,p1=,使用时p带两个*是a的值 (“两重间接取值),p带一个*是a的地址。,看了例子也许会明白的,以下程序段的输出是什么?,int *pp,*p,a=20,b=30;,pp=,printf(“%d,%dn”,*p,*pp);,对不对,?,结果:30,30,(多重间接取值),指针变量作为函数参数使用,main(),void swap(int,int);,int a=5,b=3,*p1,*p2;,clrscr();,p1=,swap(*p1,*p2);,printf(a=%d,b=%dn,a,b);,void swap(int x,int y),x=x-y;,y=10;,printf(x=%d,y=%dn,x,y);,main(),void swap(int *,int *);,int a=5,b=3,*p1,*p2;,clrscr();,p1=,swap(p1,p2);,printf(a=%d,b=%dn,a,b);,void swap(int *x,int *y),*x=*x-*y;,*y=10;,printf(x=%d,y=%dn,*x,*y);,再看一个例子,#include ,fun(int *i),static int a=1;,*i+=a+;,main(),int k=0;,fun(,fun(,printf(%dn,k);,结果:3,为了实现:,在被调函数中改变实体值,然后在主调函数中使用这些改变了的实体值,主要技术要点在于:,主调函数的实参和被调函数对应的形参都必须用地址表示(地址传递),用于作实参的地址可以是:,&变量名 &数组元素名(很少使用),数组名,指针变量名,对应的形参则为:,数组定义,指针定义,二、指针与数组,1、一维数组中的有关规定,#define N 9,main(),int a10,i,*p;,clrscr();,p=a;,for(i=0;iN;i+),scanf(%d,for(i=0;iN;i+),printf(%d ,ai);,Lets try,运行程序,观看结果,在,scanf,语句中,分别用,a+i、p+i,和&,pi,代替&,ai,试试;,在,printf,语句中,分别用*(,a+i)、*(p+i),和,pi,代替,ai,试试;,在,p=a,语句中,用&,a0,代替,a,试试。,有何感觉?,我明白了,一维数组中p=a时,p和a都可以相互替换。,有一个地方不能替换,知道是什么地方吗?,p+不能用a+替换!,为什么?,因为a是常量,5+即5=5+1是个低级错误!,注意事项, 注意指针变量当前值,P214, 注意不要超界, 常见表示法:,P215,*p+ 等价于*(p+) 先取*p值,然后 p+。,*(+p) p先自加,然后取*p的值。,*p与 *(p) 功能同上。,小结:C语言的有关规定,对,一维数组,的数组名,a,:,代表数组首地址(数组第一个元素a0的地址),即:,a=&a0 *a=a0,ai,表示右移或左移,i,个元素位置,a+i=&ai *(a+i)=ai,如果有指针变量,p=a;,或,p=,则,p,和,a,在程序中实际上可以互相替换使用。,p=&a0 *p=a0,p+i=&ai *(p+i)=ai,切记:p自加或自减不能用a自加或自减替换!,试一试看,【,例,】,有int a10=0,1,2,3,4,5,6,7,8,9,*p=a,i; 其中0i10,则对a数组元素不正确的引用是,。,A),ap-a,B),*(&ai),C),pi,D),*(*(a+i),答案:D,二维数组可以照此类推吧?,不可以的。二维数组可要大伤脑筋了!,二维数组就没这么简单了,2、二维数组中的有关规定,二维数组的地址有”,行地址,”和”,列地址,”之分,行地址表示是数组中的第几行,(不带下标的数组名),行地址,a a+i,a+i,表示第i+1行,列地址指第几个元素的地址,(单下标或双下标+&),每行的首地址,a0 ai &a00,都是列地址,列地址+1,或,列地址-1,表示左移或右移一位,对行地址取值的结果 如,*a、*(a+i),仍是某行第一个元素的地址,但已由行地址转为列地址了。所以,a+i,是表示第i+1行的行地址,而,*a+1,表示第0行第i+1个元素的地址,,*(a+i) +j,表示,&aij,,即第i+1行第j+1列的元素地址。,列地址是怎么回事?,列地址:,a0 ai &a00,(单下标或双下标+&),使用列地址时,是将整个二维数组看成同一行。,遍历数组时用单循环实现(将数组元素排队处理),不妨看一个示例,#include ,main(),int a33=1,2,3,4,5,6,7,8,9;,int *p,i,j=0;,system(cls);,p=a;,/* for (i=0;i9;i+) */,printf(%dn,*(a0+1);,Lets try,1、将a0改为,a1、a2,&a00,a,2、,将注释标记 去掉,行地址又是怎么回事呢?,行地址:,a a+i,(不带下标的数组名),对行地址取*运算后转换为列地址,如,*,a、,*(a+i),使用行地址时,是将整个二维数组看成i行j列。,遍历数组时用双循环实现(将数组元素分行列处理),使用行地址时:,a,和,*a,分别表示第0行的行地址和列地址,a+i,和,*(a+i),分别表示第i行的行地址和列地址,*(a+i)+j,表示第i行第j个元素的地址(=&aij),行地址本质上是二级地址,通过它取元素值时要多加一次,*,运算。,*,a,表示第0行0列元素值(=,a00);,*(*(a+i),表示第,i,行第0列元素值(=,ai0);,*(*(a+i)+j),表示,第,i,行第,j,列元素值(=,aij)。,看两个例子,#include ,main(),int a33=1,2,3,4,5,6,7,8,9;,int *p,i;,system(cls);,p=a;,for (i=0;i3;i+), printf(%d ,a+i);,printf(%d %d %dn,0,行,-52,FFCC,1,-52,FFCC,2,-50,FFCE,3,-48,FFD0,1,行,-46,FFD2,4,-46,FFD2,5,-44,FFD4,6,-42,FFD6,2,行,-40,FFD8,7,-40,FFD8,8,-38,FFDA,9,-36,FFDC,改成,*,(a+i)、ai分别试试,再把%d改为%p试试,【注意】二维数组中,ai、a+i与*(a+i) 等价(P225)。,原因:a+i 不是变量,C规定ai 、a+i与*(a+i) 等价(P226)。,看一个例子,#include ,main(),int a33=1,2,3,4,5,6,7,8,9;,int *p,i,j;,system(cls);,p=a;,for (i=0;i3;i+),for(j=0;j3;j+),printf(%d ,*(*(a+i)+j);,改成aij试试,用简单指针变量指向二维数组时,用简单指针变量指向二维数组时:,int a1010,*p;,p=a;,p都是列地址性质的指针(姑且称“列指针”)。此时,p可与“排队法”中的a0互换使用,但不能与“行列法”中的a互换使用。,比较一下,#include ,main(),int a33=1,2,3,4,5,6,7,8,9;,int *p,i;,system(cls);,p=a;,for (i=0;i9;i+),printf(%dn,*(p+i);,1,2,3,4,5,6,7,8,9,换成*(,a0+i,)试试,再看下一个例子,#include ,main(),int a33=1,2,3,4,5,6,7,8,9;,int *p,i,j;,system(cls);,p=a;,for (i=0;i3;i+),for(j=0;j3;j+),printf(%d ,*(*(a+i)+j);,a改成p试试,【讨论】,如果将输出语句中的*(*(a+i)+j)改为*(*(p+i)+j)可不可以?,不行!,Why?!,因为p不是“行指针”!,行指针,P229,形式:,int (*p)n,含义:p为指向含有n个元素的一维数组的指针变量。,P255,使用:二维数组可以视为由若干一维数组组成,。,行指针p是行地址性质的指针。此时,p可与“行列法”中的a互换使用,但不能与“排队法”中的a0互换使用。,行指针是如何使用的?,若 int a45;,int (*p)5;,p=a;或p=a0;,则 (*p)0=a00;,(*p)1=a01;,(*p)2=a02;,(*(p+1)0=a10;,(*(p+1)1=a11;,行指针是一种行地址,可以与二维数组用数组名表示的行地址互换使用。,事实上,有,(*(p+ i)j=pij= * (*(p+ i)+j)=aij;,示例一,main(),int a33=1,2,3,4,5,6,7,8,9;,int (*p)3,i,j;,p=a;,for(i=0;i3;i+),for(j=0;j3;j+),printf(%d ,*(*(p+i)+j);,结果:1 2 3 4 5 6 7 8 9,示例二,若有以下定义和语句,且0i4,0j3,则不能访问a数组元素的是,。,int i, (*p)3;,int a 3=1,2,3,4,5,6,7,8,9,10,11,12;,p=a;,A)*(*(a+i)+j) B)pij,C)(*(p+i)j D)pj+j,答案:D,(pj+j是个地址),示例三,main(),char a310=abc,123456,ABCDE;,char (*p)10;,p=a;,printf(%s,%sn,p+1,*(p+1);,printf(%c,%c,%cn,*(*(p+1),*(*(p+2)+1),(*(p+2)1);,结果:123456,123456,1,B,B,示例四,以下程序运行结果是,。,main(),char aa 3=a,b,c,d,e,f;,char (*p)3=aa;,int i;,for(i=0;i1),printf(%s ,argv-argc);,printf(n);,将该文件编译后,在命令行输入:test abc 123则该程序运行结果是什么?,结果:123 abc,指针数组的典型应用,P253,【例二】以下程序经过编译连接后得到的可执行文件名为echo.exe,在dos提示符下输入,,则在屏幕上将显示My computer。,#include ,main(int argc,char *argv),int i;,for (i=1;iargc;i+),printf(%s%c,argvi,(iargc-1)? :n);,A)My computer B) echo My computer,C) Mycomputer D) main(My computer),答案:B,三、指针与函数,1、指向函数的指针变量,P241,可以用指针变量指向一个函数。一个函数在编译时被分配给一个“入口地址”。,定义方法:,类型标识符 (,*,指针变量名)();,如,int (*p)( );,指向一个返回整型值的函数,用法:,设有函数fun(a,b),令p=fun; 则有 (*p)(a,b) 相当于 fun(a,b);,此时c=(*p)(a,b ) 与c=fun(a,b)等效。,三、指针与函数,2、返回指针值的函数,P246,一般形式,int *a(x,y);,表示该函数的返回值是一个指针。,第十章作业,1、所给程序段是把从键盘输入的一行字符作为字符串放在字符数组中,然后输出。请填空。,int i;,char s80,*p;,for(i=0;i79;i+),si=getchar();,if (si=n) break;,si=,;,p=,;,while(*p) putchar(*p+);,一、填空题,2、,所给源程序经编译、连接后得到的可执行文件名为file.exe。已知在系统的命令状态下输入命令行,file Beijing Shanghai,得到的输出是,Beijing,Shanghai,请填空。,main(int argc,),while (,),+argv;,printf(%sn,);,-argc;,二、上机编程题,1、编程要求:把全班若干同学的口令存入在一个指针数组中,要求用户输入用户号和口令后,程序能判断其口令输入是否正确。如果口令正确,程序运行情况示例:,请输入学生座号:5,请输入用户口令:Water,(屏幕显示应为*),口令正确。欢迎使用本软件!,如果口令不对,应允许再重新输入,如果连续三次口令输入均为错误,显示“非法用户!”后退出。,请完成以下程序,实现所要求的功能。,void main(),char *password=“Cat”,”Mouse”,”Dog”,”Sugar”,”Water”,”Meat”,”Steel”;,二、编程要求:设计一个英汉词典,若用户输入一个英语单词,要求能显示其中文意思。如果所输入的单词不在词典中,显示“无此英文单词!”。如,请输入英文单词:Computer,中文为:计算机,请完成以下程序,实现所要求的功能。,void main(),char *English=“Disk”,”Computer”,”Keyboard”,”Display”,”CPU”,”Mouse”;,char *Chinese=“磁盘”,”计算机”, ”键盘”, ”显示器”,”中央处理器”, ”鼠标器”;,
展开阅读全文