| §9.1指针的概念 | §9.2变量的指针 |
| §9.3数组的指针 | §9.4 字符串的指针 |
| §9.5函数指针 | §9.6返回指针的函数 |
| §9.7 指针数组和指向指针的指针 | §9.8 指针使用小结 |
指针是C语言的重要概念,也是C语言的特色。
使用指针,可以使程序简洁高效。
在一般程序中,指针被广泛使用。
指针(pointer):是一个变量的地址。
指针变量:是一个变量,其值是另一个变量的地址。
任何变量都在计算机内存中占有一块内存区域, 变量的值就存放在这块内存区域之中,(寄存器变量不在内存中,而是在CPU的寄存器中)。 例、
![]() |
|
一个变量的访问(访问是指取出其值或向它赋值)方式有两种:
(1)直接访问,通过变量名访问,如通过变量名i直接访问。
(2)间接访问,通过该变量的指针来访问,如通过i_pointer访问变量i。
一、指针变量的定义
指针变量有三个属性:
(1)该指针变量指向的变量的类型。如i_pointer指向的变量i是整型。
(2)该指针变量在内存中占多少内存单元。如i_pointer占两个内存单元,称为“近指针”,用near表示。如果该变量在内存中占4个内存单元,称为“远指针”,用far表示。如果未指定near或far,缺省是near。(指针变量在内存中要么占2个内存单元,要么占4个内存单元)。
(3)该指针变量指向哪一个变量,即该指针变量的值是多少。如i_pointer的值是2000。
指针变量定义的一般形式:
类型标识符 * 标识符
“*”表示定义指针变量 “标识符”是指针变量名 “类型标识符”表示该指针变量所指向的变量类型。
例、
int i,j; /* 定义两个整型变量 */ int *pointer_1, *pointer_2; float *pointer_3; char *pointer_4; void *pointer_5; char far *pointer_6;
指针变量的赋值:例、
pointer_1 = &i; pointer_2 = &j;
注意,指针变量中只能存放地址,不能将一个非地址类型的数据(如常数等)赋给一个指针变量,如:
pointer_1 = 100;
也可以在定义指针变量的同时指定其初值,如、
int a; int *p = &a;
二、指针变量的引用
有两个运算符可以引用指针变量:
(1)&:取地址运算符。如 pointer_1 = &i;
(2)*:指针运算符。用于访问指针变量所指向的变量。
如果定义:
int i,j; int *pointer_1; pointer_1 = &i; 指针变量pointer_1指向变量i,现在,对变量i有两种访问方式:
(1)直接访问。如 i = 100; j = i。
(2)通过指针变量间接访问。如:
*pointer_1 = 100; j = *pointer_1;
到这里为止,用指针变量间接访问另一个变量,似乎显得多余,但是,在复杂程序设计中,这种间接访问可以大大提高程序的效率并使程序非常简洁。因为,只要改变指针变量的值,就可以访问其他变量。例、
int i,j; int *p; p = &i; (p指向i ) *p = 100; (*p访问i) p = &j; (p指向j) *p = 200;(*p访问j)
[例9.1]
main () { int a,b; int *pointer_1, *pointer_2; /* 定义指针变量 */ a = 100; b = 10; pointer_1 = &a; pointer_2 = &b; printf("%d,%d\n",a,b); printf("%d,%d\n",*pointer_1,*pointer_2); } 程序运行结果: 100,10 100,10
说明:
1、在定义指针变量时,还未规定它指向哪一个变量,此时不能用*运算符访问指针。只有在程序中用赋值语句具体规定后,才能用*运算符访问所指向的变量。
int a; int *p; (未规定指向哪个变量) *p = 100;
int a; int *p; (未规定指向哪个变量) p = &a; (规定指向a) *p = 100; 这种错误称为访问悬挂指针(suspeded pointer)。
2、区分:*运算符在不同场合的作用,编译器能够根据上下文环境判别*的作用。
int a,b,c; int * p; (*表示定义指针) p = &a; *p = 100; (*表示指针运算符) c = a * b; (*表示乘法运算符)
3、区分*运算符的以下用法:
int a ; int *p = &a; /* 定义指针变量时指定初值,是为p指定初值 */ *p = 100; /* 给指针p所指向的变量赋值,这里是给变量a赋值 */
[例9.2] 输入a和b两个整数,按先大后小的顺序输出a和b。
main () { int *p1, *p2, *p, a, b; scanf("%d,%d",&a,&b); p1 = &a; p2 = &b; if (a < b) { p = p1; p1 = p2; p2 = p; } printf("a=%d,b=%d\n",a,b); printf("max=%d,min=%d\n", *p1, *p2); }
p1 = &a; p2 = &b; if (a<b) {p=p1; p1=p2; p2=p;}p1指向较大值,p2指向较小值. 该例不交换变量a、b的值,而是交换指针p1、p1的值。
三、指针变量作为函数的参数
[例9.3] 题目要求同[例9.2],输入a和b两个整数,按先大后小的顺序输出a和b。
int swap(int *p1, int *p2) /*交换指针p1、p2所指向的变量的值 */ { int p; p = *p1; *p1 = *p2; *p2 = p; } main () { int a, b; int *pointer_1, *pointer_2; scanf("%d,%d",&a,&b); pointer_1 = &a; pointer_2 = &b; if (a<b) swap(pointer_1, pointer_2); /* 另一种调用swap()的形式是: if (a<b) swap(&a,&b); */ printf("\n%d,%d\n",a,b); }
1、程序执行过程的说明。
| 执行pointer_1 = &a; pointer_2 = &b后,pointer_1和pointer_2分别指向a和b。 | ![]() |
| 调用函数swap(pointer_1,pointer_2),生成两个形参p1和p2。实参pointer_1的值传送给形参p1,因此p1也指向a。同理,p2指向b。 | ![]() |
| 在swap()函数内,把*p1和*p2的值进行交换,*p1是变量a,*p2是变量b,即把a和b的值进行交换。 | ![]() |
| 函数swap()调用结束后,形参p1、p2被释放,main中得到的a和b是已经被交换的值。 | ![]() |
2、使用指针变量,应注意避免指针悬挂。
int swap(int *p1, int *p2) { int *p; *p = *p1; ![]()
*p1 = *p2; *p2 = *p; }
3、函数swap()的形参是指针变量,两种调用方式:
swap(pointer_1, pointer_2);/* 传值,这里的值是一个地址 */ swap(&a, &b); /* 传地址 */
均把变量a和b的地址传送给形参,均能实现交换a和b的值。
只有函数swap()知道变量a和b的地址,才能改变其值(交换)。如果把swap()设计为下面的形式,不能实现a和b的值交换(函数不知道a、b的地址)。
int swap(int x, int y) /* 该函数交换形参的值 */ { int t; t = x; x = y; y = t; } 该函数交换形参的值,不能实现实参值的交换, 因为在C语言中,实参和形参之间使用“传值法”,数据只能单向由实参传到形参。形参值的变化不影响实参。
4、以指针变量作函数的参数,实参和形参之间仍然使用“传值法”,数据只能单向由实参传到形参。形参值的变化不影响实参,即,不能改变指针变量本身的值。但可以改变指针变量所指向的变量的值,例、
swap(pointer_1,pointer_2); swap(&a,&b);
因此,下面的程序也不能达到交换的目的。
int swap(int *p1, int *p2) /* 交换形参的值 */ { int *p; p = p1; p1 = p2; p2 = p; } main () { int a,b; int *pointer_1, *pointer_2; scanf("%d,%d",&a, &b); pointer_1 = &a; pointer_2 = &b; if (a<b) swap(pointer_1, pointer_2); printf("\n%d,%d\n", *pointer_1, *pointer_2); }
不能达到交换的原因:swap()函数交换形参(指针变量)本身,而不是交换指向的变量。
[例9.4] 输入a、b、c三个整数,按大小顺序输出。
int swap(int *pt1, int *pt2) { int p; p = *pt1; *pt1 = *pt2; *pt2 = p; } int exchange(int *q1, int *q2, int *q3) { if (*q1 < *q2) swap(q1,q2); if (*q1 < *q3) swap(q1,q3); if (*q2 < *q3) swap(q2,q3); } main () { int a,b,c, *p1, *p2, *p3; scanf("%d,%d,%d", &a, &b, &c); p1 = &a; p2 = &b; p3 = &c; exchange(p1,p2,p3); printf("\n%d,%d,%d\n", a,b,c); }
指针可以指向数组和数组元素,当一个指针指向数组后,对数组元素的访问,既可以使用数组下标,也可以使用指针。并且,用指针访问数组元素,程序的效率更高(用下标访问数组元素程序更清晰)。
一、指向数组元素的指针变量
指向数组元素的指针变量,其类型应与数组元素相同,例、
int a[10]; /* 元素为整型 */ float b[10] ; /* 元素为实型 */ int *p; /* 可以指向数组a的元素 */ int *pf; /* 可以指向数组b的元素 */
为了让指针p指向数组a,应把数组a的地址赋给指针变量p。
在§7.7中,已学过:数组名a表示该数组在内存的起始地址。可以用地址运算符&获得某个元素的地址。如&a[2]获得元素a[2]的地址。第一个元素a[0]的地址&a[0]即为数组a的起始地址。
因此,以下语句均使指针p指向数组a:
p = &a[0]; p = a; /* 把数组a的起始地址赋给p,不是把数组的全部元素赋给p */
二、通过指针引用数组元素
当使指针p指向数组a后,可以用指针p访问数组的各个元素。
![]() |
如果指针p指向数组a(指向数组的第一个元素a[0]),则: p+1指向下一个元素a[1],注意不是将p值简单加1。如果数组元素是整型,p+1表示p的地址加2;如果数组元素是实型,p+1表示p的地址加4;如果数组元素是字符型,p+1表示p的地址加1。 p+i指向元素a[i]。可以使用*(p+i)访问元素a[i]。 另外: 1、p+i也可以记作a+i。指向元素a[i]。 |
[例9.5] 输出数组的全部元素。(设10个元素,整型)。
访问各元素有三种方法:
1、下标法(常用,很直观)
main () { int a[10]; int i; for(i=0;i<10;i++) scanf("%d", &a[i]); printf("\n"); for(i=0;i<10;i++) printf("%d ",a[i]); }
2、用数组名计算数组元素的地址。(效率与下标法相同,不常用)
main () { int a[10]; int i; for(i=0;i<10;i++) scanf("%d", &a[i]); printf("\n"); for(i=0;i<10;i++) printf("%d ",*(a+i)); }
3、用指针访问各元素。(常用,效率高)
main () { int a[10]; int *p, i; for(i=0;i<10;i++) scanf("%d", &a[i]); printf("\n"); for(p=a;p<(a+10);p++) /* p++使p指向下一个元素 */ printf("%d ",*p); }
使用指针指向数组,应注意以下问题:
1、若指针p指向数组a,虽然p+i与a+i、*(p+i)与*(a+i)意义相同,但仍应注意p与a的区别(a代表数组的首地址,是不变的;p是一个指针变量,可以指向数组中的任何元素),例、
for(p=a; a<(p+10); a++) a代表数组的首地址,是不变的,a++不合法
printf("%d", *a)
2、指针变量可以指向数组中的任何元素,注意指针变量的当前值。
[例9.6] 输出数组a的10个元素。
程序:
|
|

