版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1.深刻理解并掌握指针的概念。2.掌握指针变量的定义和应用,指针变量作为函数参数的用法。3.掌握指向数组的指针与指向数组的指针变量的概念、定义和应用。4.掌握指向字符串的指针的定义和应用。5.理解指向函数的指针的定义和用法。6.了解返回指针值的函数的定义和应用,了解它与指向函数的指针变量的区别。7.了解指针数组的定义和应用,掌握指针的指针的定义和应用,了解它指针数组与指向数组的指针变量的区别。第7章指针教学要求7.2指针的定义和使用1.指针的定义2.指针的操作3.针变量的初始化7.6指针实例7.7小结7.1地址和指针的概念7.3函数与指针1.指针作为参数2.返回指针的函数3.函数指针4.函数体内的指针
7.4数组和指针1.一维数组和指针2.多维数组和指针4.指针数组3.用指针处理串7.5动态存储管理1.存储管理函数2.函数的使用教学内容7.1地址和指针的概念1.变量与地址内存地址计算机内存的组织方式是把所有单元顺序排列,每个单元(字节)有一个顺序编号,称单元的地址或称为指针。数据的存取是在地址所标识的存储单元中进行的。内存单元的地址与内存单元中的数据是两个完全不同的概念。变量地址系统分配给变量所需的内存单元的起始地址。变量是对程序中数据存储空间的抽象7.1地址和指针的概念地址0……200020012002200320042005……内存…………程序中:inti
;floatk;ik内存中每个字节有一个编号--地址编译或函数调用时为其分配内存单元变量建立后意味着为其指定了一个存储位置,分配了存储变量值所需要的存储单元。变量值的存取通过变量在内存中的地址进行。任何变量在其存在期间总有一个固定的地址。变量地址可作为数据来操作。2.指针与指针变量指针一个变量的地址指针变量专门存放变量地址的变量7.1地址和指针的概念……200020012002200320042005……内存…………10变量i的地址变量i的内容指针变量p2000变量
p的内容指针变量整型变量i3.变量值的存取直接存取一般情况下,在程序中对变量进行存取操作,实际上就是对某个地址的存储单元进行操作,只不过变量和具体地址的联系由编译系统完成,用户无须知道变量在内存中的具体地址,这种直接按变量地址存取变量值的方式称“直接存取”。例scanf("%d",&i)的执行过程是这样的:找到变量i的起始地址2000;然后将键盘输入的值10送到内存单元2000和2001中。例printf("%d",i)的执行过程与scanf()很相似:首先找到变量i的起始地址2000,然后从2000和2001中取出其值,最后将它输出。7.1地址和指针的概念
间接存取指针变量也是变量,也有自己的存储地址,设指针变量p的存储单元中放的是变量i的地址,这时要访问i,可以先找到变量p的地址,从中取出i的地址,再去访问i的内容,这种通过变量p间接得到变量i的地址,然后再存取i的值的方式称“间接存取”。指针机制是一种间接访问其他对象的手段,目的是为了更加灵活方便地对数据进行操作。7.1地址和指针的概念指针变量p:变量i:变量i的地址(指针)变量i的值指向地址存入指针变量
说明指针变量的赋值操作有特殊的意义,由于指针变量的值只能是其他对象的地址,指针变量赋值就是把某个变量的地址赋给指针(这个过程称指针指向变量),因此,在程序运行的不同时刻,指针可以指向不同的变量。指针的应用使程序代码更简洁、更高效。但使用指针会降低程序的可读性,使用不当,可能造成严重的错误甚至引起系统崩溃。C语言编译器对指针的检查较弱,查错比较困难,要求程序员控制指针的有效性。从程序设计角度讲,更应该注意用普通变量无法实现只有用指针才能实现的功能和机制,如改变调用函数环境的值等。7.1地址和指针的概念7.2指针的定义、使用和运算1.指针的定义一般形式定义指针变量的一般形式
类型名*指针变量1,
*指针变量2,…;
说明指针变量的值为某个内存单元的地址值(地址编号)。值的类型均为整型。通过指针访问的对象有类型C语言规定,任何一个指针变量,只能指向具有某种特定类型的变量。定义中的类型名限定了指针变量可以关联的对象的范围。指针变量也是变量像普通变量一样可以赋值、取值等。但要满足类型约束。7.2指针的定义、使用和运算例如int*p1;定义指向整型变量的指针变量p1,也叫整型指针。float*p2;定义指向浮点型变量的指针变量p2,也叫浮点指针。char*s;定义指向字符的指针变量s,也叫字符指针。int*p,n;a[10];指针变量可和其他变量一起定义注意:①
'*'不可省略,否则变成普通变量的定义②
int*p1,*p2;与int*p1,p2;③
指针变量名是p1、p2,不是*p1、*p2;④
指针变量只能指向定义时所规定类型的变量;⑤指针变量定义后,其不确定,应用前必须先赋值。7.2指针的定义、使用和运算空指针定义空指针是一个特殊的指针,它的值是0说明0的符号常量
为NULL(在
stdio.h中定义),系统保证这个值不会是任何变量的地址。空指针对任何指针类型赋值都是合法的
一个指针变量具有空指针值,表示当前它没有指向任何有意义的值。用
p=NULL(比p=0)的形式为好。
p=NULL与未对
p赋值不同。
用途
□避免指针变量的非法引用
在程序中常作为状态比较7.2指针的定义、使用和运算void指针定义(void*)类型的指针叫通用指针,它可以指向任何变量,C语言允许直接把任何变量的地址作为指针赋给通用指针。说明通用指针不能做间接运算。因为编译系统无法确定它所指的变量的类型。使用通用指针需要进行类型强制转换。如:(void*)gp所指空间的数据是整型数据,p是整型指针,用下式转换:p=(int*)gp;7.2指针的定义、使用和运算指针转换实际上没有带来指针值的任何变化,一个指针类型代表一种观点。如通过整型指针的间接使用,永远把被指的东西看成整型。通用指针对被指的内容不提供任何有意义的类型信息,所以不能通过这种指针间接使用被指的内容。指针转换是一种观点转换,转换成哪种类型的指针,就用哪种类型去解释被指的变量。通用指针的惟一用途就是提供一个指针值。可以看成内存中一个字节一个字节的地址,没有具体类型。7.2指针的定义、使用和运算2.指针的操作指针赋值一个指针变量可以通过不同的方法获得一个确定的地址值,从而指向一个具体的对象。⑴
取地址运算(一元运算符&)和指针赋值&放在变量名前面。效果是取得该变量的地址,得到一个该变量类型规定的指针值;例如:若有定义intx,*p;把p=&x;把x的地址赋予p,即p指向了变量x。&运算符只能应用于变量和数组元素,不可用于表达式、常量或register变量。scanf要求输入项是地址值。若p=&x;则scanf("%d",&d)和scanf("%d",p)是等价的7.2指针的定义、使用和运算⑵
指针变量赋值通过指针变量赋值,把一个指针变量的地址值赋给另一个指针变量,使这两个指针变量指向同一地址。赋值号两边的指针变量的基类型必须一样。例如:p和q都是整型指针,p指向整型变量,则执行q=p以后,q也指向变量x⑶
通过标准函数获得地址值可以通过调用库函数malloc和calloc在内存中开辟动态存储单元,并把单元地址赋值给指针变量。7.2指针的定义、使用和运算间接运算(一元运算符"*")将一元运算符"*"
放在指针变量名前(也可是地址前),效果是由一个指针得到被它指向的变量,可以像使用普通变量一样使用该表达式。如:
intm,*p,*q,n;p=&n;q=&m;则:*q相当于n,*q相当于
m。间接使用:m=*p+*q作用与m=n+m相同。间接赋值:*p=5,作用与
n=5
相同。显然:*p出现在赋值号左边,代表指针所指的存储单元,*p
出现在赋值号右边,代表指针所指的存储单元的内容。7.2指针的定义、使用和运算【例7.1】设int*p,k=0;p=&k;分析下面几行程序执行的过程和结果。*p=100;
/*把整数100赋给k变量*/*p=*p+1;/*取p所指单元的值,加1后,赋给p所指的存储单元,k变为101*/*p+=1;/*等价于k+=1;k变为102*/++*p;/*等价于++k,k变为103*/(*p)++;/*等价于(k)++,k变为104*/7.2指针的定义、使用和运算移动指针将移动指针就是对指针变量加上或减去一个整数,或通过赋值运算使指针变量指向相邻的存储单元。因此,只有当指针指向一串连续的存储单元时,指针移动才有意义。对指针进行
+、-运算中,数字“1”不再代表十进制数“1”,而是
1
个存储单元长度,如果整型变量存储单元长度是
2
个字节,整型指针移动
1
个存储单元就是移动
2
个字节,双精度型指针加1
就是移动
8个字节,依此类推。7.2指针的定义、使用和运算程序中移动指针时,不论指针的基类型是什么,只需简单地加、减一个数而不必去管它的具体长度,系统将会根据指针的基类型自动确定位移的字节数。最常用的移动操作就是加一
(++)和减一
(--)操作。它们分别代表指针向地址值增大的方向移动一个存储单元和指针向地址值减少的方向移动一个存储单元。两个指向同一串连续单元的指针可以进行相减的运算,结果是两个指针之间元素的个数,可以通过赋值使两个指针指向同一个单元。7.2指针的定义、使用和运算【例7.2】设
p、q
是指向一片连续整数空间的整型指针,p
指向第一个整数,q
指向最后一个整数,a
是整型变量,顺序执行下面的操作,分析操作的意义和结果。++p;/*p后移1个存储单元,指向第二个整数*/q--;/*q前移1个存储单元,指向倒数第二个整数*/a=*p++;/*取出p所指单元内容赋给a,指针P后移1个存储单元,指向第三个整数*/a=q-p;/*计算q和p之间整型数个数将它赋给a*/p=q--;/*将p指向q所指单元,指针q前移1个存储单元,指向倒数第三个整数*/*p=10/2;/*计算10/2
的值,赋给p
所指单元*/7.2指针的定义、使用和运算指针比较两个指针指向同一串连续的存储单元时,可以在关系表达式中对其进行比较,判断指针的位置关系。如果两个指针变量的值相等,表示它们指向同一个存储单元。另外,还可进行是否是空(NULL)指针的判断。指针基类型对指针的相关操作有一定的约束和限制,主要有以下几方面:基类型使指针只能指向基类型定义的一类变量。(如:整型的指针只能指向整型变量)限制引用满足基类型的约束。限制指针移动的跨度。7.2指针的定义、使用和运算3.指针变量的初始化一般形式
[存储类型]数据类型*指针名=初始地址值说明指针变量在定义时可以用任何合法的指针(地址)值进行初始化。如下面的定义中包含了两个指针初始化部分:inta,*p=&a,*q=NULL如果在定义指针变量时没有进行初始化:全局变量和局部静态变量将被自动地初始化为空指针(0)。局部自动变量、寄存器变量将不自动初始化,这些变量建立后的值不确定,要有明确的变量关联后才能使用这些变量。7.2指针的定义、使用和运算初始地址值是赋给指针变量,而不是赋给目标变量。变量必须已说明过,类型应一致。如:inta,*p=&a;√是正确的。而int*p=&a;inta;×
是错误的。可以用已初始化指针变量作初值。例:inti,*p=&i;int*q=p;不能用auto变量的地址去初始化
static型指针。例:inti;staticint*p=&i;×
是错误的。7.2指针的定义、使用和运算【例7.3】用指针指向两个变量,通过指针运算,选出较小的。注意指针没有初始化情况下,如何开始使用程序:main(){inta,b,min,*pa,*pb,*pmin;pa=&a;pb=&b;pmin=&min;scanf("%d%d",pa,pb);printf("a=%db=%d\n",a,b);if(*pa>*pb)*pmin=*pb;elsepmin=*pa;printf("min=%d\n",min);}7.2指针的定义、使用和运算输入:2367运行结果:a=23b=67min=237.3函数与指针已知C语言中函数的一般形式为:类型名函数名([形参表])/*头部*/{说明部分/*函数体*/语句部}
由于C语言函数参数的传递机制是值传递,函数中形参的改变并不能改变对应实参的值,把数据从被调函数返回到调用函数的惟一途径是通过return语句返回函数值,限定了函数只能返回一个数据。在函数中使用指针,可以突破这些限制,函数定义中的各部分:参数、函数名、返回值及函数体内,都可以使用指针,本节讨论这方面的情况。7.3函数与指针1.指针作为函数参数说明若函数的形参为指针类型,调用该函数时,对应实参必须是基类型相同的地址值或已指向某个存储单元的指针变量。虽然实参和形参之间还是值传递方式,但由于传递的是地址值,所以形参和实参指到了同一个存储单元。函数中,通过形参操作的存储单元与实参所指的是同一单元,因此实参的值发生了改变。利用此形式,可以把两个或两个以上的数据从被调用函数中返回到调用函数。注意:被调用函数不能改变实参指针变量的值,但可改变实参指针变量所指的变量的值。7.3函数与指针【例7.4】交换两个变量值的函数voidswape(int*p,int*q){intt;/*t是整型变量,而非指针*/t=*p;*p=*q;*q=t;}main(){inta=10,b=20;printf("Beforechanged:a=%db=%d\n",a,b);swap(&a,&b);printf("Afterchanged:a=%db=%d\n",a,b);}7.3函数与指针运行结果:Beforechanged:a=10b=20Afterchanged:a=20b=10pq&a&bab1020在函数中使用指针变量所指对象值2.返回指针的函数返回指针函数的定义一个函数可以返回一个int型、float型、char型的数据,也可以返回一个指针类型的数据。返回指针函数的定义方法如下:
类型名*函数名([形参表])
/*头部*/{说明部分
/*函数体*/语句部分}函数体内的return语句的返回值必须是地址返回值可在任何有意义的引用处使用。7.3函数与指针【例7.5】返回两个整数形参中较大数的地址int*fun(int*,int*);main(){int*pmax,a,b;printf("Entertwonumber:");scanf("%d%d",&a,&b);pmax=fun(&a,&b);printf("a=%d,j=%d*pmax=%d\n",a,b,*pmax);}int*fun(int*p,int*q)/*返回值较大的单元的地址*/{if(*p>*q)returnp;elsereturnq;}7.3函数与指针运行结果:Entertwonumber:4525
a=45b=23*pmax=453.函数指针(略)什么是函数指针一个函数在编译时,被分配了一个入口地址,这个地址就称为该函数的指针。实际上函数名就是该函数的入口地址,就象数组名是数组所占存储空间的首地址一样。可以用一个指针变量指向一个函数,然后通过该指针变量调用此函数。(间接调用)。函数指针变量的定义形式
类型名(*指针变量名)(参数类型表);实例int(*p)(
);表示p是一个指向函数入口的指针变量,即用于专门存放函数的入口地址的变量,p在程中可以先后指向不同的函数。7.3函数与指针使用函数指针采用的步骤⑴
定义函数指针变量
定义格式为int(*p)();注意与int
*p()返回指针的函数的区别形如int(*p)()的说明可以这样分析:括号的优先级最高,括号内是具有*标志的指针,紧跟其后的(
)表示它指向函数,是函数指针。形如int*p()的说明可以这样分析:p中的两侧分别为*和(),()的优先级高于*,p先与()结合,显然是函数,前面有一个*,表示函数是指针型函数,指针的基类型是int。7.3函数与指针⑵
为函数指针变量赋值赋值
赋值格式为:p=函数名;赋值时只需给出函数名,不必给出参数。⑶
通过函数指针调用函数调用格式为:
c=(*p)(实参);其含义是调用由
p指向的函数,返回值赋给
c
注意调用时,由于是通过
p间接调用函数,所以没有参数类型说明的定义,编译系统无法进行参数类型的检查,可能出现极难发现的语义错误。7.3函数与指针【例7.6】通过函数指针调用系统库函数pintf,打印一行信息。main(){int(*p)();/*定义*/p=printf;/*赋值*/(*p)("***************\n");/*调用*/}函数指针可以作为函数的参数它的用途是把指针作为参数(某一个函数地址)传递到其他函数,即函数地址的传递。由于作为实参的函数指针每次所指向的函数不同,每次调用执行的功能可能完全不同。7.3函数与指针【例7.7】设计一个处理字符串的函数,在调用时每次实现不同的功能,第一次调用求出串长,第二次调用将字符串中的字母字符全部转换成大写。分析:我们使用C语言系统提供的库函数strlen完成第一次调用,第二次使用自定义的toup函数。它们的返回值类型都是int。7.3函数与指针程序7.3函数与指针intprocess(char
str[],int(*f)()){(*f)(str);}inttoup(char*s){while(*s!='\0'){if(*s>='a')&&(*s<='z'))*s=*s-'a'+'A';s++;}}#include<string.h>#include<stdio.h>inttoup(char*s);main(){chars[]="Testingstring";intlen;int(*p)()p=strlen;len=process(s,p);printf("len=%d\n",len)p=toup;process(s,p);printf("str=%s\n",s);}定义一个返回int的函数指针指针指向第一个函数通过process。调用指针所指函数指针指向第二个函数运行结果:len=14Testingstring4.函数体内的指针函数体内的指针有可能通过与指针形参的赋值等操作指向函数体外的存储单元,因此有可能改变调用函数环境中的值。返回值是指针的函数中,return语句后的表达式值是地址。7.3函数与指针7.4数组和指针C语言数组和指针的关系极其密切。第6章中介绍了用下标法引用数组元素的方法,这是每一种高级语言都提供的方法,通过指针访问数组元素的机制是C语言特有的,它能使目标程序具有较高的质量(占用内存少,运行速度快)。1.一维数组和指针一个数组中的每个元素都在内存中占用存储单元,都有相应的地址,指针变量既然可以指向变量,当然也可以指向数组和数组元素。数组名和地址的关系数组名在C语言中被处理成一个地址常量,即数组在内存中的起始地址(数组的指针)。在其生存期内不会改变,因此,不能给数组名重新赋值。但可以用在数组名后加一个整数的办法,依次表达数组中不同元素的地址。如:定义数组inta[10];则:a与&a[0]等价,a+l,&a[1]都是a[1]的地址*(a+3),*&a[3]都表示数组元素a[3]7.4数组和指针【例7.8】通过数组首地址引用数组元素,输出数组中全部元素。#include<stdio.h>main(){inti,a[]={1,2,3,4,5};for(i=0;i<5;i++)printf("%d",*(a+i));}7.4数组和指针通过指针引用一维数组元素数组元素可看作是相应基本类型的变量,因此,只要类型匹配,可以让指针指向数组中的任何元素。就是说通过指针引用一维数组元素需要一个指向数组元素的指针变量,它的基类型与数组元素的类型相同。例如:设
int*p,*q,*p1,a[10],
n;则:p=&a[0];或p=a使p指向数组a的起始地址;
q=&a[5]使
q
指向数组
a的第六个元素a[5]。7.4数组和指针过指针引用数组元素是C语言提供的一种高效数组访问机制。例如:设p指向数组a某元素,则:*p=5将对应数组元素赋值5;p+1或(p++)也是指针,指向下一个元素;p+5指向p所指元素的后第五个元素;p-1指向p所指元素的前一个元素。7.4数组和指针指针有效范围必须满足数组空间的限制,避免越界访问。【例7.9】通过指针引用数组元素,输出数组中全部元素。#include<stdio.h>main(){inti,a[]={1,2,3,4,5};int*pfor(p=a;p<a+5;p++)printf("%d",*p);}7.4数组和指针下标法与指针法比较通过改变指针变量的值来指向不同的单元方法称为指针法;直接使用下标引用数组元素的方法称为下标法(*(a+i)属于下标法)。用指针法,目标程序占用内存少、运行速度快因为每次访问数组元素时,p中正好是当前元素的地址。而例7.8的方法每次都要先计算(a+i)才能找到对应元素的地址。指针法不如下标法直观。要仔细分析指针变量的当前指向,才能判断是第几个元素。7.4数组和指针【例7.10】下面一段程序的第一个输出结果是ffc6,给出其余的结果。#include<stdio.h>main(){inti,a[]={1,2,3,4,5,6,7,8,9,0};int*p=aprintf("%x",p);printf("%x",p+9);printf("%d",*p+9);printf("%d",*(p+9));printf("%d",*++p+9);}7.4数组和指针运行结果:ffc6ffd810011通过带下标的指针变量引用一维数组元素[]运算符
[]不仅用作表示数组元素的记号,而且是一种运算符,表示要进行变址运算,在一个基地址上加上相对位移形成一个新地址。如:C编译器将a[i]处理成*(a+i),按数组首地址加上相对位移量得到元素的地址。7.4数组和指针5种表示数组元素的方法当p指向s数组的首地址时(p=s),数组元素s[i]有5种表示的方法:①s[i];②*(s+i);③*(p+i);④p[i];⑤若p=&s[i],可使用*ps是不可变的地址常量;p是可变的指针变量。实际上,p不一定要指向s的首地址,如果p=&s[2]即p指向s[2],则p+3指向s[5],p[3]引用的数组元素是s[5]。7.4数组和指针指针、数组和函数形参我们已经介绍了数组名作为函数参数和指针作为函数参数的情形,数组名作函数形参,实质上它是一个相应类型的指针参数。如:intfun(inta[])
与intfun(int*d)是完全等价的。实参可以是同类型的指针变量或地址。这种指针形参可以在函数中修改数组元素的值(直接操作数组)。传递从形参形式上看,传递一个数组名和一个简单变量地址的方式没有任何区别,函数中无法使用sizeof判定数组实际参数的元素个数。要在函数中知道数组元素个数(操作元素个数),应在参数中传入显式的整型值。7.4数组和指针【例7.11】下面一段程序调用一个函数打印数组的前面若干个数据。#include<stdio.h>intprint(int*,int);main(){inta[]={1,2,3,4,5,6,7,8,9,0};print(a,5);
/*打印a的前5个元素*/}intprint(int*x,intn){int*p;
for(p=x;p<x+n;p++)printf("%d",*p);}7.4数组和指针2.多维数组和指针用指针可以指向一维数组,也可以指向多维数组,但在概念和使用上,多维数组的指针比一维数组指针要复杂一些。我们以二维数组为例进行说明。二维数组和地址本节的概念是理解用指针操作多维数组的基础。我们结合实例说明,设有以下定义:inta[3][4],*p;在二维数组的讨论中,将一直使用该实例。二维数组名a应理解为一个行指针。(后面分析)p=a是不合法的,因为p和a的基类型不同。同样,对二维数组名a,也不能进行赋值运算。7.4数组和指针二维数组由若干个一维数组组成实例定义的二维数组a,是由3个元素组成的数组,这三个元素是a[0],a[1],a[2],而a[0],a[1],a[2]中的每一个又是一个由4个元素组成的一维数组。
二维数组名也是一个地址常量7.4数组和指针a[0]
:a[0][0],a[0][1],a[0][2],a[0][3];
a[1]
:a[1][0],a[1][1],a[1][2],a[1][3];
a[2]
:a[2][0],a[2][1],a[2][2],a[2][3];一维数组一维数组元素的地址*(a+0)*(a+1)*(a+2)等价所指对象三个一维数组数组名
行指针基类型为一维数组(一个整行),分别表示数组中第0,1,2行的首地址;
列指针基类型为int的指针。可用*a,*(a+1),*(a+2)分别表示;a+0a+1a+2对应的值相同二维数组元素的地址二维数组元素的地址可用每行的首地址来表示。a[0]是第0行第1个元素的地址,a[0]+1是第0行第1个元素的地址,…
a[1]是第1行第0个元素的地址,a[1]+1是第1行第1个元素的地址,…
a[i]的移动以元素为单位。实例中,&a[0][0]可用a[0]+0表示,&a[0][1]可用a[0]+1表示。元素a[i][j]的地址可用五种表达式求得:①&a[i][j]②(a[i]+j)③*(a+i)+j④&a[0][0]+4*i+j⑤a[0]+4*i+j7.4数组和指针二维数组元素的引用通过二维数组地址引用根据以上分析,二维数组a[n][m]的元素a[i][j]的引用也可以用下面5种方法:a[i][j]*(a[i]+j)*(*(a+i)+j)(*(a+i))[j]*(&a[0][0]+m*i+j)通过普通指针引用可以使用一个以数组元素的类型为基类型的指针依次引用二维数组的所有元素,因为这些元素在内存中是连续顺序存放的。实例中,可以用一个整型指针,依次引用所有数组元素,移动单位是整型数。7.4数组和指针【例7.12】输出二维数组a的所有元素。#include<stdio.h>voidprint(int*,int);main(){inta[3][4]={1,2,3,4,5,6,7,8,9,0,1,2};int*p=&a[0][0];print(p,3,4)}voidprint(int*p,intm,intn){int*q=p;for(;q<p+m*n;q++)printf("%d",*q);}7.4数组和指针运行结果:123456789012通过行指针引用行指针变量(或指向一维数组的指针变量)指向由n个元素组成的一维数组的指针变量行指针变量定义格式
数据类型(*指针变量)[n];实例说明设
int
a[3][2],(*ptl)[2];圆括号优先级最高,*
首先与ptl结合,说明ptl是一个指针变量;说明指针变量ptl的基类型是一个包含两个整型元素的数组;a
与ptl的基类型相同7.4数组和指针ptl=a;
是合法赋值;
ptl+1等价于
a+1,*(ptl+1)等价于
a[1]。当
ptl指向a
数组开头(即ptl=a;)时,可以通过下面的方法引用
a[i][j]:①*(ptl[i]+j)②*(*(ptl+i)+j)③(*(ptl+i)[j]④ptl[i][j]ptl是一个变量,值可变,a
是一个常量。7.4数组和指针二维数组名作函数实参数组、指针作函数实参、形参要求实参和形参类型匹配,赋值兼容(值传递)。
例如:当传递给函数的实参是二维数组元素的地址时,要求形参指针的基类型与数组元素的类型一致。同时,应传入维数信息给函数。如:fun(int*p,intm,intn)当调用函数的实参是二维数组的名字时,则要求函数指针为行指针,因为二维数组名的基类型是一个行,这时,函数首部可以是下面的几种形式之一:
①fun(int
a[M][N])②fun(int
a[][N])
③fun(int(*a)[N])7.4数组和指针实参与形参的对应若:inta[3][4];/*a为行指针常量*/int(*p1)[4]=a;/*p1为行指针变量*/int*p2=a[0];/*p2为列指针变量*/7.4数组和指针实参形参数组名a数组名intx[][4]数组名a指针变量int(*q)[4]指针变量p1数组名intx[][4]指针变量p1指针变量int(*q)[4]指针变量p2指针变量(*q)【例7.13】编写函数,输出二维数组a中最大值及所在的行号和列号。分析设
a
为整型数组,依题意,函数需返回多个值,从
return
返回最大值,行号和列号通过指针修改主程序保存行号和列号的变量值得到。使用行指针引用二维数组元素的方法编写程序。7.4数组和指针程序7.4数组和指针#include<stdio.h>#defineM3#defineN4intmax(int(*p)[n],intint*,int*)main(){inta[M][N]={1,2,3,4,5,6,7,8,9,0,1,2};introw,col,mxmx=max(a,M,&row,&col);printf("max=%d\n",mx)printf("row=%d\n",row)printf("col=%d\n",col);}intmax(p,m,prow,pcol)int(*p)[N];intm,*prow*pcol;{intmx,i,j;mx=*(p[0]);*prow=0;*pcol=0;for(i=0;i<m;i++)for(j=0;j<N;j++)if(*(p[i]+j)>mx){mx=*(p[i]+j);*prow=i;*pcol=j;}return(mx);}结果:max=9row=2col=03.使用指针处理字符串字符指针定义
char*变量名;用途经常用来指向字符数组的元素,访问字符数组里的字符内容。最常见的情况是用字符指针指向一个字符串或字符数组。7.4数组和指针初始化初始化方式使字符指针指向字符串如:char*str="Programming";含义有三点:定义一个字符指针str;建立一个字符串常量;确定指针初值,使它指向定义的字符串常量用赋值运算使指针指向一个字符串如:char*str;str="stringone";与第一种初始化方法完全等价。7.4数组和指针用字符数组指向字符串和用字符指针指向的字符串的区别如:char*str="OK";与chara[]="OK";str是指针变量,可多次赋值;a是数组名,表示地址常量,不能赋值,且a的大小固定,预先分配存储单元。a的元素可重新赋值,不能通过str间接修改字符串常量的值。类型、大小不同。str是指针,a是数组。它们的存储表示如图所示。7.4数组和指针OKstr:a:OK【例7.14】分析下面一段程序的输出。本题是一组关于++和*运算符的练习,目的是熟悉字符指针对字符串的控制操作。main(){chars[]="ABCDEF";char*str=s;printf("%c",*str);
printf("%c",*str++);
7.4数组和指针运行结果:取str的内容,str指向'A'一元运算符
++和
*
的优先级一样,从右向左结合,结果为*(str++),(str++)求值的结果是
str
的值,此时
str指向
'A',取内容使用此值,而str
的值加
1;指向
'B';ABCDEF\0sAAstrprintf("%c",*++str);
printf("%c",(*str)++);
printf("%c\n",++*str);
printf("%s\n",s);}运行结果:AAABEDEF运行结果:AACCEABEDEF7.4数组和指针ABCDEF\0strsC++str运算,指向'C',再取str的内容取str的内容,然后,str的内容加1,'C'修改为'D'ECD先对str的内容运算加1操作,'D'修改为'E',然后,取str的内容E取s的内容,s指向'A'使用字符指针处理字符串和字符数组字符指针用于输入输出字符串输出时,输出项可以是字符串或字符数组,也可以是指向字符串的字符指针;字符串输入时,输入项可以是字符数组,也可以是字符指针。字符指针必须已经指向确切的、足够大的存储空间,以便输入字符能放在它所指的具体单元中。常规字符串处理的指针实现【例7.15】计算字符指针所指字符串的长度。分析:逐个统计串中的字符个数,直到遇到串结束符
'\0'为止。7.4数组和指针7.4数组和指针#include<stdio.h>intlength(char*);main(){char*str1="goodMorning!";charstr2[]="goodAfternon!";char*str3="";printf("%slen:%d\n",str1,length(str1));printf("%slen:%d\n",str2,length(str2));printf("str3len:%d\n",length(str3));}intlenth(char*str){intn=0;while(*str++!='\0')n++;return(n)}7.4数组和指针7.4数组和指针运行结果:GoodMorning!len:13GoodAfternoon!len:15str3len:0【例7.16】编写函数,实现字符串反转的操作分析:本例实现的方法是利用两个字符指针
s和
p,分别指向应该进行交换的两个字符,s从字符串的首字符向末字符移动,p
从字符串的末字符向首字符移动,直到两个指针相遇,如果原字符串长度是奇数,结束时
s=p,如果原字符串长度是偶数,结束时s>p。如下图所示。7.4数组和指针7.4数组和指针\0sp#include<stdio.h>voidrevstr(char*);main(){charstr[]="goodMorning!";/*用数组表示*/printf("str=%s\n",str);revstr(str);printf("str=%s\n",str);}voidrevstr(char*s){char*p=s,c;while(*p)p++;/*到结束符停止*/p--;/*退回最后一个有效字符*/while(s<p)/*用交换并移动指针*/{c=*s;*s++=*p;*p--=c;}}7.4数组和指针7.4数组和指针运行结果:str=GoodMorning!str=!gninroMdooG【例7.17】编写函数,实现字符串连接的操作分析
设函数为
scat(char*dest,char*src),将
src
所指字符串的内容连接到
dest
所指字符串后面,覆盖
dest
串末的结束符
'\0',要求
dest
所指字符串的空间足够容纳合并后的字符串。程序
7.4数组和指针7.4数组和指针#include<stdio.h>void*scat(char*,char*);main(){chars1[20]="12345";/*空间要足够大*/char*str,*s2="abcdefg";printf("1:%s%s\n",s1,s2);str=scat(s1,s2);printf("2:%s\n",str);}char*scat(char*dest,char*src){char*p=dest;while(*p)p++;/*移到第一串的结束符处*/while(*p++=*src++);/*接在结束符处*/return(dest);}7.4数组和指针7.4数组和指针运行结果:1:12345abcdefg
2:12345abcdefg【例7.18】编写函数,实现字符串比较的操作分析
函数为scomp(char*s1,char*s2),将s1和s2进行比较:若s1串>s2串,函数返回正数;若s1串=s2串,函数返回0;若s1串<s2串,函数返回负数。从两个字符串的初始位置开始,将对应位置上的字符进行比较,直到遇到对应位置上有不等的字符或其中的一个串到达了串尾,将两串当前字符的值相减,作为函数值返回。程序7.4数组和指针7.4数组和指针#include<stdio.h>voidscomp(char*,char*);main(){chars1[20]="comparestring",s2[20];intresult;gets(s2);result=scomp(s1,s2);if(result>0)printf("%s>%s\n",s1,s2);elseif(result==0)printf("%s>%s\n",s1,s2);elseprintf("%s<%s\n",s1,s2);}charscomp(char*s1,char*s2){while((*s1==*s2)&&(*s1)&&(*s2)){s1++;s2++;}return(*s1-*s2);}7.4数组和指针7.4数组和指针4.指针数组概念指针本身也是数据,也可以作为数组的元素,如果数组中的元素均为指针类型数据,则称这个数组为指针数组。指针数组中每一个元素都相当于一个指针变量。一维指针数组的定义形式类型名*数组名[数组长度];7.4数组和指针7.4数组和指针实例int*pa[5];定义一个有5个整型指针为数组元素的一维数组。用它表示另一个整型数组元素大小关系,如右图。pa&a[1]&a[4]&a[2]&a[3]&a[0]a45613367应用字符指针数组表示的字符串数组第6章中介绍了用二维字符数组表示字符串数组的方法,包括它在定义时的初始化方法,现在,我们可以定义字符型指针数组并通过赋初值来构成字符串数组。例如:char*pa[10];pa是一个字符指针数组,类型是(char*)。指针数组最多的用途是用来表示一组字符串,它使字符串的处理更加灵活。如用字符指针数组p。组织大型程序的错误信息,可将错误号与pa下标对应,方便组织错误信息的输出。7.4数组和指针7.4数组和指针字符指针数组的初始化以下面字符指针数组为例:char*day[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sta"};*day[]定义7个元素的字符指针数组,建立7个字符串常量,对数组元素进行了初始化。字符指针数组与二维字符数组比较如:charc1[][6]={"red","green","blue"};char*c[]={"red","green","blue"};7.4数组和指针7.4数组和指针7.4数组和指针7.4数组和指针c[0]c[1]c[2]指针数组表示的字符串连续存储。可能有很多空的单元。red\0green\0blue\0分散存储。靠指针联系,可能出现字符串丢失。c1[0]red\0c1[1]green\0c1[2]blue\0二维数组表示的字符串【例7.19】编写程序,比较若干种高级语言的名字,并将它们按字典顺序排序后输出。分析如果想避免排序过程中过多的数据交换,本例的思路应该对你会有启发。排序过程中,交换指针变量的值,这种方法比交换数组元素的值效率要高。程序7.4数组和指针7.4数组和指针7.4数组和指针7.4数组和指针charsort(char*a[],intn){char*temp;inti,j,k;for(i=0;i<n-1;i++){k=i;for(j=i+1;j<n;j++)
if(strcmp(a[k],a[j])>0)k=j;if(k!=i){temp=a[i];a[i]=a[k];a[k]=temp;}}}#include<stdio.h>void*sort(char*a[],int);main(){char*ln[]={"PAS","BAS","FOR","TC","ADA"};inti,m;m=sizeof(ln)/sizeof(char*);sort(ln,
m);for(i=0;i<m;i++)printf("%s\n",ln[i]);}运行结果:
ADABAS
FOR
PASTC解释7.4数组和指针7.4数组和指针
ln0PAS1BAS2FOR3TC4ADA排序前指针数组
ln0PAS1BAS2FOR3TC4ADA排序后指针数组指向指针的指针概念指针变量的内容是数据地址,如果数据本身是个指针,该指针变量就是指针的指针。例如:每一个指针数组元素都是其他数据的地址,指向指针数组元素的指针就是指向指针的指针。指向指针数据的指针变量的形式char
**p;p的前面有两个*号,根据*运算符的结合性,**p相当于*(*p),根据指针变量的定义形式,*p是一个字符指针,p是指向这个指针的变量。7.4数组和指针7.4数组和指针stringp*p【例7.20】编写程序,使用指向指针的指针打印字符串数组的字符串。#include<stdio.h>main(){char*name[]={"PAS","BAS","FOR","TC","ADA"};char**pinti,m;m=sizeof(name)/sizeof(char*);for(p=name;p<name+m;p++)printf("%s\n",*p);}7.4数组和指针运行结果:PASBASFORTCADA命令行参数(时间紧不讲)main函数的参数以往的程序中,主函数main()都使用其无参形式,实际上,main函数可以有参数,指针数组的一个重要应用是作为main函数的形参。main函数的调用是由系统调用的,调用的方法是在操作系统命令状态下输入main所在的执行文件的名字(命令),也可在Windows图形系统中通过点击图标或菜单进行,但也等价于在命令状态下发出一条命令。7.4数组和指针命令行参数main函数的参数跟在调用命令后,由操作系统传递给主函数,这种机制叫命令行参数。如typeabc.txt命令中,abc.txt是type的参数在TC中,调试带有命令行参数的程序时,参数由Options菜单的Arguments项输入。命令行的一般形式
命令名
参数1
参数2
…
参数nC语言如何看待命令行看作由空格分隔的若干字符串,每个字符串看作是一个命令行参数。由第一个字符串(命令本身)开始从0编号。程序执行时,每个参数被处理成字符串,按照规定方式使用它们。C语言通过主函数的参数获取命令行参数。7.4数组和指针主函数原型
main(intargc,char*argv[])形参说明argc:命令行上字符串总数(包括命令名本身)argv[]:字符指针数组,存储命令行的每个字符串,argv[0]是命令名,argv[1]是第一个参数串,argv[2]是第二个参数串,依此类推。argc,argv只是形参名称,可以是其他名称,但类型一定要正确。7.4数组和指针【例7.21】打印命令行参数及命令本身。程序main(intargc,char*argv[]){for(i=0;i<argc;i++)printf("%s%c\n",argv[i],i<argc-1?'
':'\n');}运行程序在TC集成环境中(设该程序名为test.c)选择“Options”菜单的“Arguments”选项,输入“aaabbbccc”,按Ctrl-F9。在DOS环境中,执行文件名为test.exe7.4数组和指针输入命令行:test
aaa
bbb
ccc
运行结果:
test
aaa
bbb
cccargv0test1aaa2bbb3ccc4【例7.22】从命令行输入三个数,分别是整型数、实型数和字符串。分析使用命令行输入参数,要注意输入值类型应与程序中的类型要求一致。程序7.4数组和指针#include<stdio.h>main(intargc,char*argv[]){intd;floatf;charstr[10];if(argc<4){printf("pramerterstooless");exit(0);从程序中退出*/}sscanf(argv[1],"%d",&d);sscanf(argv[2],"%f",&f);sscanf(argv[3],"%s",&str);printf("%s%f%d\n",str,f,d);}7.4数组和指针/*从字符串中读取数据,格式和
scanf
的要求一样*/若执行文件名test1.exe则输入命令行:
test1
123
4.56
abcd
运行结果:
abcd
4.560000
123argvargv[0]test1argv[1]123argv[2]4.56argv[3]abcd47.5动态存储管理在任何一个变量使用前,都必须完成关于存储方面的有关安排,包括存放位置、占据多少存储单元,这个工作叫存储分配。存储空间的静态分配方法是根据程序提供的信息,在变量定义时即可静态计算出需要的空间,预先分配好空间。如外部变量、局部静态变量在编译时即可决定。局部变量的大小也是静态确定的(函数在被调用时所需的空间在编译时可确定)。
静态分配的好处是处理、实现方便而简单,执行效率高,但对某些问题的空间要求预估不准确,可能引起空间浪费或空间不足的情况出现。如数组排序程序中,定义的数组空间可以存储500个数组元素,实际问题也许只有10个元素。对在写函数时无法预知的空间需求,如果能够根据运行中的动态信息,确定空间需求的实际值,分配适当大小的空间,就可以提高内存空间利用的效率。C语言提供了动态分配的机制,通过指针掌握对动态分配块的操作。存储空间的动态管理包括分配、释放回收两方面的工作,由操作系统的“动态存储管理系统”完成。C语言提供几个标准函数完成与该系统的交互工作。7.5动态存储管理1.C语言标准动态存储管理函数标准动态存储管理函数原型在标准头文件“stdlib.h”中描述。存储分配函数malloc()void*malloc(unsignedsize)形参说明size:无符号整数;(void*):返回值类型,为通用指针,需要通过类型强制转化成特定的指针类型。功能分配一块大小为
size个字节的存储块,返回指向这个块的指针,如果存储申请失败,返回空指针。7.5动态存储管理【例7.23】对数组进行排序时,动态输入元素个数,动态分配空间可用如下方法解决:intn,*data……scanf("%d",&n);data=(int*)malloc(n*sizeof(int))if(data==NULL){……}/*出现问题的处理*/……
/*正常处理*/
注意空间大小要使用
sizeof
函数进行计算;结果强制转换后才能赋值使用;调用
maloc
函数后,一定要检查返回值;得到的空间使用时不允许越界。7.5动态存储管理带初始化的存储分配函数calloc
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026中国体育法律行业市场现状供需分析及投资评估规划分析研究报告
- 2026中国消费电子行业市场现状及发展趋势分析报告
- 2026食品加工设备行业政策影响研究与市场分析
- 2026汽车零部件供应商行业市场深度调研及发展趋势和投资前景预测研究报告
- 2026中国细胞培养肉技术商业化瓶颈及竞争格局与监管政策适应策略报告
- 2026日本光学仪器行业市场供需格局投资评估规划分析研究报告
- 2026中国新型显示材料行业市场现状发展趋势及投资风险评估研究报告
- 2026能源投资发展分析及融资策略研究报告
- 2026中国移动互联网开发行业竟然研究及投资价值与全面规划报告
- 2026中国物流行业反垄断合规与竞争策略调整报告
- 企业内部食堂管理手册(标准版)
- 江西新华发行集团有限公司招聘笔试题库2026
- 电子制造技术
- 北森行测题库及答案2026
- 文书模板-单位无法派出足够的人员参加培训情况说明
- 智能灌溉自动化灌溉设备运行管理方案
- 第四版国际压力性损伤溃疡预防和治疗临床指南解读 4
- 2024年压力性损伤诊疗及护理规范
- GB/T 45845.1-2025智慧城市基础设施整合运营框架第1部分:全生命周期业务协同管理指南
- 合作种植天麻协议书
- 小麦种植技术试题及答案
评论
0/150
提交评论