Presentation is loading. Please wait.

Presentation is loading. Please wait.

程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季.

Similar presentations


Presentation on theme: "程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季."— Presentation transcript:

1 程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季

2 第 八 章 指针

3 8.1 什么是指针 指针是C语言中最重要的特性之一,要 成为一个好的 C/C++ 程序员,就必须 理解指针的概念,并善于使用它。

4 8.1.1 地址亦数据 某国情报机关最近一段时期接连损失了好几名 情报人员,经过调查,发现是在自己内部存在一名
地址亦数据 某国情报机关最近一段时期接连损失了好几名 情报人员,经过调查,发现是在自己内部存在一名 “鼹鼠”,但不知是谁。不得已,买通了一名隐藏极 深的双重间谍进行调查。很快,调查取得了重大突 破,这名间谍成功地获取了该“鼹鼠”的代号。因此 情报局长派遣一名经验丰富的特工前去取回这条极 其重要的情报。双方约定的接头地点在  大厦的 1101房间。

5  大厦 2103 1101 1102 1103 1104 1105 2103 3102 2101 2102 2103 2104 2105 3102 3101 3102 3103 3104 3105 K

6 同学录上的通讯地址:××大学×号楼 ××房间。
p q ch 2103 3102 'K' ch的地址 q的地址 字符数据 1101 2103 3102 地址也是一种数据: 街道上的门牌号码; 同学录上的通讯地址:××大学×号楼 ××房间。

7 地址和数据是否完全等价? 地址虽然是数据,但两者并非完全等价。 例如,假设用四个字节来描述一个地址, 那么从理论上说,其取值范围应该是:
-231-231-1,但是在这些整数中,并不是 所有的整数都能成为一个有效的地址。

8 x p 8.1.2 地址与类型 ? 1000 访问一个变量需要 知道: 1000 (1)它的起始地址 (2)它的数据类型 对于内存:
地址与类型 1000 访问一个变量需要 知道: (1)它的起始地址 (2)它的数据类型 对于内存: 物理上,以字节为单元 逻辑上,以不同数据类 型为单元。 1000 x 1020 p 1024 scanf(“%lf”, &x);

9 A pointer is a variable that contains the
什么是指针 The C Programming Language (K & R): A pointer is a variable that contains the memory address of another variable.

10 指针是一种变量,因而也具有变量的三个要素,但指 针是一种特殊的变量,其特殊性表现在它的类型和取 值上。具体而言:
变量名:与一般的变量命名规则相同; 变量的值:是另一个变量的内存地址; 变量的类型:包括两种类型, 指针变量本身的类型,即指针类型,它的长度为4个字节; 指针指向的变量的类型,指明了该变量的长度。 指针变量能否存放自己的地址?

11 指针 = 变量 ≠ 地址 清华大学 圆明园

12 8.2 指针变量 8.2.1 指针的定义 指针定义的一般形式: 基类型 *指针变量名; 例如: int x; /* 定义了一个整型变量 */
8.2 指针变量 指针的定义 指针定义的一般形式: 基类型 *指针变量名; 例如: int x; /* 定义了一个整型变量 */ int *q; /* 定义了一个指向整型变量的指针变量 */

13 double * “pointer to double” char * “pointer to char”
现在我们有了三种新的数据类型: int * “pointer to int” double * “pointer to double” char * “pointer to char” 基类型 + 指针类型 = 新的数据类型(why?); 这三种数据类型的相同点:它们都是指针类型,它们的变量存放的都是地址,长度都是4个字节; 这三种数据类型的不同点:它们是不同的数据类型,相互之间不能替代使用。例如,当需要一个“pointer to int”类型的指针时,不能用一个“pointer to char”类型的指针来代替。

14 x y px 2 int x, y, *px; x = 2; 指向整型变量的指针 1000 1004 1008 一些问题:
如何把变量 x 的地址存放在指针 px 当中? 如何使用指针 px 来访问或修改变量 x 的值? 为何要如此自找麻烦? 1020 px 1024

15 x y px 8.2.2 指针运算符 1. &运算符 2 int x, y, *px; x = 2; 能否:px = 1000; 1000
指针运算符 1. &运算符 2 int x, y, *px; x = 2; 1000 x 1004 y 如何把变量 x 的地址存放在指针 px 当中? 1008 能否:px = ; 1000从何而来?编程时并不知道变量的实际地址; 类型不匹配,常量与指针, (int *)1000 1020 px 1024

