指针C语言大学实用教程课件苏小红.ppt
第7章 指针,哈尔滨工业大学计算机科学与技术学院苏小红,C语言大学实用教程,内容提要,指针的概念;难点:对指针数据类型的理解 用指针做函数参数;利用字符指针存取字符串;字符数组和字符指针的区别与联系 指针数组应用;指向数组的指针与指针数组的区别 带参数的main函数;动态内存分配函数及其应用;一维、二维动态数组的实现,为什么引入指针的概念,铁杆C/C+程序员最挚爱的武器:指针C/C+的高效、高能主要来自于指针很多不可能的任务由指针完成,为什么引入指针的概念,指针为函数提供修改变量值的手段 为C的动态内存分配系统提供支持 为动态数据结构(如例链表、队列、二叉树等)提供支持 可以改善某些子程序的效率,计算机内的存储部件,所有指令和数据都保存在内存里速度快,但是掉电即失可以随机访问只要指明要访问的内存单元的地址,就可以立即访问到该单元地址是一个无符号整数,其字长一般与主机相同内存中的每个字节都有唯一的一个地址地址按字节编号,按类型分配空间,内存(Random Access Memory)地址(Address),寻址方式,如何读写内存中的数据?通过变量的地址访问变量所在的存储单元两种寻址方式直接(寻址)访问通过变量地址直接存取变量内容间接(寻址)访问通过指针变量来间接存取它所指向的变量,任何变量在存在期间总有确定存储位置,有固定地址。,指针(Pointer)的概念,指针也是一种数据类型指针变量 声明为指针类型的变量,专门存放地址数据的变量,指针的主要操作指针赋值:将程序对象的地址存入指针变量。间接访问:通过指针访问被指对象。指针还能保存其他对象的地址。下面讨论以变量为例。,指针p保存着变量x地址,也说指针p指向x。图示:,在C中使用指针常能写出更简洁有效的程序。有些问题必须用指针处理。指针在大型复杂软件中使用广泛。指针使用的水平是评价人的C程序设计能力的重要方面。C指针灵活/功能强。掌握有难度,易用错,应特别注意。应特别注意使用指针的常见错误,注意!,指针是变量,可赋值,其指向可以改变。现在p指向x,以后可能指向y。通过p访问被指对象的语句目前访问x,后来就访问y。这种新的灵活性很有用。,7.2 指针变量的定义和使用,指针有类型,只能保存特定类型的变量的地址指向int的指针p只能指向int变量。常说int指针p1等。,指针是变量,可赋值取值,有定义域与存在期。应赋给类型正确的指针值,取出的值是特定类型的指针值。用(int*)表示整型指针的类型,其他类似。,如何定义指针变量?,定义指针变量 定义指针需指明指向类型。int*p;定义了一个指针变量p,简称指针pp是变量,int*是类型p里保存一个地址。指针变量可以与其他变量一起定义。如:int*p,n,a10,*q,*p1,m;,指针操作取地址运算符&和间接访问操作*。都是一元运算符,取地址运算,int i,*p;p=,int*p;float*q;p=q;,int i;float*p;p=,int*p;p=100;,判断是真?是假?,指针变量只存放地址!,一个指针变量不能指向与其类型不同的变量!,我是真的,你猜对了吗?,应在类型相同的指针变量之间赋值,多个指针可能同时指向同一变量。变量相等是值相等,两个指针变量相等说明它们指向程序里同一东西。,间接运算符*-用来取指针指向地址的内容*p=0;-间接运算得到被指针所指的变量,这种表达式可以像普通变量一样使用,其值是p指向的内存的内容(但寻址方式和普通变量不同)。设p指向n。间接赋值:*p=17;-这里写*p相当于直接写n。,例:int*p,n=17;另一个赋值:*p=17;m=*p+*q*n;/*访问n三次*/,+*p;/*使变量n的值加1,变成18*/(*p)+;/*使变量n的值再加1,变成19。*/*p+=*q+n;/*变量n被赋以新值57*/q=/*指针q指向了数组a的元素*/,指针变量与其它类型变量的对比,共性在内存中占据一定大小的存储单元先定义,后使用特殊性 它的内容只能是地址,而不能是数据必须初始化后才能使用,否则指向不确定的存储单元,对该空间进行访问,将可能造成危险可参与的运算:加、减一个整数,自增、自减、关系、赋值只能指向同一基类型的变量,指针的指向,只能指向同一基类型的变量,否则将引起warningfloat x;int*p=TC编译warning:Suspicious pointer conversion in function mainVC编译warning C4133:=:incompatible types-from float*to int*,指针运算,算术运算int*p,a10;p=a;p+;/*p的值增加多少?*/指针的加减运算是以其指向的类型的字节长度为单位的另一个例子:int n=17;*p=,6000600160026003600460056006,使变量n的值加1,变成18,先取出变量p所指向的单元中的内容赋给m,再使p指向下一个地址单元。实际上,相当于语句:m=*(p+),可分解为下面两条语句:m=*p;p=p+1;,指针运算,int*p,*q,a10,k;p=a;q=指针运算不能乱算一般只进行指针和整数的加减运算,同类型指针之间的减法运算其它运算,比如乘法、除法、浮点运算、指针之间的加法等,并无意义,所以也不支持,指针运算,关系运算指向同一种数据类型的两个指针才能进行关系运算值为1或0p q p q p=q不能与非指针类型变量进行比较,但可与NULL(即0值)进行等或不等的关系运算判断p是否为空指针P=NULLp!=NULL,指针运算,赋值运算指针在使用前一定要赋值为指针变量赋的值必须是一个地址,main()int*p;scanf(%d,p);,main()int a,*p=,错!因为p中没有内容,即:p不指向任何变量,所以,一定要先给指针变量一个地址,指针与函数,指针既然是数据类型,自然可以做函数参数和返回值的类型指针做函数参数的经典例子:两数的互换(也是为什么我们要学习和使用指针的理由),void Swap(int*x,int*y)int temp;temp=*x;*x=*y;*y=temp;,main()int a,b;a=15;b=8;Swap(,void Swap(int x,int y)int temp;temp=x;x=y;y=temp;,main()int a,b;a=15;b=8;Swap(a,b);printf(a=%d,b=%d,a,b);,程序 1,程序 2,例7.17.2:编写函数实现两数的互换,实 参,形 参,结果有何不同?,Not Work!Why?,主调函数,被调函数,main()int a,b;a=15;b=8;Swap(a,b);printf(a=%d,b=%d,a,b);,void Swap(int x,int y)int temp;temp=x;x=y;y=temp;,5,5,a,b,实 参,形 参,9,9,程序 1,x,y,5,5,temp,9,主调函数,被调函数,main()int a,b;a=15;b=8;Swap(,void Swap(int*x,int*y)int temp;temp=*x;*x=*y;*y=temp;,&a,&a,实 参,形 参,&b,&b,程序 2,x,y,5,temp,5,a,b,9,9,5,简单变量作函数参数与指针变量作函数参数的比较,分析:要(在一函数里)通过调用函数g修改调用处的变量(如局部变量),必须在g里掌握这个变量。,用指针可以解决问题。把m的地址(也是值)通过指针参数p传给函数g,函数g内对p间接访问就能操作m,包括对m赋值(改变m)。,例,通过函数调用把变量值设置为3:void set3(int*np)*np=3;使用实例:int main()int n,m;set3(,请回忆scanf的情况。,通过参数改变调用环境的方案包括三方面:函数定义中用指针参数;函数内用间接操作实际变量;调用时以被操作变量的地址作为实参。函数swap可定义为:void swap(int*p,int*q)int temp;temp=*p;*p=*q;*q=temp;交换变量m和n的值,调用形式是:swap(,swap的参数类型是(int*),实参必须是合法的整型变量的地址。,设有变量定义:int a10,k;调用swap的实例:swap(,介绍标准库函数scanf时,强调在接受输入的变量前必须写&,就是将变量的地址传给scanf。scanf采用与swap一样的技术,通过间接访问为指定变量赋值,把输入的值赋给指定变量。,swap函数的几种错误形式(1/3),参数单向传递void Swap(int x,int y)int temp;temp=x;/*x,y为内部变量*/x=y;y=temp;,swap函数的几种错误形式(2/3),参数单向传递void Swap(int*p1,int*p2)int*p;p=p1;/*p1,p2为内部变量*/p1=p2;p2=p;,swap函数的几种错误形式(3/3),指针p没有确切地址void Swap(int*p1,int*p2)int*p;/*指针p未初始化*/*p=*p1;*p1=*p2;*p2=*p;,与指针有关的一些问题空指针值:一个特殊指针值,表示指针变量闲置(未指向任何变量)。唯一对任何指针类型都合法的值空指针值用0表示,标准库专门定义了符号常量 NULLp=NULL;和p=0;相同前一写法易看到是指针,用时必须包含标准头文件。,指针初始化指针变量定义时可用合法指针值初始化:int n,*p=若没有初始化,外部指针和局部静态指针自动初始化为用空;局部自动指针不自动初始化。,指针使用中的常见错误使用指针的最常见错误是非法间接访问:在指针未指向合法变量的情况下做间接。如:int f(.)int*p,n=3;*p=2;.p没有初始化,没有指向合法变量。,“悬空指针”指值不是(当时)合法的变量地址的指针变量,也常被称为“野指针”。间接访问悬空指针是严重错误,后果可能很严重。,常见错误写法(设p是悬空int指针,n是int变量):swap(p,编译程序不能发现scanf的错误。有些系统可能对第一个例子(假设p未初始化)给出警告。间接访问空指针也同样无理,是非法的。,例 7.3 试用函数编程实现打印最高分及其学号例7.5,字符串与字符数组、字符指针,C语言并没有为字符串提供任何专门的表示法,完全使用字符数组和字符指针来处理字符串一串以0结尾的字符字符数组每个元素都是字符类型的数组char string100;字符指针指向字符类型的指针char*p;数组和指针可以等同看待,上面三者本质上是一回事,字符指针变量与字符数组的区别,定义方法不同 char str10;char*ptr;初始化含义不同 char str10=“china”;/为数组中的每个 元素赋初值 char*ptr=“china”;/用字符串“china”的 首地址,给指针变量ptr赋初值。,赋值方法不同 char str10;str=”china”;/*错误*/strcpy(str,”china”);/*正确*/char*ptr;ptr=”china”;字符指针是变量,而数组名是地址常量输入字符串时略有不同,使用字符指针的注意事项,字符指针变量必须有明确的指向,否则使用是危险的例如,输入字符串时 char*a;scanf(%s,a);/*错误*/应为:char*a;char str10;a=str;scanf(%s,a);/*正确*/,定义字符指针时可用字符串常量初始化,如:char*p=Programming;1)定义了指针p2)建立了一个字符串常量,内容为Programming3)令p指向该字符串常量。图(a)char a=Programming;1)定义了一个12个字符元素的数组2)用Programming各字符初始化a的元素,图(b),1)指针p可重新赋值(数组不能赋值,只能通过字符串复制的方法或者为每个元素重新赋值的方法):p=Programming Language C;2)p和a类型不同,大小不同。a占12个字符的空间。3)a的元素可以重新赋值。如:a8=e;a9=r;a10=0;a的内容现在变成“Programmer”按规定,字符串常量不得修改,例7.5:字符串拷贝用字符数组编程,void MyStrcpy(char dstStr,char srcStr)int i=0;while(srcStri!=0)dstStri=srcStri;i+;dstStri=0;,void MyStrcpy(char*dstStr,const char*srcStr)while(*srcStr!=0)*dstStr=*srcStr;srcStr+;dstStr+;*dstStr=0;,当只允许函数访问地址内容,不允许修改时,可以把函数的指针参数定义为const,例7.5:字符串拷贝用字符指针编程,例7.6:计算实际字符个数,unsigned int MyStrlen(char str)int i;unsigned int len=0;for(i=0;stri!=0;i+)len+;return(len);,unsigned int MyStrlen(char*pStr)unsigned int len=0;for(;*pStr!=0;pStr+)len+;return(len);,方法2:用字符指针实现,方法1:用字符数组实现,7.3 指针与数组,C指针与数组关系密切,以指针为媒介可以完成各种数组操作。常能使程序更加简洁有效。用指针做数组操作同样要特别注意越界错误。指针和数组的关系是C语言特有的,除了由C派生出的语言(如C+),一般语言中没有这种关系。,指向数组元素的指针类型合适的指针可以指向数组元素。假定有定义:int*p1,*p2,*p3,*p4;int a10=1,2,3,4,5,6,7,8,9,10;,可以写:p1=,p4没指向a的元素,是指向a最后元素向后一个位置。C语言保证这个地址存在,但写*p4 是错误的。,写数组名得到数组首元素地址,元素类型的指针值。“p1=,指针运算 当指针p指向数组元素时说p指到了数组里。这时由p可以访问被p指的元素,还可访问数组的其他元素。例:p1指向a首元素,值合法(a0的地址),p1+1也合法(a1的地址)。p1+2、p1+3、也合法,分别为a其他元素的地址。由它们可间接访问a各元素。,例:*(p1+2)=3;/*给a2赋值*/p2=p1+5;/*使p2指向a5*/,也可由指向非首元素的指针出发访问数组其他元素:*(p2+2)=5;/*给a7赋值*/可用减法访问所指位置之前的元素:*(p2-2)=4;/*给a3赋值*/,通过指针访问数组元素时必须保证不越界。运算取得的指针值(即使不间接访问)必须在数组范围内(可过末元素一位置),否则无定义。这类运算称为“指针运算”。其他常用指针运算:用指针运算得到的值做指针更新:p2=p2-2;/*这使p2改指向a3*/,用增/减量操作做指针更新(指针应指在数组里):p3=p2;+p3;-p2;p3+=2;如果两指针指在同一个数组里,可以求差,得到它们间的数组元素个数(带符号整数)。n=p3 p2;/*也可以求 p2 p3*/,指在同一个数组里的指针可以比较大小:if(p3 p2).当p3所指的元素在p2所指的元素之后时条件成立(值为1),否则不成立(值为0)。两个指针不指在同一数组里时,比较大小没有意义。,两个同类型指针可用=和!=比较相等或不等;任何指针都能与通用指针比较相等或不等,任何指针可与空指针值(0或NULL)比较相等或不等。两指针指向同一数据元素,或同为空值时它们相等。,数组写法与指针写法如果一个指针指在一个数组里,通过指针访问数组元素的操作也可用下标形式写。设p1指向数组a0,p3指向a5。可写:p13=5;p32=8;p13一类写法称为数组写法,*(p+3)一类写法称为指针写法。两类写法有等价效力,可以自由选用。,指针与数组,数组名就是一个指针只是不能修改这个指针的指向可以定义函数的参数为数组指针也可当作数组名使用int*p,a10;p=a;数组元素的几种等价引用形式ai*(a+i)pi*(p+i),60006001600260036004600560066007,a,a+1,a+2,60006001600260036004600560066007,a,p+,p+,输入输出数组的全部元素,main()int a10;int i;for(i=0;i10;i+)scanf(%d,方法1:下标法,main()int a10;int*p,i;for(p=a;p(a+10);p+)scanf(%d,p);for(p=a;p(a+10);p+)printf(%d,*p);,方法2:指针法,例7.7:插入排序,关键是:找到该插入的位置,然后依次移动插入位置及其后的所有元素腾出这一位置放入待插入的元素,例7.7:插入排序,void Inseart(int a,int n,int x)int i,pos;for(i=0;(i ai);i+)pos=i;for(i=n-1;i=pos;i-)ai+1=ai;/*向后移动*/apos=x;/*插入元素x到位置pos*/,要插入的:X=6,7,9,10,指针与二维数组,C语言将二维数组看作一维数组,其每个数组元素又是一个一维数组按行顺序存放所有元素,aa0+0,a+1a1+0,a0+1,a0+2,&a00,&a10,&a11,a1+1,&a12,&a01,&a02,a1+2,int a23;,例7.8,任意输入英文的星期几,在查找星期表后输出其对应的数字。char weekDay710=Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday;,表7-1 星期表的内容,#include main()int i,pos;int findFlag=0;char x10;char weekDay10=Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday;printf(Please enter a string:);scanf(%s,x);for(i=0;i 7,例7.8,指针与二维数组,a 代表二维数组的首地址,第0行的地址a+i 代表第i行的地址*(a+i)即 ai 代表第i行第0列的地址*(a+i)+j 即 ai+j 代表第i行第j列的地址*(*(a+i)+j)即 aij 代表第i行第j列的元素,行地址转变成列地址,指针与二维数组,二维数组的指针列指针int*p;p=*a;/用列地址初始化,/等价于*(a+0),即第0行第0列的地址逐个元素查找元素所在位置相对于数组起始地址的偏移量i*n+j/对于m行n列的二维数组来说for(i=0;im;i+)for(j=0;jn;j+)printf(%d,*(p+i*n+j);,p,p+,代表第i行第j列的地址,指针与二维数组,二维数组的指针行指针int(*p)3;常量3规定了行指针所指一维数组的长度,它是不可省略的p=a;/用行地址初始化先逐行查找元素所在行再在行内逐列查找元素所在位置for(i=0;im;i+)for(j=0;jn;j+)printf(%d,*(*(p+i)+j);,p,p+,例7.3:在一个班级中找出最高分及其学号,void FindMax(float score,long num,int n,float pMaxScore,long pMaxNum)int i;pMaxScore=score0;pMaxNum=num0;for(i=1;i pMaxScore)pMaxScore=scorei;pMaxNum=numi;,能返回这两个值吗?,例7.3:在一个班级中找出最高分及其学号,void FindMax(float score,long num,int n,float*pMaxScore,long*pMaxNum)int i;*pMaxScore=score0;*pMaxNum=num0;for(i=1;i*pMaxScore)*pMaxScore=scorei;*pMaxNum=numi;,指针参数指定了存放这两个值的地址,例7.9:在多个班级中找出最高分及其所在班级和学号,int FindMax(int pmn,int m,int n,int*pRow,int*pCol)int i,j,max;max=p00;*pRow=0;*pCol=0;for(i=0;i max)max=pij;*pRow=i;*pCol=j;return(max);,能这样传递m个班(每班n个学生)的成绩吗?,int FindMax(int*p,int m,int n,int*pRow,int*pCol)int i,j,max;max=p0;*pRow=0;*pCol=0;for(i=0;i max)max=pi*n+j;*pRow=i;*pCol=j;return(max);,由此可见,此处的p是列指针,例7.9:在多个班级中找出最高分及其所在班级和学号,指定存储m个班(每班n个学生)成绩的首地址,#include#define CLASS 3/*班级数*/#define STUD 4/*每班学生数*/int FindMax(int*p,int m,int n,int*pRow,int*pCol);/*函数声明*/main()int scoreCLASSSTUD,i,j,maxScore,row,col;for(i=0;iCLASS;i+)printf(Please enter scores of class%d:n,i+1);for(j=0;jSTUD;j+)scanf(%d,指针、数组以及其它的类型混合,基本数据类型int、long、char、short、float、double指针是一种数据类型是从其它类型派生的类型XX类型的指针数组也是一种数据类型是从其它类型派生的类型每个元素都有一个类型任何类型都可以做指针或者数组的基础类型它们自己也可以做彼此或自己的基础类型,指针数组,元素均为指针类型数据的数组,称为指针数组 定义形式为:类型关键字*数组名数组长度;例如 char*pStr5;,例7.11,main()int i;char str10=Pascal,Basic,Fortran,Java,Visual C;for(i=0;i5;i+)printf(%sn,stri);,str,例7.11,main()int i;char*ptr=Pascal,Basic,Fortran,Java,Visual C;for(i=0;i5;i+)printf(%sn,ptri);,例7.10:字符串按字典顺序排序二维数组编程,char strN10=Pascal,Basic,Fortran,Java,Visual C;for(i=0;iN-1;i+)for(j=i+1;jN;j+)if(strcmp(strj,stri)0)strcpy(temp,stri);strcpy(stri,strj);strcpy(strj,temp);,例7.10:字符串按字典顺序排序指针数组编程,char*ptrN=Pascal,Basic,Fortran,Java,Visual C;for(i=0;iN-1;i+)for(j=i+1;jN;j+)if(strcmp(ptrj,ptri)0)temp=ptri;ptri=ptrj;ptrj=temp;,命令行参数,通过命令行参数,使用户可以根据需要来决定我们的程序干什么、怎么干main(int argc,char*argv)当你把main函数写成这样时argc的值为程序执行时参数的数目(包括命令本身)argvi为指向第i个参数的字符指针这两个内设形参用于接收命令行参数,例7.12:演示命令行参数与main函数各形参之间的关系,main(int argc,char*argv)int i;printf(The program name is:%sn,argv0);if(argc 1)printf(The other arguments are following:n);for(i=1;iargc;i+)printf(%sn,argvi);,动态分配内存,#include#include void*malloc(unsigned int size);向系统申请大小为size的内存块,把首地址返回。如果申请不成功,返回NULLvoid*calloc(unsigned int num,unsigned int size);向系统申请num个size大小的内存块,把首地址返回。如果申请不成功,返回NULLvoid free(void*p);释放由malloc()和calloc()申请的内存块。p是指向此块的指针void*类型的指针可以指向任意类型的变量,动态数组,一维动态数组例7.13 int*p=NULL;printf(Please enter array size:);scanf(%d,/像使用一维数组一样使用,这一章我们学习了,指针的概念指针是一种特殊的数据类型指针的使用原则永远要清楚每个指针指向了什么位置 永远要清楚每个指针指向的位置中的内容是什么 指针与数组之间的关系掌握二维数组在内存中的存放方式,是理解二维数组的行指针和列指针的关键指针的应用做函数参数,传地址调用动态分配内存,实现动态数组,对于动态分配的内存,不要忘记在不使用时释放,作业,P286289,7.1(5),7.27.4P289,7.77.8,