3、 使用指针时,应特别注意避免指针访问越界。在上例中,第二次for循环,p已经越过数组的范围,但编译器不能发现该问题。避免指针访问越界是程序员自己的责任。
4、指针使用的几个细节。
设指针p指向数组a(p=a),则:
① p++(或 p += 1),p指向下一个元素。
② *p++,相当于*(p++)。因为,*和++同优先级,++是右结合运算符。
③ *(p++)与*(++p)的作用不同。
*(p++):先取*p,再使p加1。 *(++p):先使p加1,再取*p。
④ (*p)++表示,p指向的元素值加1。
⑤ 如果p当前指向数组a的第i个元素,则:
*(p--)相当于a[i--],先取*p,再使p减1。 *(++p)相当于a[++i],先使p加1,再取*p。 *(--p)相当于a[--i],先使p减1,再取*p。
三、数组名作函数参数
数组名代表数组首地址,因此,它作实参在函数调用时,是把数组首地址传送给形参。这样,实参数组和形参数组共占同一段内存区域。从而在函数调用后,实参数组的元素值可能会发生变化。
[例9.7] 将数组a中n个元素按相反顺序存放。
![]() |
算法:a[0]与a[n-1]交换,a[1]与a[n-2]交换,.....,a[(n-1)/2]与a[n-int((n-1)/2)]交换。 实现:用i,j作元素位置变量,开始i=0,j=n-1。将a[i]与a[j]交换,然后i加1,j减1,直到i=(n-1)/2。 |
程序:
void inv(int x[], int n) /* 形参是数组 */ { int t,i,j,m=(n-1)/2; for(i=0; i<=m; i++) { j = n - 1 - i; t = a[i]; a[i] = a[j]; a[j] = t; } return; /* 函数的返回值类型是void,不返回任何值 */ } main () { static int i, a[10] = {3,7,9,11,0,6,7,5,4,2}; printf("the original array:\n"); for(i=0; i<10; i++) printf("%d ", a[i]); printf("\n"); inv(a,10); printf("the array hans been inverted:\n"); for(i=0; i<10; i++) printf("%d ", a[i]); printf("\n"); }
函数inv()可以用指针作形参,运行情况与用数组作形参相同。
void inv(int *x, int n) { int *p, t, *i, *j, m=(n-1)/2; i = x; /* 指针i指向数组第一个元素 */ j = x + n - 1;/* 指针j指向数组最后一个元素 */ p = x + m; /* 指针p指向数组中间一个元素 */ for(;i<=p;i++,j--) { t = *i; *i = *j; *j = t; } return; }
[例9.8] 从10个数中找出其中最大值和最小值。只找出其中最大值和最小值,不能改变元素的排列顺序)。
方法1、实参和形参均用数组变量。
int max, min; /* 全局变量,最大值和最小值 */ void max_min_value(int array[], int n) { int *p, *array_end; /* p是数组元素指针 */ array_end = array + n; /* 指向数组尾 */ max = min = *array; /* 第一个元素array[0] */ for(p=array+1; p<array_end; p++) /* p++指向下一个元素 */ if (*p > max) max = *p; else if (*p < min) min = *p; return; } main () { int i, number[10]; printf("enter 10 data\n"); for(i=0;i<10;i++) scanf("%d",&number[i]); max_min_value(number,10); printf("\nmax=%d,min=%d\n",max,min); }
方法2、形参和实参均使用指针变量。
int max, min; /* 全局变量,最大值和最小值 */ void max_min_value(int *array, int n) { int *p, *array_end; /* p是数组元素指针 */ array_end = array + n; /* 指向数组尾 */ max = min = *array; /* 第一个元素array[0] */ for(p=array+1; p<array_end; p++) /* p++指向下一个元素 */ if (*p > max) max = *p; else if (*p < min) min = *p; return; } main () { int i, number[10],*p; p = number; /* 指针p指向数组number首地址 */ printf("enter 10 data\n"); for(i=0;i<10;i++) scanf("%d",&number[i]); printf("the 10 data:\n"); for(p=number,i=0; i<10; i++,p++) printf("%d ", *p); p = number;/* for循环后,p指向数组尾,因此应为p重新赋值 */ max_min_value(p,10); printf("\nmax=%d,min=%d\n",max,min); }
小结:数组作函数的参数,实参和形参之间传送数组的首地址,首地址可以用指针表示,也可以用数组名表示,因此,实参和形参有以下四种组合情况。
组合情况 实参 形参 1 数组名 数组名 2 数组名 指针 3 指针 指针 4 指针 数组名
四、多维数组的指针
二维数组
static int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
理解为: 有三个元素a[0]、a[1]、a[2],每一个元素代表一行,每一个元素是一个包含4个元素的数组。
数组名a代表:整个二维数组的首地址,也是元素a[0][0]的地址,同时代表第一行元素的首地址。
a+1表示第二行元素的首地址,也是元素a[1][0]的地址。
a+2表示第三行元素的首地址,也是元素a[2][0]的地址。
设数组的首地址是2000,则有a等于2000。 第一行4个元素,占8字节,因此第二行的首地址是2008,即a+1等于2008。 第二行4个元素,占8字节,因此第三行的首地址是2016,即a+2等于2016。
由于把a[0]、a[1]、a[2]看成一维数组,它们代表各自数组的首地址,即:
a[0]~ &a[0][0] (~表示“相当于”) a[1]~ &a[1][0] a[2]~ &a[2][0]
根据一维数组的表示方法,有:
a[0]+1:表示一维数组中第二个元素,~ &a[0][1] a[0]+2: ~&a[0][2] a[1]+1: ~&a[1][1];
综上所述,二维数组a的地址用下图说明(教材p180,图9.25):
已知某元素的指针后,可以用*运算符访问该元素。例、
*(a[1]+2) = a[1][2] = 13
关于二维数组各种指针表示法,仅要求到此,教材p180第三行~p187[本节尾],除[例9.12]外,不要求。(讲述二维数组元素的另一种表述)。
[例9.12] 用指针变量输出数组元素的值。
main () { static int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23}; int *p; for(p=a[0]; p<a[0]+12; p++) { if ( (p-a[0])%4 == 0) printf("\n"); printf("%4d", *p); } }
注意:本例用指针顺序访问二维数组的元素。若需访问二维数组a[n][m](n行m列)的某个元素a[i][j],计算该元素的相对位置公式为:
i*m+j (i,j=0,1,2, ...)
这种方法相当于把二维数组转化为一维数组来使用。
比较直接用二维数组下标访问元素:
main () { static int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23}; int i,j; for(i=0; i<3; i++) { for(j=0;j<4;j++) printf("%4d",a[i][j]); printf("\n"); } }
这种方式虽然清晰,但需进行两层循环,且为了计算每一个元素a[i][j]的位置,均进行i*4+j的运算,执行效率非常低。
一、字符串的表现形式
C语言中,有两种方式可以实现字符串:字符数组、字符指针。
| 字符数组[例9.16] | 字符指针[例9.17] | ||||||||
|
| ||||||||
| string是数组名,代表字符数组的首地址。数组可以用下标访问,也可以用指针访问。例、string[4]表示一个元素其值是字符v,也可以用*(string+4)来访问,string+4是指向字符v的指针。 |
string是一个指针变量,“I love China!"是一个字符串常量。语句:
它把字符串常量的首地址赋给指针string.不能理解为把字符串常量赋值给指针变量。 *string ="I love China!";
|