16 x y px 2 int x, y, *px; x = 2; 我们想要做的事情: px = x 的内存地址; 我们写的语句:
1000 x 1004 y 1008 我们想要做的事情: px = x 的内存地址; 我们写的语句: 1020 px px = &x; 1024

17 x y px 2 1000 int x, y, *px; x = 2; px = &x; &:取地址运算符 1000 1004 1008
整型变量的指针类型。一般 的,如果x的类型是T,则 &x的类型为指向T型变量的 指针类型。 1020 px 1024

18 pointer_1 a &a pointer_2 b &b &a 即 a 的地址。

19 pointer_1 = &a; pointer_1 a 1000 1000 &a 1000

20 x y px 2. * 运算符 2 1000 int x, y, *px; x = 2; px = &x; 1000 1004 1008
2. * 运算符 2 1000 int x, y, *px; x = 2; px = &x; 1000 x 1004 y 1008 如何使用指针 px 来访问或修改变量 x 的值? 1020 px 1024

21 x y px 2 1000 int x, y, *px; x = 2; px = &x; 我们想要做的事情: y = px所指向的变量的值;
1004 y 1008 我们想要做的事情: y = px所指向的变量的值; 我们写的语句: 1020 px y = *px; 1024

22 x y px 2 1000 int x, y, *px; x = 2; px = &x; y = *px;
1004 y 1008 *:指针运算符(或称“间接访问”运算符) *px的类型是 int。一般的, 如果px的类型是指向T型变 量的指针类型,则 *px的类 型为T。 1020 px 1024

23 pointer_1 = &a; pointer_1 a 1000 1000 *pointer_1 &a 1000

24 a b c p q int a, b, c, *p, *q; a = 1; 1 b = 2; 2 13 c = 3; 3 1 p = &a;
0x0012FF7C 0x0012FF78 0x0012FF74 0x0012FF70 0x0012FF6C b = 2; 2 b 13 c = 3; c 3 1 p = &a; p 0012FF7C 0012FF78 q = &b; q 0012FF78 c = *p; p = q; *p = 13;

25 指针常量NULL表示一个指针不指向任何有效的数据。
int x, *p; p = &x; *p = 1; p = NULL; *p = 2; <stdio.h>

26 一个其值为NULL的指针不同于一个未初始
化的指针。一个指针在定义后,是未被初始 化的,其值是随机的,即可能指向某个无效 的地址,此时若对它进行访问,将会出错。 而NULL常量用来明确地说明一个指针不指 向任何有效的数据。

27 8.2.3 为何要使用指针? 指针的存在提供了一种共享数据的方法, 可以在程序的不同位置、使用不同的名字
为何要使用指针? 指针的存在提供了一种共享数据的方法, 可以在程序的不同位置、使用不同的名字 (指针)来访问相同的一段共享数据。如 果在一个地方对该数据进行了修改,那么 在其他的地方都能看到修改以后的结果。

28 输出结果: void MinMax(int x, int y, int min, int max) { min = x; max = y;
if(x > y) max = x; min = y; } void main( ) int a, b, min, max; a = 4; b = 7; MinMax(a, b, min, max); printf("%d, %d\n", min,max); 输出结果: ,

29 main的栈帧 a b min max 4 7 MinMax的栈帧 x y min max 4 7 4 7
void MinMax(int x, int y, int min, int max) { min = x; max = y; if(x > y) max = x; min = y; } void main( ) int a, b, min, max; a = 4; b = 7; MinMax(a,b,min,max); printf("%d, %d\n", min, max); main的栈帧 a b min max 4 7 MinMax的栈帧 x y min max 4 7 4 7

30 main的栈帧 a b min max 4 7 4 7 MinMax的栈帧 x y p1 p2 4 7
void MinMax(int x, int y, int *p1, int *p2) { *p1 = x; *p2 = y; if(x > y) *p1 = x; *p2 = y; } void main( ) int a, b, min, max; a = 4; b = 7; MinMax(a, b, &min, &max); printf("%d, %d\n", min, max); main的栈帧 a b min max 4 7 4 7 还有何方法能返回值? min、max的访问? MinMax的栈帧 x y p1 p2 4 7

