600 likes | 721 Views
Chap 11 指针进阶. 11.1 布袋中的彩色球 11.2 解密藏头诗 11.3 学生信息管理的链表实现. 本章要点. 指针数组和指向指针的指针是如何被定义和使用的? 指针如何作为函数的返回值? 指向函数的指针的意义是什么? 什么是结构的递归定义,哪种应用需要这种定义方法? 对链表这种数据结构,如何进行动态内存分配操作? 如何建立单向链表并实现插入、删除以及查找操作?. 11.1 布袋中的彩色球. 11.1.1 程序解析 11.1.2 指针数组的概念 11.1.3 指向指针的指针 11.1.4 用指针数组处理多个字符串
E N D
Chap11 指针进阶 11.1 布袋中的彩色球 11.2 解密藏头诗 11.3 学生信息管理的链表实现
本章要点 • 指针数组和指向指针的指针是如何被定义和使用的? • 指针如何作为函数的返回值? • 指向函数的指针的意义是什么? • 什么是结构的递归定义,哪种应用需要这种定义方法? • 对链表这种数据结构,如何进行动态内存分配操作? • 如何建立单向链表并实现插入、删除以及查找操作?
11.1 布袋中的彩色球 11.1.1 程序解析 11.1.2 指针数组的概念 11.1.3 指向指针的指针 11.1.4 用指针数组处理多个字符串 11.1.5 命令行参数
11.1.1 程序解析 例11-1 已知一个不透明的布袋里装有红、蓝、黄、绿、紫同样大小的圆球各一个,现从中一次抓出两个,问可能抓到的是什么颜色的球?
程序解析-例11-1 源程序 char *color[5]; color[0] = “red”; color[1] = “blue”; … 指针数组 #include<stdio.h> int main(void) { char *color[5] = {"red", "blue", "yellow", "green", "purple"}; /* 初始化 */ int count = 0, i, j; for(i = 0; i <= 4; i++) /* i代表第一个球对应的颜色下标 */ for(j = i+1; j <= 4; j++) { /* j代表第二个球对应的颜色下标 */ /* 两个球不能同色 */ count ++; printf("%6d", count); printf("%10s %10s\n", color[i], color[j]); } return 0; } 1 red blue 2 red yellow 3 red green 4 red purple 5 blue yellow 6 blue green 7 blue purple 8 yellow green 9 yellow purple 10 green purple
11.1.2 指针数组的概念 char *color[5]; 类型名 *数组名[数组长度] 数组元素是指针类型,用于存放内存地址 int a[5]; • a是一个数组,它有5个元素 • 每个元素的类型都是整型 char *color[5]; • color是一个数组,它有5个元素 • 每个元素的类型都是字符指针
指针数组的概念 char *color[5] = {"red", "blue", "yellow", "green", "purple"}; • color是一个数组,它有5个元素 • 每个元素的类型都是字符指针 • 数组元素可以处理字符串 对指针数组元素的操作相当于对同类型指针变量的操作 printf("%10s %10s\n", color[i], color[j]);
例11-2 使用指针数组输出5种颜色的英文名称 #include <stdio.h> int main(void) { int i; char *color[5] = {"red", "blue", "yellow", "green", "purple"}, *tmp; for(i = 0; i < 5; i++) /* 输出字符串的地址和内容*/ printf("%x, %s\n", color[i], color[i]); tmp = color[0]; /* 交换color[0]与color[4]*/ color[0] = color[4]; color[4] = tmp; printf("color[0]:%s, color[4]:%s\n", color[0], color[4]); return 0; } 420064, red 42005c, blue 420054, yellow 42004c, green 420044, purple color[0]:purple, color[4]:red
例11-2 图示 交换color[0]与color[4]的值
11.1.3 指向指针的指针-示例 指向指针的指针 例11-3 改写例11-1,用指向指针的指针实现。 #include<stdio.h> int main(void) { char *color[5] = {"red", "blue", "yellow", "green", "purple"}; char **pc=color; int count = 0, i, j; for(i = 0; i <= 4; i++) for(j = i+1; j <= 4; j++) { count++; printf( "%6d", count ); printf( "%10s %10s\n", color[i], color[j] ); } return 0; } pc[i], pc[j] ); 或 *(pc + i), *(pc + j)
指向指针的指针-示例分析 char *color[5] = {"red", "blue", "yellow", "green", "purple"}; char **pc=color; printf( "%10s %10s\n", *(pc + i), *(pc + j)); pc: color 或者&color[0] *pc: color[0]: "red" *(pc+i) : color[i] *color[0]: 'r' **pc: *color[0]: 'r'
ppa pa a *pa &p &a 10 **ppa *ppa 指向指针的指针-定义 指向指针的指针(二级指针) 类型名 **变量名 inta = 10; int *pa = &a; int **ppa = &pa;
ppa pa a **ppa *pa &pa &a 10 ppb pb b **ppb *pb &pb &b 20 例11-4 int a = 10, b = 20, t; int *pa = &a, *pb = &b, *pt; int **ppa = &pa, **ppb = &pb, **ppt; 操作(1):ppt = ppb; ppb = ppa; ppa = ppt;
ppa pa a **ppb *pa &pb &a 10 ppb pb b **ppa *pb &pa &b 20 例11-4 int a = 10, b = 20, t; int *pa = &a, *pb = &b, *pt; int **ppa = &pa, **ppb = &pb, **ppt; 操作(1):ppt = ppb; ppb = ppa; ppa = ppt; 操作(2):pt = pb; pb = pa; pa = pt; ?
ppa pa a **ppa *pb &pb &b 10 ppb pb b **ppb *pa &pa &a 20 例11-4 int a = 10, b = 20, t; int *pa = &a, *pb = &b, *pt; int **ppa = &pa, **ppb = &pb, **ppt; 操作(1):ppt = ppb; ppb = ppa; ppa = ppt; 操作(2):pt = pb; pb = pa; pa = pt; 操作(3):t = b; b = a; a = t;
ppa pa a **ppa *pb &pb &b 20 ppb pb b **ppb *pa &pa &a 10 例11-4 int a = 10, b = 20, t; int *pa = &a, *pb = &b, *pt; int **ppa = &pa, **ppb = &pb, **ppt; 操作(1):ppt = ppb; ppb = ppa; ppa = ppt; 操作(2):pt = pb; pb = pa; pa = pt; 操作(3):t = b; b = a; a = t;
11.1.4 用指针数组处理多个字符串 • 处理多个字符串 • 二维字符数组 char ccolor[ ][7] = {"red", "blue", "yellow", "green", "purple"}; • 指针数组 char *pcolor[ ] = {"red", "blue", "yellow", "green", "purple"}; 使用指针数组更节省内存空间
1. 用指针数组处理多个字符串-排序 #include <stdio.h> int main(void) { int i; int a[5] = {6, 5, 2, 8, 1}; void fsort( int a[ ], int n ); fsort( a, 5 ); for(i = 0; i < 5; i++) printf("%d ", a[i]); return 0; } #include <stdio.h> int main(void) { int i; char *pcolor[5]={ "red", "blue", "yellow", "green", "purple" }; void fsort(char *color[ ], int n); fsort( pcolor, 5 ); for(i = 0; i < 5; i++) printf("%s ", pcolor[i]); return 0; } 例11-5 将5个字符串从小到大排序后输出。
例11-5 字符串排序 void fsort(char *color[ ], int n) { int k, j; char *temp; for(k = 1; k < n; k++) for(j = 0; j < n-k; j++) if(strcmp(color[j],color[j+1])>0){ temp = color[j]; color[j] = color[j+1]; color[j+1] = temp; } } void fsort(int a[ ], int n) { int k, j; int temp; for(k = 1; k < n; k++) for(j = 0; j < n-k; j++) if(a[j] > a[j+1]){ temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } }
red\0 blue\0 yellow\0 green\0 purple\0 red\0 blue\0 yellow\0 green\0 purple\0 pcolor pcolor[0] pcolor[1] pcolor[2] 排序前 pcolor[3] pcolor[4] pcolor pcolor[0] pcolor[1] 排序后 pcolor[2] pcolor[3] pcolor[4]
2. 动态输入多个字符串-示例 • 用动态分配内存的方法处理多个字符串的输入示例 例11-6 输入一些球的颜色,以#作为输入结束标志,再输出这些颜色。 其中颜色数小于20,颜色的英文名称不超过10个字符。
请输入颜色名称,每行一个,#结束输入: red blue yellow # 你输入的颜色是:red blue yellow #include <stdio.h> #include<stdlib.h> #include<string.h> int main(void) { int i, n = 0; char *color[20], str[10]; printf("请输入颜色名称,每行一个,#结束输入:\n"); scanf("%s", str); while(str[0] != '#') { color[n]= (char *) malloc ( sizeof (char) * ( strlen(str) + 1 ) ); strcpy(color[n], str); n++; scanf("%s", str); } printf("你输入的颜色是:"); for(i = 0; i < n; i++) printf("%s ", color[i]); return 0; } for( i=0; i<20; i++) color[i] = NULL; for( i=0; i<n; i++) free( color[i] );
3. 对指针数组的进一步讨论 char *color[ ] = {"red", "blue", "yellow", "green", "purple"}; color:二级指针(char **),等于&color[0] color+2:指向color[2] *(color+2)和color[2]等价 color[0]:指向字符串"red"的首字符r color[0]+2:指向首字符r后的第2个字符d
对指针数组的进一步讨论 (1) color[k]*(color+k) printf("%s", color[2]); printf("%s", *(color+2)); (2) *(color[k]+j) *(*(color+k)+j) color[k][j] printf("%c %c", *(color[2]), *(color[2]+2)); printf("%c %c", color[2][0], color[2][2]);
11.1.5 命令行参数 • C语言源程序经编译和连接处理,生成可执行程序后,才能运行。 • 在DOS环境的命令窗口中,输入可执行文件名,就以命令方式运行该程序。 • 输入命令时,在可执行文件(命令)名的后面可以跟一些参数,这些参数被称为命令行参数。 test world 命令名 命令行参数
命令行参数 命令名 参数1 参数2… 参数n • 命令名和各个参数之间用空格分隔,也可以没有参数 • 使用命令行的程序不能在编译器中执行,需要将源程序经编译、链接为相应的命令文件(一般以.exe为后缀),然后回到命令行状态,再在该状态下直接输入命令文件名。
带参数的main()函数 test world! Hello world! #include <stdio.h> /* test.c */ int main(int argc, char *argv[ ]) { printf("Hello "); printf("%s", argv[1]); return 0; } • 第1个参数 argc 接收命令行参数(包括命令名)的个数 • 第2个参数 argv 接收以字符串常量形式存放的命令行参数(命令名本身也作为一个参数) argc: 2 *argv[ ]: {"test", "world!"}
例10-7 输出命令行参数 在命令行状态下输入: echo How are you? How are you? 例11-7 编写C程序echo,它的功能是将所有命令行参数在同一行上输出。 #include <stdio.h> int main(int argc, char *argv[ ]) { int k; for(k = 1; k < argc; k++) /* 从第1个命令行参数开始 */ printf("%s ", argv[k]); /* 打印命令行参数 */ printf("\n"); return 0; }
11.2 解密藏头诗 11.2.1 程序解析 11.2.2 指针作为函数的返回值 11.2.3 指向函数的指针
11.2.1 程序解析-解密藏头诗 例11-8 输入一首藏头诗(假设只有4句),输出其真实含义。 藏头诗:将这首诗每一句的第一个字连起来,所组成的内容就是该诗的真正含义。
函数的返回值是字符指针 请输入藏头诗: 一叶轻舟向东流, 帆梢轻握杨柳手, 风纤碧波微起舞, 顺水任从雅客悠。 一帆风顺 11.2.1 程序解析 #include <stdio.h> char *change(char s[ ][20], char t[ ]); int main(void) { int i; char s[4][20], t[10], *p; printf(“请输入藏头诗:\n”); for(i = 0; i < 4; i++) scanf("%s", s[i]); p = change(s, t); printf("%s\n", p); return 0; } char * change(char s[ ][20], char t[ ]) { int i; for(i= 0; i < 4; i++) { t[2*i] = s[i][0]; t[2*i+1] = s[i][1]; } t[2*i] = '\0'; return t; } printf("%s\n", change(s, t) ); 或 change(s, t); printf("%s\n", t);
11.2.2 指针作为函数的返回值 • 函数返回值的类型 • 整型、字符型、浮点型、结构类型 • 指针(返回一个地址) • 函数的定义、调用方法与其他函数一样
指针作为函数的返回值-例11-9 输入一个字符串和一个字符,如果该字符在字符串中,就从该字符首次出现的位置开始输出字符串中的字符。 要求定义函数match(s, ch),在字符串s中查找字符ch,如果找到,返回第一次找到的该字符在字符串中的位置(地址);否则,返回空指针NULL。 例如,输入字符r和字符串program后,输出rogram。
例11-9 源程序 Please Input the string: University v versity #include <stdio.h> char *match(char *s, char ch) { while(*s != '\0') if(*s == ch) return(s); /* 若找到字符ch,返回相应的地址 */ else s++; return(NULL);/* 没有找到ch,返回空指针 */ } int main(void ) { char ch, str[80], *p = NULL; printf(“Please Input the string:\n”); scanf("%s", str); getchar( ); ch = getchar( ); if( ( p = match(str, ch) ) != NULL ) printf("%s\n", p); else printf("Not Found\n"); return 0; } Please Input the string: school a Not Found 字符指针p接收match返回的地址,从p指向的存储单元开始,连续输出其中的内容,直至'\0'为止。
指针作为函数的返回值的进一步讨论 • 函数返回值的类型 • 整型、字符型、浮点型、结构类型 • 指针(返回一个地址) • 函数的定义、调用方法与其他函数一样 • 进一步讨论 • 定义函数时,可以: • 动态分配内存 • 操作这些新分配的单元 • 返回新分配单元的地址 修改例11-8,采用动态分配内存的方法
例11.8 修改-动态分配内存 char * change_d(char s[ ][20]) { int i; char *head, *p; p = (char *) calloc(8, sizeof(char)); head = p; for(i= 0; i < 4; i++) { *(p++) = s[i][0]; *(p++) = s[i][1]; } *p = '\0'; return head; } #include <stdio.h> #include <stdlib.h> char *change_d(char s[ ][20]); int main(void) { int i; char s[4][20], *p = NULL; printf("请输入藏头诗:\n"); for(i = 0; i < 4; i++) scanf("%s", s[i]); p = change_d(s); printf("%s\n", p); free(p); return 0; }
入口地址 指令1 指令2 指令3 … 指令n 11.2.3 指向函数的指针 • 每个函数都占用一段内存单元,有一个入口地址(起始地址) • 函数名:函数的入口地址 • 函数指针:一个指针变量,接收函数的入口地址,让它指向函数 • 通过函数指针调用函数 • 做为函数的参数
函数指针的定义和赋值 int fun(x ,y) { return x > y ? x : y; } • 定义 类型名 (*变量名)( ); 所指向函数的返回值的类型 int (*funptr)( ); • 定义一个函数指针funpt • funpt指向一个返回值类型为int的函数 • 赋值 funptr = fun; • 函数fun的入口地址赋给funptr • funptr指向函数fun
通过函数指针调用函数 int fun(x ,y) { return x > y ? x : y; } int (*funptr)( ); funptr = fun; • 调用函数 • 函数名 z = fun(3, 5); • 函数指针 (*funptr)(3, 5); (*函数指针名)(参数表)
函数指针做为函数的参数 • 实参:函数名或已赋值的函数指针 • 形参:函数指针,指向实参所代表函数的入口地址 例11-10 编写一个函数calc(f, a, b),用梯形公式求函数f(x)在[a, b]上的数值积分。 然后调用calc(f, a, b)计算下列数值积分。 分析: • 函数定义时,形参:函数指针f、积分区间上下限参数a,b • 函数调用时,实参:被积函数的名称(或函数指针)和积分区间的上下限
例11-10 源程序 double f1 ( double x ) { return (x*x); } double f2 ( double x ) { return (sin(x)/x); } double calc ( double (*f)(double), double a, double b ) { double z; z = (b-a)/2 * ( (*f)(a) + (*f)(b) ); /* 调用 f 指向的函数 */ return ( z ); } int main ( void ) { double result; result = calc(f1, 0.0, 1.0); /* 函数名f1作为函数calc的实参 */ printf("1: resule=%.4f\n", result); funp = f2; result = calc(funp, 1.0, 2.0); /* 函数指针funp作为函数calc的实参 */ printf("2: resule=%.4f\n", result); return 0; } 1: resule=0.5000 2: resule=0.6481
11.3 学生信息管理的链表实现 11.3.1 程序解析 11.3.2 链表的概念 11.3.3 单向链表的常用操作
9901 Wang 80 9902 Li 90 9905 Qian 80 NULL head main Create_Stu_Doc InsertDoc DeleteDoc Print_Stu_Doc InsertDoc 11.3.1 程序解析 例11-11 建立一个学生成绩信息(包括学号、姓名、成绩)的单向链表,学生记录按学号由小到大顺序排列,要求实现对成绩信息的插入、修改、删除和遍历操作。
例11-11 数据定义与函数声明 struct stud_node{ /*链表结点类型*/ int num; char name[20]; int score; struct stud_node *next; }; struct stud_node * Create_Stu_Doc(); /* 新建链表 */ struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */ struct stud_node * DeleteDoc(struct stud_node * head, int num); /* 删除 */ void Print_Stu_Doc(struct stud_node * head); /* 遍历 */
9901 Wang 80 9902 Li 90 9903 Qian 80 NULL head 11.3.2 链表的概念 • 一种动态存储分布的数据结构 • 若干个同一结构类型的“结点”依次串接而成 • 单向链表、双向链表 头指针 尾结点 结点
9901 Wang 80 9902 Li 90 9905 Qian 80 NULL head 链表的概念-结点定义 struct stud_node{ int num; char name[20]; int score; struct stud_node *next; }; 结构的递归定义
链表的概念-与数组比较 • 数组 • 事先定义固定长度的数组 • 在数组元素个数不确定时,可能会发生浪费内存空间的情况 • 链表 • 动态存储分配的数据结构 • 根据需要动态开辟内存空间,比较方便地插入新元素(结点) • 使用链表可以节省内存,提高操作效率
动态存储分配函数malloc() void *malloc(unsigned size) 在内存的动态存储区中分配一连续空间,其长度为size • 若申请成功,则返回一个指向所分配内存空间的起始地址的指针 • 若申请不成功,则返回NULL(值为0) • 返回值类型:(void *) • 通用指针的一个重要用途 • 将malloc的返回值转换到特定指针类型,赋给一个指针
p malloc()示例 int *ip = (int *) malloc( sizeof(int) ) struct student * p; p = (struct student *) malloc(sizeof(struct student)); • 调用malloc时,用 sizeof计算存储块大小 • 虽然存储块是动态分配的,但它的大小在分配后也是确定的,不要越界使用。
p 动态存储释放函数free 当某个动态分配的存储块不再用时,要及时将它释放 void free(void *ptr) 释放由动态存储分配函数申请到的整块内存空间,ptr为指向要释放空间的首地址。 free(ip); free(p);