从以上两个例子中,可以看到:
1、字符数组和字符指针的概念不同。
2、字符指针指向字符串,而C语言中,字符串按数组方式处理,因此,字符数组和字符指针的访问方式相同。例如,均可以使用%s格式控制符进行整体输入输出。但应注意,如果不是字符数组,而是整型、实型等数字型数组,不能用%s,只能逐个元素处理。
[例9.18] 将字符串a复制到字符串b。
main () { char a[] = "Iam a boy."; char b[20]; int i; for(i=0; *(a+i) !='\0'; i++) *(b+i) = *(a+i); *(b+i) = '\0'; printf("string a is: %s\n",a); printf("string b is:"); for(i=0; b[i] !='\0'; i++) printf("%c",b[i]); printf("\n"); }
[例9.19] 将字符串a复制到字符串b。(用指针处理)
main () { char a[]="Iam a boy.", b[20], *p1, *p2; int i; p1 = a; p2 = b; for(; *p1 != '\0'; p1++, p2++) *p2 = *p1; *p2 = '\0'; printf("string a is: %s\n", a); printf("string b is:"); for(i=0; b[i] !='\0'; i++) printf("%c",b[i]); printf("\n"); }
二、字符串指针作函数参数
将一个字符串从一个函数传递到另一个函数,可以使用传地址的方式,即用字符数组名或字符指针变量作参数。有以下四种情况:
实参 形参 1 数组名 数组名 2 数组名 字符指针变量 3 字符指针变量 字符指针变量 4 字符指针变量 数组名
[例9.20] 用函数调用实现字符串的复制。
(1)用字符数组作参数。
void copy_string(char from[], char to[]) { int i=0; while(from[i] != '\0') { to[i] = from[i]; i++; } to[i] = '\0'; } main () { char a[] = "I am a teacher."; char b[] = "you are a student."; printf("string_a =%s\n string_b =%s\n", a,b); copy_string(a,b); printf("string_a =%s\n string_b =%s\n", a,b); }
main()函数可以改写为(使用字符指针):
main () { char *a = "I am a teacher."; char *b = "you are a student."; printf("string_a =%s\n string_b =%s\n", a,b); copy_string(a,b); printf("string_a =%s\n string_b =%s\n", a,b); }
(2)形参用字符指针。
void (char *from, char *to) { for(; *form != '\0'; from++, to++) *to = *from; *to = '\0'; }
main () { char *a = "I am a teacher."; char *b = "you are a student."; printf("string_a =%s\n string_b =%s\n", a,b); copy_string(a,b); printf("string_a =%s\n string_b =%s\n", a,b); }
(3)对copy_string函数的几种简化
|
① *to=*from是一个赋值表达式,其值等于*from。to++和from++分别使指针指向下一个字节。 先执行赋值表达式,再判赋值表达式的值(等于*from)是否为'\0',因此,from串中的结尾字符'\0'被赋值给to。 | ||||||||||||
|
② *to++=*from++先执行*to=*from,再使to、from分别加1。 | ||||||||||||
|
③
当遇到*from='\0'时,不执行赋值运算*to++=*from++,因此,最后应加一句*to='\0'。 | ||||||||||||
|
④ 与第②种简化相同,当*from='\0'时,表达式*to++=*from++的值等于'\0'(假),结束while循环。 | ||||||||||||
|
⑤ for循环的结束条件是表达式*to++ =*from++的值(即*from的值)等于'\0'。且form中的'\0'已被赋给to。注意'\0'的ASCII码是不是0。 | ||||||||||||
|
⑥ 形参用数组,使用局部指针变量指向形参数组。 |
三、字符指针变量与字符数组的区别
| 字符数组 | 字符指针变量 | |||||
| 组成 | 由若干元素组成,每个元素中放一个字符。 | 2或4字节,存放字符串的首地址。 | ||||
| 赋初值方式 | static char str[]={"I love China!"}; | char *a = "I love China!"; | ||||
| 赋值方式 |
|
| ||||
| 占用内存 | 字符数组一个元素占一字节内存,且在编译时分配。 | 指针变量中只可以放一个地址值(近指针=2字节,远指针=4 字节)。且编译时未指定。 | ||||
|
|
一、用函数指针变量调用函数
C语言中的指针,既可以指向变量(整型、字符型、实型、数组等),也可以指向程序的代码(如函数)。
一个函数在编译时被分配一个入口地址(第一条指令的地址),这个入口地址称为函数的指针。如果一个指针变量的值等于函数的入口地址,称为指向函数的指针变量,简称为函数指针。
可以通过函数指针来调用函数。
[例9.23] 求a和b中的大者。
| 用函数名调用函数max() | 用函数指针调用函数max() | ||||||||||||||||||||||||||||||||||||||
|
| ||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||
1、函数指针定义的一般形式:
函数返回值类型 (*指针变量名)(形参类型 )
即:除函数名用(*指针变量名)代替外,函数指针的定义形式与函数的原型相同。(在函数指针定义中加入形参类型是现代程序设计风格)。例:
int (*p) (int,int);
仅当形参类型是int时,可以省略形参类型,一般不要省略。(见例9.25)
int (*p) ();
2、语句p=max,把函数max的入口地址赋给函数指针p,因此,c=(*p)(a,b)中,*p就是调用函数max。
注意:
语句p=max中,函数名代表函数的入口地址,max后不跟函数参数。
用函数指针调用函数时,应指定实参。
3、(*p)()表示一个指向函数的指针变量,它可以先后指向不同的函数。
4、指向函数的指针变量p,象p++、p--、p+n等运算是无意义的。
二、函数指针作函数的参数(不要求,跳过)
| int f1(int i); | |
| int f2(int i, int j); | |
| int sub( int (*x1)(int), int (*x2)(int,int)); | |
| main | () |
| { | |
| sub(f1,f2); | |
| } | |
| int | f1(int i) |
| { | printf("f1, i=%d\n",i); |
| return i*2; | |
| } | |
| int | f2(int i, int j) |
| { | printf("f2, i=%d,j=%d\n",i,j); |
| return i+j; | |
| } | |
| int | sub( int (*x1)(int), int (*x2)(int,int)) |
| { | int a,b, i=3, j=2; |
| a = (*x1)(i); | |
| b = (*x2)(i,j); | |
| printf("sub, a=%d, b=%d\n",a,b); | |
| } | |
[例9.24] 设一个函数process,在调用它的时候,每次实现不同的功能。输入a和b两个数,第一次调用时找出a和b中大者,第二次调用时找出a和b中小者,第三次调用时求a与b之和。
分析:将求大值、求小值、求和值分别设计为函数max、min、add。
process使用指针调用这些函数。
int max(int x, int y) ; /* 求大值 */
int min(int x, int y) ; /* 求小值 */
int
add(int x, int y) ; /* 求和值 */
| void process(int x, int y, int(*fun)(int,int) ); |
void main()
{
int a, b;
printf("enter a and
b:");
scanf("%d,%d", &a, &b);
printf("max=");
process(a, b, max);
printf("min=");
process(a, b, min);
printf("sum=");
process(a, b, add);
}
int max(int x, int y)
{
int z;
if (x>y) z = x;
else z =
y;
return z;
}
int min(int x, int y)
{
int z;
if (x<y) z = x;
else z =
y;
return z;
}
int add(int x, int y)
{
int z;
z = x + y;
return
z;
}
void process(int x, int y, int(*fun)(int,int) )
{
int
result;
| result = (*fun)(x, y); |
printf("%d\n", result);
}
[例9_25]求定积分:

分析:编写一个求定积分的通用函数:
float integral(float (*fun)(float), float a, float b);
其中,a、b表示积分区间,fun是函数指针。
函数f在区间[a,b]的定积分公式:
需要积分的函数是:

程序:
float f1(float x)
{
float f;
f = 1 + x*x;
return f;
}
float f2(float x)
{
float f;
f = 1 + x + x*x + x*x*x;
return
f;
}
float f3(float x)
{
float f;
f = x / (1 + x*x);
return
f;
}
| float integral(float (*fun)(float), float a, float b) |
void main()
{
float y1, y2, y3;
y1 = integral(f1, 0.0,
1.0);
y2 = integral(f2, 0.0, 2.0);
y3 = integral(f3, 0.0,
3.5);
printf("y1=%6.2f\ny2=%6.2f\ny3=%6.2f\n", y1,y2,y3);
}
一般形式:
类型标识符 * 函数名(参数表)
例、
int * a
(int x, int y)
声明一个函数,函数名为a,其返回值类型是“指向整型的指针”,函数形式参数为
int x 和 int
y。
[例9.26]有若干学生的成绩(每个学生四门课程),要求用户在输入学生序号(从0开始)后,能输出该学生的全部成绩。
分析:
设计一个指针pointer指向一个学生的四门成绩
float (*pointer)[4]
![]() |
pointer是一个指向一维数组的指针。数组元素个数为4(四门课程) pointer+1指向下一个学生的成绩。 输入学生序号后,使pointer指向该学生的成绩,然后返回pointer指针. |
程序:
| float * search( float (*pointer)[4], int n) ; |
void main()
{
static float score[][4]
=
{{60,70,80,90},{56,89,67,88},{34,78,90,66} };
float *p;
int i,
m;
printf("enter the number of student:");
scanf("%d",&m);
printf("The scores of No.%d are:\n", m);
p = search(score, m); /*
在score数组中查询m号学生的成绩 */
/* 查询结果为指向成绩的指针 */
for(i=0; i<4;
i++)
printf("%5.2f\t",*(p+i));
}
float * search( float (*pointer)[4], int n)
{
float *pt; /*
pt是指向实数的指针,pointer是指向数组的指针 */
pt = *(pointer+n); /* pt = (float *)(pointer +
n) */
return pt;
}
[例9.27]对上例中的学生,找出有不及格成绩的学生及其学号。
分析:上例中,search函数返回学生成绩的首地址。本例,用返回地址区分学生成绩中有无不及格课程,若有不及格课程时,仍返回学生成绩的首地址;若无不及格课程,则返回学生成绩的末地址(等于下一个学生的首地址)。

float * search( float (*pointer)[4] );
void main()
{
static float score[][4]
=
{{60,70,80,90},{56,89,67,88},{34,78,90,66} };
float *p;
int i,
j;
for(i=0; i<3; i++) /* 三个学生 */
{
p = search(score+i);
if (p
== *(score+i)) /* p指向i号学生成绩首地址,该生有不及格课程 */
{
printf("No.%d
scores:\t", i); /* 显示该生的四门课程成绩 */
for(j=0; j<4;
j++)
printf("%5.2\t", *(p+j));
printf("\n");
}
}
}
float * search( float (*pointer)[4] )
{
int i;
float *pt;
pt = *(pointer + 1); /*先设pt指向成绩末地址(等于下一个学生成绩首地址),
即先假设无不及格成绩 */
for(i=0; i<4; i++) /* 查四门课程中有无不及格成绩 */
{
if ( *(*pointer+i)<60 ) pt = *pointer; /*有不及格课程,pt指向成绩
首地址*/
return pt;
}
}
一、指针数组
概念:指针数组是一个数组,该数组中的每一个元素是指针变量。
形式:类型标识符 *数组名[数组元素个数]
例子: int * p[4];
定义一个指针数组,数组名p,有4个元素, 每一个元素是指向整型变量的指针。
区分:int (*p)[4] (指向数组的指针)
定义一个指针变量,它指向有4个元素的一维数组。
用途:处理多个字符串。
字符串本身是一维数组,多个字符串可以用二维数组来处理,但会浪费许多内存。用指针数组处理多个字符串,不会浪费内存。
![]() |
二维数组 char name[][16], 浪费许多内存 |
![]() |
指针数组 char *name[], 不浪费内存 |
[例9.28]、将若干字符串按字母顺序(由小到大)输出。
#include "stdio.h"
#include "string.h"
void sort(char *name[], int n); /* 排序函数原型 */
void print(char *name[], int
n); /* 输出函数原型 */
void main()
{
static char *name[] =
{"Follow me", "BASIC",
"Great Wall", "FORTRAN", "Computer Design"};
int n = 5;
sort(name, n); /* 排序 */
print(name, n); /* 输出 */
}
void sort(char *name[], int n) /* 冒泡法排序 */
{
char *temp;
int i, j,
k;
for(i=0; i<n-1; i++) /* n个字符串,外循环n-1次 */
{
k =
i;
for(j=i+1; j<n; j++) /* 内循环 */
if (strcmp(name[k], name[j])
> 0) k = j;
/* 比较name[k]与name[j]的大小,较小字符串的序号保留在k中 */
if (k !=
i)
{ /*交换name[i]与name[k]的指向 */
temp =
name[i];
name[i] = name[k];
name[k] = temp;
}
}
}
void print(char *name[], int n)
{
int i;
for (i=0; i<n;
i++)
printf("%s\n", name[i]);
}
二、指向指针的指针

定义举例:
char * * p;
p是一个指向指针的指针。被p指向的指针指向字符变量。
| char **p; | char * pch; | char ch; |
使用举例:
字符指针数组char *name[]指向字符串。
定义指向指针的指针p:char **p, 使其指向name。
p = name + 2;
printf("%o\n", *p); /* 以八进制形式输出name[2]的值,
它是字符串“Great Wall”的地址 */
printf("%s\n", *p); /* 输出name[2]指向的字符串 */
[例9.29]
void main()
{
static char *name[] =
{"Follow me",
"BASIC", "Great Wall", "FORTRAN", "Computer Design"};
char **p;
int
i;
for(i=0; i<5; i++)
{
p = name + i;
printf("%d %d %s\n",
i, *p, *p);
}
}
输出:
0 414 Follow me
1 424 BASIC
2 430 Great Wall
3 441
FORTRAN
4 449 Computer Design
三、main()函数的参数
运行程序的命令行中,可以包含参数,例、
命令名 参数1 参数2 ..... 参数n
例如,“命令名”是可执行文件file1.exe,执行该命令时包含两个字符串参数:
file1 China Beijing
在源程序file1.c中,用main()函数的参数来表示命令的参数,
main(int argc, char argv[])
其中,argc表示命令行参数的个数(包括命令名),指针数组argv用于存放参数(包括命令名),上例中:
argc = 3;
argv[0] ="file1.exe", argv[1] ="China", argv[2] =
"Beijing"

例、(设文件名是file1)
main(int argc, char *argv[])
{
while(argc>1)
{
++argv;
printf("%s\n",*argv);
--argc;
}
}
输入:file1 China Beijing
输出:China
Beijing
该程序可改写为:
main(int argc, char *argv[])
{
while( argc-- > 1)
printf("%s\n", *++argv);
}
例、echo(参数回送)命令的程序。
main(int argc, char *argv[])
{
while(--argc>0)
printf("%s%c",*++argv, (argc>1)?
'□':'\n');
}
或写为:
main(int argc, char *argv[])
{
int i;
for(i=1;
i<argc; i++)
printf("%s%c",argv[i], (i<argc-1)? '□':'\n');
}
一、有关指针的数据类型
| 定义 | 含义 |
| int i; | 定义整型变量i |
| int *p; | p是指向整型数据的指针变量 |
| int a[n]; | 定义数组a,元素类型为int,元素个数是n |
| int *p[n]; | p是指针数组,包含n个指针,每一个指针可以指向整型数据 |
| int (*p)[n]; | p是指向数组的指针,数组有n个整型数 |
| int f(); | f是函数,返回值是int |
| int *p(); | p是函数,返回值是指针,该指针指向整型数据 |
| int (*p)(); | p是函数指针,所指向的函数返回整型数据 |
| int **p; | p是指针,指向一个指向整型数据的指针 |
二、指针运算小结
1、指针变量加/减运算
p++、p--、p+i、p-i、p+=i、p-=i
加1表示指向下一个数据。

2、指针变量赋值
p = &a; 变量a的地址赋给p,即指针p指向a
p = array; 数组array首地址赋给p
p = &array[i]; 数组元素array[i]的地址赋给p
p = max; 函数max的入口地址赋给p
p1 = p2; 指针p2的值赋给指针p1,即p1、p2所指的数据相同
注意:
p = 1000;![]()
i
= p; ![]()
int i;
int *p = &i;
*p = 100; 使p指向的数据(即变量i)等于100。
char *p = "I love China!"; 使p指向字符串

3、空指针 p = NULL;
空指针p=NULL表示p不指向任何数据。
在stdio.h中,NULL被定义为0:
#define NULL 0
习惯上,不使用 p = 0
而使用 p = NULL
指针变量p可以与NULL作比较,例、
if (p == NULL) ...
注意:空指针不指向任何数据,与p未赋值不同。当p未赋值时,其值是不确定的,而空指针的值是确定数0。
4、指针变量相减。
当p1、p2指向同一个数组的元素,指针相假p2-p1等于p1、p2间的元素个数。
注意:指针相加无意义。
5、两个指针的比较
当p1、p2指向同一个数组的元素时,可以比较,如、p1 > p2。若p1、p2不是指向同一个数组的元素,比较无意义。
三、空类型指针 void *
void *p,表示p是空类型指针,它可以指向任何数据类型。
空类型指针与其他类型指针之间赋值时,应进行强制类型转换,例、
char *p1;
void *p2;
p1 = (char *)p2;
p2 = (void *)p1;