31 问题描述: 计算一元二次方程的根。

32 void main( ) { double a, b, c, x1, x2;
GetCoefficients(?a, ?b, ?c); /* 求解方程式的两个根x1, x2 */ SolveQuadratic(?a, ?b, ?c, ?x1, ?x2); /* 显示方程式的两个根x1, x2 */ DisplayRoots(?x1, ?x2); }

33 main( ) { double a, b, c, x1, x2; /* 从键盘读入方程式的系数a, b, c */ GetCoefficients(&a, &b, &c); /* 求解方程式的两个根x1, x2 */ SolveQuadratic(a, b, c, &x1, &x2); /* 显示方程式的两个根x1, x2 */ DisplayRoots(x1, x2); } 传值还是传地址?

34 void GetCoefficients(double *pa, double *pb, double *pc)
{ printf("请输入三个系数:\n"); printf("a: "); scanf("%lf", pa); printf("b: "); scanf("%lf", pb); printf("c: "); scanf("%lf", pc); }

35 void SolveQuadratic(double a, double b, double c,
double *px1, double *px2) { double disc, sqrtDisc; if(a == 0) ... /* 出错处理 */ disc = b * b – 4 * a * c; if(disc < 0) ... /* 出错处理 */ sqrtDisc = sqrt(disc); *px1 = (-b + sqrtDisc) / (2 * a); *px2 = (-b - sqrtDisc) / (2 * a); }

36 指针分析1 *tinky = slinky; void main() /* 有何问题? */ { int binky;
foo(&binky); } void foo(int *tinky) int slinky = 5; tinky = &slinky; *tinky = slinky;

37 指针分析2 thinking… void main() /* 输出结果是什么? */ { int* pinky;
pinky = bar(); printf(“%d”, *pinky); } int* bar() int winky = 5; return(&winky); thinking…

38 指针分析3 void main() { int binky, *pinky; binky = 1; pinky = &binky;
moo(*pinky, pinky); } void moo(int tinky, int *winky) int slinky = 2; *winky = tinky + slinky;

39 8.3 指针与数组 8.3.1 指向数组元素的指针 p a[0] int a[6], *p; p = &a[3]; a[1] a[2]
8.3 指针与数组 指向数组元素的指针 1000 a[0] int a[6], *p; p = &a[3]; 1004 a[1] 1008 a[2] 1012 a[3] p 1012 1016 a[4] 1020 a[5]

40 8.3.2 通过指针引用数组元素 main avg prices 5 FindAvg values nValues void main( )
通过指针引用数组元素 void main( ) { double prices[5], avg; avg = FindAvg(prices, 5); } double FindAvg(double values[], int nValues) main avg prices FindAvg values nValues 5

41 a++; int a[6]; a[0] = 1; *a = 1; 在C语言中,一个数组的名字就代表了这个
数组的起始地址,它可以被视为一个其值 不可变的指针,该指针指向数组当中的第 一个数组元素。 int a[6]; a[0] = 1; *a = 1; 下标法: 指针法: 这两条语句是等效的 a++;

42 指针的算术运算 a int a[6]; a[k] 等效于 *(a +k); 指针的加减运算是以 数据元素为单元的。 a[0] a+1
1000 指针的加减运算是以 数据元素为单元的。 a[0] a+1 1004 a[1] a+2 int a[6]; a[k] 等效于 *(a +k); a 是指向地址1000的, 而 a + 2是指向1008的, 因为 a + k 的实际计算 方法是:a + k * sizeof(int) 1008 a[2] a+3 1012 a[3] a+4 1016 a[4] a+5 1020 a[5]

43 在指针的算术运算中,对基类型长度的补偿 是由系统自动来完成的。 double prices[10], x;
int units[10], y; x = *(prices + 3); /* 把 x 赋值为prices[3] */ y = *(units + 3); /* 把 y 赋值为units[3] */ 在指针的算术运算中,对基类型长度的补偿 是由系统自动来完成的。

44 几种等效的表示方法 a[k] 等效于 *(a + k) &a[k] 等效于 a + k &a[0] 等效于 a

