§9.1 指针的概念
指针(pointer):是一个变量的地址。
指针变量:是一个变量,其值是另一个变量的地址。
任何变量都在计算机内存中占有一块内存区域, 变量的值就存放在这块内存区域之中,(寄存器变量不在内存中,而是在CPU的寄存器中)。
我们通过下面的图来了解一下这两个概念:
其中:2000为变量i的地址(指针) ,i_pointer用来存放变量i的地址(2000),所以i_pointer是一个指针变量.
一个变量的访问(访问是指取出其值或向它赋值)方式有两种:
(1)直接访问,通过变量名访问,如通过变量名i直接访问。
(2)间接访问,通过该变量的指针来访问,如通过i_pointer访问变量i。
请看下图:
§9.2 变量的指针
9.2.1 指针变量的定义
指针变量有三个属性:
(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;
9.2.2 指针变量的引用
有两个运算符可以引用指针变量:
(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。
运行结果:a=5,b=9
max=9,min=5
该例不交换变量a、b的值,而是交换指针p1、p1的值。
9.2.3 指针变量作为函数参数
[例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); }
§9.3 数组的指针和指向数组的指针变量
9.3.1 指向数组元素的指针变量的定义与赋值
指针可以指向数组和数组元素,当一个指针指向数组后,对数组元素的访问,既可以使用数组 下标,也可以使用指针。并且,用指针访问数组元素,程序的效率更高(用下标访问数组元素程序 更清晰)。 所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
指向数组元素的指针变量,其类型应与数组元素相同,例:
int a[10]; /* 元素为整型 */ float b[10] ; /* 元素为实型 */ int *p; /* 可以指向数组a的元素 */ float *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 */
9.3.2 通过指针引用数组元素
当使指针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]。 2、指向数组的指针变量也可以带下标,如,p[i]与*(p+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.3.3 数组名作函数参数
数组名代表数组首地址,因此,它作实参在函数调用时,是把数组首地址传送给形参。这样,
实参数组和形参数组共占同一段内存区域。从而在函数调用后,实参数组的元素值可能会发
生变化
[例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 指针 数组名
请自行阅读教材pp175~179。
9.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的运算,执行效率非常低。
§9.4 字符串的指针
9.4.1 字符串的表现形式
C语言中,有两种方式可以实现字符串:1.用字符数组实现;2.用字符指针实现。
字符数组[例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"); }
9.4.3 字符指针变量与字符数组的区别
虽然字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应混为一谈,区别如下表所示:
字符数组 | 字符指针变量 | |||||
组成 | 由若干元素组成,每个元素中放一个字符。 | 2或4字节,存放字符串的首地址。 | ||||
赋初值方式 | static char str[]={"I love China!"}; | char *a = "I love China!"; | ||||
赋值方式 |
|
| ||||
占用内存 | 字符数组一个元素占一字节内存,且在编译时分配。 | 指针变量中只可以放一个地址值(近指针=2字节,远指针=4 字节)。且编译时未指定。 | ||||
|
|
自行阅读pp195[例9.21]~196的细节
9.5.1 用函数指针变量调用函数
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等运算是无意义的。
9.5.1 用函数指针变量调用函数
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等运算是无意义的。
§9.6 返回指针的函数
一般形式:类型标识符 * 函数名(参数表)
例: 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;
}
}
§9.7 指针数组和指向指针的指针
9.7.1 指针数组
概念:指针数组是一个数组,该数组中的每一个元素是指针变量。
形式:类型标识符 *数组名[数组元素个数]
例子: 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]);
}
9.7.2 指向指针的指针
定义举例:
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
9.7.3 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');
§9.8 指针使用小结
一、有关指针的数据类型
定义 | 含义 |
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;