![]() ![]() |
||||||||||||||||
C语言程序设计(第6章指针)4 | ||||||||||||||||
作者:佚名 文章来源:不详 点击数 更新时间:2008/4/18 14:00:16 文章录入:杜斌 责任编辑:杜斌 | ||||||||||||||||
|
||||||||||||||||
变量在内存存放是有地址的,数组在内存存放也同样具有地址。对数组来说,数组名就是数组在内存安放的首地址。指针变量是用于存放变量的地址,可以指向变量,当然也可存放数组的首址或数组元素的地址,这就是说,指针变量可以指向数组或数组元素,对数组而言,数组和数组元素的引用,也同样可以使用指针变量。下面就分别介绍指针与不同类型的数组。 6.4.1 指针与一维数组 假设我们定义一个一维数组,该数组在内存会有系统分配的一个存储空间,其数组的名字就是数组在内存的首地址。若再定义一个指针变量,并将数组的首址传给指针变量,则该指针就指向了这个一维数组。我们说数组名是数组的首地址,也就是数组的指针。而定义的指针变量就是指向该数组的指针变量。对一维数组的引用,既可以用传统的数组元素的下标法,也可使用指针的表示方法。 int a[10] , *ptr; /* 定义数组与指针变量* / 做赋值操作:ptr=a; 或ptr = &a[0]; 则ptr就得到了数组的首址。其中, a是数组的首地址, &a[0]是数组元素a[0]的地址,由于a[0]的地址就是数组的首地址,所以,两条赋值操作效果完全相同。指针变量ptr就是指向数组a的指针变量。 若ptr指向了一维数组,现在看一下C规定指针对数组的表示方法: 1) ptr+n与a + n表示数组元素a[n]的地址,即&a[n] 。对整个a数组来说,共有10个元素, n的取值为0~9,则数组元素的地址就可以表示为ptr + 0~ptr + 9或a + 0~a + 9,与&a[0] ~&a[9]保持一致。 2) 知道了数组元素的地址表示方法, *(ptr + n)和*(a+n)就表示为数组的各元素即等效于a[n]。 3) 指向数组的指针变量也可用数组的下标形式表示为ptr[n],其效果相当于*(ptr + n)。 [例6-5] /*以下标法输入输出数组各元素。 下面从键盘输入10个数,以数组的不同引用形式输出数组各元素的值。 # include 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("M", a[n]); printf("\n"); } 运行程序: 1 2 3 4 5 6 7 8 9 0 1------output! 1 2 3 4 5 6 7 8 9 0 [例6-6] 采用指针变量表示的地址法输入输出数组各元素。 #include main( ) { int n,a[10],*ptr=a; / *定义时对指针变量初始化* / for(n = 0; n<=9; n++) scanf("%d", ptr + n); printf("2------output! \n"); for(n=0; n<=9; n++) printf("M", *(ptr+n)); printf("\n"); } 运行程序: 1 2 3 4 5 6 7 8 9 0 2------output! 1 2 3 4 5 6 7 8 9 0 [例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("M", *(a+n)); printf("\n"); } 运行程序: 1 2 3 4 5 6 7 8 9 0 3------output! 1 2 3 4 5 6 7 8 9 0 [例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("M", ptr[n]); printf("\n"); } 运行程序: 1 2 3 4 5 6 7 8 9 0 4----output! 1 2 3 4 5 6 7 8 9 0 [例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("M", *ptr++); printf("\n"); } 运行程序: 1 2 3 4 5 6 7 8 9 0 5-----output! 1 2 3 4 5 6 7 8 9 0 在程序中要注意*ptr++所表示的含义。*ptr表示指针所指向的变量; ptr++表示指针所指向的变量地址加1个变量所占字节数,具体地说,若指向整型变量,则指针值加2,若指向实型,则加4,依此类推。而printf(“M”, *ptr+ +)中,*ptr++所起作用为先输出指针指向的变量的值,然后指针变量加1。循环结束后,指针变量指向如图6 - 6所示:
指针变量的值在循环结束后,指向数组的尾部的后面。假设元素a[9]的地址为1000,整型占2字节,则ptr的值就为100 2。请思考下面的程序段: 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("M", *ptr++); printf("\n"); } 程序与例6 - 9相比,只少了赋值语句p t r = a;程序的运行结果还相同吗? 6.4.2 指针与二维数组 定义一个二维数组: int a[3][4]; 表示二维数组有三行四列共1 2个元素,在内存中按行存放,存放形式为图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给出了指针与二维数组的关系。 我们定义的二维数组其元素类型为整型,每个元素在内存占两个字节,若假定二维数组从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]; 若赋值: p t r = a;则用ptr++ 就能访问数组的各元素。 [例6-10] 用地址法输入输出二维数组各元素。 #include 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("M",*(a[i]+j)); /* *(a[i]+j) 是地址法所表示的数组元素* / printf("\n"); } } 运行程序: 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 [例6 - 11] 用指针法输入输出二维数组各元素。 #include 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("M", *ptr++); printf("\n"); } } 运行程序: 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 对指针法而言,程序可以把二维数组看作展开的一维数组: 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("M", *ptr++); printf("\n"); } 运行程序: 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 6.4.3 数组指针作函数的参数 学习了指向一维和二维数组指针变量的定义和正确引用后,我们现在学习用指针变量作函数的参数。 [例6-12] 调用子程序,实现求解一维数组中的最大元素。 我们首先假设一维数组中下标为0的元素是最大和用指针变量指向该元素。后续元素与该元素一一比较,若找到更大的元素,就替换。子程序的形式参数为一维数组,实际参数是指向一维数组的指针。 #include 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]; for(j = 1; j < = 9; j++) if(temp return temp; } 程序的main( )函数部分,定义数组a 共有1 0个元素,由于将其首地址传给了ptr,则指针变量ptr 就指向了数组,调用子程序,再将此地址传递给子程序的形式参数b,这样一来,b数组在内存与a 数组具有相同地址,即在内存完全重合。在子程序中对数组b 的操作,与操作数组a 意义相同。其内存中虚实结合的示意如图6 - 9所示。 main( )函数完成数据的输入,调用子程序并输出运行结果。sub_max( )函数完成对数组元素找最大的过程。在子程序内数组元素的表示采用下标法。运行程序: 1 3 5 7 9 2 4 6 8 0 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 - 1 0所示。 运行程序: 1 3 5 7 9 2 4 6 8 0 max = 9 [例6-14] 上述程序的子程序中,数组元素还可以用指针表示。 #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 ++; for( j = 1; j <= i - 1; j ++) if(temp<*b) temp=*b++; return temp; }
对上面的程序作修改,在子程序中不仅找最大元素,同时还要将元素的下标记录下来。 #include <stdio.h> main( ) { int *max();/* 函数声明* / int n,a[10],*s,i; for( i = 0; i < 10; i ++ ) / * 输入数据* / 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; } 在max()函数中,用p - a < n来控制循环结束, a是数组首地址, p用于跟踪数组元素的地址,p - a正好是所跟踪元素相对数组头的距离,或者说是所跟踪元素相对数组头的元素个数,所以在main( )中,最大元素的下标就是该元素的地址与数组头的差,即s - a。运行程序: 1 3 5 7 9 2 4 6 8 0 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趟完全的两两比较,使全部数据整理有序。 下面给出一趟排序的处理过程: 原始数据3 8 2 5 第一次相邻元素比: 3 8 2 5 第二次相邻元素比: 3 2 8 5 第三次相邻元素比: 3 2 5 8 4个元素进行3次两两比较,得到一个最大元素。若相邻元素表示为a[j]和a[j + 1],用指针变量P指向数组,则相邻元素表示为*(P + j)和*(P + j + 1)程序实现如下: # include<stdio.h> #define N 10 main( ) { void input(); / *函数声明* / 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) / *无需返回值的输入数据函数定义,形参a r r 是数组* / 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"); } 运行程序: 3 5 7 9 3 23 43 2 1 10 1 2 3 3 5 7 9 10 23 4 3 由于C程序的函数调用是采用传值调用,即实际参数与形式参数相结合时,实参将值传给形式参数,所以当我们利用函数来处理数组时,如果需要对数组在子程序中修改,只能传递数组的地址,进行传地址的调用,在内存相同的地址区间进行数据的修改。在实际的应用中,如果需要利用子程序对数组进行处理,函数的调用利用指向数组(一维或多维)的指针作参数,无论是实参还是形参共有下面四种情况:
在函数的调用时,实参与形参的结合要注意所传递的地址具体指向什么对象,是数组的首址,还是数组元素的地址,这一点很重要。 [例6-16] 用指向二维数组的指针作函数的参数,实现对二维数组的按行相加。 #include <stdio.h> #define M 3 #define N 4 main( ) { float a[M][N]; float score1,score2,score3, *pa=a[0];/* 指针变量p a指向二维数组* / /* 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相对应的是实际参数&score1、&score2和&score3,其实际含义为p1 = &score1等,即将变量的地址传递给指针变量达到按行相加。 运行程序: 1 2 3 4 3 4 5 6 5 6 7 8 10.00,18.00,26.00 [例6-17] 求解二维数组中的最大值及该值在二维数组中的位置。 我们知道,二维数组在内存中是按行存放,假定我们定义二维数组和指针如下: int a[3][4],* p = a[0]; 则指针p就指向二维数组。其在内存的存放情况如图6 - 11所示。 从上述存放情况来看,若把二维数组的首地址传递给指针p,则映射过程如图6 - 11所示。我们只要找到用p所表示的一维数组中最大的元素及下标,就可转换为在二维数组中的行列数。
在前面的课程中,我们用过了字符数组,即通过数组名来表示字符串,数组名就是数组的首地址,是字符串的起始地址。下面的例子用于简单字符串的输入和输出。 #include <stdio.h> main( ) { char str[20]; gets(str); printf("%s\n", str); } 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); } good morning! good morning! 需要说明的是,字符数组与字符串是有区别的,字符串是字符数组的一种特殊形式,存储时以“\ 0”结束,所以,存放字符串的字符数组其长度应比字符串大1。对于存放字符的字符数组,若未加“\ 0”结束标志,只能按逐个字符输入输出。 [例6-18] 字符数组的正确使用方法。 #include<stdio.h> main( ) { char str[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("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”,该字符的ASCII码值为0,表达式的值为假,循环结束,表达式的值非零,则执行循环*ptr1++ = *ptr2 ++。语句*ptr1++按照运算优先级别,先算*ptr1,再算ptr1 ++。 运行程序: 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"); printf("%s.......%s\n", ptr1, ptr2); } 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的长度之和。 |
||||||||||||||||
![]() ![]() |