1 / 108

第十章

第十章. 指针. 主要内容 :. 一、指针在程序中的用途 二、地址和指针的概念 三、变量的指针和指针变量 四、数组与指针 五、字符串与指针 六、 返回指针值的函数. 一、指针在程序中的用途. 有效的表示复杂的数据结构 能动态分配内存 方便的使用字符串 直接处理内存地址 总之,指针的应用,使程序 简洁、紧凑、高效 。. 内存地址编号. 。。。. 内存中用户数据区. 3. 6. 9. 2000. 变量 i. 。。。. 2002. 变量 j. 2004. 变量 k. 2000. 3010. 变量 p. 二、指针的概念.

Download Presentation

第十章

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第十章 指针

  2. 主要内容: 一、指针在程序中的用途 二、地址和指针的概念 三、变量的指针和指针变量 四、数组与指针 五、字符串与指针 六、返回指针值的函数

  3. 一、指针在程序中的用途 • 有效的表示复杂的数据结构 • 能动态分配内存 • 方便的使用字符串 • 直接处理内存地址 总之,指针的应用,使程序简洁、紧凑、高效。

  4. 内存地址编号 。。。 内存中用户数据区 3 6 9 2000 变量i 。。。 2002 变量j 2004 变量k 2000 3010 变量 p 二、指针的概念 1.数据在内存中的存储、读取过程 例如:整型变量 i , j , k ; 在编译时为变量分配内存,内存示意图如图 1 所示. (1) 假设变量 i,j,k 被分配的 内存地址分别为2000,2002,2004 (2) 给变量赋值为 i=3; j=6; k=9; (3) 通常,在程序中通过变量名对变量 进行存取。 • 其实程序经过编译后已将变量名转换为变量的地址,对变量值的存取都是通过变量的地址进行的。 图1

  5. 访问变量的两种方式: (1) 直接访问方式 按变量地址存取变量值的方式。 如果读取变量 i的值,直接到为变量i分配的存储单元(2000、2001字节)中取出i的值(3)即可。 (2) 间接访问方式 先将变量 i 的地址存放在另一个变量p中,如果读取变量 i的值,先找到变量p,从p中取出内容(2000,即变量i的起始地址),然后到2000、2001字节中取出i的值(3)。 即通过变量p访问为变量i分配的内存单元。

  6. i i p 2000 3 3 2000 2000 表示将数值3送到变量i中,可有两种表达方法: (1)将3送到变量i所标志的单元中。即直接访问方式 (2)将3送到变量p所指向的单元(变量i)中。 即间接访问方式 直接访问示意图 间接访问示意图 注意:我们并不关心变量p的存储地址, 对 变量p的访问属于直接访问。

  7. P *P 2000 3 i 2000 三、 指针变量 1.变量的指针 变量的地址称为变量的指针。 2. 指针变量 存储变量地址的变量称为指针变量,用来指向另一个变量。 3.*操作符 为了表示指针变量与它所指向的变量的之间的关系,在程序中用 * 符号表示“指向”。 例如,P代表指针变量,* P则表示P所指向的变量。 以下两个语句作用相同: i=3; *P=3;

  8. 三、 指针变量(续) 定义形式: 基类型 *指针变量名; 4.指针变量的定义 举例 int i , j ; int *pointer1, *pointer2; (1)指针变量名前的*,表示该变量是指针型的变量。指针变量名为pointer1,而非 *pointer1。 说明 (2)定义指针变量时必须指明基类型。 *注:以后几张幻灯片中提到的pointer1 pointer2是基于此例的。

  9. pointer1 *pointer1 3 i 三、 指针变量的引用 5.取地址运算符 & 如何使一个指针变量指向一个变量呢? 使用取地址运算符 &,即 :pointer1=&i; pointer2=&j; 赋值语句pointer1=&i;实现将变量i的地址保存入指针变量pointer1中。如右图所示。

  10. pointer1 *pointer1 3 i 三、 指针变量(续) 6.指针运算符* *:间接访问运算符 int i,*pointer1; i=3;pointer1=&i;printf("%d",*pointer1); 例如 说明 (1)上例将输出i的值。 (2)*pointer1 与普通的整型变量一样使用,但前提是pointer1 必须已经明确地指向了某整型变量.

  11. 对“&”和“*”运算符说明: int a=10,b=5,*pointer_1,* pointer_2; pointer_1=&a;//如果已执行了以上语句 pointer_2=&b; (1)&*pointer_1的含义是什么? (2)*&a的含义是什么? (3)pointer_2=&*pointer_1 ;它的作用是? (4)(*pointer_1)++相当于a++,那 *pointer_1++相当于什么呢

  12. 三、 指针变量(续) 例1: 输入两个整数,按先大后小输出这两个整数 #include <stdio.h> void main() { int *p1, *p2, *p,a,b; scanf("%d%d",&a,&b); p1=&a; p2=&b; /* 取变量的地址*/ if(a<b) { p=p1; p1=p2; p2=p; } /* 指针变量间的相互赋值*/ printf("%d%d",a,b); printf("max=%d,min=%d\n", *p1, *p2 ); }

  13. 运行情况如下: 5,9↙ a=5,b=9 max=9,min=5 当输入a=5,b=9时,由于a<b,将p1和p2交换。交换前的情况见图(a),交换后见图(b)。

  14. …... (main) 2000 2002 变量a 2004 (swap) 2006 变量b 变量temp 变量x 变量y 5 2008 9 200A …... 例将数从大到小输出 • 普通变量作为函数参数——内容传递 swap(int x,int y) { int temp; temp=x; x=y; y=temp; } main() { int a,b; scanf("%d,%d",&a,&b); if(a<b) swap(a,b); printf("\n%d,%d\n",a,b); } 5 9 COPY 值传递 9 5 5 运行结果:5, 9

  15. 10.2.3指针变量作为函数参数 函数的实参值是地址,函数的形参应定义为指针类型,此时,实参和形参之间仍然是传值方式。参见例10.3。 变量a,b的地址被传递 到函数的形参变量p1,p2中 #include <stdio.h> void swap(int *p1, int *p2) { int temp; temp=*p1;*p1=*p2; *p2=temp; } void main() { int a,b,*pointer1, *pointer2; scanf("%d%d",&a,&b); pointer1=&a; pointer2=&b; /* 取变量的地址*/ if(a<b) swap(pointer1,pointer2); /* 函数调用,实参为指针变量*/ printf("%d%d",a,b);/* 打印函数调用后,变量a,b的值,观察其变化*/ } 在被调函数中,通过间接访问, 改变了主调函数中实参变量的值。

  16. …... (main) 2000 2002 指针p2 整型temp 指针pointer_2 指针pointer_1 整型变量a 指针p1 2004 2006 整型变量b 2000 2008 2002 (swap) 200A 200C 200E ... 2010 例将数从大到小输出 swap(int *p1, int *p2) { int temp; temp=*p1; *p1=*p2; *p2=temp; } main() { int a,b; int *pointer_1,*pointer_2; scanf("%d,%d",&a,&b); pointer_1=&a; pointer_2=&b; if(a<b)swap(pointer_1,pointer_2); printf("\n%d,%d\n",a,b); } 9 5 地址传递 5 9 2000 2002 COPY 5 运行结果:9,5

  17. …... (main) 2000 2002 指针pointer_2 指针pointer_1 整型变量a 2004 2006 整型变量b 2008 200A 200C 200E ... 2010 例将数从大到小输出 swap(int *p1, int *p2) { int p; p=*p1; *p1=*p2; *p2=p; } main() { int a,b; int *pointer_1,*pointer_2; scanf("%d,%d",&a,&b); pointer_1=&a; pointer_2=&b; if(a<b)swap(pointer_1,pointer_2); printf("\n%d,%d\n",a,b); } 9 5 5 9 2000 地址传递 2002 运行结果:9,5

  18. …... (main) 2000 2002 pointer_2 指针p pointer_1 指针p1 指针p2 整型a 2004 2006 整型b 2000 2008 2002 (swap) 200A 200C **** 200E ... 2010 例将数从大到小输出 swap(int *p1, int *p2) { int *p; p=p1; p1=p2; p2=p; } main() { int a,b; int *pointer_1,*pointer_2; scanf("%d,%d",&a,&b); pointer_1=&a; pointer_2=&b; if(a<b) swap(pointer_1,pointer_2); printf("%d,%d",*pointer_1,*pointer_2); } 5 9 地址传递 2000 2002 COPY 2002 2000 2000 运行结果:5,9

  19. 内容回顾 • 指针的概念 • 指针变量的使用 • 指针作为函数参数

  20. 提问 int i,*p; *p=&i; int i=2,j,*p=&i; j=*p++;与j=(*p)++;有区别吗?

  21. 本次课程内容 • 数组元素的指针 • 通过指针引用数组元素 • 用数组名作函数参数 • 多维数组与指针

  22. §10.3 数组与指针 一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓数组元素的指针就是数组元素的地址。

  23. §10.3 数组与指针 例如,定义数组 int a[10]; • 数组在内存中占一片连续的存储区,该存储区的大小与数组的元素类型和数组长度有关。 • 每个数组元素占用相同大小的存储空间。 • 利用数组存储区的起始地址,可以逐个获得每个数组元素的存储地址。 1.数组的存储 2000 a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 2002 2004 2006 2000+4*2 注:假设数组a的存储区起始地址为2000。 由上图可知,数组元素a[i]的存储地址的计算公式为: 数组a的起始地址+i×元素类型占内存的字节数 2000+9*2 图2

  24. §10.3 数组与指针 2.指向数组元素的指针 例如, int a[10]; int*p; 对指针变量p赋值:p=&a[0]; 则使p指向了数组的第一个元素。 C语言规定: 数组名代表数组的起始地址,该地址在程序运行过程中不能修改。 即数组名a是个常量。 因此,下面对指针变量p赋值的两个语句等价: p=&a[0]; p=a; 因此定义指向数组的指针变量时,可以对其初始化:int a[10]; int*p=a; (或 int*p=&a[0]; )

  25. 对该指针变量赋值: p=&a[0];或p=a; 把a[0]元素的地址赋给指针变量p。也就是使p指向a数组的第0号元素,如图:

  26. 指针变量表达式和指针的算术运算符 (1) 可以对指针进行有限的算术运算,包括自增运算(++)、自减运算(--)、加上一个整数、减去一个整数,以及减去一个指针变量。 (2)指针运算的结果依赖于指针所指向的对象的大小。 指针的算术运算的规则是: 当一个指针加上或减去一个整数时,指针并非简单的加上或减去该整数值, 而是加上或减去该整数与指针指向的对象的大小的乘积。 例如:int a[10] ,*p=a; 则 p=p+1; 使指针变量的值更新为数组元素a[1]的地址,即p指向了数组元素a[1]。

  27. 例如:int a[10] ,*p=a,*q,x; 假定数组a的起始地址为2000,则下列每个操作后变量的值分别为多少? 指针的算术运算 除了用于数组外 没有什么意义 p=p+1; p的值为2002,p指向数组元素a[1]. q=p; q的值为2002, q指向数组元素a[1]. q+=2; q的值为2006, q指向数组元素a[3]. 两个指针相隔元素的个数 x=q-p; x 的值为2 p++; p的值为2004, p指向数组元素a[2]. 通过++ 运算, 让指针变量指向数组中 的下一个元素。 ++p; p的值为2006 , p指向数组元素a[3]. --p; p的值为2004, p指向数组元素a[2]. 通过 -- 运算, 让指针变量指向数组中 的上一个元素。 p--; p的值为2002, p指向数组元素a[1]. 注意:a++是错误的,为什么呢?

  28. 10.3.2通过指针引用数组元素 假设a是数组名,p是指向数组元素的指针变量,其初值p=a。 引用一个数组元素,可以用: (1) 下标法,如a[i]形式;   (2) 指针法,如*(a+i)或*(p+i)。

  29. 地址 p[0] a[0] a[0] a[0] *p *a 元素 地址 元素 *(p+1) *(a+1) p[1] a[1] a[1] a[1] *(p+2) *(a+2) p[2] a[2] a[2] a[2] p a a[3] a[3] p+1 a+1 p+2 a+2 a[9] a[9] p[9] *(p+9) *(a+9) a[9] ... ... p+9 a+9 指针法 下标法 [] 变址运算符 a[i] *(a+i) • 数组元素表示方法 a[i]  p[i]  *(p+i) *(a+i)

  30. pa a[0] 1 a[1] 2 a[2] 3 a[3] 4 a[4] 5 例 数组元素的引用方法 main() { int a[5],*pa,i; for(i=0;i<5;i++) a[i]=i+1; pa=a; for(i=0;i<5;i++) printf("*(pa+%d):%d\n",i,*(pa+i)); for(i=0;i<5;i++) printf("*(a+%d):%d\n",i,*(a+i)); for(i=0;i<5;i++) printf("pa[%d]:%d\n",i,pa[i]); for(i=0;i<5;i++) printf("a[%d]:%d\n",i,a[i]); }

  31. 例 int a[]={1,2,3,4,5,6,7,8,9,10},*p=a,i; 数组元素地址的正确表示:(A)&(a+1) (B)a++ (C)&p (D)&p[i]  数组名是地址常量 p++,p-- () a++,a-- () a+1, *(a+2) ()

  32. 例10.5 输出数组中的全部元素 假设有一个a数组,整型,有10个元素。要输出各元素的值有三种方法:

  33. 数组与指针 (采用三种方法输出数组元素的值) 1.下标法 2. 通过数组名计算数组元素的地址,找出元素的值 #include <stdio.h> void main() { int a[10] , i ; for(i=0; i<10;i++) scanf("%d",&a[i]); for(i=0; i<10;i++) printf("%d",a[i]); } #include <stdio.h> void main() { int a[10] , i ; for(i=0; i<10;i++) scanf("%d",&a[i]); for(i=0; i<10;i++) printf("%d",*(a+i)); } 3. 用指针变量指向数组元素 #include <stdio.h> void main() { int a[10] , i ,*p; for(i=0; i<10;i++) scanf("%d",&a[i]); for(p=a; p<(a+10); p++) printf("%d", *p); } (1)用下标法直观; (2)用前两种方法效率一样; C编译系统是将a[i]转换为 *(a+i)来处理的 (3)用第三种方法省时间, p++操作快。

  34. 例10.6 通过指针变量输出a数组的10个元素。 有人编写出以下程序: #include <stdio.h> void main() {int*p,i,a[10];  p=a; for(i=0;i<10;i++ ) scanf(″%d″,p++); printf(″\n″); for(i=0;i<10;i++,p++ ) printf(″%d″,*p); }

  35. 看一下运行情况: 1 2 3 4 5 6 7 8 9 0↙ 22153 234 0 0 30036 25202 11631 8259 8237 28483 显然输出的数值并不是a数组中各元素的值

  36. a 0 5 1 8 p p p p p p p p 2 7 3 6 4 2 5 7 6 3 例 注意指针的当前值 main() { int i,*p,a[7]; p=a; for(i=0;i<7;i++) scanf("%d",p++); printf("\n"); for(i=0;i<7;i++,p++) printf("%d",*p); } p=a; 指针变量可以指到数组后的内存单元

  37. 10.3.3用数组名作函数的参数 首先回顾第八章“函数”中介绍的数组名作函数参数的情形: #include <stdio.h> void main() { int array[10] ; ... f(array,10); ... } void f( int arr[], int n) { ... ... } 规定:如果形参数组中 元素的值发生变化, 实参数组元素的值 随之变化。 说明 (1) 实参数组名代表该数组首元素的地址。而形参是用来接收从实参传递过来的数组首元素的地址。因此,形参应该是一个指针变量(只有指针变量才能存放地址)。 (2) 虽然函数 f 的原型为 void f( int arr[], int n) ,但编译时是将arr数组按指针变量来处理的。即: void f( int arr[], int n) 与 void f( int *arr, int n) 等价

  38. 例10.7 将数组a中n个整数按相反顺序存放

  39. 0 1 2 3 4 5 6 7 8 9 i j i j i j i j i j 3 7 9 11 0 6 7 5 4 2 2 4 5 7 6 0 11 9 7 3 例 将数组a中的n个整数按相反顺序存放 void inv(int x[], int n) { int t,i,j,m=(n-1)/2; for(i=0;i<=m;i++) { j=n-1-i; t=x[i]; x[i]=x[j]; x[j]=t; } } main() { int i,a[10]={3,7,9,11,0,6,7,5,4,2}; inv(a,10); printf("The array has been reverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } m=4 实参与形参均用数组

  40. a数组 x 2 a[0] 3 i i i i 4 a[1] 7 5 a[2] 9 7 a[3] 11 p=x+m 6 a[4] 0 0 6 a[5] 11 7 a[6] 9 5 a[7] j j j j 7 4 a[8] j i 2 3 a[9] 例 将数组a中的n个整数按相反顺序存放 void inv(int *x, int n) { int t,*p,*i,*j,m=(n-1)/2; i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--) { t=*i; *i=*j; *j=t; } } main() { int i,a[10]={3,7,9,11,0,6,7,5,4,2}; inv(a,10); printf("The array has been reverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } 实参用数组,形参用指针变量

  41. 例 将数组a中的n个整数按相反顺序存放 void inv(int *x, int n) { int t,*i,*j,*p,m=(n-1)/2; i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--) { t=*i; *i=*j; *j=t; } } main() { int i,a[10],*p=a; for(i=0;i<10;i++,p++) scanf("%d",p); p=a;inv(p,10); printf("The array has been reverted:\n"); for(p=a;p<a+10;p++) printf("%d",*p); } 实参与形参均用指针变量

  42. 例 将数组a中的n个整数按相反顺序存放 void inv(int x[], int n) { int t,i,j,m=(n-1)/2; for(i=0;i<=m;i++) { j=n-1-i; t=x[i]; x[i]=x[j]; x[j]=t; } } main() {int i,*p,a[10]={3,7,9,11,0,6,7,5,4,2}; p=a;inv(p,10); printf("The array has been reverted:\n"); for(p=arr;p<arr+10;p++) printf("%d ",*p); } a[i]  p[i]  *(p+i) *(a+i) 实参用指针变量,形参用数组

  43. 归纳起来,如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况: (1) 形参和实参都用数组名,如: void main() void f(int x[ ],int n) { int a[10]; { … … f(a,10); } }

  44. (2) 实参用数组名,形参用指针变量。如: void main() void f(int *x,int n) {int a[10]; { … … f(a,10); } } (3)实参形参都用指针变量。例如: void main() void f(int *x,int n) {int a[10], *p=a; { ┇ ┇ f(p,10); } }

  45. (4) 实参为指针变量,形参为数组名。如: void main() void f(int x[ ],int n) {int a[10],*p=a; { ┇ ┇ f(p,10); } }

  46. 问题: • 使用函数计算数组的最大值,最小值和平均值并返回给调用者,使用指针并且不允许使用全局变量。

  47. int array[10]; array 10.3.4 多维数组与指针 用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。 • 回顾 • 对于一维数组: • (1)数组名array表示数组的首地址,即array[0]的地址; • (2)数组名array是地址常量 • (3)array+i是元素array[i]的地址 • (4)array[i]  *(array+i)

More Related