45 指针与数组的区别 int a[5]; pa int *pa; *pa = 2; a[0] a[1] a[2] a[3] a[4]
指针在使用前,必须先 初始化,指向一个有效 的地址。 int *pa; *pa = 2;

46 分析程序 int foobar( int *pi ) { *pi = 1024; return *pi; } int main()
int ival = foobar( pi2 ); printf("%d", ival);;

47 合法的指针运算 指针加一个整数: ptr + k 指针减一个整数: ptr – k 两个指针相减: p1 – p2
指针的关系运算: >、>=、< <=、==、!=

48 问题描述: 计算一个数组所有元素之和。 方法一 int *ptr, values[10], sum, i;
... 输入各个数组元素的值 ... sum = 0; for(i = 0; i < 10; i ++) { sum += values[i]; }

49 方法二 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... ptr = &values[0];
for(i = 0; i < 10; i ++) { sum += ptr[i]; }

50 方法三 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ...
ptr = &values[0]; /* 也可写成 ptr = values */ sum = 0; for(i = 0; i < 10; i ++) { sum += *(ptr + i); }

51 方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ...
for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values

52 方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ...
for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values

53 方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ...
for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values

54 代码分析 void main() { int index, *aptr, *bptr; int arr[8]; aptr = &index;
for (index=0; index<6; index++) arr[index] = index; bptr = aptr; aptr = &arr[2]; arr[*bptr] = *aptr; *aptr = *bptr; *bptr = *(aptr + 1); 画出此时内存状态(变量的取值) }

55 index aptr bptr 3 arr 1 6 3 4 5 2

56 int scores[MAX_SIZE];
动态数组 在定义一个数组时,必须事先指定其长度。 例如: 或者: int a[6]; #define MAX_SIZE 100; int scores[MAX_SIZE];

57 有的时候,我们事先并不知道数组的确切 长度,只有当程序运行以后我们才知道。 因此,我们希望能够这样来定义数组: 但是在C语言中,这是不可能的。 int n; printf(“请输入学生人数:”); scanf(“%d”, &n); int scores[n];

58 int *scores; 我们能做的事情是:动态地为该数组分配 所需的内存空间,即在程序运行时分配。 具体做法是:定义一个指针,然后把动态
分配的内存空间的起始地址保存在该指针 中,如: int *scores; scores = 动态分配的内存空间的起始地址;

59 void *malloc(size_t size)
动态数组 void *malloc(size_t size) 参数:申请的字节数; 功能:申请一块内存; 返回值:一个指向该内存的指针或NULL; 头文件:stdlib.h int *scores, n; printf(“请输入学生人数:”); scanf(“%d”, &n); scores = (int *)malloc(n*sizeof(int));

60 比较静态数组与动态数组 #define NUM_SCORE 10 int scores[NUM_SCORE];
for(i = 0; i < NUM_SCORE; i++) scores[i] = 0; 区别: 1. 长度固定/可变 2. 空间分配时机 3. 空间的回收 int *scores, nScores; scanf(“%d”, &nScores); scores = (int *)malloc(nScores * sizeof(int)); for(i = 0; i < nScores; i++) scores[i] = 0;

61 int *scores, nScores; scanf(“%d”, &nScores);
scores = (int *)malloc(nScores * sizeof(int)); if(scores == NULL) ... /* 内存分配失败 */ scores[3] = 100; 等价于:*(scores + 3) = 100;

62 作为局部变量的数组在函数调用结束后, 其内存空间即被释放。而对于动态数组, 即使在函数调用结束后,其内存空间依然 存在。

63 程序 分析 4 4344416 1244916 4198895 1 #define MAX_SIZE 10 void main( ) {
int a[5] = {1, -1, 2, -2, 0}; int b[5] = {3, 1, -2, 4, 1}; int *c, i; c = Add(a, b, 5); for(i = 0; i < 5; i++) printf("%d ", c[i]); } int *Add(int a[], int b[], int num) int i, c[MAX_SIZE]; for (i = 0; i < num; i++) c[i] = a[i] + b[i]; return c;

