二维数组的指针表示 与复杂的指针例子 专题研讨课之三
问题 – 计算矩阵的行列式 编写函数计算一个 NxN 矩阵 A 的行列式 矩阵 A 可以用一个二维数组表示 a[i][j] 表示第i行、第j列的元素Aij 2
问题 – 计算矩阵的行列式 今天我们不关注算法实现 定义函数的原型 函数原型可以等价地写成 double Det(double a[100][100], int n); 函数原型可以等价地写成 double Det(double a[ ][100], int n); double Det(double (*a)[100], int n); 为什么?数组到底是什么? 3
内容提要 指针的要素,多级指针 指针与数组关系 二维数组与多维数组 4
指针的要素:基类型, 地址值 类型名 * 指针变量名 例如: int * p1; p1的基类型是int char * p2; p2的基类型是char double * p3; p2的基类型是double double ** p4; p4的基类型是double* double ***p5; p5的基类型是double** 5
指针的值或者地址值 double a, x[10]; double *p1, **p2; p1和p2都是指针 p1的基类型是double,p2的基类型是double*; 目前都还没有确定(有意义的)地址值 执行赋值操作:p1 = &a; 那么 p1的值是a的地址,称作:p1指向a *p1 和 a 是同一个对象,都是变量 a x的地址值是什么? 6
代码举例 错误的代码(指针值不合法) 正确代码 读入一个字符串: 读入一个整数 引用指针 char str[100], *s; scanf(“%s”, s); 读入一个整数 int a,*p; scanf(“%d”,p); 引用指针 int c, a = 2, *p; c = 3 + *p; s = str; scanf(“%s”, s); p = &a; scanf(“%d”,p); p = & a; c = 3 + *p; 7
使用指针之前,一定要赋值 scanf(“%s”, s); scanf(“%d”,p); c = 3 + *p; 根据指针p的地址,从内存的相应地方读取一个整数(p的基类型是整数) 8
指针的基类型与(地址)值 指针的加法对地址值的影响 地址值的增量 = sizeof(基类型) 定义 基类型 p所指向对象的地址(假设) char *p; char (1 byte) 1000020 1000021 short *p; short (2 bytes) 1000040 1000042 long *p; long (4 bytes) 1000080 1000084 … 9
数组与指针 int a[5],x[100]; 数组名既是一个数组,又是一个指针 它的基类型就是它的元素的类型 它的值是首元素地址 数组名是个指针常量,不是变量,不能再赋值 int *p = a + 1; 可以 p = a + 2; 可以 a = a + 1; 不可以 10
理解运算符[] 以下程序输出什么? 运算符 [ ]的意义:x[y] *(x+y) void main() { int a[5]={1, 3, 5, 7, 9},*p = a; printf("%d\t%d\n", a[3], 3[a]); } 运算符 [ ]的意义:x[y] *(x+y) 所以 a[3] *(a+3) *(3+a) 3[a] 因此,输出是 7 7 a[i] 和 p[i] 没有区别 11
二维数组与行指针 int a[4][3]; 二维数组可以理解为 特殊的一维数组 它的每个元素都是一维数组! [0][1] [0][2] [0][0] [1][1] [1][2] [1][0] [2][1] [2][2] [2][0] [3][1] [3][2] [3][0] int a[4][3]; 二维数组可以理解为 特殊的一维数组 它的每个元素都是一维数组! a也是一个指针,它的基类型是“长度为3的int数组” 每一个a[i]都是长度为3的数组 a+i是什么? 指向谁? *( a+i ) a[i] a+i也是行指针,指向第i行 a的地址值, a[0]的地址值, 都是 a[0][0] 的地址 a[i]是一位数组,所以也是指针(数组名是指向首元素的指针) a[i]的基类型是int a[i] + j 是什么?指向谁? *( a[i]+j ) a[i][j] 基类型是一维数组的指针行指针 基类型是二维数组的指针面指针 基类型是三维数组的指针体指针 12
定义数组类型 typedef int IntA[10]; 等价于: IntA a; IntA b[5]; IntA *ap; 定义了一个行指针 int a[10]; int b[5][10]; int (*ap)[10]; 13
自定义类型-typedef语句 typedef 原类型名 新类型; typdef double Real; typdef char Byte; typdef int *Pointer; Pointer p int * p; Pointer *q int **q; 定义了一个二级指针 typdef int Array[10][10]; Array a int a[10][10]; Array *p int (*p)[10][10]; 定义了一个面指针 14
Typedef常用语结构类型 使用方便 struct _POINT { float x, y; }; typedef struct _POINT POINT; POINT p1, p2; struct _POINT p1, p2; POINT *p struct _POINT *p; typedef struct { float x, y } POINT; 无名结构 如果结构成员多,一般写成多行 typedef struct { float x; float y; } POINT; 15
typedef多个新类型 typedef struct { float x; float y; } POINT, * PPOINT; PPOINT x; x是什么类型? 16
typedef命令语法格式 类似于定义变量 double Real; typedef double Real; Real x; double x; double *PReal; typedef double *PReal; struct _Point Point; typdef struct _Point Point; Point p; struct _Point p; int Array[10]; typedef int Array[10]; Array a; int a[10]; int * PArray[20]; typedef int *PArray[20]; 这是指针数组 PArray p; int *p[20]; int (*AP)[30]; typedef int (*AP)[30]; 只是行指针 AP p; int (*p)[30]; 17
回答问题 定义函数的原型 函数原型可以等价地写成 double Det(double a[100][100], int n); 为什么? 作为形式参数 a[] 与 *a 等价 注意一下区别: double *a[100]; a是(指针)数组 double (*b)[100]; b是(数组)指针,即行指针 18
二维数组、数组指针 int a[5][10] = {55}; int (*p2)[10] = a; int * p1 = a; a的地址值 = a[0]的地址值 = a[0][0]的地址 int (*p2)[10] = a; int * p1 = a; 发生了强制类型转换! int[10]指针 int指针 编译器会警告 思考:指针的基类型 p1+1指向的地址、*p1是什么? p2+1指向的地址、*p2是什么?
指向指针的指针(二级指针) int a = 10; int *p = &a; int **pp = &p; 定义语法格式 类型名 **指针变量名; a 10 *pp *p **pp
多级指针 int a = 10; int *p = &a; int **pp = &p; 思考 pp = &(&a); 不可以!! 运算符&取得“操作数”的地址 该操作数必须在内存中有对应地址 &a变量a的地址值,是一个常量 常量没有地址值,例如5,7,10等等
多级指针 int a[5][10] = { 55 }, b = 66; int *p = &b, **pp = &p; 思考 pp = a; 发生了类型强制转换:行指针(int[10])int** a的地址值 = a[0]的地址值 = a[0][0]的地址 *pp是什么?等于什么? *pp 是 int*,它的值是 55; **pp是地址为55的内存数据(4个字节作为整数处理) 如果执行:int x = **pp;一般会报错
多级指针程序解析 程序 char *c[] = {"ENTER", "NEW", "POINT", "FIRST"}; char **cp[]= {c+3, c+2, c+1, c}; char ***cpp = cp; main() { printf("%s", **++cpp); printf("%s", *--*++cpp); printf("%s", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1); }
多级指针程序解析 char *c[] = {"ENTER", "NEW", "POINT", "FIRST"}; char **cp[]= {c+3, c+2, c+1, c}; char ***cpp = cp; 示意图: “ENTER” “NEW” “POINT” “FIRST” ccp cp[0] cp[1] cp[2] cp[3] cp c[0] c[1] c[2] c[3] c
多级指针程序解析 语句printf("%s", **++cpp); 执行情况分析: 执行++cpp后,cpp指向了cp[1],此时的*cpp等价于cp[1],**cpp等价于*cp[1],也即c[2](因为此时的cp[1]指向c[2],如图示)。 最后打印结果:POINT “ENTER” “NEW” “POINT” “FIRST” ccp cp[0] cp[1] cp[2] cp[3] cp c[0] c[1] c[2] c[3] c
多级指针程序解析 语句printf("%s", *--*++cpp); 执行情况分析: 执行++cpp后,cpp指向了cp[2], 此时的*cpp等价于cp[2], --*cpp等价于--cp[2], 执行后cp[2]指向了c[0], **cpp等价于c[0]。最后打印结果:ENTER “ENTER” “NEW” “POINT” “FIRST” ccp cp[0] cp[1] cp[2] cp[3] cp c[0] c[1] c[2] c[3] c
多级指针程序解析 语句printf("%s", *cpp[-2]+3); 执行情况分析: 注意,经过了上面两条语句的执行,此时的cpp指向了cp[2]。 cpp[-2]等价于*(cpp-2),即cp[0]。 “ENTER” “NEW” “POINT” “FIRST” ccp cp[0] cp[1] cp[2] cp[3] cp c[0] c[1] c[2] c[3] c
多级指针程序解析 语句printf("%s", *cpp[-2]+3); 执行情况分析: *cpp[-2]*cp[0]c[3]字符串“FIRST”的首地址。 *cpp[-2]+3c[3]+3 字符串“ST”的首地址。 打印结果:ST “ENTER” “NEW” “POINT” “FIRST” ccp cp[0] cp[1] cp[2] cp[3] cp c[0] c[1] c[2] c[3] c
多级指针程序解析 语句printf("%s\n", cpp[-1][-1]+1); 执行情况分析: 此时的cpp仍然指向cp[2]。 cpp[-1][-1](*(cpp-1))[-1] (cp[1])[-1]*(cp[1]-1)c[1] 字符串“NEW”的首地址。 cpp[-1][-1]+1c[1]+1“EW” 打印结果:EW “ENTER” “NEW” “POINT” “FIRST” ccp cp[0] cp[1] cp[2] cp[3] cp c[0] c[1] c[2] c[3] c
多级指针程序解析 综合结果:POINTENTERSTEW
行指针(数组指针)[续] #include <string.h> char (*defy(char *p))[5] { #include <stdio.h> #include <string.h> char (*defy(char *p))[5] { int i; for(i=0; i<3; i++) p[strlen(p)] = 'A'; return (char(*)[5])p+1; } void main() char a[] = "FROG\ØSEAL\ØLION\ØLAMB"; puts( defy(a)[1]+2 );
函数defy的参数和返回值 先看函数头 char (* defy(char *p)) [5] 按“从右到左”,找到defy defy左边是 *,表名defy()返回一个指针 假设函数的返回值是 x, 那么x的类型定义是 char (*x)[5]; 所以,函数的返回值是一个指针,而且是行指针 它的基类型是:长度为5的字符数组
如何更清晰地定义函数defy 函数defy的返回值是一个行指针,基类型是:长度为5的字符数组 利用typedef命令,程序更清晰 typedef char CA5[5]; CA5 *defy(char *p) { int i; for(i=0; i<3; i++) p[strlen(p)] = 'A'; return (CA5*)p+1; } char (*defy(char *p))[5] { int i; for(i=0; i<3; i++) p[strlen(p)] = 'A'; return (char(*)[5])p+1; }
函数defy的返回值 return (char (*)[5])p + 1; (char (*)[5])是一个强制类型转换 表示目标类型为数组指针(行指针) 该指针指向由5个char构成的一维数组。 注意这里的*必须用()括起来,强调指针优先 理解的时候可以设想*后面有一个变量p;
分析defy(a)的执行结果 A A A char a[] = "FROG\ØSEAL\ØLION\ØLAMB"; puts( defy(a)[1]+2 ); 先看defy(a)的执行 将字符串结束符\0变成A 执行三次 再看defy的返回值 (CA*)p是一个行指针 (CA*)p+1指向下一行 即指向: SEALA A A A CA5 *defy(char *p) { int i; for(i=0; i<3; i++) p[strlen(p)] = 'A'; return (CA5*)p+1; }
puts( defy(a)[1]+2 )的实际参数 执行defy(a)之后,数组a中数据是 “FROGASEALALIONALAMB” defy(a)的返回值是一个行指针 指向 SEALA defy(a)[1] *(defy(a)+1) defy(a)[1]的类型 CA5 前面定义:typedef char CA5[5]; (长为5的字符数组) defy(a)[1]也是char * 指针。 defy(a)[1]+2指向 因此,函数puts的输出是:ONALAMB
End! 下载PPT,好好研读