第08章指针.ppt
第8章:指 针,学习的意义,指针是语言中广泛使用的一种数据类型。运用指针编程是语言最主要的风格之一。C程序设计中使用指针可以:使程序简洁、紧凑、高效 有效地表示复杂的数据结构 例如链表 动态分配内存 得到多于一个的函数返回值 能象汇编语言一样处理内存地址,从而编出精练而高效的程序,学习指针是学习语言中最重要的一环,能否正确理解和使用指针是我们是否掌握语言的一个标志,可以说不懂C语言中的指针就不懂什么是C语言。,8.1 指针与指针变量的概念,1、内存地址内存中存储单元的编号,教室,教室有容量,存储单元有大小(字节单元、字单元),注意:内存单元的地址与内存单元中的数据是两个完全不同的概念。,2、变量地址系统分配给变量的内存单元的起始地址,程序中:int i;float k;,内存中每个字节有一个编号-地址,i,k,编译或函数调用时为其分配内存单元,变量是对程序中数据存储空间的抽象,注意:在TC或BC下,系统将给变量i分配2字节的单元,而VC下将是4字节的单元!,3、指针与指针变量指针:一个变量的地址指针变量:专门存放变量地址的变量,2000,指针变量,指针,4、&与*运算符 含义,含义:取变量的地址单目运算符结合性:自右向左,含义:取指针所指向变量的内容单目运算符结合性:自右向左,两者关系:互为逆运算理解,i_pointer&i&(*i_pointer)i*i_pointer*(&i),i_pointer=&i=&(*i_pointer)i=*i_pointer=*(&i),i_pointer-指针变量,它的内容是地址量*i_pointer-指针的目标变量,它的内容是数据&i_pointer-指针变量占用内存的地址,直接访问:按变量名来存取变量值间接访问:通过存放变量地址的变量去访问变量,例 i=3;-直接访问,3,例*i_pointer=20;-间接访问,20,8.2 指针变量的定义和引用,1、变量值的存取方法,10,例 k=i;k=*i_pointer;,-直接访问,-间接访问,2、指针变量与其所指向的变量之间的关系,i,*i_pointer,&i,i_pointer,i=3;,*i_pointer=3;,3、指针变量的定义,一般形式:,存储类型 数据类型符*变量名;,合法标识符,表示定义指针变量不是*运算符,指针的目标变量的数据类型,指针变量本身的存储类型,注意:int*p1,*p2;与 int*p1,p2;指针变量名是p1,p2,不是*p1,*p2 指针变量只能指向定义时所规定类型的变量 指针变量定义后,变量值不确定,应用前必须先赋值,例 int*p1,*p2;float*q;static char*name;,例 int i;int*p=,例 void main()int i;static int*p=.(),不能用auto变量的地址去初始化static型指针,4、指针变量的赋值,初始化赋值,存储类型 数据类型*指针名=初始地址值;,赋给指针变量,不是赋给目标变量,变量必须已说明过类型应一致,例 int i;int*p=,用已初始化指针变量作初值,例 int a;int*p;p=,赋值语句赋值,例,int a=20;,int*p,*q;,p=,q=p;,20,2000,2000,例 int*p=,指针变量赋值的几种错误方法:,变量a的定义在后,对a的引用超出了a的作用域,例 int a;int*pi=,pc不能指向非字符型变量,例 int a;int*p;*p=,赋值语句中,被赋值的指针变量p的前面不能再加“*”说明符,例 int*p;p=2000;,不允许直接把一个数赋值给指针变量,例 int a;static int*p=,不能用auto变量的地址去初始化static型指针,注意:一个指针变量只能指向同类型的变量如果给指针赋值时,=号右边的指针类型与左边的指针类型不同,则需要进行类型强制转换。int a;int*pi;char*pc;pi=/pc也指向了a,即pi和pc的值都是a的地址,5、零指针与空类型指针零指针:(空指针)定义:指针变量值为零 表示:int*p=0;,p指向地址为0的单元,系统保证该单元不作它用表示指针变量值没有意义,#define NULL 0int*p=NULL:,p=NULL与未对p赋值不同 用途:避免指针变量的非法引用在程序中常作为状态比较,例 int*p;.while(p!=NULL).,void*类型指针 表示:void*p;使用时要进行强制类型转换,表示不指定p是指向哪一种类型数据的指针变量,例 char*p1;void*p2;p1=(char*)p2;p2=(void*)p1;,6、引用指针变量,int a;int*p=,格式:*指针变量,int a,*p;p=,输出结果:a=11,*p=11,可写成(*p)+,而不是*p+,注意:程序在利用指针间接引用内存单元时,将按照指针变量定义时所指向的数据类型来解释引用的内存单元。,【例1】不同类型的指针操作同一内存变量,#include void main()unsigned short a;unsigned short*pi=,2000,2000,pc可操作单元,F0,F0,00,输出结果:a=F000,【例2】输入两个数,并使其从大到小输出,#include void main()int*p1,*p2,*p,a,b;scanf(%d,%d,运行结果:a=5,b=9max=9,min=5,.,.,5,2006,9,2008,2006,2008,2006,重点强调:指针变量必须先定义,后赋值,最后才能使用!没有赋值的指针变量是没有任何意义的,也绝对是不允许使用的。指针变量只能指向定义时所规定类型的变量。指针变量也是变量,在内存中也要占用一定的内存单元,但所有类型的指针变量都占用同样大小的内存单元,其具体大小取决于所使用的编译环境,如在BC3.1和VC6.0下为4个字节,在TC2.0下为2个字节。,1、指针变量的加、减运算,8.3 指针和地址运算,指针可以参与加法和减法运算,但其加、减的含义绝对不同于一般数值的加减运算。如果指针p是这样定义的:ptype*p;,并且p当前的值是ADDR,那么:,p n 的值=ADDR n*sizeof(ptype),int*pi;char*pc;long*pl;pi=(int*)1000;pc=(char*)1000;pl=(long*)1000;,pi+;/pi的值将是1002(假设int型占2byte)pi-=2;/pi的值将是998pc+;/pc的值将是1001pc-=2;/pc的值将是999pl+;/pl的值将是1004pl-=2;/pi的值将是996,注意:两个指针相加没有任何意义,但两个指针相减则有一定的意义,可表示两指针之间所相差的内存单元数或元素的个数,在后面的学习中就会体会到。,2、指针变量的关系运算,若p1和p2指向同一数组,则 p1p2 表示p1指的元素在后 p1=p2 表示p1与p2指向同一元素若p1与p2不指向同一数组,比较无意义p=NULL或p!=NULL,1、数组的指针,8.4 指针与数组,数组的指针其实就是数组在内存中的起始地址。而数组在内存中的起始地址就是数组变量名,也就是数组第一个元素在内存中的地址。,例:short int a10;,int a10;int k;for(k=0;k 10;k+)ak=k;/利用数组下标,int a10;int k;for(k=0;k 10;k+)*(a+k)=k;/利用数组的指针,2、指向数组的指针变量,8.4 指针与数组,如果将数组的起始地址赋给某个指针变量,那么该指针变量就是指向数组的指针变量。,例:short int a10,p=a;,注意:p+1指向数组的下一个元素,而不是简单地使指针变量p的值+1。其实际变化为p+1*size(size为一个元素占用的字节数)。例如,假设指针变量p的当前值为2000,则p+1为2000+1*2=2002,而不是2001。,char str10;int k;for(k=0;k 10;k+)strk=A+k;/也可写成*(str+k)=A+k,char str10;int k;char*p;p=str;for(k=0;k 10;k+)pk=A+k;/也可写成*(p+k)=A+k,char str10;int k;char*p;p=str;for(k=0;k 10;k+)*p+=A+k;/相当于*p=A+k;p+;,下面是对数组元素赋值的几种方法,它们从功能上是等价的,执行完后,p仍然指向数组str的首地址,执行完后,p指向数组元素str9的下一内存单元,注意:数组名是地址常量,切不可对其赋值,也不可做+或-运算。例如:int a10;如果在程序中出现a+或a-则是错误的。,【例】数组元素的引用方法,void main()int a5,*pa,i;for(i=0;i 5;i+)ai=i+1;pa=a;for(i=0;i 5;i+)printf(*(pa+%d):%dn,i,*(pa+i);for(i=0;i 5;i+)printf(*(a+%d):%dn,i,*(a+i);for(i=0;i 5;i+)printf(pa%d:%dn,i,pai);for(i=0;i 5;i+)printf(a%d:%dn,i,ai);,例:int a=1,2,3,4,5,6,7,8,9,10,*p=a,i;数组元素地址的正确表示:(A)&(a+1)(B)a+(C)&p(D)&pi,数组名是地址常量p+,p-()a+,a-()a+1,*(a+2)(),例:注意指针变量的运算,void main()int a=5,8,7,6,2,7,3;int y,*p=,6,输出结果:5 6,void main()int i,*p,a7;p=a;for(i=0;i 7;i+)scanf(%d,p+);printf(n);for(i=0;i 7;i+,p+)printf(%d,*p);,例 注意指针的当前值,p=a;,指针变量可以指到数组后的内存单元,8.5 指针与字符串1、字符串表示形式 用字符数组实现,例:void main()char string=“I love China!”;printf(“%sn”,string);printf(“%sn”,string+7);,运行结果:I love China!China!,用字符指针实现,例:void main()char*string=“I love China!”;printf(“%sn”,string);string+=7;while(*string)putchar(string0);string+;,字符指针初始化:把字符串首地址赋给string char*string;string=“I love China!”;,*string!=0,运行结果:I love China!China!,2、字符指针变量与字符数组char*cp;与 char str20;str由若干元素组成,每个元素放一个字符;而cp中存放字符串首地址 char str20;str=“I love China!”;()char*cp;cp=“I love China!”;()str是地址常量;cp是地址变量 cp接受键入字符串时,必须先开辟存储空间,例 char str10;scanf(“%s”,str);()而 char*cp;scanf(“%s”,cp);(),改为:char*cp,str10;cp=str;scanf(“%s”,cp);(),3、字符串与数组关系字符串用一维字符数组存放字符数组具有一维数组的所有特点数组名是指向数组首地址的地址常量数组元素的引用方法可用指针法和下标法数组名作函数参数是地址传递等区别存储格式:字符串结束标志赋值方式与初始化输入输出方式:%s%c,char str=“Hello!”;()char str=“Hello!”;()char str=H,e,l,l,o,!;()char*cp=“Hello”;()int a=1,2,3,4,5;()int*p=1,2,3,4,5;(),char str10,*cp;int a10,*p;str=“Hello”;()cp=“Hello!”;()a=1,2,3,4,5;()p=1,2,3,4,5;(),scanf(“%s”,str);printf(“%s”,str);gets(str);puts(str);,4、字符指针变量使用注意事项,当字符指针指向字符串时,除了可以被赋值之外,与包含字符串的字符数组没有什么区别。,char str10,*pstr;pstr=12345;/pstr指向12345strcpy(str,pstr);/将pstr所指向的字符串复制到数组str中pstr=str;printf(The Length of str is:%dn,strlen(pstr);/输出字符串的长度5,注意“野指针”操作:,如果一个指针没有指向一个有效内存就被引用,则被称为“野指针”操作或空指针赋值。野指针操作尽管编译时不会出错,但很容易引起程序运行时表现异常,甚至导致系统崩溃。,char*pstr;char str8;scanf(%s,pstr);/野指针操作,pstr没有指向有效内存strcpy(pstr,hello);/野指针操作pstr=str;/pstr指向数组str所对应内存单元的首地址strcpy(pstr,0123456789);/不是野指针,但会造成数组越界,为什么“野指针”操作会给程序运行带来极大的不确定性,甚至造成系统崩溃呢?,pstr 0001,指针pstr所占内存,char*pstr;pstr=a;,极其危险!,为什么“野指针”赋值会给程序运行带来极大的危险?,再次提醒:指针变量只有与内存建立联系以后才可使用,否则将造成程序运行异常,甚至导致系统死机!,【例】利用字符指针实现字符串的倒序排列,#include#include void main()char str200,ch;char*p,*q;gets(str);/读取一个字符串 p=str;/p指向字符串的首地址 q=p+strlen(p)-1;/q指向字符串的末地址 while(p q)/交换p和q各自指向的字符 ch=*p;/将p所指向的字符保存在ch中*p+=*q;/先将q指向的字符赋给p指向的字符单元,p再增1*q-=ch;/先将ch的值赋给q指向的字符单元,q再减1 printf(%sn,str);,运行结果:I love China!anihC evol I,p=str;,q=p+strlen(p)-1,!,I,a,n,L,i,o,h,v,C,e,End,程序执行过程演示:,8.6 指针与动态内存分配,1、静态内存分配,当程序中定义变量或数组以后,系统就会给变量或数组按照其数据类型及大小来分配相应的内存单元,这种内存分配方式称为静态内存分配。,int k;/系统将给变量k分配2个字节(VC下分配4个字节)的内存单元char ch10;/系统将给这个数组ch分配10个字节的内存块,首地址就是ch的值,静态内存分配一般是在已知道数据量大小的情况下使用,例如,要对10个学生的成绩按降序输出,则可定义一个数组:int score10;用于存放10个学生的成绩,然后再进行排序。,如果事先并不知道学生的具体人数,编写程序时,人数由用户输入,然后再输入学生的成绩。那有如何如何处理呢?,int n;int scoren;scanf(%d,如何解决?,动态内存分配,2、动态内存分配,所谓动态内存分配是指在程序运行过程中,根据程序的实际需要来分配一块大小合适的连续的内存单元。程序可以动态分配一个数组,也可以动态分配其它类型的数据单元。动态分配的内存需要有一个指针变量记录内存的起始地址。C语言中动态内存分配其实就是使用一个标准的库函数malloc,其函数的原型为:,void*malloc(unsigned int size);,说明:size这个参数的含义是分配的内存的大小(以字节为单位)。返回值:失败,则返回值是NULL(空指针)。成功,则返回值是一个指向空类型(void)的指针(即所分配内存块的首地址)。,2、动态内存分配,int n,*pscore;scanf(%d,/可对pscore所指向的单元进行其它处理,例如:根据学生人数来建立数组的问题可以用动态内存分配来解决,其方法如下:,pscore 0100,共n*sizeof(int)个字节内存单元,关于malloc的使用有几点需强调一下:malloc前面必须要加上一个指针类型转换符,如前面的(int*)。因为malloc的返回值是空类型的指针,一般应与右边的指针变量类型一致。malloc所带的一个参数是指需分配的内存单元字节数,尽管可以直接用数字来表示,但一般写成如下形式:分配数量*sizeof(内存单元类型符)malloc可能返回NULL,表示分配内存失败,因此一定要检查分配的内存指针是否为空,如果是空指针,则不能引用这个指针,否则会造成系统崩溃。所以在动态内存分配的语句的后面一般紧跟一条if语句以判断分配是否成功。,3、动态内存释放,计算机中最宝贵的资源就是内存。因此需要动态分配内存的程序一定要坚持“好借好还,再借不难”的原则。释放动态内存的函数free其原型为:,void free(void*block);,例:free(pscore);,注意:调用malloc和free函数的源程序中要包含stdlib.h或malloc.h或alloc.h(在TC、BC下)。malloc和free一般成对出现!,【例】编写程序先输入学生人数,然后输入学生成绩,最后输出学生的平均成绩、最高成绩和最低成绩。,#include#include#include void main()int num,i;int maxscore,minscore,sumscore;int*pscore;float averscore;printf(input the number of student:);scanf(%d,printf(input the scores of students now:n);for(i=0;i maxscore)maxscore=pscorei;if(pscorei minscore)minscore=pscorei;sumscore=sumscore+pscorei;averscore=(float)sumscore/num;,printf(-n);printf(the average score of the students is%.1fn,averscore);printf(the highest score of the students is%dn,maxscore);printf(the lowest score of the students is%dn,minscore);free(pscore);/释放动态分配的内存,运行结果:input the number of student:4input the scores of students now:45 76 88 94-the average score of the students is 75.8the highest score of the students is 94the lowest score of the students is 45,8.7 指针作为函数的参数,参数传递方式:传值调用和传址调用传值调用:将参数值传递给形参。实参和形参占用各自的内存单元,互不干扰,函数中对形参值得改变不会改变实参的值,属于单向数据传递方式。传址调用:将实参的地址传递给形参。形参和实参占用同样的内存单元,对形参值得改变也会改变实参的值,属于双向数据传递方式。,运行结果:b=0,运行结果:b=5,为什么结果不一样呢?,void swap(int x,int y)int temp;temp=x;x=y;y=temp;void main()int a,b;scanf(%d,%d,【例】将数从大到小输出,5,9,5,5,9,COPY,void swap(int x,int y)int temp;temp=x;x=y;y=temp;void main()int a,b;scanf(%d,%d,【例】将数从大到小输出,5,9,运行结果:5,9,值传递,void swap(int*p1,int*p2)int p;p=*p1;*p1=*p2;*p2=p;void main()int a,b;int*p_1,*p_2;scanf(%d,%d,5,9,2000,2002,5,9,COPY,5,【例】将数从大到小输出,void swap(int*p1,int*p2)int p;p=*p1;*p1=*p2;*p2=p;void main()int a,b;int*p_1,*p_2;scanf(%d,%d,5,9,2000,2002,5,9,【例】将数从大到小输出,运行结果:9,5,地址传递,void swap(int*p1,int*p2)int*p;p=p1;p1=p2;p2=p;void main()int a,b;int*p_1,*p_2;scanf(%d,%d,运行结果:5,9,5,9,2000,2002,COPY,2000,地址传递,2000,2002,【例】将数从大到小输出,例.A班有m(mfloat fun(int*p,int t);/float fun(int p,int t);,void main()int a20,b20,m,n,i;float avg1,avg2,sub;scanf(%d,float fun(int*p,int t)float ave,sum=0;int i;for(i=0;it;i+)sum+=pi;ave=sum/t;return ave;,8.11 带参数的main函数,命令行 在操作系统状态下,为执行某个程序而键入的一行字符,命令行一般形式,命令名 参数1 参数2 参数3 参数n,参数之间以一个或多个空格隔开,例如:C:copy.exe source.cpp c:bakprg.cpp,有3个字符串参数的命令行,带参数的main函数的形式,void main(int argc,char*argv),命令行中参数个数,形参名任意,元素指向命令行参数中各字符串首地址,argc和argv与命令行参数之间的对应关系,命令行参数的传递,第一个参数:main所在的可执行文件名,【例1】输出命令行参数,/test.cppvoid main(int argc,char*argv)while(argc 1)+argv;printf(%sn,*argv);-argc;,void main(int argc,char*argv)while(argc-0)printf(%sn,*argv+);,1.编译、链接test.cpp,生成可执行文件test.exe2.在DOS状态下运行(test.exe所在路径下),例如:C:TC test.exe hello world!,运行结果:hello world!,运行结果:test hello world!,本章小结,1.指针是语言中一个重要的组成部分,使用指针编程有以下优点:提高程序的编译效率和执行速度。通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯。可以实现动态的存储分配。便于表示各种数据结构,编写高质量的程序。,2.指针的运算取地址运算符&:求变量的地址取内容运算符*:表示指针所指的变量赋值运算,把变量地址赋予指针变量同类型指针变量相互赋值把数组,字符串的首地址赋予指针变量把函数入口地址赋予指针变量,本章小结,加减运算 对指向数组,字符串的指针变量可以进行加减运算,如p+n,p n,p+,p-等。对指向同一数组的两个指针变量可以相减。对指向其它类型的指针变量作加减运算是无意义的。,关系运算 指向同一数组的两个指针变量之间可以进行大于、小于、等于比较运算。指针可与0比较,p=0表示p为空指针。,本章小结,3.与指针有关的各种说明和意义见下表,