好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

项目项目中指针的应用.ppt

103页
  • 卖家[上传人]:cl****1
  • 文档编号:587623204
  • 上传时间:2024-09-06
  • 文档格式:PPT
  • 文档大小:415KB
  • / 103 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 1 l技能目标技能目标2 l知识目标知识目标3 l项目任务与解析项目任务与解析4 l 主要内容主要内容6.1 6.1 任务任务1313:使用指针查找学生最高、最低成绩:使用指针查找学生最高、最低成绩6.2 6.2 任务任务1414:使用指针查找成绩不合格的学生:使用指针查找成绩不合格的学生6.3 6.3 任务任务1515:使用指针对学生的成绩进行排序:使用指针对学生的成绩进行排序6.4 6.4 必备知识与理论必备知识与理论6.5 6.5 扩展知识与理论扩展知识与理论5 •1. 问题描述•对一门课的成绩,查找最高、最低成绩,用指针来实现•2. 具体实现•P154-P155程序•3. 知识分析•使用指针可以指向数组,这时使用数组可以完成的查询算法也可以用指针来实现6.1 6.1 任务任务1313:使用指针查找学生最高、最低成绩:使用指针查找学生最高、最低成绩6 •1. 问题描述•对一门课的成绩,查找成绩不合格的学生,用指针来实现•2. 具体实现•P155程序•3. 知识分析•使用指针可以指向数组,这时使用数组可以完成的查询算法也可以用指针来实现6.2 6.2 任务任务1414:使用指针查找成绩不合格的学生:使用指针查找成绩不合格的学生7 •1. 问题描述•对一门课的成绩进行排序,用指针来实现。

      •2. 具体实现•P155-P156程序•3. 知识分析•使用指针可以指向数组,这时使用数组可以完成的排序算法也可以用指针来实现6.3 6.3 任务任务1515:使用指针对学生的成绩进行排序:使用指针对学生的成绩进行排序8 6.1 6.1 指针基础指针基础 指指针针是是CC语语言言中中种种广广泛泛使使用用的的数数据据类类型型利利用用指指针针变变量量可可以以表表示示各各种种数数据据结结构构;;能能很很方方便便地地使使用用数数组组和和字字符符串串;;并并能能像像汇汇编编语语言言一一样样处处理理内内存存地地址址,,从从而而编编出出精精练练而而高高效效的的程程序序指指针针极极大大地地丰丰富了C语言的功能富了C语言的功能6.4.1 地址与指针地址与指针1. 程序实体的内存地址程序实体的内存地址 一个程序一经编译,在其执行过程中,就会一个程序一经编译,在其执行过程中,就会为变量、数组以及函数分配存储空间这些变量、为变量、数组以及函数分配存储空间这些变量、数组、函数都称为程序实体,也具有某一种数据数组、函数都称为程序实体,也具有某一种数据类型这些被分配了内存空间的程序实体,都具类型这些被分配了内存空间的程序实体,都具有自己的内存地址。

      有自己的内存地址6.4 必备知识与理论9 #include int main(void){int i1,i2;float f1,f2;double d1,d2;printf("数据大小:int,%d;float,%d;double,%d\n",sizeof(i1),sizeof(f1),sizeof(d1)); /* 输出类型宽度 */printf("%ld,%ld\n",&i1,&i2);/* 输出变量地址 */printf("%ld,%ld\n",&f1,&f2);printf("%ld,%ld\n",&d1,&d2); return 0;}10 解释:(1)变量存储空间的分类顺序:先声明的后分配;撤销的顺序与之相反:先建立的后撤销这种机制就称为栈机制,好像往一个只能允许进出一个盘子的桶里放盘子,先放进的后拿出在C语言程序中,局部变量就是被分配在栈区的,并且是以高端为栈底建立的所以后建的变量的地址小2)每个变量只有一个地址,但占用的空间不同空间的大小因类型而异同时,数据的存储方式也不同,如实型数据采用浮点存储,而整型数采用定点存储11 2. 指针的概念 从根本上说,目标程序是按照地址访问这些程序实体的。

      C语言不仅提供了用变量名访问内存数据的能力,还提供了直接使用内存地址访问内存数据的能力这个内存地址就称为指针它好像一个指路标指向要访问的内存数据12 6.4.2 指针变量及其定义指针变量及其定义 变量可以用来存放数值(如整数、实数等),也可以用来存放地址(另一个变量的地址),这种专门用于存储指针(地址)的变量就称为指针变量1. 指针变量的定义 在定义指针变量时,需要用指针声明符*表示此变量不是一般的变量,而是用来存放其他变量地址的指针变量由于每一个变量都是属于一个特定类型的,因此在定义指针变量时,需要声明该变量的类型,以便能通过指针能正确访问特定类型的数据定义一个指针的语法格式为: 基类型标识符 * 指针变量名;13 int * pi1,*pi2;double *pd1,*pd2;说明:(1)“基类型”,就是指针要指向的数据的类型2)定义指针变量时,在指针变量名前加符号“*”称为指针声明符,用于说明它后面的名字是一个指针变量名例如语句:int i1,i2,*pi1,*pi2;定义了两个数据变量i1和i2,还定义了两个指向int类型的指针pi1和pi214 2. 指针变量的初始化和赋值和其他变量一样,指针变量也可以初始化。

      可以用变量的地址对指针变量进行初始化,但必须注意:该变量的类型必须和指针变量的基类型相同也可以用一个指针变量的值给另一个指针变量赋值,但它们应该具有相同的基类型例如:int i1,i2,i3; int *pi1=&i1,*pi2=&i2;pi1=&i3;pi2=pi1;注意,不要将一个变量的值赋给指向它的指针变量例如pi1=i1;或pi2=i1;都是错误的应该是将变量的地址赋给指向它的指针变量如:pi1=&i1;或pi2=&i1;15 6.4.3 指针变量的引用指针变量的引用1. 引用指针变量 当一个指针变量被初始化或被赋值后,它就指向一个特定的变量这时,就可以使用指针访问它所指向的内存空间在C语言中使用指针访问它所指向的内存空间的方法是在指针变量名前加一个“*”号例如int i,*pi; pi = &i; *pi=5; 此处的“*”是“指针运算符”又称为“间接访问运算符”,它作用于指针变量pi表示指针变量pi所指向的存储空间,即变量ipi相当于变量i16 2. 使用指针变量需要注意的问题(1)使用指针,首先应当区分指针变量与它所指向的存储单元之间的不同include int main(void){ int i1=10,i2=20,*p,*p1,*p2;p1=&i1;p2=&i2;printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2);p=p1;p1=p2;p2=p; printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2);}17 i1&i2i2&i1p1p2交换前指针指向交换后指针指向18 #include int main(void){ int *p1,*p2,i1=10,i2=20,i;p1=&i1;p2=&i2;printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2);i=*p1;*p1=*p2;*p2=i;printf (“i1=%d,*p1=%d;i2=%d,*p2=%d\n”,i1,*p1,i2,*p2);}19 i1&i2i2&i1p1p2ii=*p1*p1=*p2①①②②③③*p2=i20 (2)可以引用指针所指向的单元的值,但应注意,指针必须经过初始化或赋值,使它有确定的值,指向有效的程序实体,才能正确地引用其指向的单元的内容。

      如果指针变量未经赋值,它并不是没有值,而是一个未知或不确定的值,它所指向的存储单元也是未知的或不确定的在未知和不确定的存储单元中存储的可能是无用数据,也可能是系统的重要数据读取无用数据的操作是无意义的;有些非法入侵者也可以利用这种方法获取系统的重要数据;而向存储有重要数据的位置写入新的数据可能会造成系统的潜在危险,甚至可能造成系统瘫痪所以,通常把没有指向有效程序实体的指针称为无效指针21 3. 关于运算符“*”和“&”的讨论指针和所指向的存储单元(变量)之间可以进行两种运算:“*”和“&”假定有定义:int x=10;int *px=&x;则通过“*”和“&”运算,可以建立如下相当关系:px ~ &x: 指针px的值就是变量x的地址px ~ x:: *px就是px所指向的变量,即xx ~ px ~ x: 先对x取地址,就是x的指针&x,再对指针进行间接访问,就是变量xpx ~ &x ~ px:先对指针进行间接访问运算就得到x,再对x取地址,也就是x的指针它们都是一元运算符,具有自右向左的结合性,并且优先级高于算术运算而低于自增/自减运算符例如,*px++,相当于*(px++),即取与px所指单元相邻的前一个单元中的内容。

      px)++ ,为先取px所指单元的值,然后将其加122 6.1.4 指针的运算指针的运算指针不能进行下列运算:• 两个指针相加、相乘、相除、移位;、• 不同指针之间的相减、赋值指针可以进行的运算:• 移动;• 同类型指针的比较和相减运算;23 1. 指针移动通过移动指针可以改变指针指向的内存位置移动指针的方法有两种:(1)同类型指针间赋值同类型指针间赋值,就是使一个指针指向另外一个指针所指向的位置24 #include int main(void){int i1,i2;int *pi1=&i1,*pi2=&i2;printf(“指针间赋值前:pi1=%ld,pi2=%ld\n",pi1,pi2);pi1=pi2; /* 指针间赋值 */printf(“指针间赋值后:pi1=%ld,pi2=%ld\n",pi1,pi2); return 0;}运行结果:指针间赋值前:pi1=215052,pi2=215048指针间赋值后:pi1=215048,pi2=21504825 (2)指针与小整数相加、减#include int main(void){int i,*pi;double d,*pd;pi=&i;pd=&d;printf("pi=%ld,pi+1=%ld\n",pi,pi+1);printf("pd=%ld,pd+1=%ld\n",pd,pd+1);return 0;}运行结果:pi=215052,pi+1=215056pd=215040,pd+1=215048通过指针与小整数的相加减去引用它所指向的数据单元是很危险的。

      26 地址指针变量pipi+1pd+1pd地址指针变量pi2pi1赋值前赋值后27 2. 同类型指针间的比较和相减运算 指针间的的比较和相减运算,主要用于指向同一数组的两个元素的两个指针之间,例如有一整型数组a,如已定义:int a[10];int*p1=&a[1],*p2=&a[5]; 如p1的值为2000,p2的值为2016,则p2-p1的值不等于16,而等于两个数组元素下标之差,即两个元素之间的元素个数 指向同一数组不同元素的两个指针之间的关系运算,是比较它们之间的地址大小如果p1>p2,表示p1指向下标值小的元素,p2. 指向下标值大的元素如果两个指针相等,表明它们指向同一数组元素28 6.4.5 指向指针变量的指针与多级指针指向指针变量的指针与多级指针 一个指针变量可以指向一个整型数据,或一个实型数据,或一个字符型数据,也可以指向一个指针型数据这就是指向指针的指针指向指针的指针形成二级指针一级指针pipddoublel类型int类型ppdppi二级指针29 二级指针的定义如下:int i;int *pi=&i;int **ppi=&*pi;也可以这样定义:int i,*pi,**ppi;pi=&i;ppi=π30 6.4.6 指向指向void类型的指针类型的指针 ANSI C标准允许使用空基类型(void)指针,即不指定指针指向一个固定的类型,定义形式为:void *p; 这表示指针变量p不指向一个确定的类型数据。

      它的作用仅仅是用来存放一个地址,而不能指向非void类型的变量例如下面的写法是不对的:int *p1;void *p2;int i;p2=&i;printf (″%d″,*p2);31 如果确实需要将&i的值放在p2中,应先进行强制类型转换,使之成为(void *)类型,在将p2赋值给p1时,同样应进行类型转换例如:p2=(void *)&i;p1=(int *)p2;printf (″%d″,*p1);注意:执行p1=(int *)p2后,p2本身的类型并没有改变只是在向p1赋值前先进行转换,生成一个(int *)型的临时数据赋给p1也可以定义一个返回基类型为void的指针的函数:void *f (int x,int y)函数f带回的是一个基类型为“空”的地址如果想在主调函数中引用此地址,也需根据需要进行强制类型转换例如:int a,b;char *p;void *f(int,int);p=(char*)f(a,b);32 6.4.7 数组元素的指针引用数组元素的指针引用1. 一维数组元素的指针引用 数组元素在内存是连续存储的,并通过下标引用数元素下标增1,数据改变一个数据单元位置。

      在学习了指针之后,已经看到定义了一个指针后,可以通过指针的移动来引用连续的存储单元前已说明,数组名代表数组首元素的地址,即是一个指针那么,数组元素和指针之间有什么关系呢? C语言的数组元素有两种引用方式:下标引用方式和指针引用方式请看下面的例子33 #include int main(void){int a[5]={1,3,5,7,9},i,*p;printf("下标法:");for(i=0;i<5;i++)printf ("%d,",a[i]);printf ("\n数组名法:");for(i=0;i<5;i++)printf ("%d,",*(a+i));printf ("\n指针变量法:");for(p=a;p

      对一个二维数组a来说,可以把它看成是由下列元素组成的一维数组: a[0],a[1],a[2],…,a[i],… 这里,a[i]既是是广义一维数组a的一个元素,又是一个一维数组a[i]的名字,是指向a[i]的起始元素的指针常量36 数组元素a[0][0]a[0][1]a[0][2]a[1][0]a[1][1]二级(行)指 针a(a+1)一级(列)指 针a[0] (&a[0][0])a[1] (&a[1][0])a[0]+1 (&a[0][1])a[0]+2 (&a[0][2])a[1]+1 (&a[1][1])a[1]+2 (&a[1][2])元 素地 址a[1][2]一级(列)指针引用*a[0] *a[1] *(a[0]+1) *(a[0]+2)*(a[1]+1) *(a[1]+2) 二级(行)指针引用**a**(a+1)*(*a+1)*(*a+2)*(*(a+1)+1)*(*(a+1)+2)37 由图中可以得到如下结论:(1)二维数组a,可以看成由两个元素组成的向量,这两个元素一个称为a[0],一个称为a[1]。

      按照上节的讨论,数组名a指向a[0],a+1指向a[1]而a[0]和a[1]本身又都是一维数组,它们分别由{a[0][0],a[0][1],a[0][2]}和{a[1][0],a[1][1],a[1][2]}组成由于数组名是指针,所以a[0]和a[1]都是一级指针,它们的基类型(所向的存储单元中的数据的类型)是int类型而数组a是由两个一级指针组成的数组,这两个指针具有相同的类型(指向基类型为int的指针变量),并且a指向a数组首元素(即a[0]的首地址---&a[0])所以,a是一个指向指针的指针,即二级指针也就是说,一个二维数组名是一个二级指针2)从图中可以看出,a[0]的值是&a[0][0](例如1245032),a的值是a[0]地址,实际上a[0]地址也与&a[0][0](即1245032)相同38 (3)从图中可以看出,一个二维数组的元素可以用下标法引用,也可以用一级指针引用,还可以用二级指针引用有下面的引用关系:a[i][j] ~ *(a[i]+j) ~ *(*(a+i)+j)#include #define N 2#define M 3int main(void){int i,j,a[N][M]={{10,11,12},{20,21,22}};for(i=0;i

      现在讨论用什么样的存储方式处理多个字符串比较合适假定有多个字符串,按照数组与指针的关系组合,可以有如下几种定义方式:char str[M][N];char *str[N];char **str;41 在这3种声明中,每一种声明都使用了两个类型声明符(char、[ ]和*)在这种情况下,如何确定所声明的变量的含义呢?一般说来,这些类型声明符实际上是运算符在声明中的应用它们虽然在这里不是作为运算符使用,但在优先级和结合性上还是要按照运算符的规则与名字相结合因此可以采用下面的方法来理解: 首先按照优先级看哪个声明符应当与名字相结合在优先级相同的情况下按结合性看哪个声明符与名字结合 剩下的声明符是补充说明下面结合图来对上述3种表示方式的意义进行说明假定图中需要存储的字符串有下列5个:“C”42 “C++”“Visual BASIC”“Java”“Ada”(1)char str[M][N];① 在这个声明中,有两个相同的数组类型说明符[],按照由左向右的结合性,可以首先确定str是一个大小为M的向量(一维数组)② 对于数组自然要说明类型由剩下的char和[N]补充说明,这个数组是长度为N的字符数组类型,即它的每个元素都是长度为N的字符数组。

      所以,这个语句定义的str是字符数组类型的数组,或者说str是二维字符数组如果在定义数组时进行以下的初始化:char str[5][13]={ “C”,“C++”,“Visual BASIC”,“Java”,“Ada”};则数组中的存储情况如图所示用这种方式存储的几个字符串占有连续的存储空间43 (2)char *str[N];① 在这个声明中,有两个不相同的数组类型声明符[]和*其中数组类型声明符的优先级别高,可首先确定str是一个大小为N的一维数组② 剩下的char和*补充声明:这个数组的每个元素都是字符类型指针所以,这个语句定义的str是字符指针数组其存储方式如图所示用这种方式存储的几个字符串长度可以不同,不一定占有连续的存储空间3)char **str;① 在这个声明中,有两个相同的类型声明符*按照由右向左的结合性,可以首先将后面的一个*与名字结合,得出结论:str是一个指针② 对于指针就要声明指向什么由剩下的char和*补充声明:这个指针是指向字符指针的所以,这个语句定义的str是指向字符指针的指针,即指向字符的二级指针44 char str[M] [N]① []的结合为“左向右”故str是一个大小为M的数组②str的元素是字符数组ulBS\0AsiVI C\0 \0 \0 \0\0\0\0\0\0C\0 \0\0 \0 \0 \0\0\0\0++C\0 \0a \0 \0 \0\0\0\0vaJ\0 \0\0 \0 \0 \0\0\0\0adA\0 \0str[0]二维字符数组strstr[1]str[2]str[3]str[4]占有连续存储空间u lBS\0AsiVI C\0C\0++Ca \0vaJ\0adA字符指针数组str不一定占有连续存储空间char * str [N]① []优先,故str是一个大小为N的数组②str的元素是字符指针str[0]str[1]str[2]str[3]str[4]45 u lBS\0AsiVI C\0C\0++Ca \0vaJ\0adA二级字符指针str不一定占有连续存储空间char * * str ① str是一个指针②str指向字符指针*str46 #include #define M 5#define N 13int main(void){char *s1="abc",*s2="wxyz",*s3="ijklmn";char str1[M][N]={"C","C++","Visual BASIC","Java","Ada"};char *str2[M]={s2,str1[2],s1,"ijklm"};char **str3=&s2;int i;printf("\n ");printf("&s1=%ld,&s2=%ld,&s3=%ld\n",&s1,&s2,&s3);printf("\n ");47 printf("&s1=%ld,&s2=%ld\n",&s1,&s2);printf("\n ");for(i=0;i<5;i++) printf("&str1[%d]=%ld:str1[%d]=%s\n ",i,&str1[i],i,str1[i]);printf("\n ");for(i=0;i<5;i++)printf("&str2[%d]=%ld:str2[%d]=%s\n ",i,&str2[i],i,str2[i]); printf("\n "); for(i=0;i<5;i++)printf("str3+%d=%ld:*(str3+%d)=%s\n ",i,str3+i,i,*(str3+i));printf("\n ");return 0;}48 u lBS\0AsiVI C\0 \0 \0 \0\0\0\0\0\0C\0 \0\0 \0 \0 \0\0\0\0++C\0 \0a \0 \0 \0\0\0\0vaJ\0 \0\0 \0 \0 \0\0\0\0adA\0 \0\0 \0 \0 \0\0\0\0adA\0 \0临时字符数组①②③ulBS\0AsiVI C\0C\0++Ca \0vaJ\0adAstr[0]字符指针数组strstr[1]str[2]str[3]str[4]str[4]临时字符指针变量①②③49 #include #include int main ( ){ char *s0="Java",*s1="Visual BASIC",*s2="C";char *string[3]={s0,s1,s2};char *p;int i;printf ("排序前:\n");for(i=0;i<3;i++) printf("&string[%d]=%ld->%s\n",i,&string[i],string[i]);printf ("&s0=%ld->%s\n",&s0,s0); ("&s1=%ld->%s\n",&s1,s1);printf ("&s2=%ld->%s\n",&s2,s2);printf("\n");50 if (strcmp (string[0],string[1])>0){p=string[0];string[0]=string[1];string[1]=p;}if (strcmp (string[0],string[2])>0){p=string[0];string[0]=string[2];string[2]=p;}if (strcmp (string[1],string[2])>0){p=string[1];string[1]=string[2];string[2]=p;}printf ("排序后:\n");for(i=0;i<3;i++)printf("&string[%d]=%ld->%s\n",i,&string[i],string[i]);printf ("&s0=%ld->%s\n",&s0,s0);printf ("&s1=%ld->%s\n",&s1,s1);printf ("&s2=%ld->%s\n",&s2,s2);printf("\n");return 0;}51 (2)本程序实际上说明了冒泡排序的过程,并用strcmp(string[I],string[j])进行两个字符串的比较。

      有了本例的基础,读者可以编写出顺序输出n个字符串的程序,也可以采用循环结构实现字符串的排序算法,例如用选择法,起泡法等 上例中,指针数组string的每一个元素都是一个地址(指向一个字符串),如果另外设一个指针变量p,用来指向string数组中的元素,那么这个p就是一个二级指针(又称“双重指针”)52 采用二级指针的字符串冒泡排序程序include #include #define N 3int main ( ){ char *string[N]={ "Java","Visual BASIC","C"};char **p=&string[0];char *ptemp;int i,j;printf ("排序前:\n");for(i=0;i%s\n",i,*(p+i));printf("\n");for(j=0;j<=N-2;j++) for(i=0;i<=N-j-1;i++)if(strcmp(*(p+i),*(p+i+1))>0){ptemp=*(p+i);*(p+i)=*(p+i+1);*(p+i+1)=ptemp;}printf ("排序后:\n");for(i=0;i%s\n",i,*(p+i));printf("\n");return 0;}54 6.4.9 内存的动态分配与动态数组的建立内存的动态分配与动态数组的建立1. 动态分配的概念通过前面的讨论已经知道,使用指针方式进行内存空间的访问是非常危险的。

      于是可以设想,如果能在程序运行时能为指针分配一个连续的存储空间,将会使得指针的使用变得安全C语言提供了这一功能这一功能称为内存的动态分配前面讨论过,全局变量是在编译时在内存静态存储区分配的,非静态的局部变量是程序运行时在栈区自动分配的,而为指针进行内存空间的动态分配是在程序运行过程中在自由内存区——堆(heap)区分配的堆可以形成比较大的存储空间,供动态分配使用动态分配的特点是,可以由程序员控制,在需要时分配,在不需要时释放,还可以根据需要改变所分配存储空间的大小这些功能重要通过stdlib.h库中的4个函数实现 55 函数原型返 回功能说明void *malloc(unsigned int size);成功:返回所开辟空间首地址失败:返回空指针向系统申请size字节的堆存储空间void *calloc(unsigned int num, unsigned int size);成功:返回所开辟空间首地址失败:返回空指针按类型申请num个size大小的堆空间void free(void *p);无返回值释放p指向的堆空间void *realloc(void *p,unsigned int size);成功:返回新开辟空间首地址失败:返回空指针将p指向堆空间变为size大小56 说明:(1)viod *p是说明p是void*类型指针,声明其基类型是未确定的类型,可以用强制转换的方法将其转换为任何别的类型。

      例如double *pd=NULL;pd=(double *)calloc(10,sizeof(double));表示将向系统申请10个连续的double类型的存储空间,并用指针pd指向这个连续的空间的首地址并且用(double)对calloc()的返回类型进行转换,以便把double类型数据的地址赋值给指针pd2)使用sizeof的目的是用来计算一种类型的占有的字节数,以便适合不同的编译器3)由于动态分配不一定成功,为此要附加一段异常处理程序,不致程序运行停止,使用户不知所措通常采用这样的异常处理程序段:if(p==NULL) /* 或者if(!p)*/{printf(“No enough memry!\n”);exit(1);}57 2. 动态数组的建立 通过前面的学习,可以建立这样的概念:数组就是用于存储同类型数据的连续空间在C语言中,这个空间可以用下标形式表示,也可以用指针形式表示动态数组指的是不在程序开始时定义固定大小的数组,而是在需要时建立数组,不需要时释放在下面的例子中的动态数组用指针形式引用58 #include #include #define STUDENT_NUM 3int main ( ){ double *p=NULL,sum=0.0;int i;p=(double *)calloc(STUDENT_NUM*sizeof(double)); if(!p){printf(“Memory request failed!\n”);exit(1);59 }printf(“请输入学生的成绩:”);for(i=0;i

      6.5 扩展知识与理论61 6.5.1 指针参数与函数的地址传送调用指针参数与函数的地址传送调用 指针作参数,就是传送地址的值,并且要求在实参与形参之间传送类型相同的数据的地址,其中包括同样的级别而就形式而言,形参与实参之间的关系各如图所示几种数组名变量地址指针变量指针变量数组名实参形参传送地址62 1. 简单变量地址传送#include int main(void){void s *p1,int *p2);int a=3,b=5;printf("交换前:a=%d,b=%d\n",a,b);s);/* 简单变量地址传送 */63 printf("交换后:a=%d,b=%d\n",a,b);return 0;}void s *p1,int *p2)/* 用指针接收变量地址 */{int temp;temp=*p1,*p1=*p2,*p2=temp;}64 2. 数组地址传送 传输数组地址,有可能使主调函数和被调函数在同一个数组上进行操作,避免了传送数组实体所造成的程序效率不高下面介绍几种数组地址的传输方式1)数组名→数组名传输 数组名到数组名之间的数组地址传输方式。

      65 #include #define N 10int main(void){ void ArrMax(int [],int * ,int n);int array[N]={1,8,10,2,-5,0,7,15,4,-5};int max,*p=&max;ArrMax(array,p,N);printf (“max=%d”,max);66 return 0;}void arrMax(int arr[],int *pt,int n){ int i;*pt=arr[0];for (i=1;i*pt)*pt=arr[i];}运行结果如下:max=1567 说明: 本例的主函数调用函数main()时,传送了三个参数:• 函数名:在函数ArrMax()中使用下标方式引用主函数中的数组array• 指向变量max的指针:用于在函数ArrMax()中引用主函数中的变量max,把通过与array中的每个元素比较,始终在max中放最大的元素• 和数组大小:用于控制重复结构的循环次数68 (2)数组名→指针传送 在上述程序中,也可以将函数ArrMax()的形参改用指针。

      程序如下:#include #define N 10int main(void){ void ArrMax(int *,int * ,int n);int array[N]={1,8,10,2,-5,0,7,15,4,-5};int max,*p=&max;ArrMax(array,p,N);69 printf (“max=%d”,max);return 0;}void ArrMax(int *arr,int *pt,int n){ int i;*pt=arr[0];for (i=1;i*pt)*pt=arr[i];}执行结果还是:max=1570 3. 字符指针参数int stringlen(const char *str){len=0;while(*str++) len++;return len;}71 说明:(1)在项目五中具有相同功能的函数为int stringlen(char str[]){int i=0,len=0;while(str[i++])len++;return len;}由于指向字符的指针与字符数组类型相同,所以,*str++与str[i++]等价。

      但是采用指针使程序更为简洁,不需要定义数组72 (2)可能读者会问,这里会不会出现乱用指针的问题答案是:不会因为在参数传递时,实参是要计算长度的字符串名,即字符数组名而该字符数组是已经被分配类空间的这个函数中字符指针的活动范围是从字符串的起始地址到字符串结束标志符之间,当某一次循环中遇到字符串结束标志符’\0’时,*str的值为0(’\0’的ASCII码为0),while中表达式为假,循环中止,因而不会出现超界问题3)这个函数在参数表中使用了“const”,它表明在本函数的执行过程中,要将字符串“锁定”,即不允许对字符串作任何改变因为,函数的功能只是求字符串的长度73 void *stringcopy(char *dest, const char *src){char *temp=dest;while(*dest++=*src++);return temp;}说明:(1)由于目标字符串要改变,而源字符串只是复制,所以仅用const修饰源字符串2)temp是一个指向字符的指针,用它来保存目的串的首地址因为在复制的过程中,随着一连串的自增操作,dest的值不再指向目的串的首地址74 下面再看一个函数。

      int stringcomp(const char *s1, const char *s2){for(;*s1==*s2;s1++,s2++)if(!*s1) return 0;return *s1-*s2;}75 说明:(1)这个函数由一个for循环结构组成循环的初始条件就是s1指向进行比较的一个字符串的首地址,s2指向另一个字符喘的首地址这个条件已经在参数传递时实现了,所以在for结构中初始条件确省2)该函数中for循环的条件是:*s1==*s2,即两个指针的当前应用相等,也即对应位置的字符相同,就再比较下一个字符3)for循环中的修正表达式为:s1++,s2++从而保证两个指针分别指乡各自的字符串中的相同位置4)表达式if(!*s1) return 0的意思是当s1指向字符‘\0’时,函数返回0实际上,由于这个条件表达式是在for结构中的,而for结构的重复条件是*s1==*s2,即两个字符指针当前指向的字符相等所以当s1指向字符串结束符时,s2也一定是指向了字符串结束符,也就是两个字符串都结束,用返回0表示两个字符串完全相同76 #include″stdio.h″#define N 80void delchar (char *p,char x){ char *q=p;for (;*p!='\0';p++)if (*p!=x)*q++=*p;*q='\0';}int main(void){ 77 void delchar (char *p,char x);char c[N],*pt=c,x;printf("enter a string:");gets (pt);printf ("enter the character deleted:");x=getchar( );delchar (pt,x);printf ("The new string is:%s\n",c);}运行情况如下:enter a string: I have 50 Yuan. enter the character deleted:0 The new string is:I have 5 Yuan.78 本程序的功能:在主函数中,定义字符数组c并使pt指向c;从键盘输入字符串和需要删去的字符;然后调用delchar()函数。

      形参指针变量p和被删字符x由main函数中实参pt和x传递到函数delchar()在delchar()中实现删除字符请注意在delchar()中并未定义另一个数组,而是在c数组中删去指定的字符在刚开始执行此函数时,指针变量p和q都指向c数组中图6.17第一个字符当*p不等于x时,*p赋给*q,然后p和q都自加1,即同步下移,见图6.17(a)中p′和q′当某一次*p==x时,不执行“*q++=*p;”语句,q不自加1,而p继续加1,p与q不再指向同一元素,q仍指向c[8],而p指向c[9],见图6.17(b)中的p′和q′在执行下一次循环时由于(*p!=x)为真,执行“*q++=*p;”语句,将c[9]值赋给c[8],使c[8]中原值(字符0)被空格取代然后q和p均自加1,以后将c[10]c[9],c[11]c[10]…,c[13]c[12],c[14]c[13],最后用“*9=′\0′;”语句给c[14](即*9的当前值)赋予“\0”注意c数组中c[15]的值并未改变,因此a[15]仍为“\0”,a数组中有两个′\0′可以看到c数组各元素值改变了,在main函数中可以输出数组c中的字符串(遇第一个“\0”即停止)。

      79 5e空格0ayun.hI空格av\0┇p,ptqp'q'5e空格0空格┇y┇.\0hIav\0┇p,pt'p'c[7]c[8]c[9]c[14]c[15]80 6.5.2 带参主函数带参主函数直到现在,用到的main函数都是不带参数的,因此main函数的第一行是:main( void)其实main函数也可以有参数有参数的main函数的原型为:int main (int argc,char *argv[]);也就是说,带参数main函数的第一个形参argc是一个整型变量,第二个形参argv是一个指针数组,其每个元素都指向字符型数据(即是一个字符串)这两个参数的值从哪里传递而来呢?main函数是主函数,它不能被程序中其它函数调用、因此显然不可能从其它函数向它传递所需的参数值,只能从程序以外传递而来也就是在启动一个程序时,从程序的命令行中给出的例如有个程序,程序名为c. 通常只要在操作系统的命令状态下,键入命令:cfile就可以开始执行这个程序81 不过,C语言还允许在这个命令行中键入要程序处理的其他字符串例如,要让cfile程序处理一个字符串“hardware”,可以键入命令行:c如果要让cfile程序处理两个字符串,如“hardware”和“software”,则可以键入命令行:c software那么,键入这些字符串后,C程序怎么接收的呢?实际上,这些字符串就是由main的两个参数接收的。

      如下页图所示,当键入上述命令时,操作系统将把“cfile”、“hardware”和“software”保存在内存中,并把它们的地址依次存放在数组argv中,同时把字符串的个数,也即数组argv的大小存放在变量argc中82 #include″stdio.h″int main (int argc, char * argv[]){ while (argc>1){++argv;printf (″%s\n″,*argv);--argc;} return 0;}leifcdwrahtwfosar\0erae\0\0argv[0]argv[1]argv[2]argvargc=383 如果从键盘输入的命令行为:c software则输出为:hardware software 因为argc初值为3,每次循环减1,故其循环两次第一次时,先使argv指向argv[1],然后输出argv[1]指向的字符串“hardware”,第二次开始时,又使argv指向argv[2],然后输出“software”84 用带参的main函数可以直接从命令行得到参数值(这些值是字符串),在程序运行时可以根据输入的命令行中的不同情况进行相应的处理。

      例如,在使用数据文件时,可以根据不同的需要输入不同的命令行;以打开不同的文件利用main函数中的参数可以使程序从系统得到所需的数据,或者说,增加了一条系统向程序传递数据的渠道,增加了处理问题的灵活性其实main的形参名并不一定非用argc和argv不可,只是习惯上一般用这两个名字如果改用别的名字,其数据类型不能改变,即第一个形参为int型,第二个形参为指针数组85 顺便说明一个问题:在本例中用到argv++的运算,是使argv的值自加而argv是数组名以前说过数组名代表一个常量,它是数组起始地址,它是不能进行自加运算的,是不能改变其本身的值的例如下面程序是不能通过编译的:main ( ){ int a[5];int i;for (i=0;i<5;i++,a++)printf (“%d”,*a);}86 错误在于a不能进行自加运算,a++不合法这是由于在编译时给a数组分配一段内存单元,a代表数组起始地址,是一常量注意a是main函数中的数组名,不是形参如果将a设为形参数组,情况就不同了:void fun(int a[],int n){ int i;for (i=0;i

      在编译时并未分配其固定的内存单元只是在调用函数fun时才将arr的起始地址传给a,实际上a是一个指针变量,定义fun函数的第一行相当于:fun (int * a,int n) 这里,a是指针变量,a++是合法的所以例6-19中argv++是合法的它的作用是使argv指针下移一个元素88 6.3.3 返回指针值的函数返回指针值的函数 一个函数在被调用之后可以带回一个值返回到主调函数,这个值可以是整型、实型、字符型等类型,也可以带回一个指针类型的数据例如前面介绍的内存动态分配函数malloc()和calloc()都是返回指针的函数 例:编写一个函数,它的作用是在一个字符串中找一个指定的字符,返回该字符的地址(库函数中有标准函数strchr(),要求自己编写具有同样功能的stringchr())89 函数如下:char *stringchr (char *str,char ch){ while (*str++!=’\0’) if (*str==ch) return (str);return (0);}可以用下面的main函数调用它include 90 int main (void){char *stringchr (char *str, char ch);char *pt,ch,line[]=”I love China”;ch=’C’;pt=stringchr (line,ch);printf (“\n字符串的起始地址:%o。

      \n”,line);printf (“最先出现字符%c的地址是:%o\n”,ch,pt);printf (“这是该字符串中的第 %d (从0开始)个字符\n”,pt-line);}91 这里4577550和457757是八进制的地址,十进制地址则分别是1245032和1245039下图表明了这个程序的执行过程:str的初值是数组line的起始地址,即& line[0]将*str与ch比较,如果*str(即str当前指向的字符)不等于ch的值,则使str++,即使str下移一个字符,直到str指向字符C为止(图中的str),将ptr值返回主调函数,str是字符C的地址92 再看一例: 编写一个函数stringcat(),使一个字符串str2接到另一个字符串str1的后面,原来str1字符串最后的“\0”被str2的第一个字符取代函数返回str1的值(也是在标准库函数中有一个标准函数strcat()这里的stringcat()要求自己编)strovlIhCe\0anistr'数组linech=’C’93 char *stringcat (char *str1,char *str2){char *p;for (p=str1;*p!=’\0’;p++);do{ *p++=*str2++;} while (*str2!=’\0’);*p=’\0’;return (str1);}94 可以用main函数来调用它:#include int main(void){ char *stringcat (char *str1,char *str2);char string1[20]=”C language”,string2[]=” is fun.”,*pt;pt=stringcat (string1,string2);printf (“The new string is:%s\n”,pt);return 0;}95 运行结果如下:The new string is: C language is fun.说明: stringcat()函数中的for语句作用是使p指向string1最后的“\0”。

      do…while循环的作用是将字符串string2中的字符按照p的指示位置逐个传到string1中去开始,string2中第一个字符*str2的值赋给string1字符串中原来存放“\0”的单元然后str2和p都同步下移一个位置,再使*str2赋给*p,直到遇到string2中的“\0”为止注意应该再赋一个“\0”给*p,即加到新串的末尾这个过程如图6.21所示 函数stringcat()返回string1的首地址给主函数中的字符指针pt,并由printf()函数输出连接后的新字符串96 str1string1a nlCgaug\0\0\0e\0 \0\0\0\0\0\0fsi\0.nustr2string2str1string1a nlCgaug\0\0\0e\0 \0\0\0\0\0\0pstr1string1a nlCgaugsien .uf\0\0p97 6.5.4 6.5.4 指向函数的指针指向函数的指针一个函数包括一系列的指令,在内存中占据一片存储单元,它有一个起始地址,即函数的入口地址,通过这个地址可以找到该函数,这个地址就称为函数的指针也可以定义一个指针变量,使它的值等于函数的入口地址,那么通过这个指针变量也能调用此函数,这个指针变量称为指向函数的指针变量。

      定义一个指向函数的指针变量的一般形式如下:类型标识符 (*指针变量名)();例如:int(*p)();它表示p指向一个“返回整型值的函数”注意*p两侧的括弧不能省略,如果写成:“int *p();”就成了“返回指针值的函数”了98 由于函数可以返回一个值所以在C语言中,可以认为函数也具有数据类型定义函数时必须定义函数返回值的类型(void也是一种类型)同理,在定义一个指向函数的指针变量时,除了需要用指针声明符(*)和指针变量名外,还必须声明它所指向的函数的类型 在定义了指向函数的指针变量以后,可以将一个已经定义的函数的入口地址赋给它,使指针变量指向一个特定的函数如:p=fun1; fun1代表函数fun1的入口地址,函数名与数组名一样,都是常指针注意在将函数的入口地址赋给指针变量时,只写函数名而不要有括号和参数表,例如不应写成以下形式:p=fun1();99 或p=fun1(a,b); 因为fun1(a,by)是函数调用,将得到一个函数值,而不是函数fun1的入口地址 定义并初始化一个指向函数的指针后,就可以通过指针调用它所指向的函数: (*指针变量) (实参表列)例如:(*p)(a,b),相当于fun1(a,b)。

      100 用指向函数的指针变量调用arradd()求二维数组中全部元素之和程序如下:#include #define N 3#define M 4int main( ){ int arradd (int arr[],int n);static int a[N][M]={1,3,5,7,9,11,13,15,17,19,21,23};int * p,total1,total2;int (*pt)();pt=arradd;101 p=a[0];total1=arradd(p,N*M);total2=(*pt)(p,N*M);printf (“total1=%d\ntotal2=%d\n”,total1,total2);return 0;}arradd (int arr[],int n){ int i, sum=0;for (i=0;i

      可以用指向函数的指针变量作为被调用函数的实参,由于该指针变量是指向某一函数的,因此先后使指针变量指向不同的函数,就可以在被调用函数中调用不同的函数103 。

      点击阅读更多内容
      猜您喜欢
      备考2025广东省资产评估师之资产评估基础强化训练试卷B卷附答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑物理与建筑设备模拟预测参考题库及答案.docx 备考2025广东省资产评估师之资产评估基础基础试题库和答案要点.docx 备考2025广东省资料员之资料员专业管理实务题库练习试卷B卷附答案.docx 备考2025贵州省主治医师之全科医学301通关考试题库带答案解析.docx 备考2025广东省资料员之资料员基础知识自测提分题库加答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑物理与建筑设备考前冲刺试卷A卷含答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑物理与建筑设备能力检测试卷B卷附答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑物理与建筑设备高分通关题型题库附解析答案.docx 备考2025广东省资料员之资料员基础知识过关检测试卷B卷附答案.docx 备考2025广东省资料员之资料员专业管理实务考前冲刺试卷A卷含答案.docx 备考2025广东省资料员之资料员基础知识自我提分评估(附答案).docx 备考2025广西壮族自治区一级注册建筑师之建筑材料与构造每日一练试卷B卷含答案.docx 备考2025广东省资产评估师之资产评估基础押题练习试卷B卷附答案.docx 备考2025广东省资产评估师之资产评估基础押题练习试题B卷含答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑经济、施工与设计业务管理题库练习试卷A卷附答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑结构试题及答案.docx 备考2025广西壮族自治区一级注册建筑师之建筑物理与建筑设备押题练习试卷A卷附答案.docx 备考2025广东省资料员之资料员基础知识题库练习试卷B卷附答案.docx 备考2025广西壮族自治区一级注册建筑师之设计前期与场地设计考前冲刺试卷A卷含答案.docx
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.