第八章 指 针 8.1 指针的概念与定义 8.2 指针作函数参数 8.3 指针与数组 8.4 指针与函数 8.5 复杂指针
8.1 指针的概念与定义 8.1.1 指针的概念 图 8.1 内存分配表
8.1.2 指针的定义及使用 1. 指针的定义 指针是一种存放地址值的变量,像其它变量一样,必须在使用前定义。指针变量的命名遵守与其它变量相同的规则, 即必须是唯一的标识符。指针定义的格式如下: 类型名 *指针名;
例 8.1 指针与非指针的定义 例 8.2 指针的指向 int *point; scanf("%d", point); 例 8.1 指针与非指针的定义 char *pcl,*pc2; /* pcl和pc2均为指向char型的指针*/ float *pf, percent;/* pf是float型的指针,而percent为普通的float型变量*/ 例 8.2 指针的指向 int *point; scanf("%d", point);
2. 指针的有关运算符 两个有关的运算符: &: 取地址运算符。 *: 指针运算符(或称“间接访问”运算符)。 例如:&a为变量a的地址, *p为指针p所指向的存贮单元的内容。 &运算符只能作用于变量,包括基本类型变量和数组的元素、 结构体类型变量或结构体的成员(第九章), 不能作用于数组名、 常量或寄存器变量。 例如:
表达式&r、 &a[0]、 &a[i]是正确的, 而&(2*r)、 &a、&k是非法表示。 double r, a[20]; int i; register int k; 表达式&r、 &a[0]、 &a[i]是正确的, 而&(2*r)、 &a、&k是非法表示。 单目运算符*是&的逆运算,它的操作数是对象的地址, *运算的结果是对象本身。 单目*称为间访运算符,“间访”就是通过变量的地址而不是变量名存取(或引用)变量。例如, 如果pc是指向字符变量c的指针,则*(&c)和*pc表示同一字符对象c。因而赋值语句 *(&c)=′a′; *pc=′a′; c=′a′; 效果相同,都是将′a′存入变量c。
例 8.3 取地址运算符。 int variable, *point; point=&variable;
3. 指针的使用 例 8.4 指针的使用。 main( ) {int a, b, *p1, *p2; a=10; b=20; 例 8.4 指针的使用。 main( ) {int a, b, *p1, *p2; a=10; b=20; p1=&a; p2=&b; printf("%d\t%d\n", *p1, *p2); p1=&b; p2=&a; } 程序运行结果: 10 20 20 10
程序说明: (1) 在两个printf函数调用语句中的*是指针运算符,这一单目运算符的运算对象应该是指针或地址, 它的作用是得到指针指向变量的值。 (2) 在第一个printf函数调用时, 可以假设内存的分配如图8.2所示。 图 8.2 内存分配表
(3) 在第二个printf函数调用时,内存的分配将如图8.3所示。 图 8.3 内存分配表
例 8.5 指针的使用。 程序运行结果: 10 20.5 10 20.5 main( ) {int a, *pi; 例 8.5 指针的使用。 main( ) {int a, *pi; float f, *pf; a=10; f=20.5; pi=&a; pf=&f; printf("%d\t%4.1f\n", a, f); printf("%d\t%4.1f\n", *pi, *pf); } 程序运行结果: 10 20.5 10 20.5
程序说明: 图 8.4 内存分配表
8.2 指针作函数参数 例 8.6 函数参数的传递。 void swap(int x, int y); main() { 8.2 指针作函数参数 例 8.6 函数参数的传递。 void swap(int x, int y); main() { int a, b; a=10; b=20; swap(a, b); printf("a=%d, b=%d\n", a, b); }
void swap(int x, int y) 运行结果: a=10, b=20 { int temp; temp=x; x=y; y=temp; } 运行结果: a=10, b=20
图 8.5 swap函数被调用时的内存分配图 图 8.6 swap函数调用结束时的内存分配图
例 8.7 指针作函数参数。 void swap(int * x, int * y); main() { 例 8.7 指针作函数参数。 void swap(int * x, int * y); main() { int a, b, *p1, *p2; a=10; b=20; p1=&a; p2=&b; swap(p1, p2); printf("a=%d, b=%d\n", a, b); /* 或printf(″%d %d″, *p1, *p2) */ } void swap(int *pa, int *pb) int temp; temp=*pa; *pa=*pb; *pb=temp; } 运行结果: a=20, b=10
图 8.7 swap函数被调用时的内存分配图 图 8.8 swap函数调用结束时的内存分配图
例 8.8 指针作函数参数。 void swap(int *, int *); main() { 例 8.8 指针作函数参数。 void swap(int *, int *); main() { int a, b, *p1, *p2; a=10; b=20; p1=&a; p2=&b; swap(p1, p2); printf("a=%d, b=%d\n", a, b); } void swap(int *pa, int *pb) int *temp; temp=pa; pa=pb; pb=temp; 运行结果: a=10, b=20
图 8.9 swap函数被调用时的内存分配图 图 8.10 swap函数调用结束时的内存分配图
例 8.9 指针作函数参数。 void swap(int *, int *); main() { 例 8.9 指针作函数参数。 void swap(int *, int *); main() { int a, b, *p1, *p2; a=10; b=20; p1=&a; p2=&b; swap(p1, p2); printf("a=%d, b=%d\n", a, b); } void swap(int *pa, int *pb) int *temp; *temp=*pa; *pa=*pb; *pb=*temp; }
可以将swap函数改变成这样: void swap(int *pa, int *pb) { int *temp, t; *temp=*pa; *pa=*pb; *pb=*temp; } 此时可以得到运行结果: a=20, b=10
8.3 指 针 与 数 组 8.3.1 指向一维数组的指针 图 8.11 指向数组元素的指针
实际上,C语言允许这样的表达方式: pa[i]和. (a+i), 它们等价于 实际上,C语言允许这样的表达方式: pa[i]和*(a+i), 它们等价于*(pa+i)和a[i]。由此可见,引用数组元素有两种等价的形式: 通过下标引用和通过指针引用。以数组a为例, 假定pa指向a[0],元素的下标引用和指针引用的对应关系如下(写在同一行上的表达式是对同一元素的等价引用形式): a[0] *pa *a或*(a+0) a[1] *(pa+1) *(a+1) a[2] *(pa+2) *(a+2) … … … a[9] *(pa+9) *(a+9)
元素地址的对应关系如下: &a[0] pa a或a+0 &a[1] pa+1 a+1 &a[2] pa+2 a+2 … … … &a[9] pa+9 a+9
8.3.2 数组作函数参数 例 8.10 数组名作函数参数 main() { int *p, i, a[10]; p=a; 8.3.2 数组作函数参数 例 8.10 数组名作函数参数 main() { int *p, i, a[10]; p=a; for(i=0; i<10; i++) scanf("%d", p++); sort(p, 10); for(p=a, i=0; i<10; i++)
{ printf("%5d", *p); p++; } printf("\n"); } sort(int x[], int n) { int i, j, k, t; for(i=0; i<n-1; i++) { k=i; for(j=i+1; j<n; j++) if(x[j]>x[k]) k=j; if(k!=i) { t=x[i]; x[i]=x[k]; x[k]=t; }
函数sort的形参x可以认为是main函数中数组a的别名, 所以在函数sort中对x的操作就是对a的操作,使得数组a得以排序, 完成了程序的要求。注意在main函数中使用指针p时,指针当前指向的变化。当然,main函数中调用sort函数时实参可以是指针,也可以是数组名,在函数sort中形参可以是数组名, 也可以是指针。 可将上例的程序改写如下: main() { int i, a[10]; for(i=0; i<10; i++) scanf("%d", &a[i]); sort(a, 10);
printf("%5d", a[i]); } sort(int *x, int n) { int i, j, k, t; for(i=0; i<n-1; i++) {k=i; for(j=i+1; j<n; j++) if(x[j]>x[k]) k=j; if(k!=i) { t=x[i]; x[i]=x[k]; x[k]=t; }
8.3.3 指针和字符串 在C语言中,字符串(例如"I am a student")指在内存中存放的一串以′\0′结尾的若干个字符。例如,可以这样定义和初始化一个字符数组: char string[]="I am a student"; 数组长度由字符串长度加1确定。也可以定义一个字符数组, 然后用标准输入函数从外部设备读入一个字符串。例如: char string[20]; scanf("%s", string);
数组长度应能足够存放读入的最大长度的字符串。 利用指针也可以表达字符串, 而且比用字符数组更为方便灵活。例如,可以这样定义和初始化一个字符指针: char *point="I am a student"; point是指向字符串"I am a student"的指针,即字符串的首地址赋给了字符指针,因此使一个字符指针指向一个字符串。 也可以采用下面的方式: char *point; point="I am a student";
例 8.11 字符串拷贝函数。 #include<stdio.h> void my-strcpy(char *t, char *s) 例 8.11 字符串拷贝函数。 #include<stdio.h> void my-strcpy(char *t, char *s) { while((*t=*s)!=′\0′) { s++; t++; } }
下列对my-strcpy函数的调用都是正确的: (1)my-strcpy(s1,"I am a student"); (2) ps1=&s1[0]; ps2="I am a student"; my-strcpy(ps1, ps2); (3) ps2=&s2[0]; my-strcpy(ps2,"I am a student"); my-strcpy(s1,s2); 或 my-strcpy(s1,ps2);
my-strcpy的定义可以写成更简练的形式; void my-strcpy(char *t, char *s) { while((*t++=*s++)!=′\0′); } 复制过程继续的条件是被复制的字符为非0(非′\0′)。 由于组成字符串的任何字符(′\0′除外)的值都为非0, 所以my-strcpy还可以进一步简化为 void my-strcpy(char *t, char *s) while(*t++=*s++); }
例 8.12 字符串比较函数。 int my-strcmp(char *s, char *t) { 例 8.12 字符串比较函数。 int my-strcmp(char *s, char *t) { for(; *s==*t; s++, t++) if(*s==′\0′) return 0; return(*s-*t); }
8.3.4 指向多维数组的指针 设有一个二维数组的定义为 static int a[2][4]={{1, 3, 5, 7}, {2, 4, 6, 8}}; 我们可以假设数组a在内存中的分配情况如下:
详细区分说明如下:
例 8.13 多维数组。 运行结果: 1 3 5 7 2 4 6 8 10 20 30 40 main() { 例 8.13 多维数组。 main() { static int a[3][4]={{1, 3, 5, 7}, {2, 4, 6, 8}, {10, 20, 30, 40}}; int *p; for(p=a[0]; p<a[0]+12; p++) /* 注1 */ { if((p-a[0])%4==0) printf("\n"); /* 注2 */ printf("%4d", *p); } } 运行结果: 1 3 5 7 2 4 6 8 10 20 30 40
例 8.14 多维数组。 运行结果: 1 3 5 7 2 4 6 8 10 20 30 40 main() { 例 8.14 多维数组。 main() { static int a[3][4]={{1, 3, 5, 7}, {2, 4, 6, 8}, {10, 20, 30, 40}}; int i, j, (*p)[4]; p=a; for(i=0; i<3; i++) { for(j=0; j<4; j++) printf("%4d", *(*(p+i)+j)); printf("\n"); } } 运行结果: 1 3 5 7 2 4 6 8 10 20 30 40
(1) 形参说明为指向数组元素的指针, 实参为数组元素的地址或指向元素的指针。 例如: 调用函数f, 用数组元素的地址作实参: int a[2][3]; void f(int *, int); … f(a[0], 2*3); …
调用函数f,用指向数组元素的指针作实参: int a[2][3], *pi; void f(int *, int); … pi=a[0]; /* 或pi=&a[0][0] */ f(pi, 2*3); … pi是指向元素 a[0][0]的指针。 函数f的定义: 〖HT5”〗 void f(int *pi, int size) { } 形参pi说明为列指针。
(2) 形参说明为行指针, 实参为行地址或行指针。例如, 调用函数f, 用行指针作实参: { int a[2][3]; void f(int (*)[3], int); … f(a, 2); }
实参 a是行指针,类型为 int(*)[3];实参 2是二维数组 a的行数。 调用函数f, 用行指针作实参: int a[2][3], (*pa)[3]; void f(int(*)[3], int); … pa=a; f(pa, 2); …
pa是行指针,赋值语句“pa=a; ”使pa指向a的第0行, pa的类型与a的类型相同。 函数f的定义: void f(int (*pa)[3], int size) { … }
例 8.15 行指针作函数参数 输入一个用年、月、日表示的日期,定义函数day- of-year将它转换成该年的第几天;输入某年的第几天,定义函数 month-day将它转换成该年的某月某日。 #include<stdio.h> /* day-of-year: 从月份和日期计算为一年中的第几天 */ int day-of-year(int year, int month, int day, int *pi) { int i, leap; leap=year%4==0&&year%100![KG-*4]=0||year%400==0; for(i=1; i<month; i++) day+=*(pi+leap*13+i); return (day); }
/* month-day: 从一年中的第几天计算月份和日期 */ void month-day(int year, int yday, int (*pdaytab)[13], int *pmonth, int pday) { int i, leap; leap=year%4==0&&year%100![KG-*4]=0||year%400==0; for(i=1; yday>*(*(pdaytab+leap)+i); i++) yday-=*(*(pdaytab+leap)+i); *pmonth=i; *pday=yday; } int main(void) {
int daytab[2][13]={ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }, y, m, d, yd; printf("input year, month, day: \n"); scanf("%d%d%d", &y, &m, &d); yd=day-of-year(y, m, d, &daytab[0][0]); printf("day of year is %d\n", yd); printf("input year, day-of-year: \n"); scanf("%d%d", &y, &yd);
month-day(y, yd, daytab, &m, &d); printf("%d, %d in %d\n", m, d, y); return 0; } input year, month, day: (输出) 1995 12 11 (输入) day of year is 345 (输出) input year, day-of-year: (输出) 1994 280 (输入) 10, 7 in 1994 (输出)
8.3.5 指针数组 指针变量可以同其它变量一样作为数组的元素, 由指针变量组成的数组称为指针数组,组成数组的每个元素都是相同类型的指针。 指针数组说明的形式为 类型名 *数组名[常量表达式]: 其中“*数组名[常量表达式]”是指针数组说明符。 例如: int *ps[10];
设有二维数组说明: int a[4][4]; 用指针数组表示数组a, 就是把a看成4个一维数组, 并说明一个有4个元素的指针数组pa,用于集中存放a的每一行元素的首地址,且使指针数组的每个元素pa[i]指向a的相应行。于是可以用指针数组名pa或指针数组元素pa[i]引用数组a的元素。 指针数组pa的说明和赋值如下:
int *pa[4], a[4][4]; pa[0]=&a[0][0]; 或pa[0]=a[0]; pa[1]=&a[1][0]; 或pa[1]=a[1]; pa[2]=&a[2][0]; 或pa[2]=a[2]; pa[3]=&a[3][0]; 或pa[3]=a[3]; *(*(pa+i)+0), *pa[i]或*(pa[i]+0)(i=0, 1, 2, 3)引用第i行第0列元素 a[i][0] ; *(*(pa+i)+1)或*(pa[i]+1)引用第i行第1列元素 a[i][1]; …。
用指针数组表示二维数组在效果上与数组的下标表示是相同的,只是表示形式不同。用指针数组表示时,需要额外增加用作指针的存贮开销;但用指针方式存取数组元素比用下标速度快,而且每个指针所指向的数组元素的个数可以不相同。例如,可用有5个元素的指针数组和5个不同长度的整型数组来描述下面的三角矩阵:
存贮三角矩阵的数组和每一行的指针可说明如下: int a1[1], a2[2], a3[3], a4[4], a5[5], *pa[5]; 下面的语句使pa的每个元素指向三角矩阵的每一行: pa[1]=&a1[0]; pa[2]=&a2[0]; pa[3]=&a3[0]; pa[4]=&a4[0]; pa[5]=&a5[0];
8.4 指 针 与 函 数 8.4.1 指向函数的指针 C语言可以定义指向函数的指针,函数型指针的定义形式: 8.4 指 针 与 函 数 8.4.1 指向函数的指针 C语言可以定义指向函数的指针,函数型指针的定义形式: 类型标识符 (*指针名)(); 例如: int (*fp)(); 说明: fp是指向int类型函数的指针。与指向数组的指针说明类似,说明符中用于改变运算顺序的()不能省。如果将(*fp)()写成* fp(), 则 fp成为返回值为指针类型的函数
例 8.16 指向函数的指针。 设一个函数operate,在调用它的时候,每次实现不同的功能。输入a和b两个数,第一次调用operate得到a和b中最大值,第二次得到最小值,第三次得到a与b之和。 main() { int max(), min(), sum(), a, b; /* 必须进行函数声明, 否则无法调用 */ printf("Enter two number: "); scanf("%d%d", &a, &b); printf("max="); operate(a, b, max); printf("min="); operate(a, b, min); printf("sum="); operate(a, b, sum);
} max(int x, int y) { if(x>y) return(x); else return(y); min(int x, int y) if(x<y) return(x); sum(int x, int y) return(x+y); operate(int x, int y, int (*fun)()) printf("%d\n", (*fun)(x, y)); }
程序运行情况: Enter two number: 5 9 max=9 min=5 sum=14
8.4.2 返回指针的函数 C的函数可以返回除数组,共用体变量和函数以外的任何类型数据和指向任何类型的指针。 指针函数定义的一般形式: 类型标识符 *函数名(参数表){ } 其中“*函数名(参数表)”是指针函数定义符。 例如: int *a(int x, int y){ }
例 8.17 指针函数 #include<string.h> #define STRLEN 81 例 8.17 指针函数 #include<string.h> #define STRLEN 81 #include <stdio.h> char *maxstr(char *str1, char *str2) { if(strcmp(str1, str2)>=0) return(str1); else return(str2); } main() char string1[STRLEN], string2[STRLEN], *result; printf("Input two strings: \n"); scanf("%s%s", string1, string2); result=maxstr(string1, string2); printf("The max string is: %s\n", result);
程序运行情况: Input two strings: abcde abcee The max string is: abcee
8.5 复 杂 指 针 8.5.1 指向指针的指针 定义了指针pointer,它指向char型,用它可以存放字符型变量的地址,并且可以用它对所指向的变量进行间接访问。 进一步定义: char **p-p; 从运算符*的结合性可以知道, 上述定义相当于: char *(*p-p);
例 8.18 指向指针的指针。 main() { int a, *pointer, **p-p; a=20; 例 8.18 指向指针的指针。 main() { int a, *pointer, **p-p; a=20; pointer=&a; p-p=&pointer; printf("%d, %u, %u\n" , a, pointer, p-p); printf("%d, %u, %d\n", *pointer, *p-p, * *p-p); }
运行结果: 20, 2000, 2050 20, 2000, 20 图 8.12 内存分配表
例 8.19 指向指针的指针。 main() { static char *country[]={"CHINA", "ENGLAND", “ FRANCE", "GERMANY"}; char * *p; int i; for(i=0; i<4; i++) p=country+i; printf("%s\n", *p); } }
运行结果: CHINA ENGLAND FRANCE GERMANY
8.5.2 命令行参数 例 8.20 echo程序。 #include<stdio.h> 8.5.2 命令行参数 例 8.20 echo程序。 #include<stdio.h> main(int argc, char *argv[]) { int i; for(i=1; i<argc; i++) printf("%s%s", argv[i], (i<argc-1)? " ": "\n"); printf("\n"); return 0; }
程序运行的命令行情况: echo hello world 运行结果: hello world argv[0]为所调用程序的名字,所以argc至少为1。如果argc为1,则在程序名后面没有命令行参数。
例 8.21 命令行的三个参数,前两个为两个整数,第三个确定程序输出的为最大值还是最小值。 #include<stdio.h> #include<stdlib.h> #include<string.h> int max(int x, int y) { return(x>y?x: y); } int min(int x, int y) return(x>y?y: x); }
main(int argc, char *argv[]) { int a, b; char *operate-flag; if(argc<2) printf("usage: abc integer1 interger2 operate-flag\n"); exit(0); } a=atoi(argv[1]); b=atoi(argv[2]); operate-flag=argv[3]; if(strcmp(operate-flag, "max")==0)
程序生成abc.exe后, 设命令行为 abc 20 30 max 输出为 The max of 20 and 30 is: 30 printf("The %s of%s and %s is: %d\n", argv[3], argv[1], argv[ 2], max(a, b)); else if(strcmp(operate-flag, "min")==0) printf("The %s of %s and %s is: %d\n", argv[3], argv[1], argv [2], min(a, b)); else printf("operate-flag should be max or min\n"); } 程序生成abc.exe后, 设命令行为 abc 20 30 max 输出为 The max of 20 and 30 is: 30
8.5.3 复杂指针的理解 注意:(1) 数组的元素不能为函数(可以为函数的指针)。 (2) 函数的返回值不能为数组或函数(可以为函数或数组的指针)。 *是指针类型的标志,[ ]是数组类型的标志,( )是函数类型的标志。( )和[ ]的优先级高于*,当[ ]、 *或( )和*同时出现时,先解释()和[]后解释*;()和[]属于同一优先级,二者同时出现时,按从左到右顺序解释; 整个说明符解释完之后,再加上类型标识符就是最终的解释。()也用于改变由优先级和结合性隐含的运算顺序,对嵌套的(),从内向外解释。
(1) char **pp; pp: 指向字符指针的指针。 (2) int (*daytab)[13]; daytab: 指向由13个整型元素组成的数组的指针。 (3) int *daytab[13]; daytab: 由13个整型指针构成的指针数组。 (4) void *comp(); comp: 返回值为空类型指针的函数。 (5) void (*comp)(); comp: 指向无返回值函数的指针。
(6) int *p(char *a); p是一个函数, 它的形参是一字符型指针, 它的返回值是一整型指针。 (7) int (*p)(char*a); p是一个指针, 它指向一个函数, 函数有一字符指针作形参, 返回值为整型量。 (8) int p(char (*a) [ ]); p是一个返回整型量的函数, 它有一个形参, 形参的类型是指向字符数组的指针。 (9) int *p(char *a[ ]); p是一个函数, 返回一整型指针, 它的形参是一个字符指针数组。