64 4 0 0 2 1 void main( ) { int a[5] = {1, -1, 2, -2, 0};
int b[5] = {3, 1, -2, 4, 1}; int *c, i; c = Add(a, b, 5); if(c == NULL) return; for(i=0; i<5; i++) printf("%d", c[i]); } int *Add(int a[], int b[], int num) int i, *c; c =(int*)malloc(MAX_SIZE*sizeof(int)); if(c == NULL) return(NULL); for (i = 0; i < num; i++) c[i] = a[i] + b[i]; return c;

65 操作系统 程序 栈帧2 栈帧1 全局变量 内存分布状况 动态分配 自动分配 全局变量区域 静态分配

66 内存分布状况 操作系统 程序 全局变量 int *price, *unit; price = malloc(n*sizeof(int));
块1 块2 全局变量 int *price, *unit; price = malloc(n*sizeof(int)); unit = malloc(n*sizeof(int)); 内存分布状况 price unit 全局变量区域 vc…

67 动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); 堆由堆管理器管理,用户具有排他性使用权 values scores

68 动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); values = NULL; values原来指向的内存单元现在无法访问。 内存泄漏! values scores

69 动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); free(values); values[0] = 1; values = NULL; values scores

70 代码分析 void main() { int i, *a, *b, list[4]; a = &i;
for (i = 0; i < 4; i++) list[i] = *a; b = (int*)malloc(4*sizeof(int)); a = list + 2; *b = *a; b++; 画出此时内存状态(栈、堆、变量的取值) }

71 main 4 i a b 2 ? 1 2 3 list

72 8.3.4 指针? 数组? 在C语言中,一个指针可能是指向单一的一 个变量,也可能是指向一组相同类型的数
指针? 数组? 在C语言中,一个指针可能是指向单一的一 个变量,也可能是指向一组相同类型的数 组元素,从外观上,两种指针的形式完全 一样,因此只能靠程序员去加以区分。 int x, a[6], *ptr; ptr = &x; /* 合法 */ ptr = a; /* 合法 */ ptr x? a?

73 分析结果 1 15 5 2 9 15 void main( ) { int a[ ] = {1,3,5};
int b[ ] = {2,4,6}; int *p; p = &a[0]; bar(p); printf("%d %d %d\n",a[0],a[1],a[2]); p = &b[0]; p ++; foo(p); printf("%d %d %d\n",b[0],b[1],b[2]); } void bar(int p2[ ]) { p2[1] = 15; } void foo(int p1[ ]) *p1 += 5;

74 8.3.5 二维数组与指针 1. 指针数组 一个数组,其元素均为指针类型变量,称为 指针数组。即数组中的每一个元素都是一个 指针。
二维数组与指针 1. 指针数组 一个数组,其元素均为指针类型变量,称为 指针数组。即数组中的每一个元素都是一个 指针。 定义形式:类型名 * 数组名[数组长度]; 例如: int *pa[4];

75 指针数组的使用 int *pa[4]; char *pb[4]; pa[0] pa[1] pa[2] pa[3] pa[4] pb[0]
x pa[0] pa[1] pa[2] pa[3] pa[4] pb[0] pb[1] pb[2] pb[3] pb[4] Hello! y Basic z Computer m Software n Tsinghua

76 main(int argc, char *argv[ ]);
例如,假设程序名为sort,在运行时命令行 的情况如下: sort source.txt destination.txt argv[0] argv[1] argv[2] argc = 3 sort命令

77 2. 指向指针的指针 指向指针的指针:一种特殊的指针,其基 类型也为指针类型。如 int **p。 通常的指针: 指向指针的指针: p
2. 指向指针的指针 指向指针的指针:一种特殊的指针,其基 类型也为指针类型。如 int **p。 通常的指针: 指向指针的指针: p ptr ptr *p x *ptr x *ptr **p

78 已有的三种指针类型: int * “pointer to int” double * “pointer to double” char * “pointer to char” 现在又可以增加三种新的数据类型: int ** “pointer to pointer to int” double ** “pointer to pointer to double” char ** “pointer to pointer to char”

79 在foo函数中修改主函数中的变量 void main() { int value; foo(???); }
void foo(???) //p ??? = 1; value p 1

80 如果主函数中的变量是一个指针呢? void main() { int *a; foo(???); } a void foo(???) //p
??? = malloc(8); a p

