[例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 1 2 3 4 6.4.3 数组指针作函数的参数 学习了指向一维和二维数组指针变量的定义和正确引用后,我们现在学习用指针变量作 函数的参数。 [例6-12] 调用子程序,实现求解一维数组中的最大元素。 我们首先假设一维数组中下标为0的元素是最大和用指针变量指向该元素。后续元素与该 元素一一比较,若找到更大的元素,就替换。子程序的形式参数为一维数组,实际参数是指 向一维数组的指针。 # include <stdio.h> m a i n ( ) { int sub_max(); / * 函数声明* / int n,a[10],*ptr=a; / *定义变量,并使指针指向数组* / int max; f o r ( n = 0 ; n < = i - 1 ; n + + ) / *输入数据* / s c a n f ( " % d " , & a [ n ] ) ; m a x = s u b _ m a x ( p t r , 1 0 ) ; / * 函数调用,其实参是指针* / p r i n t f ( " m a x = % d / n " , m a x ) ; } int sub_max(b,i) / * 函数定义,其形参为数组* / int b[],i; { int temp,j; t e m p = b [ 0 ] ; f o r ( 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 ( )函数完成对数组元 素找最大的过程。在子程序内数组元素的表示采用下标法。运行程序: R U N 1 3 5 7 9 2 4 6 8 0 m a x = 9 [例6-13] 上述程序也可采用指针变量作子程序的形式参数。 # include <stdio.h> m a i n ( ) { int sub_max(); int n,a[10],*ptr=a; int max; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , & a [ n ] ) ; m a x = s u b _ m a x ( p t r , 1 0 ) ; p r i n t f ( " m a x = % d / n " , m a x ) ; } int sub_max(b,i) / *形式参数为指针变量* / int *b,i; { int temp,j; t e m p = b [ 0 ] ; / *数组元素指针的下标法表示* / f o r ( j = 1 ; j < = i - 1 ; j + + ) if(temp<b[j]) temp=b[j]; return temp; } 在子程序中,形式参数是指针,调用程序的实际参数p t r为指向一维数组a的指针,虚实结 合,子程序的形式参数b得到p t r的值,指向了内存的一维数组。数组元素采用下标法表示,即 一维数组的头指针为b,数组元素可以用b [ j ]表示。其内存中虚实参数的结合如图6 - 1 0所示。 运行程序: R U N 1 3 5 7 9 2 4 6 8 0¿ m a x = 9 [例6-14] 上述程序的子程序中,数组元素还可以用指针表示。 # include <stdio.h> m a i n ( ) { int sub_max(); int n,a[10],*ptr=a; int max; f o r ( n = 0 ; n < = 9 ; n + + ) s c a n f ( " % d " , & a [ n ] ) ; m a x = s u b _ m a x ( p t r , 1 0 ) ; p r i n t f ( " m a x = % d / n " , m a x ) ; } int sub_max(b,i)/ *子程序定义* / int *b,i; { int temp,j; t e m p = * b + + ; f o r ( j = 1 ; j < = i - 1 ; j + + ) if(temp<*b) temp=*b++; return temp; } 在程序中,赋值语句t e m p = * b + +;可以分解为:t e m p = * b;b + +;两句,先作t e m p = * b;后 作b + +;程序的运行结果与上述完全相同。 对上面的程序作修改,在子程序中不仅找最大元素,同时还要将元素的下标记录下来。 # include <stdio.h> m a i n ( ) { int *max();/* 函数声明* / int n,a[10],*s,i; f o r ( i = 0 ; i < 1 0 ; i + + ) / * 输入数据* / scanf("%d",a+i); s = m a x ( a , 1 0 ) ; / *函数调用* / p r i n t f ( " m a x = % d , i n d e x = % d / n " , * s , s - a ) ; } int *max(a,n) / *定义返回指针的函数* / int *a,n; { int *p,*t; / * p 用于跟踪数组,t用于记录最大值元素的地址* / f o r ( 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正好是所跟踪元素相对数组头的距离,或者说是所跟踪元素相对数组头的元素个数,所以在m a i n ( )中,最大元素的下标就是该元素的地址与数组头的差,即s - a。运行程序: R U N 1 3 5 7 9 2 4 6 8 0¿ m a x = 9 , i n d e x = 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趟完全的两两比较,使全部数据整理有序。 下面给出一趟排序的处理过程: 4个元素进行3次两两比较,得到一个最大元素。若相邻元素表示为a [ j ]和a [ j + 1 ],用指针 变量P指向数组,则相邻元素表示为* ( P + j )和* ( P + j + 1 )程序实现如下: # include<stdio.h> #define N 10 m a i n ( ) { void input(); / *函数声明* / void sort(); void output(); int a[N],*p; / *定义一维数组和指针变量* / i n p u t ( a , N ) ; / *数据输入函数调用,实参a是数组名* / p = a ; / *指针变量指向数组的首地址* / s o r t ( p , N ) ; / *排序,实参p是指针变量* / o u t p u t ( p , N ) ; / *输出,实参p是指针变量* / } void input(arr,n) / *无需返回值的输入数据函数定义,形参a r r 是数组* / int arr[],n; { int i; printf("input data:/n"); for ( i = 0 ; i < n ; i + + ) / *采用传统的下标法*/ s c a n f ( " % d " , & a r r [ 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 - a r r < n ; ptr + + ) / *输出数组的n个元素* / printf ( " % 4 d " , * ptr ) ; printf ( " / n " ) ; }
由于C程序的函数调用是采用传值调用,即实际参数与形式参数相结合时,实参将值传给形式参数,所以当我们利用函数来处理数组时,假如需要对数组在子程序中修改,只能传递数组的地址,进行传地址的调用,在内存相同的地址区间进行数据的修改。在实际的应用中, 假如需要利用子程序对数组进行处理,函数的调用利用指向数组(一维或多维)的指针作参数,无论是实参还是形参共有下面四种情况: 我们知道,二维数组在内存中是按行存放,假定我们定义二维数组和指针如下: int a[3][4],* p = a [ 0 ] ; 则指针p就指向二维数组。其在内存的存放情况如图6 - 11所示。 从上述存放情况来看,若把二维数组的首地址传递给指针p,则映射过程如图6 - 11 所示。我们只要找到用p所表示的一维数组中最大的元素及下标,就可转换为在二维数组中的 行列数。 # include<stdio.h> m a i n ( ) { int a[3][4],*ptr,i,j,max,maxi,maxj; / * m a x 是数组的最大, m a x i 是最大元素所在行, m a x j 是最大元素所在列* / f o r ( i = 0 ; i < 3 ; i + + ) f o r ( j = 0 ; j < 4 ; j + + ) s c a n f ( " % d " , & a [ i ] [ j ] ) ; p t r = a [ 0 ] ; / * 将二维数组的首地址传递给指针变量* / m a x _ a r r ( p t r , & m a x , & m a x i , 1 2 ) ; m a x j = m a x i % 4 ; / * 每行有四个元素,求该元素所在列* / m a x i = m a x i / 4 ; / * 求该元素所在行* / printf("max=%d,maxi=%d,maxj=%d",max,maxi,maxj); } int max_arr(b,p1,p2,n) int *b,*p1,*p2,n; / * b 指向二维数组的指针, p 1指向最大值,p 2 指向最大值在一维数组中的位置, * / / * n 是数组的大小* / { int i; *p1=b[0]; *p1=0; f o r ( i = 1 ; i < n ; i + + ) / * 找最大* / if (b[i]>*p1) {*p1=b[i]; *p2=i;} } 运行程序: 6.4.4 指针与字符数组 在前面的课程中,我们用过了字符数组,即通过数组名来表示字符串,数组名就是数组的首地址,是字符串的起始地址。下面的例子用于简单字符串的输入和输出。 #include<stdio.h> main() { char str[20]; gets(str); printf("%s/n",str); } 现在,我们将字符数组的名赋予一个指向字符类型的指针变量,让字符类型指针指向字 符串在内存的首地址,对字符串的表示就可以用指针实现。其定义的方法为:charstr[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! goodmorning! 需要说明的是,字符数组与字符串是有区别的,字符串是字符数组的一种非凡形式,存储时以“/0”结束,所以,存放字符串的字符数组其长度应比字符串大1。对于存放字符的字符数组,若未加“/0”结束标志,只能按逐个字符输入输出。 [例6-18]字符数组的正确使用方法。 #include<stdio.h> main() { charstr[10],*p=str; int i; scanf("%s",str);/*输入的字符串长度超过10*/ for(i=0;i<10;i++) printf("%c",*p++);/*正确输出*/ printf("/n"); p=str; printf("%s",p);/*字符数组无'/0'标志,输出出错*/ puts(str);/*字符数组无'/0'标志,输出出错*/ } 对上述程序中字符数组以字符串形式输出,若无“/0”标志,则找不到结束标志,输出出 错。 [例6-19]用指向字符串的指针变量处理两个字符串的复制。 字符串的复制要注重的是:若将串1复制到串2,一定要保证串2的长度大于或等于串1。 #include<stdio.h> main() { char str1[30],str2[20],*ptr1=str1,*ptr2=str2; printf("inputstr1:"); gets(str1);/*输入str1*/ printf("inputstr2:"); 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”,该字符的ASCII码值为0,表达式的值为假,循环结束,表达式的值非零,则执行循环*ptr1++=*ptr2++。语句*ptr1++按照运算优先级别,先算*ptr1,再算ptr1++。 现在,我们修改程序中语句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("inputstr1:"); gets(str1); printf("inputstr2:"); 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"); printf("%s.......%s/n",ptr1,ptr2); } 需要注重的是,串复制时,串1的长度应大于等于串2;串连接时,串1的长度应大于等于串1与串2的长度之和。