490 likes | 636 Views
C 语言程序设计 — 提高篇. 第 1 章 指针 ( 2 ). 内容概述. 指向多维数组的指针 指向函数的指针 返回指针值的函数 指针数组 指向指针的指针. 教学目标. 复述数组的指针的含义,正确地通过指针引用数组元素,数组名,牢记指向数组的指针作为函数参数的传递过程 ; 描述函数指针的概念,正确地利用指针调用函数 ; 正确地使用返回指针的函数的定义和调用 ; 牢记指针数组的概念,初步认识指向指针的指针 ; 熟记有关指针的数据类型和指针的基本运算。. 1.1 指向二维数组的指针.
E N D
C语言程序设计—提高篇 第1章 指针(2)
内容概述 • 指向多维数组的指针 • 指向函数的指针 • 返回指针值的函数 • 指针数组 • 指向指针的指针
教学目标 • 复述数组的指针的含义,正确地通过指针引用数组元素,数组名,牢记指向数组的指针作为函数参数的传递过程 ; • 描述函数指针的概念,正确地利用指针调用函数; • 正确地使用返回指针的函数的定义和调用; • 牢记指针数组的概念,初步认识指向指针的指针; • 熟记有关指针的数据类型和指针的基本运算。
1.1指向二维数组的指针 1. 多维数组的地址: 将一维数组内容扩充,也可用一指针变量指向多维数组,以二维数组为例。 设: char a[3][4] = {{‘1’, ‘2’,’ 3’,’ 4’}, { ‘5’, ‘6’,’7’,’ 8’}, {‘9’, ’10’, ’11’, ’12’}};
设首地址a:2000, 则a+1:2004, a+2: 2008 从一维数组中可知:a[0]与a, (a+0)等价 所以:a[0][i]的地址&a[0][i]可表示为a+i a[0][i]的地址:a[0]+i, a+i, 和&a[0][i] a[1][i]的地址:a[1]+i, (a+1)+i, &a[1][i] a[i][j]的地址:a[i]+j= (a+i)+j 于是:a[i][j]可表示为 *(*(a+i)+j)
2. 多维数组的指针 有了多维数组的地址概念后,可用指针变量来指向多维数组 • 指向数组元素 当用一个指针变量指向第1行的首地址后,即可搜索到全部元素。 设二维数组的大小为m*n, 则第i, j个元素相对于a[0][0]的个数为i n + j (i=0,1,…,n–1) 设:int p; p=a[0]; 则a[i][j] 的地址为p+i n+j
指针p 数组a a[i][j]的地址:p+in+j
例1 设有一3×4的二维数组,利用指针逐行逐个输出元素。 #include <stdio.h> void main(){ int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int *p; p=*a; printf("a[2][3]=%d\n",*(p+2*4+3)); printf("number of a is:"); for(;p<a[0]+12; p++) { if((p-a[0])%4==0) printf("\n"); printf("%-4d", *p); } }
指向数组的每一行 以上例为例: 当p=a, 若要使p+1为下一行首地址, 这时,需将p的定义改为:int (p)[4]; 于是引用第(i, j)个元素的方法: ((p+i)+j) 第i行首地址,相当于a[i] 注意: • 不能去掉( ),否则为int p[4]为指针数组 • 之所以这样定义是为了使p+1的地址为a[1]
a是一个长度为3的数组 数组元素是长度为4的数组 a a+1 a+2 char a[3][4]; a、a+1、a+2都是指针,它们的基类型是长度为4的字符数组,它们与下面定义的指针p同类型 char (*p)[4]; *a *(a+1) *(a+2)
*a a[0] *a+1 a[0]+1 *a+2 a[0]+2 *a+3 a[0]+3 a a+1 a+2 a[1] *(a+1) a[2]+1 *(a+2)+1 a[1]+3 *(a+1)+3 a[2] *(a+2) char * 基类型为 char[4] 的指针 char a[0][3] *(*a+3) a[0] a[1][3] *(*(a+1)+3) a[1] a[2][3] *(*(a+2)+3) a[2] char *
指向二维数组的指针总结 • 表示二维数组 • a:指向二维数组的指针类型 • 表示第i行 • a[i]、*(a+i):指向一维数组的指针类型 • 表示第i行j列的元素 • a[i][j]、 *(*(a+i)+j) • *(a[i]+j)、(*(a+i))[j]:char类型 • 注意a和*a都是指针,但是基类型不同 • 注意*(a+i)和*a+i的区别
指向数组元素的指针变量 • 指向二维数组的元素 • 类型为 char *p; • 根据一维数组元素和二维数组元素的对应关系,可以访问所有的二维数组元素 • 基类型为一维数组的指针变量 • 指向二维数组的行 • 类型为 char (*p)[4]; • 把每一行作为一个一维数组来处理
一维数组与二维数组 • char a[M][N];↔ char a[M*N]; • a[i][j]↔ a[i*N+j] • 使用指向元素的指针访问二维数组元素 • char a[M][N]; • char *p=a[0];/* p=*a; */ • 则p[i*N+j]、*(p+i*N+j)、a[i][j] • 表示二维数组第i行j列的元素
二维数组是基类型为一维数组的指针 • 可以使用与二维数组同类型的指针变量 • 使用指向行的指针访问二维数组元素 • int a[M][N]; • int (*p)[N]=a;/* p=a; */ • 则p[i]、*(p+i)、a[i]表示数组的第i行 • 且p[i][j]、*(*(p+i)+j)、*(p[i]+j)、 • (*(p+i))[j]表示二维数组第i行j列的元素
二维数组的指针作函数参数 • 二维数组的地址也可以用作函数参数 • 用指向数组元素的指针作为参数 • 用指向二维数组的行的指针作为参数 • 举例 void foo(int *p, int n); void bar(int (*p)[4], int n); int a[3][4];/* 定义二维数组 */ foo(*a, 12);/* 二维数组的行作为参数 */ bar(a, 3);/* 二维数组名作为参数 */
1.2指向函数的指针 • 函数的指令存储在内存中的一段空间中 • 函数也有相应的内存地址 • 函数的入口地址就是函数的指针 • 函数名代表函数的入口地址 • 函数的指针可以用相应类型的指针变量表示,即指向函数的指针变量 • 函数也可以用通过指针变量间接调用
指向函数的指针变量 • 定义形式 • 类型 (*变量名)([参数类型列表]); • 说明 • 与函数原型类似,函数名用(*变量名)代替 • “参数类型列表”可以省略,但一般不要省略 • 主要用于函数的参数 • 先赋值,后使用,一般用同类型函数名赋值 • 不能进行算术运算和关系运算 • 指向函数的指针变量也称函数指针变量
类型说明符 (*指针变量名)( ); • 类型说明符表示被指函数的返回值的类型。 • (* 指针变量名)表示*后面的变量是定义的指针变量。 • 空括号表示指针变量所指的是一个函数。 例如: int (*pf)( ); 表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
指向函数的指针变量使用举例 例2:#include <stdio.h> int max(int x, int y){ return x>y?x:y; } void main() { int (*p)(int, int);/*定义指针变量*/int a, b, c; scanf("%d%d", &a, &b); p = max;/* 用函数名赋值 */c = (*p)(a, b);/* c=max(a,b); */printf("max=%d\n",c);}
指向函数的指针用作函数参数举例 • 一元函数定积分的梯形法数值求解
例3: double integral(double (*f)(double), double a, double b) { double s, h; int n=100, i; h = (b-a)/n; s = ((*f)(a)+(*f)(b))/2.0; for(i=1; i<n; i++) s += (*f)(a+i*h); return s*h; }
#include <stdio.h> #include <math.h> void main() { double y1, y2, y3; y1 = integral(sin, 0.0, 1.0); y2 = integral(cos, 0.0, 2.0); y3 = integral(exp, 0.0, 3.5); printf("%lf\n%lf\n%lf\n", y1,y2,y3); }
说明: (1)与其它指针变量相同,指向函数的指针变量在使用前也必须进行初始化操作,具体形式为“指针变量=函数名”。不要写成“指针变量=函数名(形式参数)”的形式。 (2)指向函数的指针变量可以先后指向不同的函数,但需注意函数返回值的类型与定义指针变量时所说明类型的一致性。 (3)通过指向函数的指针变量调用函数时,只需将“(*指针变量名)”代替传统调用中的函数名即可。 (4)对于指向函数的指针变量,++和--等运算是无意义的。
1.3返回指针值的函数 • 函数的返回值可以是指针类型 • 定义形式 • 类型 *函数名(参数列表); • 举例 • int *foo(int x, int y); • 说明 • 函数调用可以结合使用*和[]运算符 • 注意与指向函数的指针区别 • int (*foo)(int x, int y);
所谓函数类型是指函数返回值的类型。 • 在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。 • 定义指针型函数的一般形式: • 类型说明符 *函数名(形参表) • { …… /*函数体*/ • } • 函数名之前加了*号表明这是指针型函数,即返回值是指针。 • 类型说明符表示了返回的指针值所指向的数据类型。
例如: • int *ap(int x,int y) • { • ...... /*函数体*/ • } • ap是一个返回指针值的指针型函数。 • 返回的指针指向一个整型变量。
返回指针值的函数举例(1) 例4: #include <stdio.h> int *f(int *px, int *py)/* 返回整型指针 */ { return *px>*py?px:py;/* 较大数的地址 */ } void main(){ int a=2, b=3, c=9; *f(&a,&b)=c;/* 赋值给a和b中较大的数 */ printf("%d\n", b);/* 输出9 */ }
返回指针值的函数举例(2) 例5:#include <stdio.h> int *f(int *a, int *b){/* 返回整型指针 */ return *a>*b?a:b;/* 返回第一个元素 */ }/* 较大的数组地址 */ void main() { int i, a[]={1,2,3,4}, b[]={5,6,7,8}; for (i=0; i<4; i++) printf("%d\n", f(a,b)[i]); } /* 打印数组b的元素 */
注意函数指针变量和指针型函数在写法和意义上的区别。注意函数指针变量和指针型函数在写法和意义上的区别。 • int (*p)()和int *p()是两个完全不同的量。 • int (*p)()是一个变量说明,说明p 是一个指向函数入口的指针变量,该函数的返回值是整型量。 • int *p() 则不是变量说明而是函数说明,说明p是一个指针型函数,其返回值是一个指向整型量的指针,*p两边没有括号。
1.4指针数组和指向指针的指针 • 指针数组 • 类型 *数组名[长度]; • 元素是指针类型的数组 • 举例,char *p[4]; • 注意与基类型为数组的指针区分 char (*p)[4]; • 指向指针的指针 • 基类型为指针类型的指针 • 举例,char **p;
一个数组的元素值为指针则这个数组称为是指针数组。一个数组的元素值为指针则这个数组称为是指针数组。 • 指针数组是一组有序的指针的集合。 • 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。 • 指针数组说明的一般形式: • 类型说明符 *数组名[数组长度] • 其中类型说明符为指针值所指向的变量的类型。 • 例如: int *pa[3]; 表示pa是一个指针数组,它有三个数组元素, 每个元素值都是一个指针,指向整型变量。
CHINA name[0] AMERICA name[1] AUSTRALIA name[2] FRANCE name[3] GERMAN name[4] 指针数组举例 例6:字符串排序 void sort(char *name[],int n); void print(char *name[],int n); #include <string.h> #include <stdio.h> void main() { char *name[]={ "CHINA","AMERICA", "AUSTRALIA", "FRANCE","GERMAN"}; int n=5; sort(name,n); print(name,n); }
CHINA name[0] AMERICA name[1] AUSTRALIA name[2] FRANCE name[3] GERMAN name[4] CHINA name[0] AMERICA name[1] AUSTRALIA name[2] FRANCE name[3] GERMAN name[4] void sort(char *name[],int n) { char *pt; int i,j,k; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(strcmp(name[k],name[j])>0) k=j; if(k!=i) { pt=name[i]; name[i]=name[k]; name[k]=pt;} } } void print(char *name[],int n) { int i; for (i=0;i<n;i++) printf("%s\n",name[i]); }
注意: • (1)不能直接进行name[k]>name[j]这样的比较,因为name[k]和name[j]的值是两个地址,这里需要比较的是它们所指向的两个字符串。 • (2)排序过程中并未改变字符串的存储位置,只是改变了name数组元素的值,即改变了name数组中各元素的指向。
例7:/* 把所有名字的所有字母全部改成大写 */ void main() { char *name[]={"Tom", "John", "Kate"}; int i, j; for (i=0; i<3; i++) for (j=0; *(name[i]+j); j++) if (name[i][j]>=‘a' && name[i][j]<='z') name[i][j]-=32; }
指向指针的指针举例 p2 p a &p &a 10 • 指针数组名同样表示指针数组的首地址,是一个指针变量的地址,即是指针的指针。 • 指针变量在内存中仍有一个存储单元,该单元的地址,即指针的指针。如下图: 指向指针的指针变量的定义: 类型说明符 **指针名 int a, *p=a;**p2=&p;
例8指向指针的指针变量 #include <stdio.h> void main() { int a,*p1,**p2; a=10; p1=&a; p2=&p1; printf("*p1=%d\n",*p1); printf("**p2=%d\n",**p2); }
例9:/* 利用指向字符指针的指针打印字符串数组 */ #include <stdio.h> void main() { char *name[]={"Tom", "John", "Kate"}; char **p; int i; p = name; for (i=0; i<3; i++) printf("%s\n", *p++); }
命令行参数 • main函数的几种形式 • int main(); • int main(int argc, char *argv[]); • int main(int argc, char **argv); • 说明 • 返回值类型一般为int,也可以是其他类型 • argc为命令行参数的个数 • argv为命令行参数字符串数组 • 命令行参数包括文件名本身
命令行参数举例—echo命令 例10: #include <stdio.h> int main(int argc, char *argv[]) { while(--argc > 0) printf("%s%c", *++argv, (argc>1)?' ':'\n'); return 0; } >echo C Language argc == 3; argv[0] == "echo"; argv[1] == "C"; argv[2] == "Language";
复杂的声明形式 • 复杂类型变量的声明容易混淆 • 指针数组和指向数组的指针 int *a[5]; int (*a)[5]; • 指向函数的指针和返回指针值的函数 void (*f)(); void *f(); • 过于复杂的声明形式使程序晦涩难懂,而且容易出错 • 可以用typedef关键字把复杂类型的变量声明用若干个容易理解的小步骤表示
分析声明形式的方法 • 从标识符开始,逐层分析其意义 • 按运算符优先级和结合方向的顺序进行 • 可能涉及的运算符包括 • ()自左向右结合 改变结合顺序;或声明一个函数,向外一层是函数返回值类型声明 • []自左向右结合 声明一个数组,向外一层是数组元素类型声明 • * 自右向左结合 声明一个指针类型,向外一层是指针基类型声明
x是一个长度为3的数组 数组的元素是指针类型 指针是指向函数的 函数的返回值是指针类型 指向长度为5的字符数组 声明形式分析举例 char (*(*x[3])())[5];
void类型指针 • 定义形式 • void *p; • 说明 • 定义一个指针,但不指定它指向的数据类型 • 不能通过*p引用它指向的数据 • void*指针可以与其他任何类型的指针相互赋值和比较,而不需要显式的强制类型转换 • 经常作为函数形参和返回值的类型
练 习 程序调试题:调用函数f,求二维数组a中全体元素之和。 #include <stdio.h> float f( _______1_______) { float y=0; int i,j; for(i=0;i<m;i++) for(j=0;j<n;j++) y=y+*(*(x+i)+j); return y; } void main() { float a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}},*b[3]; int i; for(i=0;i<3;i++) b[i]=___2___; printf("%.2f\n",f(b,3,4)); } (1) float *x[],mint m,int n (2) a[i]
练 习 下面程序的运行结果是 #include”stdio.h” #include”string.h” void main() {char a[10]=“AB”,b[10]=“LMNP”; int i=0; strcat(a,b); while(a[i++]!=‘\0’) b[i]=a[i]; puts(b); } (a) LB (b ) ABLMNP ( c) AB (d) LBLMNP √
练 习 写出下面程序的运算结果 #include <stdio.h> #include <string.h> void main() { char c,*a="Office"; int i; for (i=0;i<strlen(a)/2;i++) { c=*a; strcpy(a,a+1); a[strlen(a)]=c; puts(a); } getch(); } fficeO ficeOf iceOff
练 习 程序阅读题# include<string.h>void main(){ char a[5][20]={"abc", "def", "123", "456", "#$%"}; int i, j; for(i=0; i<4; i++) { j=0; while(a[i][j]) j++;strcat(*(a+i)+j,*(a+i+1)); } for(i=0; i<4; i++) puts(*(a+i));} 输出结果: abcdef def123 123456 456#$%