81 main() { int count, *randoms; count = GetRandomArray(randoms); printf(“最后的那个随机数是:%d\n”, randoms[count-1]); } int GetRandomArray(int array[]) { int i, count; printf(“需要多少随机数? ”); scanf(“%d”, &count); array = (int *) malloc(count * sizeof(int)); srand((unsigned)time(NULL)); for (i = 0; i < count; i++) array[i] = rand( ); return count; } 程序 找错

82

83 main的栈帧 … array 动态数组 (*array)
main() { int count, *randoms; count = GetRandomArray(&randoms); printf(“最后的那个随机数是:%d\n”, randoms[count-1]); } int GetRandomArray(int **array) { int i, count; printf(“需要多少随机数? ”); scanf(“%d”, &count); *array = (int *) malloc(count * sizeof(int)); srand((unsigned)time(NULL)); for (i = 0; i < count; i++) (*array)[i] = rand( ); return count; } 动态数组 main的栈帧 count randoms (*array) GetRandomArray array

84 函数之间的地址传递方法: 如果你是想把主函数中的地址,从上往下传,传到被调用的函数里面,那么形参和实参均为指针;
如果你是想把被调用函数中的地址,从下往上传,传到主函数当中去,那么形参和实参均为指向指针的指针。

85 1 … ① 0012ff7c ③00430430 ⑥ 0012ff7c ② 0012ff7c ⑤ 0012ff70 ⑦ 0012ff74
void main( ) { int homer, *lisa; int *bart[3]; lisa = &homer; bart[0] = lisa; *(bart + 2) = malloc(2*sizeof(int)); Test(lisa, &bart[1]); } void Test(int *marge, int **maggie) { *maggie++ = marge; **maggie = 1; } 430430 homer (int ) 12ff7c lisa (int *) ① 0012ff7c 12ff78 bart[2] (int *) 12ff74 bart[1] (int *) ⑥ 0012ff7c 12ff70 bart[0] (int *) ② 0012ff7c 12ff6c 12ff18 marge (int *) 12ff1c maggie (int **) ⑦ 0012ff74 ⑤ 0012ff70 ④ 0012ff7c

86 3. 二维数组与指针 int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}}; a 是一个数组名,它包含有3行、4列, 共12个数组元素。a[0]、a[1]和a[2]都可 看成是一个一维数组名,而a[i][j]就表示 数组第 i 行第 j 列的那个数组元素。

87 a 在定义二维数组 int a[3][4]之后,内存的分布 情况如下: 0x0012FF7C 0x0012FF78 内存分布: 按行存放,
先顺序存放 第一行的所 有元素,再 存放第二行 的元素。 0x0012FF74 0x0012FF70 a[2] 0x0012FF6C 0x0012FF68 0x0012FF64 0x0012FF60 a[1] 0x0012FF5C 0x0012FF58 0x0012FF54 a[0] 0x0012FF50 a

88 怎么做?如何定位a[i][j]? 程序员眼中的二维数组:一个3×4的二 维表格; 内存分布:连续存放的数据元素;
C编译器的任务:把程序员眼中的二维表 格映射到内存当中的相应元素。 怎么做?如何定位a[i][j]?

89 a[0]、a[1]、a[2]的处理:它们都是一个一维数组名,类似于一个指针,可以进行指针的各项操作,但并没有占用实际的内存空间;
a[i][j]的定位: a[0]、a[1]、a[2]的处理:它们都是一个一维数组名,类似于一个指针,可以进行指针的各项操作,但并没有占用实际的内存空间; 数组名a的处理:类似于一个特殊的指针,它没有占用实际的内存空间,而且它指向的是一个固定长度的一维数组,故每一步的跨度为该数组的长度。 对应于内存首地址开始的第 k个 数组元素,k = i*每行的元素个数 + j,它的内 存地址为a + k * sizeof(int);

90 a 1 3 5 7 9 11 13 15 17 19 21 23 a+1 a+2 a[0] a[0]+1 a[0]+2 a[0]+3
2000 2004 2008 2012 a+1 2016 2020 2024 2028 a+2 2032 2036 2040 2044

91 int test1(???); int test2(???); void main( ) { int a[3][4], i;
for(i = 0; i < 3; i ++) { test1(a[i]); /* 处理第i行的数组元素 */ } test2(a); /* 处理整个数组 */ } int test1(???); int test2(???);

