版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C语言程序设计活页式教程项目6使用指针处理数据指针是C语言最具特色的数据类型,它极大地丰富了C语言的功能。正确而灵活地使用指针,可以有效地构造出复杂的数据结构,可以更加方便灵活地操作数组元素,可以使程序更简洁、紧凑和高效。指针的概念比较复杂,初学者时常会感到较难理解。因此,学习指针时必须从指针的概念入手,了解什么是指针,如何定义指针变量,指针与其他类型变量的区别,并掌握指针在数组、函数等方面的应用。只有在少数应用场景必须用到指针,如申请内存空间、释放内存空间、用指针变量代替函数等情形。而处理普通数据、一维数组、二维数组时,可以选择不使用指针。项目任务知识目标学习目标任务1:用指针处理普通类型数据任务2:用指针处理一维数组中的数据任务3:用指针处理二维数组中的数据任务4:用指针替代函数任务5:用链表存储数据(1)了解指针类型和指针变量。(2)掌握指向普通数据的指针变量的使用。(3)掌握指向一维数组的指针变量的使用。(4)掌握指向二维数组的指针变量的使用。(5)掌握指向函数的指针变量的使用。(6)掌握使用链表存储无限数据。任务准备任务实施任务描述任务1用指针处理普通类型数据任务描述本任务通过用指针变量存储普通类型变量的地址、用指针变量读取或修改普通类型变量的值、输出并对比指针变量存储的地址等子任务,以及任务准备和任务拓展中的案例,向学生讲解指针变量的定义和运算操作,让学生理解指针类型和指针变量,具备灵活使用指针变量的能力。任务准备任务实施Part
1Part
2Part
3任务描述任务准备1.指针的概念要理解指针的概念,必须先弄清楚数据在计算机内存中是如何存储和读取的。程序中的一个变量实质上代表了“内存中的某个存储单元”。在计算机中,所有的数据以及正在运行的程序都是存放在存储器(简称内存)中的。内存由线性连续的存储单元组成,一般把存储器中的1字节称为一个存储单元,不同的数据类型占用的存储单元数不同,如int类型的变量占4个单元(有的系统占2个单元),char类型的变量占1个单元等。为了能正确地对存储单元进行访问,必须为每个存储单元编号。根据存储单元的编号,能准确地找到该存储单元,存储单元的编号也称地址。由于根据存储单元的编号或地址就可以找到所需的存储单元,所以通常也将这个地址称为指针。存储单元的指针和存储单元的内容是两个不同的概念,可以用一个简单的例子来说明它们之间的关系。到宾馆去探访客人时,前台工作人员将根据提供的客人名称或者房间号找到客人。在这里,客人名称相当于变量名称,房间号相当于指针,而客人是变量或指针中的内容。对于一个存储单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。任务准备在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个存储单元的地址或称为某个存储单元的指针。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配存储单元。编译系统会根据程序中定义的变量类型,分配一定长度的空间,所分配存储单元的首地址称为变量的地址,此后,这个变量对应的存储地址也就确定了。若程序需要处理这个变量,就可以通过该地址来处理对应的变量。例如,假设指针tp中存放了字符变量ch的地址,通常可形象地描述为tp指向ch,如图6-1所示。图中设有字符变量ch,其内容为'A',ch占用了0022FED4(内存地址通常用十六进制数表示)号单元。设有指针变量tp,内容为0022FED4,这种情况称为tp指向变量ch,或说tp是指向变量ch的指针。当要访问变量ch的存储空间时,可以采用直接访问的方式(使用ch访问),还可以通过指针tp来访问(使用对应的运算符)。通过指针tp来访问ch的存储空间,其访问过程是:先访问指针tp的存储空间,其中存放的是变量ch的地址,再根据该地址访问变量ch的存储空间。这是对变量的存储空间访问的另外一种方式,称为间接访问。图6-1指针变量示意图tpch0022FED4
'A'0022FED4任务准备总的来说,指针就是地址,而存储地址的变量就是指针变量。一个指针变量却可以在不同时刻被赋予不同的地址。定义指针变量的目的是通过指针变量中的地址访问存储单元。既然指针变量的值是一个地址,那么这个地址不仅可以是普通变量的地址,也可以是其他数据结构的地址,如一个数组的首地址或一个函数的首地址。在C语言中,一种数据类型或数据结构往往都占有一组连续的存储单元。用“地址”这个概念并不能很好地描述一种数据类型或数据结构,而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是指向一个数据结构的,因而概念更为清楚,表示更为明确。这也是引入“指针”概念的一个重要原因。在C语言中,指针提供了一种间接访问其他对象(指普通变量、结构体变量、数组、函数等)的手段,可以通过为指针变量赋不同的值,使其指向发生改变。利用这种机制能够更加灵活方便地实施对各种对象的操作。任务准备2.指针变量的定义与其它类型的变量一样,指针变量必须先定义才能使用。指针变量的定义格式如下:类型名*指针变量名;例如:int*tp1;char*tp2;double*tp3;上述变量的定义中,int、char和double都是类型名,tp1、tp2、tp3都是指针变量名,而星号*是定义指针变量必不可少的说明符,说明定义的是指针变量。如果没有星号*,上述语句就变成了普通变量的定义。int*tp1,*tp2,tp3;同类型的指针变量、普通变量可以一起定义,如上述定义变量的语句中,tp1和tp2是指向int数据的指针变量,或者说tp1和tp2是存储int数据的地址的指针变量,而tp3就是存储int数据的普通变量。任务准备3.指针变量的运算指针变量的运算,是指对指针变量中存储的地址进行的运算。常见指针变量的运算有:对指针变量赋值;取出指针变量中存储的地址,再通过地址取出对应的存储单元中的数据;对指针变量中存储的地址进行加减运算(移动地址);将指针变量中存储的地址与其他地址进行比较。(1)指针变量的赋值和取内容由于指针变量存储的是地址,所以给指针变量赋值常常伴随着取变量的地址,这就必然要用到取地址运算符&。取地址运算符&是单目运算符,它的功能是取变量的地址,在输入一个整数的代码scanf("%d",&x)中已经用到过该运算符。取内容运算符*也是单目运算符,它的功能是取出某地址对应的存储单元中的数据。运算符*后一般跟一个指针变量或一个地址常量(如数组名)。任务准备【实例1】定义2个整型变量a和b并输入值,定义2个整型指针变量tp1和tp2分别指向a和b,用指针变量输出a和b的值。#include<stdio.h>intmain(){ inta,b; int*tp1=&a,*tp2; tp2=&b; printf("请输入a和b的值,以逗号隔开:"); scanf("%d,%d",&a,&b); printf("a=%d,b=%d\n",*tp1,*tp2); return1;}编译运行的结果如图6-2所示:在实例1中,有两处用到了取地址符&,一处是用&取出变量a和b的地址,分别赋值给指针变量tp1和tp2;另一处是用scanf()函数接收用户输入时参数中出现了&a和&b,可以理解为将用户输入的两个整数分别存储到地址&a和&b对应的存储单元中。运算符*后面跟的是一个地址,它的作用是取出该地址对应的存储单元中的数据。在实例1中,指针变量tp1和tp2分别存储变量a和b的地址。因此,*tp1的作用是取出变量a的地址中的数据,即变量a的值;*tp2的作用是取出变量b的地址中的数据,即变量b的值。图6-2指针变量任务准备(2)指针变量的算术运算常见的指针变量的算术运算,是让指针变量加上或减去一个整数,使指针变量指向相邻的存储单元。例如int*tp中,tp是一个指向整型数据的指针变量,它记录的是拥有4个字节的存储单元的首地址,因为int类型的数据占4个字节。那么,tp+1、tp+n、tp-1、tp++、--tp等式子都是合法的,它们表示地址向前或向后移动几个存储单元。那么,一个指针变量所指向的存储单元到底是多大呢?占多少个字节?指针变量加1后地址增加多少?下面以实例2来加以说明。任务准备【实例2】分别定义char、int、double类型的变量,用指针变量存储它们的地址,分别输出指针变量的地址,以及指针变量加1后的地址。#include<stdio.h>intmain(){ charch='a',*tp1=&ch; intx=5,*tp2=&x; doubley=3.14159,*tp3=&y; printf("tp1=%p,tp1+1=%p\n",tp1,tp1+1); printf("tp2=%p,tp2+1=%p\n",tp2,tp2+1); printf("tp3=%p,tp3+1=%p\n",tp3,tp3+1); return1;}编译运行的结果如图6-3所示:图6-3指针变量的地址值任务准备在输出函数scanf()中,格式%p用于以十六进制输出一个地址。从在实例2的运行结果不难看出,char字符型变量ch占用1个字节的存储空间,因此它的地址加1(即tp1+1)代表向后移动1个字节空间。同理,int整型变量x占用4个字节的存储空间,x的地址加1(即tp2+1)代表向后移动4个字节空间,由原先的地址0060FEEC变为0060FEF0;double双精度浮点型变量y占用8个字节的存储空间,y的地址加1(即tp3+1)代表向后移动8个字节空间,由原先的地址0060FEE0变为0060FEE8。由于系统和运行环境的差异,在不同的计算机上运行实例2所得到的地址结果会不一样。有的编译系统认为int类型只占2个字节,也会导致地址的差值不一样。综上所述,指针变量中的地址对应的存储单元的大小,由指针变量的类型来决定。而指针变量进行加减运算后的地址变化,也是以它的类型所占的空间大小为单位的。打个比方,姚朋身高2.3米,潘江身高1.5米,假设以姚朋、潘江作为数据类型来定义指针变量:姚朋*tp1;潘江*tp2;那么,tp1+1代表姚朋向前走一步,由于姚朋身高腿长,他向前走一步的距离是1米;而tp2+1代表潘江向前走一步,跨度只有0.5米。所以说,指针变量进行加减运算后的地址变化,由指针变量的类型决定。任务准备(3)指针变量的关系运算与其它类型的变量一样,指针变量也可以进行关系运算。假定有两个指针变量tp1和tp2,它们之间的关系运算代表的意义如表6-1所示:表6-1指针变量关系运算的意义关系运算代表的意义tp1==tp2表示tp1和tp2指向同一空间tp1>tp2表示tp1指向的地址位置更大tp1<tp2表示tp1指向的地址位置更小任务准备在C语言中,当没有给某指针变量分配任何有效内存地址时,通常用NULL初始化该指针变量,NULL表示空指针常量。因此,常常在访问任何指针变量之前检查它是否是空指针,代码如下所示:intx=10;int*tp1=&x;if(tp1!=NULL){ /*代码省略*/}在C语言中,判断指针变量是否为NULL,是指针变量最常见的关系运算。但将指针变量赋值为NULL与不赋值是不同的。指针变量未赋值时,变量中存储的地址是一个随机数,不能直接使用,否则可能造成严重错误。而当把指针变量赋值为NULL后,则可以使用,只不过该变量不指向任何变量。任务准备5.指针变量作函数的参数和返回值函数的参数不仅可以是整型、浮点型、字符型、数组等类型,也可以是指针类型。当整型、浮点型、字符型数据作函数的参数时,传入函数的是数值的副本(即将数据复制一份传入函数中),这就是我们常说的“传值”。而当指针、数组类型的数据作函数的参数时,传入函数的是地址的副本(即将地址复制一份传入函数中),这就是我们常说的“传引用”。(1)函数参数传值以传值的形式将数据的副本传入函数,并在函数中修改这个数据副本,对函数外的原数据没有影响,因为函数修改的是原数据的副本。任务准备【实例3】定义一个函数swap()用于交换两个整型变量的值。在main()函数中调用该函数,检验swap()函数是否能将两个整型变量的值交换。#include<stdio.h>voidswap(inta,intb);intmain(){ intx=17,y=8; printf("调用swap()前,x=%d,y=%d\n",x,y); swap(x,y); printf("调用swap()后,x=%d,y=%d\n",x,y); return1;}voidswap(inta,intb){ printf("swap()内交换前,a=%d,b=%d\n",a,b); intc; c=a; a=b; b=c; printf("swap()内交换后,a=%d,b=%d\n",a,b);}编译运行的结果如图6-4所示:图6-4函数参数传值任务准备从在实例3的运行结果不难看出,调用swap()函数并没有将实际参数x和y的值交换,只是在swap()函数内部,形式参数a和b的值被交换了。也可以理解为,变量x的副本a与变量y的副本b发生了交换,但变量x和变量y本身并没有发生交换。实例3的执行过程如图6-5所示,从图示可以看出,实际参数x和y在调用swap()函数前后的值没变化,发生交换的只是形式参数a和b的值。图6-5参数传值示意图任务准备(2)函数参数传址传址是指将地址常量或变量(即指针)作为一个函数的参数。如果一个函数的形式参数是指针变量,那么在调用该函数时,实际参数可以是地址常量(如数组名)、变量的地址(如&x)或指针变量。以传址的形式将数据所在的地址的副本传入函数,并在函数中通过取内容运算符*来修改地址副本指向的原数据,就能够修改函数外的原数据。这就好比有人将你家房门钥匙复制一份,用复制的钥匙同样能够打开房门进入你家,能将你家中的物品洗劫一空。任务准备【实例4】定义一个函数swap()代入两个整型变量的地址,实现交换变量的值。在main()函数中调用该函数,检验swap()函数是否能将两个整型变量的值交换。#include<stdio.h>voidswap(int*a,int*b);intmain(){ intx=17,y=8; printf("调用swap()前,x=%d,y=%d\n",x,y); swap(&x,&y); printf("调用swap()后,x=%d,y=%d\n",x,y); return1;}voidswap(int*a,int*b){ printf("swap()内交换前,*a=%d,*b=%d\n",*a,*b); intc; c=*a; *a=*b; *b=c;printf("swap()内交换后,*a=%d,*b=%d\n",*a,*b);}编译运行的结果如图6-6所示:图6-6函数参数传址任务准备从在实例4的运行结果不难看出,通过将变量x和y的地址传入swap()函数后,可以在swap()函数内部修改变量x和y的值,从而实现了它们值的交换。实例4的执行过程如图6-7所示,从图示可以看出,变量x和y在调用swap()函数后的值发生了交换。这里假设变量x的地址是FE8C,假设变量y的地址是FED4。图6-7参数传址示意图任务准备(3)函数返回指针在C语言中,函数的返回值类型不仅可以是诸如int、char、double之类的简单数据类型,还可以是指针类型。当一个函数的返回值是内存地址的时候,我们称这个函数为指针函数。指针函数的定义格式如下:数据类型*函数名(形式参数列表){ ……}例如下面定义的fun()函数,它返回一个指向整型数据的指针变量。可以理解为从该函数返回的地址开始的4个字节中,存储的是一个整型数据。int*fun(intn){ int*p; /*代码省略*/ returnp;}任务准备C语言中的数组能存储同类型的批量数据,但不足的是,数组一旦定义,它的元素个数就不能改变。因此,对于数据个数不确定的场合,数组是不适用的。而接下来的实例5中的input()函数就能实现根据欲存储数据的个数来申请内存空间,返回空间的首地址。实例5中的input()函数的功能是,根据欲存储的整数个数n来申请内存空间,并将该内存空间的首地址作为返回值返回。申请内存空间需要用到库函数malloc(),它在头文件malloc.h中定义,用法如下:(数据类型*)malloc(个数*sizeof(数据类型));例如: int*p=(int*)malloc(5*sizeof(int));malloc()函数的作用是,在内存的动态存储区中分配一个指定长度的连续空间,并返回分配区域的起始地址。sizeof是C语言中的一个操作符,它的作用是返回一个类型所占的内存字节数。sizeof(int)返回4。上述代码的作用是,申请5个int数据所占据的内存空间,即20字节的连续空间,将连续空间的起始地址强制转换为int*指针类型后,赋值给指针变量p。任务准备【实例5】定义一个函数int*input(intn),要求用户输入n个整数并存储,返回这n个整数的首地址。在main()函数中要求用户输入欲输入的整数个数,调用函数input()实现这批整数的存储,然后再输出。#include<malloc.h>int*input(intn);intmain(){ intx,i,*tp; printf("请输入整数个数:"); scanf("%d",&x); tp=input(x); printf("输入的所有整数:"); for(i=0;i<x;i++) printf("%d,",*(tp+i)); return1;}int*input(intn){ inti; int*p=(int*)malloc(n*sizeof(int)); for(i=0;i<n;i++) { printf("请输入第%d个整数:",i+1); scanf("%d",p+i);/*p+i是地址*/ } returnp;}任务准备编译运行的结果如图6-8所示:从在实例5的运行结果不难看出,input()函数申请了能存储5个整数的连续空间,并将该空间的首地址返回给main()函数中的指针变量tp,再通过tp将存储的5个整数全部输出。图6-8返回指针的函数任务准备任务实施Part
1Part
2Part
3任务描述任务实施【任务1】定义一个整型变量并赋初值,定义一个指针变量存储整型变量的地址,并输出指针变量所指向的整型数据。1.任务分析本任务考查最基础的指针变量操作,包括赋值和取内容。任务实施2.任务实现实现代码如下,请将代码中空白处补充完整。#include<stdio.h>intmain(){ intx=10,*tp; tp=
; printf("tp=
,*tp=
\n",tp,*tp); printf("tp+1=%p,*(tp+1)=%d\n",tp+1,*(tp+1)); return1;}编译运行的结果如图6-9所示:图6-9指向整型数据的指针任务实施3.任务总结本任务将变量x的地址&x赋值给指针变量tp,然后用*tp取出地址中的数据,即变量x的值。将指针变量的值以十六进制地址的形式输出,可以看出tp+1的地址比tp的地址大4,刚好是一个int数据所占据的空间。这充分说明int*类型的指针变量加1,意味着地址向前移动4个字节。输出结果6356728是变量x后面4个字节中的数据,它是不确定的,不同的编译环境会取到不同的数值。此处*(tp+1)是向大家展示,通过指针变量可以访问未经编译器分配的地址中的数据,这种做法是不安全的,也是不提倡的。任务准备任务实施任务描述任务2用指针处理一维数组中的数据任务描述本任务通过用指针变量来访问一维数组中的元素,向学生讲解用指针变量访问一维数组元素的各种形式,让学生具备灵活处理批量数据的能力。任务准备任务实施Part
1Part
2Part
3任务描述任务准备1.指针变量访问一维数组元素数组是一种存储批量相同类型数据的构造类型。一维数组中的元素按顺序存放在地址连续的内存单元中,每一个数组元素都有各自的地址,而数组名就是数组的首地址。对于数组元素,可以使用“数组名[下标]”形式访问,也可以通过指针变量访问。不同之处在于,数组名是地址常量,而指针变量是存储地址的变量。一维数组和指针的定义如下:inta[5]={11,22,33,44,55};int*tp=a;doubleb[5]={1.23,2.34,3.45,4.56,5.67};double*tp2=&b[0];对数组a的第i个元素的访问,可以通过以下形式:a[i];tp[i];*(tp+i);*tp++; /*先*tp取出地址中的数据,再tp++将地址加1,指向下一个数组元素*/*(a+i);任务准备在将数组名a赋值给指针变量tp后,由于a和tp都是数组的首地址,所以a与tp几乎可以通用。唯独*a++在编译时会报错,这是因为a是常量,不能执行a++操作。通常使用一维字符数组存储一个字符串常量,而指针变量又可以存储一维数组的首地址,因此字符串常量的存储可以用以下两种方式:charch[]="abcdefg";char*cp="abcdefg";但对于存储用户输入的字符串的情形,就只能先定义一维字符数组并分配空间,再接收用户输入的字符串。这种情形下不能使用字符指针变量,因为指针变量只能存储一个地址,它并没有足够的空间来存放用户输入的字符串。两种代码如下:charch[200];char*cp;scanf("%s",ch); /*正确做法,ch有分配空间*/scanf("%s",cp); /*错误做法,cp没有分配空间*/任务准备【实例1】定义一个整型数组和一个整型指针变量,用多种形式输出数组中的元素。intmain(){ inta[5]={11,22,33,44,55}; int*tp,i; tp=a; for(i=0;i<5;i++) printf("%d,",a[i]); printf("\n"); for(i=0;i<5;i++) printf("%d,",tp[i]); printf("\n"); for(i=0;i<5;i++) printf("%d,",*(tp+i)); printf("\n"); for(i=0;i<5;i++) printf("%d,",*tp++); printf("\n"); return0;}编译运行的结果如图6-10所示:从运行结果可以看出,在循环中用a[i]、tp[i]、*(tp+i)、*tp++、*(a+i)均可依次取出数组中的元素。数组名b是数组的首地址,元素b[0]是数组的第0个元素,对b[0]取地址后,&b[0]就是第0个元素的地址,也是数组的首地址。所以对于一维数组b来说,b与&b[0]是等价的。图6-10指向整型数组的指针任务准备【实例2】定义一个double数组和一个double指针变量,用指针变量输出数组中的元素。intmain(){ inti; doubleb[5]={1.23,2.34,3.45,4.56,5.67}; double*tp2=b; for(i=0;i<5;i++) printf("%.2lf,",b[i]); printf("\n"); tp2=&b[0]; for(i=0;i<5;i++) printf("%.2lf,",b[i]); printf("\n"); return0;}编译运行的结果如图6-11所示:从运行结果可以看出,给指针变量tp2赋值b或&b[0],均能正确输出数组b中的元素,充分证明一维数组名b与&b[0]是等价的。图6-11指向double型数组的指针任务准备2.一维数组的地址关系对于一维整型数组a来说,a、a+1、&a[0]、&a[0]+1、&a、&a+1都表示地址,它们又有什么不同呢?下面我们用实例3来说明这些地址的不同之处。任务准备【实例3】定义一个整型数组a,输出a、a+1、&a[0]、&a[0]+1、&a、&a+1的地址,比较它们的不同之处。#include<stdio.h>intmain(){ inta[10]={1,2,3,4,5,6,7,8,9,0}; printf("a=%p\n",a); printf("a+1=%p\n",a+1); printf("&a[0]=%p\n",&a[0]); printf("&a[0]+1=%p\n",&a[0]+1); printf("&a=%p\n",&a); printf("&a+1=%p\n",&a+1); return0;}编译运行的结果如图6-12所示:图6-12数组的各种地址任务准备需要注意的是,因为编译环境不同,运行后输出的地址也会不同。本实例中,数组名a的地址是0060FEC8,a+1的地址是0060FECC,两者相差4,即一个int数据所占据的字节数。说明a+1是下一个元素a[1]的地址。第0个元素的地址&a[0]是0060FEC8,与a相同;&a[0]+1是0060FECC,与a+1相同。&a[0]与&a[0]+1的地址也相差4,说明&a[0]+1与a+1都是元素a[1]的地址。这也间接说明对于一维数组a来说,a与&a[0]等价。对数组名a取地址,得到&a是0060FEC8,与a、&a[0]相等。但&a+1是0060FEF0,与a+1、&a[0]+1不同。计算&a+1与&a的差值,发现两者相差40,而40个字节刚好是10个int数据占据的空间。说明&a+1与&a相比,跨过了10个int数据的存储空间。综上所述,可以将一维数组的相关地址进行分类,a、a+1、&a[0]、&a[0]+1记录的是数组元素的地址,对a或&a[0]进行加减运算时,地址的跨度是一个数组元素所占空间的倍数。而&a、&a+1记录的是整个数组的地址(可以理解为一行数据的地址),对&a进行加减运算时,地址的跨度是整个数组所占空间的倍数。例如本实例中整型数组a有10个元素,与&a相比,&a+1的地址跨度刚好是10个int数据占据的空间,即40个字节。任务准备任务实施Part
1Part
2Part
3任务描述任务实施【任务1】在main()函数中定义一个整型数组并初始化,定义并调用函数将一维整型数组中最小的数与第一个数对换,把最大的数与最后一个数对换。然后输出一维数组的所有元素。1.任务分析本任务采用指针变量来访问数组元素。先通过指针变量p访问数组中的n个元素,指针变量max和min记录数组最大值、最小值所在的地址,再通过取内容运算符*取出地址中的数据,最后用临时变量temp实现最小的数与第一个数、最大的数与最后一个数的对换。任务实施2.任务实现实现代码如下,请将代码中空白处补充完整。#include<stdio.h>voidduihuan(int*p,intn);voidshuchu(int*p,intn);intmain(){ inta[10]={59,15,-100,-76,96,22,38,75,6,34}; printf("对换前:"); shuchu(a,10); duihuan(
,
); printf("\n对换后:"); shuchu(a,10); return0;}voidduihuan(int*p,intn){ inti,*max=p,*min=p,temp; for(i=0;i<n;i++) { /*max记录最大值的地址*/ if(
>*max) max=
; /*min记录最小值的地址*/ if(
<*min) min=
; } temp=*p;*p=*min;*min=temp; temp=*(p+n-1);*(p+n-1)=*max;*max=temp;}voidshuchu(int*p,intn){ inti; for(i=0;i<n;i++) printf("%d,",*(p+i));}任务实施编译运行的结果如图6-13所示:从运行结果来看,最小值-100与第一个数59成功对换,最大值96与最后一个数34成功对换。图6-13利用指针实现数据元素对换任务实施3.任务总结将数组首地址赋值给指针变量p后,p+i就是就是数组第i个元素的地址,*(p+i)就能取出数组第i个元素的值。任务实施【任务2】统计一个字符串中的整数,存储到整型数组中,然后输出所有整数。1.任务分析假如有“a123x45617960?3025ab5876”这个字符串,内有数字和非数字字符,将其中连续的数字作为一个整数,依次存放到整型数组a中。例如123放在a[0],456放在a[1]…以此类推。统计共有多少个整数,并输出这些数。统计字符串中的整数需要设置一个标记变量flag,当字符是数字时将flag设为1,不是数字时设为0。当本字符是数字时,只需要将字符对应的数字累加到整数num上,并将flag设为1。当本字符不是数字而上一个字符是数字时,说明是一个整数的结束,这时就要将整数num存储到整型数组a中,并将flag设为0。当字符串结束时,如果结束符'\0'之前的字符是数字,那么也要将整数num存储到整型数组a中。任务实施2.任务实现实现代码如下,请将代码中空白处补充完整。intmain(){ char*str="a123x45617960?3025ab5876"; inta[50];/*分别是计数、整数值、标志位、循环变量*/ intcount=0,num=0,flag=0,i; charch=*str; while(ch!=
) { if(
) /*是数字*/ { num=10*num+(
); flag=1; } else /*非数字*/ {/*如果上一个字符是数字*/ if(flag==1) {/*将整数存入数组,并计数*/ a[
]=num; } flag=0;/*重新开始存储下一个整数*/ num=
; } str++; /*指向下一个字符*/ ch=*str; } if(flag==1)/*如果'\0'之前是数字*/ a[count++]=num; printf("共%d个整数:\n",count); for(i=0;i<count;i++) printf("%d\t",a[i]); return0;}任务实施编译运行的结果如图6-14所示:从运行结果来看,字符串“a123x45617960?3025ab5876”中的5个整数均被正确存储和输出。图6-14统计字符串中的整数任务实施3.任务总结当程序中有字符串常量作为初始值时,编译系统会分配内存空间来存储该字符串常量,然后将内存空间的首地址赋值给指针变量或数组名。数字9与字符'9'是类型不同的两个常量,它们的整型数值分别是9和57。将字符'9'转换成对应的数字9,可以用字符'9'减去字符'0',它们的差值就是整型数值9。将字符串“123”变成整数123的做法是,先将变量num设初值为0,然后循环访问字符串中每一个字符。每次访问一个字符时,都是把变量num乘以10再加上该字符对应的整数值,这样三次循环后num的取值分别是1、12、123。任务准备任务实施任务描述任务3用指针处理二维数组中的数据任务描述本任务通过用指针变量来访问二维数组中的元素,向学生讲解用指针变量访问二维数组元素的各种形式,以及与二维数组相关的各类地址的写法,让学生具备灵活处理批量数据的能力。任务准备任务实施Part
1Part
2Part
3任务描述任务准备1.二维数组地址和元素的访问形式二维数组实质上也是一个一维数组,只不过它的每一个元素又是一个一维数组,即二维数组可以看作是由若干个一维数组组成的。二维数组的逻辑结构如表6-2所示,可以看出二维数组a的每一行就是一个一维数组。表6-2二维数组的逻辑结构任务准备下面定义一个3行5列的二维整型数组并初始化:inta[3][5]={ {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};该二维数组常见地址和元素的访问形式,如表6-3所示:表6-3二维数组地址和元素的访问形式访问形式数据类型含义a行地址第0行的地址,等同&a[0]a+1行地址第1行的地址,等同&a[1]*a元素地址第0行第0列元素的地址,等同&a[0][0]、a[0]*a+1元素地址第0行第1列元素的地址,等同&a[0][1]、a[0]+1*(a+1)元素地址第1行第0列元素的地址,等同&a[1][0]、a[1]*(a+1)+1元素地址第1行第1列元素的地址,等同&a[1][1]、a[1]+1**a元素值第0行第0列元素的值,等同a[0][0],值为1**(a+1)元素值第1行第0列元素的值,等同a[1][0],值为6*(*(a+1)+1)元素值第1行第1列元素的值,等同a[1][1],值为7&a二维数组地址3行5列二维数组的首地址,对该地址进行算术运算,跨度是3行5列共15个int数据所占据的空间,即60个字节。&a+1二维数组地址3行5列二维数组的首地址往后移3行5列得到的地址任务准备【实例1】定义一个3行5列的二维整型数组并初始化,用多种形式输出该二维数组相关的地址和元素值。intmain(){ inta[3][5]={ {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15} }; printf("a指第0行的地址:%p,&a[0]=%p\n",a,&a[0]); printf("a+1指第1行地址:%p,&a[1]=%p\n",a+1,&a[1]); printf("*a指第0行第0列元素地址:%p,&a[0][0]=%p\n",*a,&a[0][0]); printf("*a+1指第0行第1列元素地址:%p,&a[0][1]=%p\n",*a+1,&a[0][1]); printf("*(a+1)指第1行第0列元素地址:%p,&a[1][0]=%p\n",*(a+1),&a[1][0]); printf("*(a+1)+1指第1行第1列元素地址:%p,&a[1][1]=%p\n",*(a+1)+1,&a[1][1]); printf("**a指第0行第0列元素值:%d,a[0][0]=%d\n",**a,a[0][0]); printf("**(a+1)指第1行第0列元素值:%d,a[1][0]=%d\n",**(a+1),a[1][0]); printf("*(*(a+1)+1)指第1行第1列元素的值:%d,a[1][1]=%d\n",*(*(a+1)+1),a[1][1]); printf("&a指3行5列二维数组的首地址:%p\n",&a); printf("&a+1指数组首地址往后移3行5列得到的地址:%p\n",&a+1); return0;}任务准备编译运行的结果如图6-15所示:图6-15实例1运行结果任务准备将运行结果中的访问形式和对应数值填入表6-4中,并对数据作分析对比,找出不同访问形式之间的规律。需要说明的是,不同的编译环境得到的地址是不一样的。表6-4访问形式和对应数值访问形式数据类型数值备注a行地址0060FEB4
a+1行地址0060FEC8地址与a相差20个字节,即5个int数据占据的空间。*a元素地址0060FEB4
*a+1元素地址0060FEB8地址与*a相差4个字节,即1个int数据占据的空间。*(a+1)元素地址0060FEC8
*(a+1)+1元素地址0060FECC地址与*(a+1)相差4个字节,即1个int数据占据的空间。**a元素值1
**(a+1)元素值6
*(*(a+1)+1)元素值7
&a二维数组地址0060FEB4地址与a相同,但代表的维度层级不一样,运算跨度不一样。&a+1二维数组地址0060FEF0地址与&a相差60个字节,即3*5个int数据占据的空间。任务准备从上表可以看出,二维数组名a是第0行的首地址,其它地址都是对数组名a进行运算而得来的。a表示二维数组第0行的首地址。a+1表示向前跨一行,是第1行的地址。*a表示进入第0行内部,是第0行第0列元素的地址。同理,*(a+1)表示进入第1行内部,是第1行第0列元素的地址。*a+1表示地址*a向前跨过一个元素,是第0行第1列元素的地址。同理,*(a+1)+1表示地址*(a+1)向前跨过一个元素,是第1行第1列元素的地址。**a表示进入元素地址*a的内部取出数据,即第0行第0列元素的值。**(a+1)表示进入元素地址*(a+1)的内部取出数据,即第1行第0列元素的值。*(*(a+1)+1)表示进入元素地址*(a+1)+1的内部取出数据,即第1第第1列元素的值。&a表示跳出a所在行的维度(一行的跨度为5个int数据占据的空间),进入拥有3行5列共15个元素的维度(即整个二维数组所在的维度),它是该3行5列二维整型数组的首地址。&a+1表示地址&a向前跨过一个拥有3行5列共15个元素的二维整型数组,它与地址&a的差值是60,刚好就是15个int数据占据的空间,即3*5*4个字节。任务准备对比以上描述可以看出,二维数组的维度层级依次是:整个二维数组地址、二维数组的行地址、行内元素的地址、元素的值,它们分别对应&a、a、*a、**a。从实例1的输出结果可以看出,a+i是一个行地址(第i行的地址),*(a+i)+j是一个元素地址(第i行第j列元素的地址),*(*(a+i)+j)则是一个元素值(第i行第j列元素的值)。由此我们可以得出结论:取内容运算符*的作用是进入下一层的维度,取地址运算符&的作用是跳出现有维度进入上一层的维度。而对地址进行算术运算所导致的地址跨度,取出于该地址所在的维度。例如,二维数组名a是第0行的首地址,那么*a就是第0行第0列元素的地址,**a就是第0行第0列元素的值,&a就是整个二维数组的首地址。虽然&a的地址值与a相同,但对它们进行算术运算所导致的地址跨度不同。由于&a是一个3行5列的二维整型数组的首地址,所以&a+1相对于&a的地址跨度是一个3行5列二维整型数组所占据的字节数,即3*5*4=60。又由于a是一个有5个元素一维整型数组(即第0行)的首地址,所以a+1相对于a的地址跨度是5个整型元素所占据的字节数,即5*4=20。任务准备2.行指针行指针变量用于存储一行数组元素的起始地址。二维数组的每一行可以看作是一个一维数组,因此可以定义一个行指针变量来指向二维数组中的某一行。声明行指针变量的格式为:数据类型(*指针变量)[n];如用式子char(*cp)[10]定义的指针变量cp,它是一个指向拥有10个元素的一维字符数组的变量,它是该一维字符数组的首地址。cp+1相对cp的跨度是10个字符占据的字节数。任务准备假设有二维整型数组a和一个指向拥有5个元素的一维整型数组的行指针变量tp,它们的定义如下:inta[3][5]={ {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};int(*tp)[5];可以将a、a+1、&a[0]、&a[1]赋值给行指针变量tp,因为它们都表示某一行的地址。代码如下:tp=a;tp=a+1;tp=&a[0];tp=&a[1];任务准备另外还有一个“指针数组”的概念,它与行指针的定义相似,但意义完全不同。指针数组实质上还是一个数组,只不过该数组的元素类型是指针,数组的每个元素都用来存储地址,它的定义格式如下:数据类型*数组名[n];如用式子double*d[10]定义一个数组d,它是一个拥有10个元素的一维数组,数组的每个元素都存储指向double数据的地址。元素d[i]返回一个地址,该地址指向的存储单元占8个字节,里面存放的是double数据。任务准备任务实施Part
1Part
2Part
3任务描述任务实施【任务1】定义一个3行5列的二维整型数组并初始化,再定义一个指向拥有5个元素的一维整型数组的行指针变量,用行指针变量输出二维整型数组中的所有元素。1.任务分析本任务先让行指针变量tp指向二维数组a的第0行,再通过行指针变量来获取二维数组的所有元素值并输出。本任务尝试用多种访问形式来表示第0行的地址和第i行第j列元素,让学生能更灵活地使用行指针变量访问二维数组。任务实施2.任务实现本任务实现代码如下,请将代码中空白处补充完整。#include<stdio.h>intmain(){ inti,j; inta[3][5]={ {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15} }; int(*tp)[5]; tp=
; /*或tp=&a[0]; */ for(i=0;i<3;i++) { for(j=0;j<5;j++) {/*用tp表示第i行第j列元素*/ printf("%d,",
); /*printf("%d,",*(tp[i]+j));*/ /*printf("%d,",tp[i][j]);*/ } printf("\n"); } return0;}编译运行的结果如图6-16所示:可以修改实现代码,使用*(*(tp+i)+j)、*(tp[i]+j)、tp[i][j]中的任何一个,都可以正确输出二维数组的所有元素。图6-16指向二维数组的指针任务实施3.任务总结在任务1中将二维数组第0行的首地址a赋值给行指针变量tp后,二维数组的第i行第j列元素的访问形式有:*(*(tp+i)+j) 或 *(*(a+i)+j)*(tp[i]+j) 或 *(a[i]+j)tp[i][j] 或 a[i][j]因此,当把二维数组名a赋值给行指针变量tp后,tp可以代替a来访问数组元素。在C语言中,二维数组和行指针还可以作为函数的参数,还可以用指向二维数组元素的指针变量来访问二维数组的所有元素。下面以两个拓展任务为例,来学习一下这两种用法。任务实施【任务2】分别用二维整型数组、行指针变量作函数参数,输出二维数组的全部元素。1.任务分析用二维数组作函数参数时,代入的是第0行的首地址,形式参数只需指明一行有多少列元素。用行指针变量作函数参数时,代入的也是某行的首地址,形式参数一样需要指明一行有多少列元素。任务实施2.任务实现本任务实现代码如下,请将代码中空白处补充完整。#include<stdio.h>voidshuchu1(intb[][5],intn);voidshuchu2(int(*p)[5],intn);intmain(){ inta[3][5]={ {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15} }; shuchu1(a,3); printf("------------------\n"); shuchu2(
,
); return0;}voidshuchu1(intb[][5],intn)/*只需给出列数为5*/{ inti,j; for(i=0;i<n;i++) { for(j=0;j<5;j++) { printf("%d,",b[i][j]); } printf("\n"); }}任务实施voidshuchu2(int(*p)[5],intn){ inti,j; for(i=0;i<n;i++) { for(j=0;j<5;j++) { printf("%d,",
); } printf("\n"); }}编译并运行代码,输出结果如图6-17所示:图6-17用指针输出二维数组元素任务实施3.任务总结无论是二维数组作函数参数,还是行指针作函数参数,代入的都是某一行的地址。而且参数必须指定一行有多少个元素,因为这直接决定了一行的跨度是多大。本任务中的形式参数intb[][5]和int(*p)[5]就指定了一行有5个元素,而行的数量则由参数n决定。任务实施【任务3】定义一个整型指针变量,让它指向二维整型数组第0行第0列的元素,然后用该指针变量输出二维数组的全部元素。1.任务分析为了便于理解,二维数组中的元素被人为地分成几行几列。事实上,二维数组中的数据在内存中是连续存储的,各元素并没有行列之分。因此,也可以用一个指向二维数组第0行第0列元素的指针变量,逐个访问二维数组中的所有元素。任务实施2.任务实现本任务实现代码如下,请将代码中空白处补充完整。#include<stdio.h>intmain(){inta[3][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; inti,*tp=
; for(i=0;i<
;i++) { if(i%5==0) printf("\n"); printf("%d,",
); } return0;}编译并运行代码,输出结果如图6-18所示:图6-18指向二维数组元素的指针任务实施3.任务总结二维数组在初始化时,可以将初始数据分成多行写,也可以像一维数组一样写在一行。指针变量tp是指向整型数据的指针变量,所以它可以被赋值为二维数组元素的地址。对tp进行算术运算时,地址的跨度也仅仅是一个数组元素所占据的字节数。由于二维数组中的元素在内存中是连续存储的,当tp被赋值为第0行第0列元素的地址后,可以通过tp指向二维数组中的所有元素,进而取出元素值。任务准备任务实施任务描述任务4用指针替代函数任务描述本任务通过定义指向函数的指针变量,实现用指针变量代替函数,以及将函数作为另一个函数的参数。任务准备任务实施Part
1Part
2Part
3任务描述任务准备1.返回指针值的函数函数的返回值类型可以是整型、浮点型、字符型等普通类型,也可以是指针类型,即函数返回的数据是一个地址。通常把返回指针类型的函数称作“指针函数”,它的声明格式如下:数据类型*函数名(参数列表)例如声明返回整型数组最大值地址的函数,格式如下:int*maxAddr(inta[],intn);任务准备【实例1】定义一个返回整型数组最大值地址的函数,在main()函数中调用并输出最大值。#include<stdio.h>int*maxAddr(inta[],intn);intmain(){ int*tp; inta[]={55,-87,14,101,23,42,77,50}; tp=maxAddr(a,8); printf("最大值:%d\n",*tp); return0;}int*maxAddr(inta[],intn){ int*p=a,i; for(i=0;i<n;i++) { if(a[i]>*p) p=a+i; } returnp;}编译运行的结果如图6-19所示:在函数maxAddr()中始终用指针变量p记录最大值的地址,最后返回该指针变量。函数maxAddr()就是一个返回指针值的函数,简称为指针函数。图6-15实例1运行结果任务准备2.指向函数的指针与数组一样,函数经系统编译后,它的目标代码在内存中连续存放。函数名本身就是一个地址,是函数的入口地址。在C语言中,指针变量除了可以指向普通变量和数组外,还可以指向函数,即指针变量也可以存储函数的地址。我们通常把指向函数的指针变量称为“函数指针”,它的定义格式为:类型名(*指针变量名)(参数列表)这里有两个需要区分的概念:指针函数和函数指针。指针函数本质上是一个函数,只不过该函数的返回类型是指针;函数指针本质上是一个指针变量,只不过该指针变量指向函数。任务准备某指向函数的指针变量定义如下:int(*fp)(intx,doubley);或:int(*fp)(int,double);上述声明函数指针的代码中,定义了一个指向函数的指针变量fp,它所指向的函数有一个int型参数和一个double型参数,函数返回类型为int。也就是说,指针变量fp可以存储类似intfun(intx,doubley)形式函数的地址。指向函数的指针变量的声明和调用代码如下:int(*fp)(intx,doubley); /*声明指针函数*/intfun(intx,doubley); /*声明函数*/fp=fun; /*将函数名赋值给指针变量*/inta=(*fp)(15,3.14); /*通过指针变量调用函数fun*/任务准备【实例2】定义一个指向函数的指针变量,让该变量分别指向自定义函数add()和sub(),从而实现对两个整数的相加、相减运算。#include<stdio.h>intadd(inta,intb);intsub(inta,intb);intmain(){ intx=15,y=5,z; int(*tp)(inta,intb); tp=add; z=(*tp)(x,y); printf("tp=add时,z=%d\n",z); tp=sub; z=(*tp)(x,y); printf("tp=sub时,z=%d\n",z); return0;}intadd(inta,intb){ returna+b;}intsub(inta,intb){ returna-b;}任务准备编译运行的结果如图6-20所示:从运行结果来看,指向函数的指针变量tp确实可以分别代替函数add()和sub(),来实现两个整数的加和减。如果将参数不同的intfun(intx,doubley)赋值给指针变量tp,tp是否依然可以代替函数fun()呢?答案是:系统会给出编译警告,但并不报错,程序运行无法得到正确结果。图6-20实例2运行结果任务准备任务实施Part
1Part
2Part
3任务描述任务实施【任务1】定义函数实现一维整型数组的冒泡排序,要求排序规则由代入的指向函数的指针变量决定。任务实施1.任务分析我们已经学习过冒泡排序的原理和实现。对一维整型数组进行冒泡排序,代码如下:voidmaopao2(inta[],intn){ inttemp,i,j; for(i=0;i<n;i++) { for(j=0;j<n-1-i;j++) { /*从大到小排序*/ if(a[j]<a[j+1]) { temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } }}函数maopao2()要求代入一维整型数组的首地址和元素个数,而排序的顺序由a[j]与a[j+1]的大小决定。当a[j]<a[j+1]时,相邻的两个数交换,说明是从大到小排序,当a[j]>a[j+1]时,相邻的两个数交换,说明是从小到大排序。任务实施从上面的分析来看,在调用maopao2()函数进行排序前,必须按排序规则修改maopao2()函数中的代码。但这种每当排序规则改变时就去修改排序函数代码的做法,违背了软件工程中“高内聚,低耦合”的思想。通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低,程序模块的可重用性、移植性就更强。简单来说,就是函数与函数之间不要有太多关联,不能因为排序规则变了就不得不修改排序函数的代码。因此,我们提倡将排序规则作为参数代入到排序函数中。本任务就是先创建排序规则的函数,再用指向函数的指针变量将排序规则函数代入排序函数中,从而实现按代入的规则来排序。任务实施2.任务实现本任务实现代码如下,请将代码中空白处补充完整。#include<stdio.h>voidmaopao(inta[],intn,int(*com)(int,int));voidshuchu(inta[],intn);intxiaoda(inta,intb);intdaxiao(inta,intb);intchu5(inta,intb);intmain(){ intb[]={58,94,12,7,45,75,25,56,43}; /*排序规则由代入的xiaoda决定,而不需要去修改maopao()函数的代码*/ maopao(b,9,
); /*maopao(b,9,daxiao);*/ /*maopao(b,9,chu5);*/ shuchu(b,9); return1;}任务实施/*改进后的冒泡排序,排序规则从参数代入*/voidmaopao(inta[],intn,
){ inttemp,i,j; for(i=0;i<n;i++) { for(j=0;j<n-1-i;j++) { /*if(a[j]>a[j+1])*/ if(
==1){ /*交换规划由com()函数决定*/ temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } }}/*输出一维数组元素*/voidshuchu(inta[],intn){ inti; for(i=0;i<n;i++) printf("%d,",a[i]);}/*从小到大排序,即a>b时交换*/intxiaoda(inta,intb){ intc=0; /*a>b就交换,说明是从小到大排序*/ if(a>b) c=1; returnc;}任务实施/*从大到小排序,即a<b时交换*/intdaxiao(inta,intb){ intc=0; if(a<b) c=1; returnc;}/*比较a,b除以5后的余数大小,按余数从大到小排序*/intchu5(inta,intb){ intc=0; inta1=a%5; intb1=b%5; if(a1<b1) c=1; returnc;}任务实施编译运行的结果如图6-21所示:改进后的冒泡排序函数的声明和调用如下:/*声明函数*/voidmaopao(inta[],intn,int(*com)(int,int));maopao(b,9,xiaoda); /*调用函数*/而冒泡排序的规则从由a[j]与a[j+1]的大小来决定,变成了由变量com所指向的函数的返回值来决定,也就是说maopao()函数把排序的规则交给了代入的函数来决定。后续如果出现其它排序规则,只需要按指定格式创建一个新函数,将排序规则写在该函数中,最后作为参数代入maopao()函数,即可实现按新规则排序,而不需要修改maopao()函数的代码。图6-21任务1运行结果任务实施例如,如果要比较一维整型数组元素除以5后的余数大小,并按余数从大到小排序,则可以定义如下函数:intchu5(inta,intb){ intc=0; inta1=a%5; intb1=b%5; if(a1<b1) c=1; returnc;}调用maopao()函数排序时,只需将该函数名作参数代入即可:maopao(b,9,chu5);编译运行的结果如图6-22所示:图6-22按chu5()规则的排序结果数据{94,58,43,12,7,56,45,75,25}除以5后的余数为{4,3,3,2,2,1,0,0,0}。从运行结果来看,排序规则确实是各个整数除以5后的余数从大到小排序。任务实施3.任务总结本任务用指针变量代替指定排序规则的函数,巧妙地将实现交换动作的代码与指定排序规则的代码剥离,函数maopao()只负责写元素交换的代码,而函数xiaoda()、daxiao()、chu5()
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024版房屋修建承包合同范本
- 专用机械设备运输协议2024版版A版
- 二零二五年度智能化建筑系统集成与勘测合同范本3篇
- 2025年打印机网络安全协议3篇
- 2024版美容院员工劳动协议范本版B版
- 2024年高效食堂管理及餐饮服务承包合同书一
- 2024高端牙科美容服务定制合同
- 2024版铸铁部件供应协议样本版B版
- 武汉体育学院《中学化学教材分析》2023-2024学年第一学期期末试卷
- 二零二五年度绿色节能型家装水电施工总承包合同范本3篇
- 2020年上海市高考英语二模试卷(a卷)
- 对账单标准模板
- 小学科学教科版四年级下册第二单元《电路》复习教案(2023春新课标版)
- 创业计划书(成人用品店)
- 电机的结构及工作原理
- GB 6245-2006消防泵
- 空调维修保养服务突发事件应急处置方案
- 东岸冲沙闸及进水闸施工方案
- 宠物入住酒店免责协议
- 2022年沪教版(全国)九年级化学下册第6章溶解现象章节测试试卷(精选含答案)
- 河南省地图含市县地图矢量分层地图行政区划市县概况ppt模板
评论
0/150
提交评论