006 - 下载 第6章 指 针 指针是

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 下载 第6章 指 针 指针是 C语言的精华部分,通过利用指针,我们能很好地利用内存资源,使其发挥最大的 效率。有了指针技术,我们可以描述复杂的数据结构,对字符串的处理可以更灵活,对数组 的处理更方便,使程序的书写简洁,高效,清爽。但由于指针对初学者来说,难于理解和掌 握,需要一定的计算机硬件的知识做基础,这就需要多做多练,多上机动手,才能在实践中 尽快掌握,成为 C的高手。 6.1 指针与指针变量 过去,我们在编程中定义或说明变量,编译系 统就为已定义的变量分配相应的内存单元,也就是 说,每个变量在内存会有固定的位置,有具体的地 址。由于变量的数据类型不同,它所占的内存单元 2000 1 变量a 2002 2 变量b 2004 3.4 变量x 2008 4.5 数也不相同。若我们在程序中做定义为: int a=1, b=2; float x=3.4, y=4.5; double m=3.124; char ch1='a', ch2='b'; 让 我们先看一下编译系统是怎样为变量分配内 存的。变量 a , b是整型变量,在内存各占 2 个字节; x , y 是实型,各占 4个字节; m是双精度实型,占 8 个 3.124 变量y 变量m 2012 2020 a 变量ch1 2021 b 变量ch2 字节; c h 1 , c h 2是字符型,各占 1 个字节。由于计算 机内存是按字节编址的,设变量的存放从内存 2 0 0 0 单元开始存放,则编译系统对变量在内存的安放情 况为图 6-1 所示。 变量在内存中按照数据类型的不同,占内存的 图6-1 不同数据类型的变量在内存中 占用的空间 大小也不同,都有具体的内存单元地址,如变量 a 在内存的地址是 2 0 0 0 ,占据两个字节后, 变量 b 的内存地址就为 2 0 0 2 ,变量 m 的内存地址为 2 0 1 2 等。对内存中变量的访问,过去用 scanf("%d%d%f",&a,&b,&x) 表示将数据输入变量的地址所指示的内存单元。那么,访问变量, 首先应找到其在内存的地址,或者说,一个地址唯一指向一个内存变量,我们称这个地址为 变量的指针。如果将变量的地址保存在内存的特定区域,用变量来存放这些地址,这样的变 量就是指针变量,通过指针对所指向变量的访问,也就是一种对变量的“间接访问” 。 设一组指针变量 p a、 p b 、p x 、 p y 、p m 、 p c h 1、 p c h 2 ,分别指向上述的变量 a、 b 、 x 、 y、 m、ch1、ch2 ,指针变量也同样被存放在内存,二者的关系如图 6-2所示: 在图 6 - 2中,左部所示的内存存放了指针变量的值,该值给出的是所指变量的地址,通过 该地址,就可以对右部描述的变量进行访问。如指针变量 p a的值为 2 0 0 0,是变量 a在内存的地 92 C 语言程序设计 下载 址。因此, pa就指向变量 a。变量的地址就是指针,存放指针的变量就是指针变量。 2000 1000 2000 1002 2002 pb 1004 2004 px 1006 2008 2012 变量a 2002 2 变量b 3.4 变量x py 1008 1 pa pm 2004 2008 1010 2020 2021 pch2 变量y pch1 1012 4.5 2012 1014 3.124 变量m 1016 2020 a 变量ch1 2021 b 变量ch2 图6-2 指针变量与变量在内存中的关系 6.2 指针变量的定义与引用 6.2.1 指针变量的定义 在C程序中,存放地址的指针变量需专门定义; int *ptr1; float *ptr2; char *ptr3; 表示定义了三个指针变量 p t r 1 、 p t r 2、p t r 3。p t r 1 可以指向一个整型变量, p t r 2可以指向一 个实型变量, ptr3 可以指向一个字符型变量,换句话说, ptr1、ptr2、ptr3 可以分别存放整型变 量的地址、实型变量的地址、字符型变量的地址。 定义了指针变量,我们才可以写入指向某种数据类型的变量的地址,或者说是为指针变 量赋初值: int *ptr1,m= 3; float *ptr2, f=4.5; char *ptr3, ch='a'; ptr1=&m; ptr2=&f; ptr3=&ch; 上述赋值语句 p t r 1 = & m表示将变量 m 的地址赋给指针变量 p t r 1,时 p t r 1 就指向 m 。三条 赋值语句产生的效果是 ptr1 指向 m;ptr2指向 f ;ptr3指向 ch 。用示意图 6-3描述如下: 第6章 指 下载 针 ptr1 m ptr2 f ptr3 ch &m 3 &f 4.5 &ch 93 a 图6-3 赋值语句的效果 需要说明的是,指针变量可以指向任何类型的变量,当定义指针变量时,指针变量的值 是随机的,不能确定它具体的指向,必须为其赋值,才有意义。 6.2.2 指针变量的引用 利用指针变量,是提供对变量的一种间接访问形式。对指针变量的引用形式为: *指针变量 其含义是指针变量所指向的值。 [例6-1] 用指针变量进行输入、输出。 main() { int *p,m; scanf("%d",&m); p=&m; /* 指针 p指向变量 m*/ printf("%d",*p); /* p是对指针所指的变量的引用形式 ,与此m意义相同 */ } 运行程序: RUN ↵ 3↵ 3 上述程序可修改为: main() { int *p,m; p=&m; scanf("%d",p); printf("%d", m); } /* p是变量 m的地址 ,可以替换 &m*/ 运行效果完全相同。请思考一下若将程序修改为如下形式: main() { int *p,m; scanf("%d",p); p=&m; printf("%d", m); } 会产生什么样的结果呢?事实上,若定义了变量以及指向该变量的指针为: int a,*p; 94 C语言程序设计 下载 若p=&a; 则称 p 指向变量 a,或者说 p具有了变量 a的地址。在以后的程序处理中,凡是可 以写 &a的地方,就可以替换成指针的表示 p,a 就可以替换成为 *p。 6.3 指针运算符与指针表达式 6.3.1 指针运算符与指针表达式 在C中有两个关于指针的运算符: • & 运算符 : 取地址运算符, &m即是变量 m的地址。 • * 运算符:指针运算符, *ptr表示其所指向的变量。 [例6-2] 从键盘输入两个整数,按由大到小的顺序输出。 main() { int *p1,*p2,a,b,t; /* 定义指针变量与整型变量 */ scanf("%d,%d",&a,&b); p1=&a; /* 使指针变量指向整型变量 */ p2=&b; if(*p1<*p2) { /* 交换指针变量指向的整型变量 */ t=*p1; *p1=*p2; *p2=t; } printf("%d,%d\n",a,b); } 在程序中,当执行赋值操作 p 1 = & a 和 p 2 = & b后,指针实实在在地指向了变量 a 与b ,这时 引用指针 *p1与*p2,就代表了变量 a 与b。 运行程序 : RUN ↵ 3,4 ↵ 4,3 在程序运行过程中,指针与所指的变量之间的关系如图 6-4所示: p1 a *p1 &a 3 p2 p1 a *p1 b *p2 &b 4 a) &a 4 p2 b *p2 &b 3 b) 图6-4 程序运行中指针与变量之间的关系 当指针被赋值后,其在内存的安放如 a),当数据比较后进行交换,这时,指针变量与所指 向的变量的关系如 b)所示,在程序的运行过程中,指针变量与所指向的变量其指向始终没变。 下面对程序做修改。 第6章 指 下载 针 95 [例6-3] main() { int *p1,*p2,a,b,*t; scanf("%d,%d",&a,&b); p1=&a; p2=&b; if(*p1<*p2) { /* 指针交换指向 */ t=p1; p1=p2; p2=t; } printf("%d,%d\n",*p1,*p2); } 程序的运行结果完全相同,但程序在运行过程中,实际存放在内存中的数据没有移动, 而是将指向该变量的指针交换了指向。其示意如图 6-5: *p1 p1 &a 3 &b 3 p2 b *p2 p2 b p1 p1 a &b 4 a *p2 &a a) 4 b) 图6-5 修改后的程序在运行中指针与变量之间的关系 当指针交换指向后, p 1 和p 2由原来指向的变量 a 和b 改变为指向变量 b 和a,这样一来, * p 1 就表示变量 b ,而 * p 2 就表示变量 a。在上述程序中,无论在何时,只要指针与所指向的变量满 足p=&a ;我们就可以对变量 a 以指针的形式来表示。此时 p等效于 &a,*p等效于变量 a 。 6.3.2 指针变量作函数的参数 函数的参数可以是我们在前面学过的简单数据类型,也可以是指针类型。使用指针类型 做函数的参数,实际向函数传递的是变量的地址。由于子程序中获得了所传递变量的地址, 在该地址空间的数据当子程序调用结束后被物理地保留下来。 [例6-4] 利用指针变量作为函数的参数,用子程序的方法再次实现上述功能。 main() { void chang(); /* 函数声明 */ int *p1,*p2,a,b,*t; scanf("%d,%d",&a,&b); p1=&a; p2=&b; chang(p1,p2); /* 子程序调用 */ printf("%d,%d\n",*p1,*p2); 96 C 语言程序设计 下载 return 0; } void chang(int *pt1,int *pt2) { /* 子程序实现将两数值调整为由大到小 */ int t; if (*pt1<*pt2) /* 交换内存变量的值 */ { t=*pt1; *pt1=*pt2; *pt2=t;} return; } 由于在调用子程序时,实际参数是指针变量,形式参数也是指针变量,实参与形参相结 合,传值调用将指针变量传递给形式参数 pt1和pt2。但此时传值传递的是变量地址,使得在子 程序中 pt1和pt2具有了 p1 和p2的值,指向了与调用程序相同的内存变量,并对其在内存存放的 数据进行了交换,其效果与 [ 例6-2]相同。 思考下面的程序,是否也能达到相同的效果呢? main() { void chang(); int *p1,*p2,a,b,*t; scanf("%d,%d",&a,&b); p1=&a; p2=&b; chang(p1,p2); printf("%d,%d\n",*p1,*p2); } void chang(int *pt1,int *pt2) { int *t; if (*pt1<*pt2) { t=pt1; pt1=pt2; pt2=t; } return; } 程序运行结束,并未达到预期的结果,输出与输入完全相同。其原因是对子程序来说, 函数内部进行指针相互交换指向,而在内存存放的数据并未移动,子程序调用结束后, main() 函数中 p1和p2保持原指向,结果与输入相同。 6.4 指针与数组 变量在内存存放是有地址的,数组在内存存放也同样具有地址。对数组来说,数组名就 是数组在内存安放的首地址。指针变量是用于存放变量的地址,可以指向变量,当然也可存 放数组的首址或数组元素的地址,这就是说,指针变量可以指向数组或数组元素,对数组而 言,数组和数组元素的引用,也同样可以使用指针变量。下面就分别介绍指针与不同类型的 数组。 第6章 指 下载 针 97 6.4.1 指针与一维数组 假设我们定义一个一维数组,该数组在内存会有系统分配的一个存储空间,其数组的名 字就是数组在内存的首地址。若再定义一个指针变量,并将数组的首址传给指针变量,则该 指针就指向了这个一维数组。我们说数组名是数组的首地址,也就是数组的指针。而定义的 指针变量就是指向该数组的指针变量。对一维数组的引用,既可以用传统的数组元素的下标 法,也可使用指针的表示方法。 int a[10] , *ptr; /* 定义数组与指针变量 */ 做赋值操作: ptr=a; 或 ptr=&a[0]; 则ptr就得到了数组的首址。其中, a是数组的首地址, &a[0]是数组元素 a[0]的地址,由于 a [ 0 ] 的地址就是数组的首地址,所以,两条赋值操作效果完全相同。指针变量 p t r 就是指向数 组a的指针变量。 若ptr指向了一维数组,现在看一下 C规定指针对数组的表示方法: 1) ptr+n 与a + n表示数组元素 a [ n ] 的地址,即 &a[n] 。对整个 a 数组来说,共有 1 0个元素, n 的取值为 0 ~9 ,则数组元素的地址就可以表示为 p t r + 0 ~ p t r + 9或 a + 0~ a + 9 ,与 &a[0] ~& a [ 9 ] 保持一致。 2) 知道了数组元素的地址表示方法, * ( p t r + n )和* ( a + n )就表示为数组的各元素即等效于 a[n] 。 3) 指向数组的指针变量也可用数组的下标形式表示为 ptr[n],其效果相当于 *(ptr+n)。 [例6-5] /* 以下标法输入输出数组各元素。 下面从键盘输入 10个数,以数组的不同引用形式输出数组各元素的值。 # include <stdio.h> main() { int n,a[10],*ptr=a; for(n=0;n<=9;n++) scanf("%d",&a[n]); printf("1------output! \n"); for(n=0;n<=9;n++) printf("%4d",a[n]); printf("\n"); } 运行程序: RUN ↵ 1234567890 ↵ 1------output! 1234567890 [例6-6] 采用指针变量表示的地址法输入输出数组各元素。 # include<stdio.h> main() { int n,a[10],*ptr=a; /* 定义时对指针变量初始化 */ 98 C语言程序设计 for(n=0;n<=9;n++) scanf("%d",ptr+n); printf("2------output! \n"); for(n=0;n<=9;n++) printf("%4d",*(ptr+n)); printf("\n"); } 运行程序: RUN ↵ 1234567890 ↵ 2------output! 1234567890 [例6-7] 采用数组名表示的地址法输入输出数组各元素。 main() { int n,a[10],*ptr=a; for(n=0;n<=9;n++) scanf("%d",a+n); printf("3------output! \n"); for(n=0;n<=9;n++) printf("%4d",*(a+n)); printf("\n"); } 运行程序: RUN ↵ 1234567890 ↵ 3------output! 1234567890 [例6-8] 用指针表示的下标法输入输出数组各元素。 main() { int n,a[10],*ptr=a; for(n=0;n<=9;n++) scanf("%d",&ptr[n]); printf("4------output! \n"); for(n=0;n<=9;n++) printf("%4d",ptr[n]); printf("\n"); } 运行程序: RUN ↵ 1234567890 ↵ 4----output! 下载 第6章 指 下载 针 99 1234567890 [例6-9] 利用指针法输入输出数组各元素。 main() { int n,a[10],*ptr=a; for(n=0;n<=9;n++) scanf("%d",ptr++); printf("5------output! \n"); ptr=a; /* 指针变量重新指向数组首址 */ for(n=0;n<=9;n++) printf("%4d",*ptr++); printf("\n"); } 运行程序: RUN ↵ 1234567890 ↵ 5-----output! 1234567890 在程序中要注意 * p t r + + 所表示的含义。 * p t r表示指针所指向的变量; p t r + + 表示指针所指 向的变量地址加 1个变量所占字节数,具体地说,若指向整型变量,则指针值加 2,若指向实 型,则加 4,依此类推。而 p r i n t f (“ % 4 d”, * p t r + + ) 中, * p t r + +所起作用为先输出指针指向的变 量的值,然后指针变量加 1。循环结束后,指针变量指向如图 6-6所示: a[0] a[1] a[2] a[3] 1 2 3 a[4] a[5] a[6] a[7] a[8] 4 5 6 7 8 9 a[9] 0 ptr 图6-6 例6-9中循环结束后的指 针变量 指针变量的值在循环结束后,指向数组的尾部的后面。假设元素 a [ 9 ] 的地址为 1 0 0 0 , 整型 占2字节,则 ptr的值就为 1002。请思考下面的程序段: main() { int n,a[10],*ptr=a; for(n=0;n<=9;n++) scanf("%d",ptr++); printf("4------output! \n"); for(n=0;n<=9;n++) printf("%4d",*ptr++); printf("\n"); } 程序与例 6-9相比,只少了赋值语句 ptr=a;程序的运行结果还相同吗? 6.4.2 指针与二维数组 定义一个二维数组: 100 C语言程序设计 下载 int a[3][4]; 表示二维数组有三行四列共 12个元素,在内存中按行存放,存放形式为图 6-7: 其中 a 是二维数组的首地址, & a [ 0 ] [ 0 ]既可以看作数组 0行 0 列的首地址,同样还可以看作 是二维数组的首地址, a [ 0 ]是第 0行的首地址,当然也是数组的首地址。同理 a [ n ] 就是第 n行的 首址; &a[n][m] 就是数组元素 a[n][m]的地址。 既然二维数组每行的首地址都可以用 a [ n ]来表示,我们就可以把二维数组看成是由 n 行一 维数组构成,将每行的首地址传递给指针变量,行中的其余元素均可以由指针来表示。下面 的图 6-8 给出了指针与二维数组的关系: 图6-7 二维数组在内存中的存放 图6-8 指针与二维数组的关系 我们定义的二维数组其元素类型为整型,每个元素在内存占两个字节,若假定二维数组 从1000单元开始存放,则以按行存放的原则,数组元素在内存的存放地址为 1000~1022。 用地址法来表示数组各元素的地址。对元素 a [ 1 ] [ 2 ], & a [ 1 ] [ 2 ]是其地址, a [ 1 ] + 2也是其地 址。分析 a [ 1 ] + 1与 a [ 1 ] + 2的地址关系,它们地址的差并非整数 1 ,而是一个数组元素的所占位 置2,原因是每个数组元素占两个字节。 对0行首地址与 1行首地址 a与 a + 1 来说,地址的差同样也并非整数 1,是一行,四个元素占 的字节数 8。 由于数组元素在内存的连续存放。给指向整型变量的指针传递数组的首地址,则该指针 指向二维数组。 int *ptr, a[3][4] ; 第6章 指 下载 针 101 若赋值: ptr=a;则用 ptr++ 就能访问数组的各元素。 [例6-10] 用地址法输入输出二维数组各元素。 # include <stdio.h> main() { int a[3][4]; int i,j; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d",a[i]+j); /* 地址法 */ for(i=0;i<3;i++) { for(j=0;j<4;j++) printf("%4d",*(a[i]+j)); /* *(a[i]+j) 是地址法所表示的数组元素 */ printf("\n"); } } 运行程序: RUN ↵ 1 2 3 4 5 6 7 8 9 10 11 ↵ 12 12 3 4 56 7 8 9 10 11 12 [例6-11] 用指针法输入输出二维数组各元素。 # include<stdio.h> main() { int a[3][4],*ptr; int i,j; ptr=a[0]; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d",ptr++); ptr=a[0]; for(i=0;i<3;i++) { for(j=0;j<4;j++) printf("%4d",*ptr++); printf("\n"); } } 运行程序: RUN ↵ 1 2 3 4 5 6 7 8 9 10 11 ↵ 12 12 3 4 /* 指针的表示方法 */ 102 5 9 6 10 C语言程序设计 7 11 下载 8 12 对指针法而言,程序可以把二维数组看作展开的一维数组: main() { int a[3][4],*ptr; int i,j; ptr=a[0]; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d",ptr++); ptr=a[0]; for(i=0;i<12 ;i++) printf("%4d",*ptr++); printf("\n"); } /* 指针的表示方法 */ 运行程序: RUN ↵ 1 2 3 4 5 6 7 8 9 10 11 ↵ 12 12 3 4 5 6 7 8 9 10 11 12 6.4.3 数组指针作函数的参数 学习了指向一维和二维数组指针变量的定义和正确引用后,我们现在学习用指针变量作 函数的参数。 [例6-12] 调用子程序,实现求解一维数组中的最大元素。 我们首先假设一维数组中下标为 0 的元素是最大和用指针变量指向该元素。后续元素与该 元素一一比较,若找到更大的元素,就替换。子程序的形式参数为一维数组,实际参数是指 向一维数组的指针。 # include <stdio.h> main() { int sub_max(); int n,a[10],*ptr=a; int max; for(n=0;n<=i-1;n++) scanf("%d",&a[n]); max=sub_max(ptr,10); printf("max=%d\n",max); } int sub_max(b,i) int b,i; { int temp,j; temp=b[0]; /* 函数声明 */ /* 定义变量,并使指针指向数组 */ /* 输入数据 */ /* 函数调用,其实参是指针 */ /* 函数定义,其形参为数组 */ 第6章 指 下载 针 103 for(j=1;j<=9;j++) if(temp<b[j]) temp=b[j]; return temp; } 程序的 m a i n ( ) 函数部分,定义数组 a 共有 1 0 个元素,由于将其首地址传给了 p t r ,则指针 变量 ptr 就指向了数组,调用子程序,再将此地址传递给子程序的形式参数 b,这样一来, b 数组在内存与 a 数组具有相同地址,即在内存完全重合。在子程序中对数组 b 的操作,与操 作数组 a 意义相同。其内存中虚实结合的示意如图 6-9所示。 m a i n ( ) 函数完成数据的输入,调用子程序并输出运行结果。 s u b _ m a x ( ) 函数完成对数组元 素找最大的过程。在子程序内数组元素的表示采用下标法。运行程序: RUN ↵ 1357924680 ↵ max=9 [例6-13] 上述程序也可采用指针变量作子程序的形式参数。 # include <stdio.h> main() { int sub_max(); int n,a[10],*ptr=a; int max; for(n=0;n<=9;n++) scanf("%d",&a[n]); max=sub_max(ptr,10); printf("max=%d\n",max); } int sub_max(b,i) /* 形式参数为指针变量 */ int *b,i; { int temp,j; temp=b[0]; /* 数组元素指针的下标法表示 */ for(j=1;j<=i-1;j++) if(temp<b[j]) temp=b[j]; return temp; } 在子程序中,形式参数是指针,调用程序的实际参数 p t r为指向一维数组 a的指针,虚实结 合,子程序的形式参数 b得到 ptr的值,指向了内存的一维数组。数组元素采用下标法表示,即 一维数组的头指针为 b,数组元素可以用 b[j] 表示。其内存中虚实参数的结合如图 6-10所示。 运行程序: RUN ↵ 1357924680 ↵ max=9 [例6-14] 上述程序的子程序中,数组元素还可以用指针表示。 # include <stdio.h> main() 104 C语言程序设计 下载 { int sub_max(); int n,a[10],*ptr=a; int max; for(n=0;n<=9;n++) scanf("%d",&a[n]); max=sub_max(ptr,10); printf("max=%d\n",max); } int sub_max(b,i) * 子程序定义 */ / int *b,i; { int temp,j; temp=*b++; for(j=1;j<=i-1;j++) if(temp<*b) temp=*b++; return temp; } 主程序 a ptr 主程序 子程序 a ptr 子程序 a[0] b[0] a[1] b[1] a[1] a[2] b[2] a[2] a[3] b[3] a[3] a[4] b[4] a[4] a[5] b[5] a[5] a[6] b[6] a[6] a[7] b[7] a[7] a[8] b[8] a[8] a[9] b[9] 图6-9 例6-12程序在内存中虚实结合示意图 a[0] b a[9] 图6-10 例6-13程序在内存中虚实结合示意图 在程序中,赋值语句 temp=*b++;可以分解为: temp=*b ;b++;两句,先作 temp=*b;后 作b++;程序的运行结果与上述完全相同。 对上面的程序作修改,在子程序中不仅找最大元素,同时还要将元素的下标记录下来。 # include <stdio.h> main() { int *max();/* 函数声明 */ int n,a[10],*s,i; for(i=0;i<10;i++)/* 输入数据 */ 第6章 指 下载 针 105 scanf("%d",a+i); s=max(a,10); /* 函数调用 */ printf("max=%d,index=%d\n",*s,s-a); } int *max(a,n) /* 定义返回指针的函数 */ int *a,n; { int *p,*t; /*p 用于跟踪数组, t用于记录最大值元素的地址 */ for(p=a,t=a;p-a<n;p++) if(*p>*t) t=p; return t; } 在m a x ()函数中,用 p - a < n来控制循环结束, a 是数组首地址, p用于跟踪数组元素的地 址, p - a 正好是所跟踪元素相对数组头的距离,或者说是所跟踪元素相对数组头的元素个数, 所以在 main()中,最大元素的下标就是该元素的地址与数组头的差,即 s-a。运行程序: RUN ↵ 1357924680 ↵ max=9,index=4 [例6-15] 用指向数组的指针变量实现一维数组的由小到大的冒泡排序。编写三个函数用 于输入数据、数据排序、数据输出。 在第 5 章的例题中,我们介绍过选择法排序及算法,此例再介绍冒泡排序算法。为了将一 组n个无序的数整理成由小到大的顺序,将其放入一维数组 a[0]、a[1]...a[n-1]。冒泡算法如下: (开序) ① 相邻的数组元素依次进行两两比较,即 a [ 0 ]与a [ 1 ] 比、 a [ 1 ]与a [ 2 ] 比. . . a [ n - 2 ]与 a [ n - 1 ]比, 通过交换保证数组的相邻两个元素前者小,后者大。此次完全的两两比较,能免实现 a [ n - 1 ]成 为数组中最大。 ② 余下 n - 1 个元素,按照上述原则进行完全两两比较,使 a [ n - 2 ]成为余下 n - 1 个元素中最 大。 ③ 进行共计 n-1趟完全的两两比较,使全部数据整理有序。 下面给出一趟排序的处理过程: 原始数据 38 25 第一次相邻元素比: 38 25 第二次相邻元素比: 32 85 第三次相邻元素比: 3 25 8 4个元素进行 3 次两两比较,得到一个最大元素。若相邻元素表示为 a [ j ] 和 a [ j + 1 ],用指针 变量 P指向数组,则相邻元素表示为 *(P+j)和*(P+j+1)程序实现如下: # include<stdio.h> #define N 10 main() { void input(); /* 函数声明 */ 106 C语言程序设计 下载 void sort(); void output(); int a[N],*p; /* 定义一维数组和指针变量 */ input(a,N); /* 数据输入函数调用,实参 a是数组名 */ p=a; /* 指针变量指向数组的首地址 */ sort(p,N); /* 排序,实参 p是指针变量 */ output(p,N); /* 输出,实参 p是指针变量 */ } void input(arr,n) /* 无需返回值的输入数据函数定义 ,形参arr 是数组 */ int arr,n; { int i; printf("input data:\n"); for(i=0;i<n;i++) /* 采用传统的下标法 */ scanf("%d",&arr[i]); } void sort(ptr,n) /* 冒泡排序,形参 ptr 是指针变量 */ int *ptr,n; { int i,j,t; for(i=0;i<n-1;i++) for(j=0;j<n-1-i;j++) 相临两个元素进行比较 */ if (*(ptr+j)>*(ptr+j+1))/* { t=*(ptr+j); /* 两个元素进行交换 */ *(ptr+j)=*(ptr+j+1); *(ptr+j+1)=t; } } void output(arr,n) /* 数据输出 */ int arr,n; { int i,*ptr=arr; /* 利用指针指向数组的首地址 */ printf("output data:\n"); for(;ptr-arr<n;ptr++) /* 输出数组的 n个元素*/ printf("%4d",*ptr); printf("\n"); } 运行程序: RUN ↵ 3 5 1 2 7 3 9 3 3 5 23 7 43 2 1 ↵10 9 10 23 43 由于 C程序的函数调用是采用传值调用,即实际参数与形式参数相结合时,实参将值传给 形式参数,所以当我们利用函数来处理数组时,如果需要对数组在子程序中修改,只能传递 数组的地址,进行传地址的调用,在内存相同的地址区间进行数据的修改。在实际的应用中, 如果需要利用子程序对数组进行处理,函数的调用利用指向数组(一维或多维)的指针作参 数,无论是实参还是形参共有下面四种情况: 第6章 指 下载 实 参 形 针 107 参 1 数组名 数组名 2 数组名 指针变量 3 指针变量 数组名 4 指针变量 指针变量 在函数的调用时,实参与形参的结合要注意所传递的地址具体指向什么对象,是数组的 首址,还是数组元素的地址,这一点很重要。 [例6-16] 用指向二维数组的指针作函数的参数,实现对二维数组的按行相加。 # include <stdio.h> #define M 3 #define N 4 main() { float a[M][N]; float score1,score2,score3, *pa=a[0];/* 指针变量 pa 指向二维数组 */ /* score1,score2,score3 分别记录三行的数据相加 */ int i,j; void fun(); for(i=0;i<M;i++) for(j=0;j<N; j++) /* 二维数组的数据输入 */ scanf("%f",&a[i][j]); fun(pa,&score1,&score2,&score3); /* 函数调用,不仅传递数组首地址,还要传递变量的地址 */ printf("%.2f,%.2f,%.2f\n",score1,score2,score3); } void fun(b,p1,p2,p3) float b[ ][N],*p1,*p2,*p3; {int i,j; *p1=*p2=*p3=0; for(i=0;i<M;i++) for(j=0;j<N;j++) { if(i==0) *p1=*p1+b[i][j]; /* 第0行的数据相加 */ if(i==1) *p2=*p2+b[i][j]; /* 第1行的数据相加 */ if(i==2) *p3=*p3+b[i][j]; /* 第2行的数据相加 */ } } 程序中与形式参数 p 1、p 2 和p 3 相对应的是实际参数 & s c o r e 1、 & s c o r e 2和& s c o r e 3 ,其实际 含义为 p1=&score1等,即将变量的地址传递给指针变量达到按行相加。运行程序, RUN ↵ 1234 ↵ 3456 ↵ 5678 ↵ 10 .00 ,18 .00 ,26 .00 [例6-17] 求解二维数组中的最大值及该值在二维数组中的位置。 108 C语言程序设计 下载 我们知道,二维数组在内存中是按行存放,假定我们定义二维数组和指针如下: int a[3][4] *p=a[0]; , 则指针 p就指向二维数组。其在内存的存放情况如图 6-11所示。 a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3] P[0] p[1] p[2] p[3] p[4] p[5] p[6] p[7] p[8] p[9] p[10] p[11] 图6-11 例6-17中二维数组在内存中的存放 从上述存放情况来看,若把二维数组的首地址传递给指针 p ,则映射过程如图 6 - 11 所示。我们只要找到用 p所表示的一维数组中最大的元素及下标,就可转换为在二维数组中的 行列数。 # include<stdio.h> main() { int a[3][4],*ptr,i,j,max,maxi,maxj; /*max 是数组的最大, maxi 是最大元素所在行, maxj 是最大元素所在列 */ for(i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d",&a[i][j]); ptr=a[0]; /* 将二维数组的首地址传递给指针变量 */ max_arr(ptr,&max,&maxi,12); maxj=maxi%4; /* 每行有四个元素,求该元素所在列 */ maxi=maxi/4; /* 求该元素所在行 */ printf("max=%d,maxi=%d,maxj=%d",max,maxi,maxj); } int max_arr(b,p1,p2,n) int *b,*p1,*p2,n; /*b 指向二维数组的指针, p1 指向最大值, p2 指向最大值在一维数组中的位置, */ /*n 是数组的大小 */ { int i; *p1=b[0]; *p1=0; 找最大*/ for(i=1;i<n;i++)/* if (b[i]>*p1) {*p1=b[i]; *p2=i;} } 运行程序: RUN ↵ 4 7 8 9↵ 3 7 9 3↵ 1 5 2 6↵ max=9,maxi=0,maxj=3 6.4.4 指针与字符数组 在前面的课程中,我们用过了字符数组,即通过数组名来表示字符串,数组名就是数组 的首地址,是字符串的起始地址。下面的例子用于简单字符串的输入和输出。 第6章 指 下载 针 109 #include <stdio.h> main() { char str[20]; gets(str); printf("%s\n",str); } RUN ↵ good morning! ↵ good morning! ↵ 现在,我们将字符数组的名赋予一个指向字符类型的指针变量,让字符类型指针指向字 符串在内存的首地址,对字符串的表示就可以用指针实现。其定义的方法为: char str[20] , *P=str;这样一来,字符串 str就可以用指针变量 P来表示了。 #include <stdio.h> main() { char str[20],*p=str ; /* p=str 则表示将字符数组的首地址传递给指针变量 p */ gets(str); printf("%s\n",p); } RUN ↵ good morning! ↵ good morning! ↵ 需要说明的是,字符数组与字符串是有区别的,字符串是字符数组的一种特殊形式,存 储时以“ \ 0”结束,所以,存放字符串的字符数组其长度应比字符串大 1 。对于存放字符的字 符数组,若未加“ \0”结束标志,只能按逐个字符输入输出。 [例6-18] 字符数组的正确使用方法。 # include<stdio.h> main() { char str[10],*p=str; int i; scanf("%s",str); for( i=0;i<10;i++) printf("%c",*p++); printf("\n"); p=str; printf("%s",p); puts(str); } /* 输入的字符串长度超过 10*/ /* 正确输出 */ /* 字符数组无 '\0' 标志,输出出错 */ /* 字符数组无 '\0' 标志,输出出错 */ 对上述程序中字符数组以字符串形式输出,若无“ \0”标志,则找不到结束标志,输出出 错。 [例6-19] 用指向字符串的指针变量处理两个字符串的复制。 110 C 语言程序设计 下载 字符串的复制要注意的是:若将串 1复制到串 2,一定要保证串 2的长度大于或等于串 1。 #include<stdio.h> main() { char str1[30],str2[20],*ptr1=str1,*ptr2=str2; printf("input str1:"); gets(str1); /* 输入str1*/ printf("input str2:"); gets(str2); /* 输入str2*/ printf("str1------------str2\n"); printf("%s.......%s\n",ptr1,ptr2); while(*ptr2) *ptr1++=*ptr2++; 字符串复制 */ /* *ptr1='\0'; /* 写入串的结束标志 */ printf("str1------------str2\n"); printf("%s.......%s\n",str1,str2); } 在程序的说明部分,定义的字符指针指向字符串。语句 while(*ptr2) *ptr1++=*ptr2++; 先 测试表达式的值,若指针指向的字符是“ \ 0” ,该字符的 A S C I I码值为 0,表达式的值为假,循 环结束,表达式的值非零,则执行循环 *ptr1++=*ptr2++。语句 *ptr1++按照运算优先级别,先 算*ptr1 ,再算 ptr1++。 运行程序: RUN ↵ input str1: I love China! ↵ input str2: I love Chengdu! ↵ str1--------------------str2 I love China! ....... I love Chengdu! str1--------------------str2 I love Chengdu! ....... I love Chengdu! 现在,我们修改程序中语句 printf("%s.......%s\n",str1,str2)为printf("%s.......%s\n",ptr1, ptr2); 会出现什么结果呢?请思考。 [例6-20] 用指向字符串的指针变量处理两个字符串的合并。 # include<stdio.h> main() { char str1[50],str2[20],*ptr1=str1,*ptr2=str2; printf( "input str1:"); gets(str1); printf( "input str2:"); gets(str2); printf( "str1------------str2\n"); printf( "%s.......%s\n",ptr1,ptr2); while(*ptr1) ptr1++; /* 移动指针到串尾 */ while(*ptr2) *ptr1++=*ptr2++; 串连接*/ /* *ptr1='\0'; /* 写入串的结束标志 */ ptr1=str1; ptr2=str2; printf( "str1------------------str2\n"); 第6章 指 下载 针 111 printf( "%s.......%s\n",ptr1,ptr2); } RUN ↵ input str1: I love China! ↵ input str2: I love Chengdu! ↵ str1--------------------str2 I love China! ....... I love Chengdu! str1------------------------------------------str2 I love China! I love Chengdu! ...... I love Chengdu!. 需要注意的是,串复制时,串 1的长度应大于等于串 2;串连接时,串 1的长度应大于等于 串1与串 2的长度之和。 6.5 指针的地址分配 我们可以定义指针变量指向任何类型的变量。在上述的处理过程中,指针变量指向的变 量通过传递变量的地址来实现。指针变量的取值是内存的地址,这个地址应当是安全的,不 可以是随意的,否则,写入内存单元的值将会使得已存放的数据或程序丢失。应使用编译系 统提供的标准函数来实现地址分配。 A N S I 标准建议设置了两个最常用的动态分配内存的函数 malloc() 和 f r e e ( ) ,并包含在 stdlib.h中,但有些 C编译却使用 malloc.h 包含。使用时请参照具体的 C编译版本。 我们这里所指的动态内分配其含义是指:当定义指针变量时,其变量的取值是随机的, 可能指向内存的任一单元。若指针的指向是不安全的内存地址,在该地址空间上的数据交换 就会产生意料不到的效果。为此,在程序的执行过程中,要保证指针操作的安全性,就要为 指针变量分配安全地址。在程序执行时为指针变量所做的地址分配就称之为动态内存分配。 当无需指针变量操作时,可以将其所分配的内存归还系统,此过程我们称之为内存单元的释 放。 malloc( )用以向编译系统申请分配内存; free( ) 用以在使用完毕释放掉所占内存。 [例6-21] 两个字符串的交换。 #include <stdlib.h> #include <string.h> #include <stdio.h> main() { char *ptr1,*ptr2,*temp; ptr1=malloc(30); /* 态为指针变量分配长度为 30 字节的存储空间 */ 动 ptr2=malloc(20); temp=malloc(30); printf("input str1:"); gets(ptr1); /* 输入字符串 */ printf("input str2:"); gets(ptr2); printf("str1------------str2\n"); printf("%s.......%s\n",ptr1,ptr2); strcpy(temp,ptr1);/* 串复制 */ 112 C语言程序设计 下载 strcpy(ptr1,ptr2); strcpy(ptr2,temp); printf("str1------------str2\n"); printf("%s.......%s\n",ptr1,ptr2); free(ptr1); free(ptr2); } 为指针变量分配的存储空间长度取决于存放字符的多少。在上述的程序中,两个串的交 换可以通过标准函数 strcpy() 来 完成,也可以通过串指针交换指向完成,用 t e m p = p t r 1 ; p t r 1 = p t r 2 ;p t r 2 = t e m p ;三条赋值语句实现。但是,利用指针交换指向,其物理意义与串通过 函数进行的复制完全不同。前者是存放串地址的指针变量数据交换,后者是串在内存物理空 间的数据交换。指针变量用完后,将指针变量所占的存储空间释放。 运行程序: run↵ input str1: China↵ input str2: Chengdu ↵ str1------------str2 China----------Chengdu str1------------str2 Chengdu----- China 6.6 指针数组 前面介绍了指向不同类型变量的指针的定义和使用,我们可以让指针指向某类变量,并 替代该变量在程序中使用;我们也可以让指针指向一维、二维数组或字符数组,来替代这些 数组在程序中使用,给我们在编程时带来许多方便。 下面我们定义一种特殊的数组,这类数组存放的全部是指针,分别用于指向某类的变量, 以替代这些变量在程序中的使用,增加灵活性。指针数组定义形式: 类型标识 *数组名 [数组长度 ] 例如: char *str[4]; 由于 [ ] 比*优先权高,所以首先是数组形式 str[4 ] ,然后才是与“ *”的结合。这样一来指 针数组包含 4 个指针 s t r [ 0 ]、 s t r [ 1 ] 、 s t r [ 2 ] 、 s t r [ 3 ] ,各自指向字符类型的变量。例如: int * ptr[5]; 该指针数组包含 5 个指针 p t r [ 0 ] 、 p t r [ 1 ]、 p t r [ 2 ] 、 p t r [ 3 ] 、 p t r [ 4 ] ,各自指向整型类型的变 量。 [例6-22] 针对指针数组的应用,我们分别用指针数组的各指针指向字符串数组、指向一 维整型数组、指向二维整型数组。 #include <stdlib.h> #include <stdio.h> main() { char *ptr1[4]={"china","chengdu","sichuang","chongqin"}; /* 指针数组 ptr1 的4个指针分别依此指向 4个字符串 */ int i,*ptr2[3],a[3]={1,2,3},b[3][2]={1,2,3,4,5,6}; 第6章 指 下载 针 113 for (i=0;i<4;i++) printf("\n%s",ptr1[i]); /* 依此输出 ptr1 数组4个指针指向的 4个字符串 */ printf("\n"); for(i=0;i<3;i++) ptr2[i]=&a[i]; /* 将整型一维数组 a的3个元素的地址传递给指针数组 ptr2*/ for(i=0;i<3;i++)/* 依此输出 ptr2 所指向的 3个整型变量的值 */ printf("%4d",*ptr2[i]); printf("\n"); for(i=0;i<3;i++) ptr2[i]=b[i]; /* 传递二维数组 b的每行首地址给指针数组的 4 个指针*/ for(i=0;i<3;i++)/* 按行输出 */ printf("%4d%4d\n",*ptr2[i],*ptr2[i]+1); } 程序中指针数组与所指对象的关系如图 6-12所示。 图6-12 例6-22程序中指针数组与所指对象的关系 p t r 1指针数组中的 4 个指针分别指向 4 个字符串,如图 6 - 11的 a )所示,程序中依此输出; p t r 2 指针数组共有 3个指针,若将整型一维数组 a中各元素地址分别传递给指针数组的各指针, 则ptr2[0] 就指向 a[0];ptr2[1] 就指向 a[1];ptr2[2]就指向 a[2]。若将二维数组各行的首地址分别 传递给指针数组的各指针,如图 6 - 11 b)所示,这样一来, p t r 2 [ 0 ]就指向了 b数组的第 0 行,该 行有两个元素,其地址为 ptr2[0] 与ptr2[0]+1;相应指针数组第 i个元素 ptr2[i]指向的 b数组的第 i 行两个元素地址分别为 ptr2[i]与 ptr[i]+1。 运行程序: RUN ↵ china chengdu sichuang chongqin 1 23 1 2 2 4 5 6 在处理二维字符数组时,我们可以把二维字符数组看成是由多个一维字符数组构成,也 就是说看成是多个字符串构成的二维字符数组,或称为字符串数组。 指针数组对于解决这类问题(当然也可以解决其它问题)提供了更加灵活方便的操作。 有一点需要说明,若定义一个指针数组后,指针数组各元素的取值(即地址)要注意安全性。 114 C语言程序设计 下载 如定义指针数组: char *ptr[3]; 我们说该数组包含三个指针,但指针的指向是不确定的,指针现在可能指向内存的任一 地址。假定现在作语句: scanf("%s", ptr[ i ] ), 则输入的字符串在内存的存放其地址由 ptr[ i ] 决定。除非给指针数组元素赋值安全的地址。 [ 例6-23] 定义字符指针数组,包含 5 个数组元素。同时再定义一个二维字符数组其数组 大小为 5*10,即 5行10列,可存放 5个字符串。若将各字符串的首地址传递给指针数组各元素, 那么指针数组就成为名副其实的字符串数组。下面对各字符串进行按字典排序。 在字符串的处理函数中, s t r c m p ( s t r 1 , s t r 2 ) 函数就可以对两个字符串进行比较,函数的返 回值 > 0、 = 0、< 0 分别表示串 s t r 1 大于 s t r 2 、 s t r 1等于 s t r 2、 s t r 1小于 s t r 2 。再利用 s t r c p y ( ) 函数实 现两个串的复制。下面选用冒泡排序法。 #include <stdlib.h> #include <string.h> #include<stdio.h> main() { char *ptr1[4],str[4][20],temp[20]; /* 定义指针数组、二维字符数组、用于交换的一维字符数组 */ int i,j; for (i=0;i<4;i++) gets(str[i]); /* 输入4个字符串 */ printf("\n"); for(i=0;i<4;i++) ptr1[i]=str[i]; /* 将二维字符数组各行的首地址传递给指针数组的各指针 */ printf("original string:\n"); for(i=0;i<4;i++) /* 按行输出原始各字符串 */ printf("%s\n",ptr1[i]); printf("ordinal string:\n"); for(i=0;i<3;i++) /* 冒泡排序 */ for(j=0;j<4-i-1;j++) if(strcmp(ptr1[j],ptr1[j+1])>0) { strcpy(temp,ptr1[j]); strcpy(ptr1[j],ptr1[j+1]); strcpy(ptr1[j+1],temp); } for( i=0;i<4;i++) /* 输出排序后的字符串 */ printf("%s\n" , ptr1[i]); } 运行程序: RUN ↵ jkjkdkddfs ↵ fhfgkjkfgkf ↵ hkfgkgfkklg ↵ jjkdjdk ↵ original string: 第6章 指 下载 针 115 jkjkdkddfs fhfgkjkfgkf hkfgkgfkklg jjkdjdk ordinal string: fhfgkjkfgkf hkfgkgfkklg jjkdjdk jkjkdkddfs 程序中一定要注意指针的正确使用。一旦将二维字符数组的各行首地址传递给指针数组 的各指针,则相当于给指针分配了安全可操作的地址,地址空间大小由二维字符数组来决定。 当然也可由编译系统为指针分配地址用于字符串的存放。 [例6-24] 利用 malloc()函数为指针分配存储空间,实现字符串的排序。 #include <stdlib.h> #include <string.h> #include <stdio.h> main() { char *ptr1[4],*temp; int i,j; for (i=0;i<4;i++) { ptr1[i]=malloc(20); /* 为指针数组各指针分配 20 字节的存储空间 */ gets(ptr1[i]); } printf("\n"); printf("original string:\n"); for(i=0;i<4;i++) printf("%s\n",ptr1[i]); printf("ordinal string:\n"); for(i=0;i<3;i++) for(j=0;j<4-i-1;j++) if(strcmp(ptr1[j],ptr1[j+1])>0) { temp=ptr1[j]; /* 利用指向字符串的指针,进行指针地址的交换 */ ptr1[j]=ptr1[j+1]; ptr1[j+1]=temp; } for( i=0;i<4;i++) /* 字符串输出 */ printf("%s\n" , ptr1[i]); } 运行程序,其结果与上述例 6-23完全相同。 [ 例6-25] 对 已排好序的字符指针数组进行指定字符串的查找。字符串按字典顺序排列, 查找算法采用二分法,或称为折半查找。 折半查找算法描述: 116 C 语言程序设计 下载 1. 设按开序(或降序)输入 n 个字符串到一个指针数组。 2. 设low 指向指针数组的低端, high 指向指针数组的高端, mid=(low+high)/2 3. 测试 mid所指的字符串,是否为要找的字符串。 4. 若按字典顺序, m i d 所指的字符串大于要查找的串,表示被查字符串在 l o w和m i d 之间, 否则,表示被查字符串在 mid 和high之间。 5. 修改 low式high的值,重新计算 mid,继续寻找。 #include <stdlib.h> #include <alloc.h> #include <string.h> #include <stdio.h> main() { char *binary(); /* 函数声明 */ char *ptr1[5],*temp; int i,j; for (i=0;i<5;i++) { ptr1[i]=malloc(20); /* 按字典顺序输入字符串 */ gets(ptr1[i]); } printf("\n"); printf("original string:\n"); for(i=0;i<5;i++) printf("%s\n",ptr1[i]); printf("input search string:\n"); temp=malloc(20); 输入被查找字符串 */ gets(temp); /* i=5; temp=binary(ptr1,temp,i ); /* 调用查找函数 */ if (temp) printf("succesful-----%s\n" ,temp); else printf("no succesful!\n"); return; } char *binary(char *ptr,char *str,int 定义返回字符指针的函数 * / n) { /*折半查找 */ int hig,low,mid; low=0; hig=n-1; while(low<=hig) { mid=(low+hig)/2; if (strcmp(str,ptr[mid])<0) hig=mid-1; else if(strcmp(str,ptr[mid])>0) low=mid+1; else return(str); /* 查帐成功,返回被查字符串 */ 第6章 指 下载 } return NULL; } 针 117 /* 查找失败,返回空指针 */ 运行程序: RUN ↵ chengdu ↵ chongqin ↵ beijing ↵ tianjin ↵ shanghai ↵ original string: chengdu chongqin beijing tianjin shanghai input search string: beijing ↵ succesful----- beijing [例6-26] 在一个已排好序的字符串数组中,插入一个键盘输入的字符串,使其继续保持 有序。 在上述程序查找成功的基础上,我们将该字符串插入到字符数组中。插入的位置可以是 数组头、中间或数组尾。查找的算法采用折半算法,找到插入位置后,将字符串插入。 #include <stdlib.h> #include <alloc.h> #include <string.h> #include <stdio.h> main() { int binary(); /* 查找函数声明 */ void insert(); /* 插入函数声明 */ char *temp,*ptr1[6]; int i,j; for (i=0;i<5;i++) { ptr1[i]=malloc(20); /* 为指针分配地址后 */ gets(ptr1[i]); /* 输入字符串 */ } ptr1[5]=malloc(20); printf("\n"); printf("original string:\n"); for(i=0;i<5;i++) /* 输出指针数组各字符串 */ printf("%s\n",ptr1[i]); printf("input search string:\n"); temp=malloc(20); gets(temp); /* 输入被插字符串 */ 118 C 语言程序设计 下载 i=binary(ptr1,temp,5 ); 寻找插入位置 i*/ /* printf("i=%d\n",i); insert(ptr1,temp,5,i); /* 在插入位置 i处插入字符串 */ printf("output strings:\n"); for(i=0;i<6;i++) /* 输出指针数组的全部字符串 */ printf("%s\n",ptr1[i]); return; } int binary(char *ptr,char *str,int n) { /*折半查找插入位置 */ int hig,low,mid; low=0; hig=n-1; if (strcmp(str,ptr[0])<0) return 0; /* 若插入字符串比字符串数组的第 0个小,则插入位置为 0 */ if (strcmp(str,ptr[hig])>0) return n; /* 若插入字符串比字符串数组的最后一个大,则应插入字符串数组的尾部 */ while(low<=hig) { mid=(low+hig)/2; if (strcmp(str,ptr[mid])<0) hig=mid-1; else if(strcmp(str,ptr[mid])>0) low=mid+1; else return(mid); /* 插入字符串与字符串数组的某个字符串相同 */ } return low; /* 插入的位置在字符串数组中间 */ } void insert(char *ptr,char *str,int n,int i) { int j; for (j=n;j>i;j--) /* 将插入位置之后的字符串后移 */ strcpy(ptr[j],ptr[j-1]); strcpy(ptr[i],str); 将被插字符串按字典顺序插入字符串数组 */ } 在程序中,字符串数组的 6 个指针均分配 p i(整型变量) 5 存放 2 0 字节的有效地址。语句 p t r 1 [ 5 ] = m a l l o c &i ( 2 0 ) 保证插入字符串后,也具有安全的存储空 p j(实型变量) 间,字符串的长度以串中最长的为基准向系 &j 5.3 统申请存储空间,以保证在串的移动中有足 够的存储空间。 6.7 指向指针的指针 一个指针变量可以指向整型变量、实型 p &ch ch (字符变量) ‘a’ p1(双重指针) p2(指针变量) &p2 &x 图6-13 双重指针 x( 整型变量) 4 下载 第6章 指 针 119 变量、字符类型变量,当然也可以指向指针类型变量。当这种指针变量用于指向指针类型变 量时,我们称之为指向指针的指针变量,这话可能会感到有些绕口,但你想到一个指针变量 的地址就是指向该变量的指针时;这种双重指针的含义就容易理解了。下面用一些图来描述 这种双重指针,见图 6-13 。 在图中,整型变量 i 的地址是 & i ,将其传递给指针变量 p ,则 p 指向 i ;实型变量 j 的地址 是&j,将其传递给指针变量 p,则 p指向 j; 字符型变量 ch的地址是 &ch,将其传递给指针变量 p, 则p 指向 ch; 整型变量 x的地址是 &x,将其传递给指针变量 p2 ,则 p2指向 x,p2是指针变量,同 时,将 p 2 的地址 & p 2 传递给 p 1 ,则 p 1指向 p 2 。这里的 p 1就是我们谈到的指向指针变量的指针 变量,即指针的指针。 指向指针的指针变量定义如下: 类型标识符 **指针变量名 例如: float **ptr; 其含义为定义一个指针变量 p t r ,它指向另一个指针变量(该指针变量又指向一个实型变 量) 。由于指针运算符“ *”是自右至左结合,所以上述定义相当于: float *(*ptr); 下面看一下指向指针变量的指针变量怎样正确引用。 [例6-27] 用指向指针的指针变量访问一维和二维数组。 #include <stdio.h> #include <stdlib.h> main() { int a[10],b[3][4],*p1,*p2,**p3,i,j; /*p3 是指向指针的指针变量 */ for(i=0;i<10;i++) scanf("%d",&a[i]); /* 一维数组的输入 */ for (i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d",&b[i][j]); /* 二维数组输入 */ for (p1=a,p3=&p1,i=0;i<10;i++) printf("%4d",*(*p3+i)); /* 用指向指针的指针变量输出一维数组 */ printf("\n"); for (p1=a;p1-a<10;p1++) /* 用指向指针的指针变量输出一维数组 */ { p3=&p1; printf("%4d",**p3); } printf("\n"); for(i=0;i<3;i++) /* 用指向指针的指针变量输出二维数组 */ { p2=b[i]; p3=&p2; for (j=0;j<4;j++) printf("%4d",*(*p3+j)); printf("\n"); } for(i=0;i<3;i++) /* 用指向指针的指针变量输出二维数组 */ 120 C 语言程序设计 下载 { p2=b[i]; for(p2=b[i];p2-b[i]<4;p2++) { p3=&p2; printf("%4d",**p3); } printf("\n"); } } 程序的存储示意如图 6 - 1 4 所示,对一维数组 a 来说,若把数组的首地址即数组名赋给指针 变量 p 1 , p 1 就指向数组 a ,数组的各元素用 p 1 表示为, * ( p 1 + i) ,也可以简化为 * p 1 + i 表示。 如果继续作将 p 3 = & p 1 ,则将 p 1 的地址传递给指针变量 p 3 , * p 3 就是 p 1。用 p 3 来表示一维数组 的各元素,只需要将用 p1表示的数组元素 *(p1+i )中的 p1换成 *p3即可,表示为 *(*p3+i)。 图6-14 例6-27程序的存储示意图 同样,对二维数组 b来说, b[i] 表示第 i行首地址,将其传递给指针变量 p2,使其指向该行。 该行的元素用 p 2 表示为 * ( p 2 + i )。若作 p 3 = & p 2 ,则表示 p 3指向 p 2 ,用 p 3表示的二维数组第 i行 元素为: *(*p3+i)。这与程序中的表示完全相同。 运行程序: RUN ↵ 123 135 246 579 1 1 1 2 5 1 2 5 4567890 ↵ 7↵ 8↵ 2↵ 2 3 4 2 3 4 3 5 7 4 6 8 7 9 2 3 5 7 4 6 8 7 9 2 5 5 6 6 7 7 8 8 9 9 0 0 第6章 指 下载 针 121 [例6-28] 利用指向指针的指针变量对二维字符数组的访问。 #include <stdio.h> #include <stdlib.h> main() { int i; static char c[16]={"c language","fox","computer","home page"}; /* 二维字符数组 */ static char *cp={c[0],c[1],c[2],c[3]};/* 指针数组 */ static char **cpp; /* 指向字符指针的指针变量 */ cpp=cp; /* 将指针数组的首地址传递给指向字符指针的指针变量 */ for (i=0;i<4;i++) /* 按行输出字符串 */ printf("%s\n",*cpp++); printf("-----------\n"); for (i=0;i<4;i++) /* 按行输出字符串 */ { cpp=&cp[i]; printf("%s\n",*cpp); } } 运行程序: RUN ↵ c language fox computer home page ---------c language fox computer home page 程序中需要注意的是,执行 c p p = c p 是将指针数组的首地址传递给双重指针,所以 * (cpp+i )表示第 i行的首地址,而不是 cpp+i。在程序设计时一定分清。 6.8 main函数的参数 C 程序最大的特点就是所有的程序都是用函数来装配的。 m a i n ( )称之为主函数,是所有程 序运行的入口。其余函数分为有参或无参两种,均由 main()函数或其它一般函数调用,若调用 的是有参函数,则参数在调用时传递。 main() { ... y1=f1(x1,x2); ... } f1(int a,int b) 122 C 语言程序设计 下载 { .... Y2=f2(x3,x4); .... } f2( int m,int n) { .... ..... } 在前面课程的学习中,对 m a i n ( ) 函数始终作为主调函数处理,也就是说,允许 m a i n ( )调用 其它函数并传递参数。事实上, main()函数既可以是无参函数,也可以是有参的函数。对于有 参的形式来说,就需要向其传递参数。但是其它任何函数均不能调用 main()函数。当然也同样 无法向 main()函数传递,只能由程序之外传递而来。这个具体的问题怎样解决呢? 我们先看一下 main( ) 函数的带参的形式: main(argc,argv) int argc,char * argv; { ..... } 从函数参数的形式上看,包含一个整型和一个指针数组。当一个 C的源程序经过编译、链 接后,会生成扩展名为 . E X E的可执行文件,这是可以在操作系统下直接运行的文件,换句话 说,就是由系统来启动运行的。对 main()函数既然不能由其它函数调用和传递参数,就只能由 系统在启动运行时传递参数了。 在操作系统环境下,一条完整的运行命令应包括两部分:命令与相应的参数。其格式为: 命令 参数 1 参数 2 . . . . 参数 n↵ 此格式也称为命令行。命令行中的命令就是可执行文件的文件名,其后所跟参数需用空 格分隔,并为对命令的进一步补充,也即是传递给 main() 函数的参数。 命令行与 main()函数的参数存在如下的关系: 设命令行为: program str1 str2 str3 str4 str5 ↵ 其中 p r o g r a m 为文 件 名, 也 就是 一 个由 p r o g r a m . c 经编译、链接后生成的可执行文件 p r o g r a m . e x e ,其后各跟 5个参数。对 main( ) 函数 来说,它的参数 a rg c 记录了命令行中命令与参数的 个数,共 6 个,指针数组的大小由参数 a rg c 的值决 定,即为 char *arg v [ 6 ] ,指针数组的取值情况如图 6-15 所示。 数组的各指针分别指向一个字符串。应当引起 图6-15 指针数组的取值情况 注意的是接收到的指针数组的各指针是从命令行的开始接收的,首先接收到的是命令,其后 才是参数。 下面用实例来说明带参数的 main()函数的正确使用。 第6章 指 下载 [例 6-29] 针 123 利用图形库函数绘制一个变化的环。它是把一个半径为 R 1的圆周分成 n 份,然 后以每个等分点为圆心,以 Rs为半径画 n个圆(关于作图的详细理论本教材第 9 章第 1节作了专 门介绍,这里只作简单分析) 。利用 m a i n ( )函数的带参数形式,我们可以从键盘以命令行的方 式输入 R1和Rs及屏幕的背景色。 #include <graphics.h> /* 含图形库函数的头文件 */ 包 #include <math.h> #define pi 4.1415926 main(argc,argv) int argc;char *argv; /* 定义带参数的 main()*/ { int x,y,r1,rs,color; double a; int gdriver=DETECT,gmode; initgraph(&gdriver,&gmode,"..\\bgi ");/* 启动图形工作方式 */ r1=atoi(argv[1]); /* 计算基础圆半径 */ rs=atoi(argv[2]); /* 计算同心圆半径 */ color=atoi(argv[3]); /* 背景色 */ cleardevice(); /* 清除图形屏幕 */ setbkcolor(color); /* 设置背景色 */ setcolor(4); /* 设置图形显示颜色 */ for(a=0; a<=2*pi;a+=pi/18) /* 绘制同心圆 */ { x=r1*cos(a)+320; y=r1*sin(a)+240; circle(x,y,rs); /* 以圆心坐标为 x、y,半径为 rs 画圆*/ } getch(); /* 等待按键继续 */ closegraph(); /* 关闭图形工作方式 */ } 若程序名为 L 6 - 2 9 . c,经编译、连结生成可执行文件 L 6 - 2 9 . e x e 。在操作系统的环境下运行 程序,命令行方式为: l6-29 40 20 3 ↵ 则命令行与 m a i n ( ) 函数的参数有如图 6 - 1 6 所示的关 系。 图 6 - 1 6 中, a rg v [ 0 ] 是程序名, a rg v [ 1 ] 是 r 1 的值, argv[2]是rs的值, argv[3]是屏幕的背景色。 由于指针数组均存放字符串,所需的圆半径及背景 图6-16 例6-29的命令行与main( )函数的 参数间的关系 色彩通过 atoi()函数转换为整型。 通过带参数的 main()函数,我们可以为自己的程序设置口令,在运行程序的命令行中给出 所需的口令,正确则继续,否则退出。程序图形输出如图 6-17所示。 [ 例 6-30] 将 上述程序作修改,在程序的入口处添置密码,若给定密码正确,则显示图 形。 #include <graphics.h> 124 C语言程序设计 下载 #include <math.h> #define pi 4.1415926 main(argc,argv) int argc;char *argv; { int x,y,r1,rs,color; double a; int gdriver=DETECT,gmode; if (strcmp(argv[1],"pass")!=0) 设置口令的比较 */ /* { printf("password error!\n"); exit(0); } initgraph(&gdriver,&gmode,"..\\bgi "); r1=atoi(argv[2]); rs=atoi(argv[3]); color=atoi(argv[4]); cleardevice(); setbkcolor(color); setcolor(4); for(a=0; a<=2*pi;a+=pi/18) { x=r1*cos(a)+320; y=r1*sin(a)+240; circle(x,y,rs); } getch(); closegraph(); } 图6-17 例6-29程序输出的图形 在操作系统的环境下运行程序, 命令行 中增加口令“ pass” ,命令行方式为: l6-30 pass 20 40 3 ↵ 指针数组的存储字符串如图 6-18所示。 若给定字符串 a rg v [ 1 ]的值是 pass,则程序 正确运行,否则程序退出。口令正确的情况 下,显示的图形为图 6-17 中的一个。 图6-18 例6-30程序中指针数组的存储字符串 ...
View Full Document

This note was uploaded on 04/05/2010 for the course FINANCE AN FRE6851 taught by Professor Gallagher,evan during the Spring '09 term at NYU Poly.

Ask a homework question - tutors are online