92 test1函数:a[i]是一个一维数组名,因此可以把形参定义为int *pa(或int pa[ ]、 int pa[4]);
test2函数:a是一个二维数组名,它指向的是长度为4个数组元素的一维数组。因此我们需要引入一种新的指针类型:即指向由若干个元素所组成的一维数组的指针,如:int (*arr)[4]。当然,也可使用传统方法,即把形参设定为int arr[3][4],或 int arr[ ][4],它们都是等价的,都是定义了一个指针,4个字节。

93 int *p; char *p; int **p; int (*p)[2]; char (*p)[3]; p = malloc(100); p++; p

94 int test1(int *pa) { pa[1] = 2; } int test2(int (*arr)[4]) arr[i][j] = 2;

95 4. 动态的二维数组 在C语言中,用malloc函数动态分配的是一 整块的连续的内存空间,并返回其起始地
4. 动态的二维数组 在C语言中,用malloc函数动态分配的是一 整块的连续的内存空间,并返回其起始地 址,但这并不能作为一个二维数组来使用, 因为编译器缺乏足够的信息来进行地址的 运算。例如,不能采用以下方法来动态地 定义一个二维数组a[3][4]: int *a; a = (int *)malloc(12 * sizeof(int)); 因为编译器无法计算出元素a[i][j]的地址。

96 方法一 对于静态二维数组a[3][4],可以把a看成是 一个特殊的一维数组,它的三个元素a[0]、
因此可以采用指针数组的方法。 a (int **) 指针数组

