Upload
darren
View
99
Download
0
Embed Size (px)
DESCRIPTION
Chap 11 指针进阶. 11.1 布袋中的彩色球 11.2 解密藏头诗 11.3 学生信息管理的链表实现. 本章要点. 指针数组和指向指针的指针是如何被定义和使用的? 指针如何作为函数的返回值? 指向函数的指针的意义是什么? 什么是结构的递归定义,哪种应用需要这种定义方法? 对链表这种数据结构,如何进行动态内存分配操作? 如何建立单向链表并实现插入、删除以及查找操作?. 11.1 布袋中的彩色球. 11.1.1 程序解析 11.1.2 指针数组的概念 11.1.3 指向指针的指针 11.1.4 用指针数组处理多个字符串 - PowerPoint PPT Presentation
Citation preview
Chap 11 指针进阶
11.1 布袋中的彩色球
11.2 解密藏头诗
11.3 学生信息管理的链表实现
1
本章要点 指针数组和指向指针的指针是如何被定义和使用的? 指针如何作为函数的返回值? 指向函数的指针的意义是什么? 什么是结构的递归定义,哪种应用需要这种定义方
法? 对链表这种数据结构,如何进行动态内存分配操作? 如何建立单向链表并实现插入、删除以及查找操作?
2
11.1 布袋中的彩色球
11.1.1 程序解析11.1.2 指针数组的概念11.1.3 指向指针的指针11.1.4 用指针数组处理多个字符串11.1.5 命令行参数
3
11.1.1 程序解析例 11-1 已知一个不透明的布袋里装有红、
蓝、黄、绿、紫同样大小的圆球各一个,现从中一次抓出两个,问可能抓到的是什么颜色的球?
4
程序解析-例 11-1 源程序#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
指针数组
char *color[5];color[0] = “red”; color[1] = “blue”; …
5
11.1.2 指针数组的概念char *color[5];
类型名 * 数组名 [ 数组长度 ]数组元素是指针类型,用于存放内存地址
int a[5]; a 是一个数组,它有 5 个元素 每个元素的类型都是整型
char *color[5]; color 是一个数组,它有 5 个元素 每个元素的类型都是字符指针
6
char *color[5] = {"red", "blue", "yellow", "green", "purple"}; color 是一个数组,它有 5 个元素 每个元素的类型都是字符指针 数组元素可以处理字符串
对指针数组元素的操作相当于对同类型指针变量的操作printf("%10s %10s\n", color[i], color[j]);
指针数组的概念
7
#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, red42005c, blue420054, yellow42004c, green420044, purple
例 11-2 使用指针数组输出 5 种颜色的英文名称
color[0]:purple, color[4]:red
8
交换 color[0] 与 color[4] 的值
例 11-2 图示
9
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)
10
指向指针的指针-示例分析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'
11
指向指针的指针-定义指向指针的指针(二级指针) 类型名 ** 变量名
int a = 10;
int *pa = &a;
int **ppa = &pa;
&a
pa a10&p
ppa
*pa
*ppa **ppa
12
&a
pa a
10&pa
ppa**ppa*pa
&b
pb b
20&pb
ppb**ppb*pb
int a = 10, b = 20, t;
int *pa = &a, *pb = &b, *pt;
int **ppa = &pa, **ppb = &pb, **ppt;
例 11-4
操 作 (1) : ppt = ppb; ppb = ppa; ppa = ppt;
13
pa appa
&a 10&pb**ppb*pa
&b
pb b
20&pa
ppb**ppa*pb
int a = 10, b = 20, t;
int *pa = &a, *pb = &b, *pt;
int **ppa = &pa, **ppb = &pb, **ppt;
例 11-4
操 作 (1) : ppt = ppb; ppb = ppa; ppa = ppt; 操作 (2) : pt = pb; pb = pa; pa = pt; ?
14
pa appa
&b 10&pb**ppa*pb
&a
pb b
20&pa
ppb**ppb*pa
int a = 10, b = 20, t;
int *pa = &a, *pb = &b, *pt;
int **ppa = &pa, **ppb = &pb, **ppt;
例 11-4
操 作 (1) : ppt = ppb; ppb = ppa; ppa = ppt; 操作 (2) : pt = pb; pb = pa; pa = pt;
操作 (3) : t = b; b = a; a = t; 15
pa appa
&b 20&pb**ppa*pb
&a
pb b
10&pa
ppb**ppb*pa
int a = 10, b = 20, t;
int *pa = &a, *pb = &b, *pt;
int **ppa = &pa, **ppb = &pb, **ppt;
例 11-4
操 作 (1) : ppt = ppb; ppb = ppa; ppa = ppt; 操作 (2) : pt = pb; pb = pa; pa = pt;
操作 (3) : t = b; b = a; a = t; 16
11.1.4 用指针数组处理多个字符串 处理多个字符串
二维字符数组char ccolor[ ][7] = {"red",
"blue", "yellow", "green", "purple"};
指针数组char *pcolor[ ] = {"red", "blue",
"yellow", "green", "purple"};
使用指针数组更节省内存空间
17
1. 用指针数组处理多个字符串-排序例 11-5 将 5 个字符串从小到大排序后输出。#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;
} 18
例 11-5 字符串排序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; }}
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;
}
}
19
pcolor[0]
pcolor[1]
pcolor[2]
pcolor[3]
pcolorred\0
blue\0
yellow\0
green\0
purple\0pcolor[4]
排序前
pcolor[0]
pcolor[1]
pcolor[2]
pcolor[3]
pcolor
pcolor[4]
排序后
red\0
blue\0
yellow\0
green\0
purple\0 20
2. 动态输入多个字符串-示例 用动态分配内存的方法处理多个字符串的输入示例
例 11-6 输入一些球的颜色,以 # 作为输入结束标志,再输出这些颜色。
其中颜色数小于 20 ,颜色的英文名称不超过 10 个字符。
21
#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;}
请输入颜色名称,每行一个, # 结束输入:redblue yellow#你输入的颜色是: red blue yellow
for( i=0; i<20; i++) color[i] = NULL;
for( i=0; i<n; i++) free( color[i] );
22
3. 对指针数组的进一步讨论char *color[ ] = {"red", "blue", "yellow", "green", "purple"};
color :二级指针 (char **) ,等于 &color[0]
color+2 :指向 color[2] *(color+2) 和 color[2] 等价
color[0] :指向字符串 "red" 的首字符 rcolor[0]+2 :指向首字符 r 后的第 2 个字符 d
23
对指针数组的进一步讨论(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]);
24
11.1.5 命令行参数 C 语言源程序经编译和连接处理,生成可执行程序
后,才能运行。 在 DOS 环境的命令窗口中,输入可执行文件名,就
以命令方式运行该程序。 输入命令时,在可执行文件(命令)名的后面可以
跟一些参数,这些参数被称为命令行参数。 test world
命令名 命令行参数
25
命令行参数命令名 参数 1 参数 2 … 参数 n
命令名和各个参数之间用空格分隔,也可以没有参数 使用命令行的程序不能在编译器中执行,需要将源程序经
编译、链接为相应的命令文件(一般以 .exe 为后缀),然后回到命令行状态,再在该状态下直接输入命令文件名。
26
带参数的 main() 函数
第 1 个参数 argc 接收命令行参数 ( 包括命令名 ) 的个数 第 2 个参数 argv 接收以字符串常量形式存放的命令行参
数 ( 命令名本身也作为一个参数 )
#include <stdio.h> /* test.c */int main(int argc, char *argv[ ]){ printf("Hello "); printf("%s", argv[1]); return 0;}
test world!Hello world!
argc: 2*argv[ ]: {"test", "world!"}
27
例 10-7 输出命令行参数例 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;
}
在命令行状态下输入:echo How are you?How are you?
28
11.2 解密藏头诗
11.2.1 程序解析11.2.2 指针作为函数的返回值11.2.3 指向函数的指针
29
11.2.1 程序解析-解密藏头诗
例 11-8 输入一首藏头诗(假设只有 4句),输出其真实含义。藏头诗:将这首诗每一句的第一个字连起来,所组
成的内容就是该诗的真正含义。
30
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);
31
11.2.2 指针作为函数的返回值 函数返回值的类型
整型、字符型、浮点型、结构类型指针(返回一个地址)
函数的定义、调用方法与其他函数一样
32
指针作为函数的返回值-例 11-9输入一个字符串和一个字符,如果该字符在字符串中,
就从该字符首次出现的位置开始输出字符串中的字符。
要求定义函数 match(s, ch) ,在字符串 s 中查找字符ch ,如果找到,返回第一次找到的该字符在字符串中的位置(地址);否则,返回空指针 NULL 。
例如,输入字符 r 和字符串 program 后,输出rogram 。
33
例 11-9 源程序
#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:University vversity
字符指针 p接收match返回的地址,从 p指向的存储单元开始,连续输出其中的内容,直至 '\0'为止。
Please Input the string:school aNot Found
34
指针作为函数的返回值的进一步讨论 函数返回值的类型
整型、字符型、浮点型、结构类型指针(返回一个地址)
函数的定义、调用方法与其他函数一样 进一步讨论
定义函数时,可以: 动态分配内存 操作这些新分配的单元 返回新分配单元的地址
修改例 11-8,采用动态分配内存的方法
35
例 11.8 修改-动态分配内存#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;}
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;}
36
11.2.3 指向函数的指针 每个函数都占用一段内存单元,
有一个入口地址(起始地址) 函数名:函数的入口地址 函数指针:一个指针变量,接收
函数的入口地址,让它指向函数通过函数指针调用函数做为函数的参数
指令 1
指令 2
指令 3
…
指令 n
入口地址
37
函数指针的定义和赋值 定义类型名 (* 变量名 )( );
所指向函数的返回值的类型 int (*funptr)( ); 定义一个函数指针 funpt funpt 指向一个返回值类型为 int 的函数
赋值funptr = fun; 函数 fun 的入口地址赋给 funptr funptr 指向函数 fun
int fun(x ,y){ return x > y ? x : y;}
38
通过函数指针调用函数int (*funptr)( );funptr = fun;调用函数
函数名z = fun(3, 5);函数指针(*funptr)(3, 5);
(* 函数指针名 )( 参数表 )
int fun(x ,y){ return x > y ? x : y;}
39
函数指针做为函数的参数 实参:函数名或已赋值的函数指针 形参:函数指针,指向实参所代表函数的入口地址例 11-10 编写一个函数 calc(f, a, b) ,用梯形公式求函数 f(x)
在 [a, b]上的数值积分。
然后调用 calc(f, a, b)计算下列数值积分。
分析: 函数定义时,形参:函数指针 f 、积分区间上下限参数
a , b 函数调用时,实参:被积函数的名称(或函数指针)和积分区间的上下限
40
例 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.50002: resule=0.6481
41
11.3 学生信息管理的链表实现
11.3.1 程序解析
11.3.2 链表的概念
11.3.3 单向链表的常用操作
42
11.3.1 程序解析
例 11-11 建立一个学生成绩信息(包括学号、姓名、成绩)的单向链表,学生记录按学号由小到大顺序排列,要求实现对成绩信息的插入、修改、删除和遍历操作。
main
InsertDoc DeleteDoc Print_Stu_Doc Create_Stu_Doc
InsertDoc
head 9905 Qian 80 NULL9901 Wang 80 9902 Li 90
43
例 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); /* 遍历 */44
11.3.2 链表的概念
一种动态存储分布的数据结构若干个同一结构类型的“结点”依次串接而成 单向链表、双向链表
头指针 结点 尾结点
head 9903 Qian 80 NULL9901 Wang 80 9902 Li 90
45
链表的概念-结点定义
struct stud_node{ int num; char name[20]; int score;
struct stud_node *next;};
结构的递归定义
head 9905 Qian 80 NULL9901 Wang 80 9902 Li 90
46
链表的概念-与数组比较 数组
事先定义固定长度的数组在数组元素个数不确定时,可能会发生浪费内存
空间的情况
链表动态存储分配的数据结构根据需要动态开辟内存空间,比较方便地插入新
元素(结点)使用链表可以节省内存,提高操作效率
47
动态存储分配函数 malloc()void *malloc(unsigned size)
在内存的动态存储区中分配一连续空间,其长度为size
若申请成功,则返回一个指向所分配内存空间的起始地址的指针
若申请不成功,则返回 NULL (值为 0 )返回值类型: (void *)
通用指针的一个重要用途 将 malloc 的返回值转换到特定指针类型,赋给一个指
针 48
malloc() 示例int *ip = (int *) malloc( sizeof(int) )
struct student * p;
p = (struct student *) malloc(sizeof(struct student));
调用 malloc 时,用 sizeof 计算存储块大小 虽然存储块是动态分配的,但它的大小在分配后也是确定的,不要越界使用。
p
49
动态存储释放函数 free 当某个动态分配的存储块不再用时,要及时
将它释放void free(void *ptr) 释放由动态存储分配函数申请到的整块内存空间,
ptr 为指向要释放空间的首地址。
free(ip);free(p); p
50
11.3.3 单向链表的常用操作
1. 链表的建立2. 链表的遍历3. 插入结点4. 删除结点
51
1. 链表的建立struct stud_node *head, *tail, *p;head = tail = NULL;size = sizeof(struct stud_node);p = (struct stud_node *) malloc(size);
head
tail
tail
pnum name score next
p
52
1. 链表的建立
53
程序段 head = tail = NULL; scanf("%d%s%d", &num,name, &score); while(num != 0){ p = (struct stud_node *) malloc(size);
p->num = num; strcpy(p->name, name); p->score = score; p->next = NULL;
if(head == NULL) head = p; else tail->next = p; tail = p; scanf("%d%s%d", &num, name, &score); }
54
2. 链表的遍历
ptr->num
ptr->score
ptr ptr
for(ptr = head; ptr != NULL; ptr = ptr -> next)
printf("%ld, %d", ptr -> num, ptr -> score);
ptr=ptr->next
head 9905 Qian 80 NULL9901 Wang 80 9902 Li 90
55
链表的遍历-函数void Print_Stu_Doc(struct stud_node * head) { struct stud_node * ptr; if(head == NULL){ printf("\nNo Records\n"); return; } printf("\nThe Students' Records Are: \n"); printf(" Num Name Score\n"); for(ptr = head; ptr!=NULL; ptr = ptr->next) printf("%8d %20s %6d \n", ptr->num, ptr->name, ptr->score);
}
56
s->next = ptr->next
ptr->next = s
先连后断
head
ptr
s
3. 插入结点
57
ptr->next = s
s->next = ptr->next
行吗?
head
ptr
s
3. 插入结点
58
ptr->next = s
s->next = ptr->next
行吗?
head
ptr
s
3. 插入结点
老大不高兴后果很严重
59
ptr2=ptr1->next
ptr1->next=ptr2->next
head
ptr1 ptr2
free(ptr2)
先接后删
4. 删除结点
ptr2
head
ptr1
60