程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季
第 八 章 指针
8.1 什么是指针 指针是C语言中最重要的特性之一,要 成为一个好的 C/C++ 程序员,就必须 理解指针的概念,并善于使用它。
8.1.1 地址亦数据 某国情报机关最近一段时期接连损失了好几名 情报人员,经过调查,发现是在自己内部存在一名 8.1.1 地址亦数据 某国情报机关最近一段时期接连损失了好几名 情报人员,经过调查,发现是在自己内部存在一名 “鼹鼠”,但不知是谁。不得已,买通了一名隐藏极 深的双重间谍进行调查。很快,调查取得了重大突 破,这名间谍成功地获取了该“鼹鼠”的代号。因此 情报局长派遣一名经验丰富的特工前去取回这条极 其重要的情报。双方约定的接头地点在 大厦的 1101房间。
大厦 2103 1101 1102 1103 1104 1105 2103 3102 2101 2102 2103 2104 2105 3102 3101 3102 3103 3104 3105 K
同学录上的通讯地址:××大学×号楼 ××房间。 p q ch 2103 … 3102 'K' ch的地址 q的地址 字符数据 1101 2103 3102 地址也是一种数据: 街道上的门牌号码; 同学录上的通讯地址:××大学×号楼 ××房间。
地址和数据是否完全等价? 地址虽然是数据,但两者并非完全等价。 例如,假设用四个字节来描述一个地址, 那么从理论上说,其取值范围应该是: -231-231-1,但是在这些整数中,并不是 所有的整数都能成为一个有效的地址。
x p 8.1.2 地址与类型 ? 1000 访问一个变量需要 知道: 1000 (1)它的起始地址 (2)它的数据类型 对于内存: 8.1.2 地址与类型 1000 访问一个变量需要 知道: (1)它的起始地址 (2)它的数据类型 对于内存: 物理上,以字节为单元 逻辑上,以不同数据类 型为单元。 ? 1000 x 1020 p 1024 scanf(“%lf”, &x);
A pointer is a variable that contains the 8.1.3 什么是指针 The C Programming Language (K & R): A pointer is a variable that contains the memory address of another variable.
指针是一种变量,因而也具有变量的三个要素,但指 针是一种特殊的变量,其特殊性表现在它的类型和取 值上。具体而言: 变量名:与一般的变量命名规则相同; 变量的值:是另一个变量的内存地址; 变量的类型:包括两种类型, 指针变量本身的类型,即指针类型,它的长度为4个字节; 指针指向的变量的类型,指明了该变量的长度。 指针变量能否存放自己的地址?
指针 = 变量 ≠ 地址 清华大学 圆明园
8.2 指针变量 8.2.1 指针的定义 指针定义的一般形式: 基类型 *指针变量名; 例如: int x; /* 定义了一个整型变量 */ 8.2 指针变量 8.2.1 指针的定义 指针定义的一般形式: 基类型 *指针变量名; 例如: int x; /* 定义了一个整型变量 */ int *q; /* 定义了一个指向整型变量的指针变量 */
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”类型的指针来代替。
x y px 2 int x, y, *px; x = 2; 指向整型变量的指针 1000 1004 1008 一些问题: 如何把变量 x 的地址存放在指针 px 当中? 如何使用指针 px 来访问或修改变量 x 的值? 为何要如此自找麻烦? 1020 px 1024
x y px 8.2.2 指针运算符 1. &运算符 2 int x, y, *px; x = 2; 能否:px = 1000; 1000 8.2.2 指针运算符 1. &运算符 2 int x, y, *px; x = 2; 1000 x 1004 y 如何把变量 x 的地址存放在指针 px 当中? 1008 能否:px = 1000; 1000从何而来?编程时并不知道变量的实际地址; 类型不匹配,常量与指针, (int *)1000 1020 px 1024
x y px 2 int x, y, *px; x = 2; 我们想要做的事情: px = x 的内存地址; 我们写的语句: 1000 x 1004 y 1008 我们想要做的事情: px = x 的内存地址; 我们写的语句: 1020 px px = &x; 1024
x y px 2 1000 int x, y, *px; x = 2; px = &x; &:取地址运算符 1000 1004 1008 整型变量的指针类型。一般 的,如果x的类型是T,则 &x的类型为指向T型变量的 指针类型。 1020 px 1024
pointer_1 a &a pointer_2 b &b &a 即 a 的地址。
pointer_1 = &a; pointer_1 a 1000 1000 &a 1000
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
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
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
pointer_1 = &a; pointer_1 a 1000 1000 *pointer_1 &a 1000
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;
指针常量NULL表示一个指针不指向任何有效的数据。 int x, *p; p = &x; *p = 1; p = NULL; *p = 2; <stdio.h>
一个其值为NULL的指针不同于一个未初始 化的指针。一个指针在定义后,是未被初始 化的,其值是随机的,即可能指向某个无效 的地址,此时若对它进行访问,将会出错。 而NULL常量用来明确地说明一个指针不指 向任何有效的数据。
8.2.3 为何要使用指针? 指针的存在提供了一种共享数据的方法, 可以在程序的不同位置、使用不同的名字 8.2.3 为何要使用指针? 指针的存在提供了一种共享数据的方法, 可以在程序的不同位置、使用不同的名字 (指针)来访问相同的一段共享数据。如 果在一个地方对该数据进行了修改,那么 在其他的地方都能看到修改以后的结果。
输出结果: 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); 输出结果: -858993460, -858993460
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
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
问题描述: 计算一元二次方程的根。
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); }
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); } 传值还是传地址?
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); }
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); }
指针分析1 *tinky = slinky; void main() /* 有何问题? */ { int binky; foo(&binky); } void foo(int *tinky) int slinky = 5; tinky = &slinky; *tinky = slinky;
指针分析2 thinking… void main() /* 输出结果是什么? */ { int* pinky; pinky = bar(); printf(“%d”, *pinky); } int* bar() int winky = 5; return(&winky); thinking…
指针分析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;
8.3 指针与数组 8.3.1 指向数组元素的指针 p a[0] int a[6], *p; p = &a[3]; a[1] a[2] 8.3 指针与数组 8.3.1 指向数组元素的指针 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]
8.3.2 通过指针引用数组元素 main avg prices 5 FindAvg values nValues void main( ) 8.3.2 通过指针引用数组元素 void main( ) { double prices[5], avg; avg = FindAvg(prices, 5); } double FindAvg(double values[], int nValues) main avg prices FindAvg values nValues 5
a++; int a[6]; a[0] = 1; *a = 1; 在C语言中,一个数组的名字就代表了这个 数组的起始地址,它可以被视为一个其值 不可变的指针,该指针指向数组当中的第 一个数组元素。 int a[6]; a[0] = 1; *a = 1; 下标法: 指针法: 这两条语句是等效的 a++;
指针的算术运算 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]
在指针的算术运算中,对基类型长度的补偿 是由系统自动来完成的。 double prices[10], x; int units[10], y; x = *(prices + 3); /* 把 x 赋值为prices[3] */ y = *(units + 3); /* 把 y 赋值为units[3] */ 在指针的算术运算中,对基类型长度的补偿 是由系统自动来完成的。
几种等效的表示方法 a[k] 等效于 *(a + k) &a[k] 等效于 a + k &a[0] 等效于 a
指针与数组的区别 int a[5]; pa int *pa; *pa = 2; a[0] a[1] a[2] a[3] a[4] 指针在使用前,必须先 初始化,指向一个有效 的地址。 int *pa; *pa = 2;
分析程序 int foobar( int *pi ) { *pi = 1024; return *pi; } int main() int ival = foobar( pi2 ); printf("%d", ival);;
合法的指针运算 指针加一个整数: ptr + k 指针减一个整数: ptr – k 两个指针相减: p1 – p2 指针的关系运算: >、>=、< <=、==、!=
问题描述: 计算一个数组所有元素之和。 方法一 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... sum = 0; for(i = 0; i < 10; i ++) { sum += values[i]; }
方法二 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... ptr = &values[0]; for(i = 0; i < 10; i ++) { sum += ptr[i]; }
方法三 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... ptr = &values[0]; /* 也可写成 ptr = values */ sum = 0; for(i = 0; i < 10; i ++) { sum += *(ptr + i); }
方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values
方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values
方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values
代码分析 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); 画出此时内存状态(变量的取值) }
index aptr bptr 3 arr 1 6 3 4 5 2 0 1 2 3 4 5 6 7
int scores[MAX_SIZE]; 8.3.3 动态数组 在定义一个数组时,必须事先指定其长度。 例如: 或者: int a[6]; #define MAX_SIZE 100; int scores[MAX_SIZE];
有的时候,我们事先并不知道数组的确切 长度,只有当程序运行以后我们才知道。 因此,我们希望能够这样来定义数组: 但是在C语言中,这是不可能的。 int n; printf(“请输入学生人数:”); scanf(“%d”, &n); int scores[n];
int *scores; 我们能做的事情是:动态地为该数组分配 所需的内存空间,即在程序运行时分配。 具体做法是:定义一个指针,然后把动态 分配的内存空间的起始地址保存在该指针 中,如: int *scores; scores = 动态分配的内存空间的起始地址;
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));
比较静态数组与动态数组 #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;
int *scores, nScores; scanf(“%d”, &nScores); scores = (int *)malloc(nScores * sizeof(int)); if(scores == NULL) ... /* 内存分配失败 */ scores[3] = 100; 等价于:*(scores + 3) = 100;
作为局部变量的数组在函数调用结束后, 其内存空间即被释放。而对于动态数组, 即使在函数调用结束后,其内存空间依然 存在。
程序 分析 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; 4 4344416 1244916 4198895 1
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; 4 0 0 2 1
操作系统 程序 栈帧2 栈帧1 全局变量 内存分布状况 堆 动态分配 栈 自动分配 全局变量区域 静态分配
内存分布状况 操作系统 程序 全局变量 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…
动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); 堆由堆管理器管理,用户具有排他性使用权 栈 堆 values scores
动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); values = NULL; values原来指向的内存单元现在无法访问。 栈 堆 内存泄漏! values 空 scores
动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); free(values); values[0] = 1; values = NULL; 空 栈 堆 values scores
代码分析 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++; 画出此时内存状态(栈、堆、变量的取值) }
栈 main 4 i a b 堆 2 ? 1 2 3 list
8.3.4 指针? 数组? 在C语言中,一个指针可能是指向单一的一 个变量,也可能是指向一组相同类型的数 8.3.4 指针? 数组? 在C语言中,一个指针可能是指向单一的一 个变量,也可能是指向一组相同类型的数 组元素,从外观上,两种指针的形式完全 一样,因此只能靠程序员去加以区分。 int x, a[6], *ptr; ptr = &x; /* 合法 */ ptr = a; /* 合法 */ ptr x? a?
分析结果 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; 1 15 5 2 9 15
8.3.5 二维数组与指针 1. 指针数组 一个数组,其元素均为指针类型变量,称为 指针数组。即数组中的每一个元素都是一个 指针。 8.3.5 二维数组与指针 1. 指针数组 一个数组,其元素均为指针类型变量,称为 指针数组。即数组中的每一个元素都是一个 指针。 定义形式:类型名 * 数组名[数组长度]; 例如: int *pa[4];
指针数组的使用 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
main(int argc, char *argv[ ]); 例如,假设程序名为sort,在运行时命令行 的情况如下: sort source.txt destination.txt argv[0] argv[1] argv[2] argc = 3 sort命令
2. 指向指针的指针 指向指针的指针:一种特殊的指针,其基 类型也为指针类型。如 int **p。 通常的指针: 指向指针的指针: p 2. 指向指针的指针 指向指针的指针:一种特殊的指针,其基 类型也为指针类型。如 int **p。 通常的指针: 指向指针的指针: p ptr ptr *p x *ptr x *ptr **p
已有的三种指针类型: 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”
在foo函数中修改主函数中的变量 void main() { int value; foo(???); } void foo(???) //p ??? = 1; value p 1
如果主函数中的变量是一个指针呢? void main() { int *a; foo(???); } a void foo(???) //p ??? = malloc(8); a p
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的栈帧 … 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
函数之间的地址传递方法: 如果你是想把主函数中的地址,从上往下传,传到被调用的函数里面,那么形参和实参均为指针; 如果你是想把被调用函数中的地址,从下往上传,传到主函数当中去,那么形参和实参均为指向指针的指针。
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 *) ③00430430 12ff74 bart[1] (int *) ⑥ 0012ff7c 12ff70 bart[0] (int *) ② 0012ff7c 12ff6c 12ff18 marge (int *) 12ff1c maggie (int **) ⑦ 0012ff74 ⑤ 0012ff70 ④ 0012ff7c
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 列的那个数组元素。
a 在定义二维数组 int a[3][4]之后,内存的分布 情况如下: 0x0012FF7C 0x0012FF78 内存分布: 按行存放, 先顺序存放 第一行的所 有元素,再 存放第二行 的元素。 0x0012FF74 0x0012FF70 a[2] 0x0012FF6C 0x0012FF68 0x0012FF64 0x0012FF60 a[1] 0x0012FF5C 0x0012FF58 0x0012FF54 a[0] 0x0012FF50 a
怎么做?如何定位a[i][j]? 程序员眼中的二维数组:一个3×4的二 维表格; 内存分布:连续存放的数据元素; C编译器的任务:把程序员眼中的二维表 格映射到内存当中的相应元素。 怎么做?如何定位a[i][j]?
a[0]、a[1]、a[2]的处理:它们都是一个一维数组名,类似于一个指针,可以进行指针的各项操作,但并没有占用实际的内存空间; a[i][j]的定位: a[0]、a[1]、a[2]的处理:它们都是一个一维数组名,类似于一个指针,可以进行指针的各项操作,但并没有占用实际的内存空间; 数组名a的处理:类似于一个特殊的指针,它没有占用实际的内存空间,而且它指向的是一个固定长度的一维数组,故每一步的跨度为该数组的长度。 对应于内存首地址开始的第 k个 数组元素,k = i*每行的元素个数 + j,它的内 存地址为a + k * sizeof(int);
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
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(???);
test1函数:a[i]是一个一维数组名,因此可以把形参定义为int *pa(或int pa[ ]、 int pa[4]); test2函数:a是一个二维数组名,它指向的是长度为4个数组元素的一维数组。因此我们需要引入一种新的指针类型:即指向由若干个元素所组成的一维数组的指针,如:int (*arr)[4]。当然,也可使用传统方法,即把形参设定为int arr[3][4],或 int arr[ ][4],它们都是等价的,都是定义了一个指针,4个字节。
int *p; char *p; int **p; int (*p)[2]; char (*p)[3]; p = malloc(100); p++; p
int test1(int *pa) { … pa[1] = 2; } int test2(int (*arr)[4]) arr[i][j] = 2;
4. 动态的二维数组 在C语言中,用malloc函数动态分配的是一 整块的连续的内存空间,并返回其起始地 4. 动态的二维数组 在C语言中,用malloc函数动态分配的是一 整块的连续的内存空间,并返回其起始地 址,但这并不能作为一个二维数组来使用, 因为编译器缺乏足够的信息来进行地址的 运算。例如,不能采用以下方法来动态地 定义一个二维数组a[3][4]: int *a; a = (int *)malloc(12 * sizeof(int)); 因为编译器无法计算出元素a[i][j]的地址。
方法一 对于静态二维数组a[3][4],可以把a看成是 一个特殊的一维数组,它的三个元素a[0]、 因此可以采用指针数组的方法。 a (int **) 指针数组
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);
方法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。
方法二 基本思路:把指针a设定为指向由若干个元 素所组成的一维数组的指针,而不是指向 指针的指针,如:int (*a)[4]。 a+1
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] = ...
8.4 指针与字符串 8.4.1 字符串的表示形式 1. 字符串的存储 "China" 字符串存储在字符数组当中,并以字符‘\0’ 8.4 指针与字符串 8.4.1 字符串的表示形式 1. 字符串的存储 字符串存储在字符数组当中,并以字符‘\0’ 来作为结束标记。 "China" C h i n a \0
2. 字符数组方式 基本思路:首先定义一个字符数组,然后用它来存放一个字符串; 2. 字符数组方式 基本思路:首先定义一个字符数组,然后用它来存放一个字符串; 每个数组元素只能存放一个字符,因此字符数组的长度必须大于所存放的字符串的长度; 对字符数组只能对各个元素赋值,不能用以下方法对整个字符数组赋值: char str[14]; str = "hello, world"; 可以在定义字符数组的时候对它进行初始化: char str[ ] = "China"; 它等价于char str[ ] = {'C', 'h', 'i', 'n', 'a', '\0'};
3. 字符串常量 字符串常量:用一对双引号括起来的一串字符序列,如"hello, world" 、"China"; 3. 字符串常量 字符串常量:用一对双引号括起来的一串字符序列,如"hello, world" 、"China"; 字符串常量的使用方式:单独使用或用一个指针来指向它,例如: printf("The answer is %.2f. \n", x); strcpy(str, "hello, world"); char *str; str = "hello, world"; 如果用指针来指向一个字符串常量,那么该指针当中存放的是字符串的首地址,而不是把字符串整个放在指针中。
字符串常量存放在什么地方? 操作系统 程序 全局变量 char *s1, *s2; s1 = "hello, world"; "China" 堆 栈 全局变量 char *s1, *s2; s1 = "hello, world"; s2 = "China"; 数据段 s1[0] = 'c'; // ?? 存放在“数据段” (Data Segment) 中,由系统自动来 管理。 s1 s2
8.4.2 字符串的访问 对字符串中某个字符的访问方法: 数组下标法:如 str[i]; 指针法:如 *(p + i),*p; 8.4.2 字符串的访问 对字符串中某个字符的访问方法: 数组下标法:如 str[i]; 指针法:如 *(p + i),*p; 对字符串的访问方法: 数组名法:如 printf(“%s”, str); 指针法:如 p = str;printf(“%s”, p);
调整字符顺序 问题描述: 编写一个程序,输入一个字符串,该字符串只包含三种类型的字符:小写字母、大写字母和空格。然后生成一个新的字符串,把所有的小写字母放在最前面,所有的空格放在中间,所有的大写字母放在最后,而且这些小写、大写字母原来的顺序不能乱。最后把这个新的字符串输出。 讨论 输入字符串:B_a_Ab ab__BA
1. 计算出各部分的长度; 2. 用三个指针分别指向其起始位置 3. 逐个字符的拷贝 pLow pBlank pUp
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++; }
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);
8.4.3 指针字符串练习题 1. 下列语句中,不能把字符串“Hello!”赋给 字符数组b的语句是( ) 8.4.3 指针字符串练习题 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
2. 对于变量的声明 int (*pa)[4],下列描述中正确的是( ) pa是一个指向某数组中第四个元素的指针,该元素是int型变量; pa是一个具有4个元素的指针数组,每个元素是一个int型指针; 假设pa = 1000,那么执行语句pa++后,pa的值为1004; pa是一个指向数组的指针,所指向的数组是4个int型元素。 D
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);
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!
- END -