97 int **CreateGrid(int nRows, int nCols)
{ int **array, i; array = (int **)malloc(nRows * sizeof(int *)); for(i = 0; i < nRows; i ++) array[i] = (int *)malloc(nCols * sizeof(int)); return(array); } main( ) { int **a, i, j; a = CreateGrid(3, 4); a[i][j] = FreeGrid(a, 3, 4);

98 方法1与静态二维数组的区别: 存储位置不同:静态二维数组是位于该函数的栈帧当中,当函数调用结束后,即被释放;而方法1创建的动态数组位于堆空间,必须用free函数来显示地释放; 占用空间不同:静态二维数组的a和a[0]、a[1]和a[2]都不占用内存空间,而方法1的a是指向指针的指针,a[0]、a[1]和a[2]都是指针,它们都需要占用4个字节的内存空间; 对a[i][j]的定位方式不同:静态二维数组方式:a + i*每行元素个数 + j;方法1:间接访问的方式(非连续存放):*(a + i) + j。

99 方法二 基本思路:把指针a设定为指向由若干个元 素所组成的一维数组的指针,而不是指向 指针的指针,如:int (*a)[4]。 a+1

100 int *CreateGrid(int nRows, int nCols)
{ int *array; array = malloc(nRows * nCols * sizeof(int)); return(array); } main( ) { int (*a)[4], i, j; a = (int (*)[4]) CreateGrid(3, 4); a[i][j] = ...

101 8.4 指针与字符串 8.4.1 字符串的表示形式 1. 字符串的存储 "China" 字符串存储在字符数组当中,并以字符‘\0’
8.4 指针与字符串 字符串的表示形式 1. 字符串的存储 字符串存储在字符数组当中,并以字符‘\0’ 来作为结束标记。 "China" C h i n a \0

102   2. 字符数组方式 基本思路:首先定义一个字符数组,然后用它来存放一个字符串;
2. 字符数组方式 基本思路:首先定义一个字符数组,然后用它来存放一个字符串; 每个数组元素只能存放一个字符,因此字符数组的长度必须大于所存放的字符串的长度; 对字符数组只能对各个元素赋值,不能用以下方法对整个字符数组赋值: char str[14]; str = "hello, world"; 可以在定义字符数组的时候对它进行初始化: char str[ ] = "China"; 它等价于char str[ ] = {'C', 'h', 'i', 'n', 'a', '\0'};

103 3. 字符串常量 字符串常量:用一对双引号括起来的一串字符序列,如"hello, world" 、"China";
3. 字符串常量 字符串常量:用一对双引号括起来的一串字符序列,如"hello, world" 、"China"; 字符串常量的使用方式:单独使用或用一个指针来指向它,例如: printf("The answer is %.2f. \n", x); strcpy(str, "hello, world"); char *str; str = "hello, world"; 如果用指针来指向一个字符串常量,那么该指针当中存放的是字符串的首地址,而不是把字符串整个放在指针中。

104 字符串常量存放在什么地方? 操作系统 程序 全局变量 char *s1, *s2; s1 = "hello, world";
"China" 全局变量 char *s1, *s2; s1 = "hello, world"; s2 = "China"; 数据段 s1[0] = 'c'; // ?? 存放在“数据段” (Data Segment) 中,由系统自动来 管理。 s1 s2

105 8.4.2 字符串的访问 对字符串中某个字符的访问方法: 数组下标法:如 str[i]; 指针法:如 *(p + i),*p;
字符串的访问 对字符串中某个字符的访问方法: 数组下标法:如 str[i]; 指针法:如 *(p + i),*p; 对字符串的访问方法: 数组名法:如 printf(“%s”, str); 指针法:如 p = str;printf(“%s”, p);

106 调整字符顺序 问题描述: 编写一个程序,输入一个字符串,该字符串只包含三种类型的字符:小写字母、大写字母和空格。然后生成一个新的字符串,把所有的小写字母放在最前面,所有的空格放在中间,所有的大写字母放在最后,而且这些小写、大写字母原来的顺序不能乱。最后把这个新的字符串输出。 讨论 输入字符串:B_a_Ab ab__BA

107 1. 计算出各部分的长度; 2. 用三个指针分别指向其起始位置 3. 逐个字符的拷贝 pLow pBlank pUp

108 void main() { char src[100], dest[100]; int i, length; int up, low, blank; char *pUp, *pLow, *pBlank; up = 0; low = 0; blank = 0; gets(src); length = strlen(src); for(i = 0; i < length; i++) if(src[i] >= 'a' && src[i] <= 'z') low++; else if(src[i] >= 'A' && src[i] <= 'Z') up++; else if(src[i] == ' ') blank++; }

109 pLow = dest; pBlank = pLow + low; pUp = pBlank + blank; for(i = 0; i < length; i++) { if(src[i] >= 'a' && src[i] <= 'z') *pLow++ = src[i]; else if(src[i] >= 'A' && src[i] <= 'Z') *pUp++ = src[i]; else if(src[i] == ' ') *pBlank++ = src[i]; } dest[length] = '\0'; printf("%s\n", dest);

110 8.4.3 指针字符串练习题 1. 下列语句中,不能把字符串“Hello!”赋给 字符数组b的语句是( )
指针字符串练习题 1. 下列语句中,不能把字符串“Hello!”赋给 字符数组b的语句是( ) char b[10] = {'H', 'e', 'l', 'l', 'o', '!'}; char b[10]; strcpy(b, "Hello!"); char b[10]; b = "Hello!"; char b[10] = "Hello!"; C

111 2. 对于变量的声明 int (*pa)[4],下列描述中正确的是( )
pa是一个指向某数组中第四个元素的指针,该元素是int型变量; pa是一个具有4个元素的指针数组,每个元素是一个int型指针; 假设pa = 1000,那么执行语句pa++后,pa的值为1004; pa是一个指向数组的指针,所指向的数组是4个int型元素。 D

112 3. 程序找错 char *GetMorning( ) { char str[] = “Morning”; return str; }
3. 程序找错 char *GetMorning( ) { char str[] = “Morning”; return str; } void main( ) char msg[32]; int i; for(i = 1; i <= 32; i++) msg[i] = ‘\0’; /* 将msg 清零 */ msg = “Good”; /* 拷贝“Good” 字符串 */ msg[5] = ‘ ’; /* 添加一个空格 */ strcat(msg, GetMorning()); /* 添加”Morning”字符串 */ printf(“%s”, msg);

113 4. 程序分析 void func2(char *str) { int len, i, j; char val[50], *p; len = strlen(str); i = 0; j = len − 1; p = val; while((i < len / 2) && (j >= len / 2)) { *p++ = str[j--]; *p++ = str[i++]; } *p = 0; strcpy(str, val); } void main( ) { char str[50] = "ogauain!sotltrnc"; func2(str); printf("%s", str); } 输出结果是: ____________________ congratulations!

114 - END -


Download ppt "程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季."

Similar presentations


Ads by Google