1 / 63

第十章 指针

C 语言程序设计. 第十章 指针. 莆田学院 《C 语言程序设计 》 精品课程组 2005 年 5 月 制作. 目录. 一、指针概述 二、指针与数组 三、指针与函数 作业. 一、指针概述. 1 .地址 ( address ) P201 如果在程序中定义了一个 “ 实体 ” ( 变量、数组、函数 …… ),编译时系统就要给这些实体分配内存单元。 分配规则:. 什么是内存单元“地址”?.

thais
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. C语言程序设计 第十章 指针 莆田学院《C语言程序设计》精品课程组 2005年5月 制作

  2. 目录 • 一、指针概述 • 二、指针与数组 • 三、指针与函数 • 作业

  3. 一、指针概述 1.地址(address)P201 如果在程序中定义了一个“实体”(变量、数组、函数 ……),编译时系统就要给这些实体分配内存单元。 分配规则:

  4. 什么是内存单元“地址”? • 内存单元是以字节为单位,每个字节都有一个编号(即“地址”)。如果将内存比作一个旅馆,内存单元就好比“床位”,而实体则好比“旅客”。这些“旅客”(实体)中,有单人型(char)、夫妇型(int)、家庭型(float,long,double等),还有团体型(数组等)。每个“实体”占用的内存单元是不同的。如: char a;int b;float c;int d[3];int max( )

  5. 内存单元与地址 地址 main() { char a; int b; float c; int d[3]; int max( ); …… } 通常我们关心的不是各个内存单元的具体地址值,而是每个实体的“起始地址”。

  6. 如何表示实体地址? • 实体地址表示法1:直接访问(实体名) • 普通变量a,b,c —— &a,&b,&c • 数组d[3] —— d(数组名), &d[0],&d[1],&d[2] 对二维数组,可用单下标法表示每行首地址。 如 对char x[3][4],可用x[0]、x[1]、x[2]分别表示其第 一、二、三行的首地址。 • 函数max( ) —— max(函数名) “入口地址” & 取地址运算符(适用于普通变量或数组元素) • 实体地址表示法2:间接访问(指针) 适合于地址运算(加减等)

  7. 2.指针(pointer) • 实体地址的一种表示法(便于编程处理)。 • 指针是一种特殊的数据类型——存放的是某个实体的地址值。 实际上我们在C程序中用到的并不是代表地址的“指针”,而是另有所指啊! 那为什么不就叫“地址”呢?! • 变量的“指针” 变量在内存单元的占用的地址(首地址)

  8. 3.指针变量 P202 是不是说地址有”整型”, ”字符型”, ”实型”之分? • 存放“指针”(地址值)的特殊变量。 • 定义方法: 类型标识符*变量名 如 int *a; char *b; float *c; 通常在C语言中,所谓“指针”就是指“指针变量”。 从现在开始,我们所说的“指针”除非另加说明,否则均表示“指针变量”。

  9. 为什么要使用指针变量? C程序中访问(读写)变量有两种方式: • 直接访问(按名单预留的座位入座) 利用实体名访问变量。访问变量的过程—— 变量(实体)名→定义时分配的地址→变量值 好比“先坐再买票”看电影:来一个观众,分配一个空位给他去坐,并且还要在纸上记一个某人坐在哪里。这种方式对用户来说很方便(“直接就座”),但对系统来说,“找某人”就极不方便(间接:查名字→座号)。 • 间接访问 (先买票,后按号入座) 把变量地址先存放在“指针”中,再通过“指针”访问变量。 好比先买票(票—指针,座号—地址),再“按号入座”看电影。这种方式对用户来说属于“间接就座”,便对系统查找来说就很直接,且便于处理。尤其对于数组(团体),可通过指针简单自加或自减,对整个数组进行处理。 习惯用语: 若指针变量p存放了变量a的地址,我们称“p指向a”。

  10. 指针变量——不要谈”指”色变 • 指针是C语言学习中的一大难点。 • 难——难在概念。 main() { int a,*p1,*p2=&a; a=100; p1=p2; *p1=*p2; …… } 学了半天,我还是一头雾水

  11. 首先——搞定*p • 请看以下变量声明语句 int a,*p1; char b,*p2; a,b普通变量(存放某个数值或字符) p1,p2 指针变量(存放某个实体的地址) 如果是int *a,p1; char *b,p2; 变量声明时,如果变量名前带 *号,表示该变量是个指针变量

  12. 注意——不同的*p 讨论: 程序中引用变量时, 对指针变量p, 不带*号引用表示? 带*号引用表示? 以下程序中哪些语句是错误的? main() { int a,*p; a=3; p=3; /*或者 p=a;*/ p=&a; *p=a; /*或者*p=3*/ } 两个特殊的运算符 &变量名 取该变量的地址 * 指针变量名 取该地址处存放的值

  13. 有关*p的小结 • 变量声明时,*p表示定义了一个用来存放变量地址而非数据(数值、字符等)的指针变量。 • 程序中引用时,*p表示取指针变量p所指变量的值。 main() { int a,*p1,*p2=&a; a=100; p1=p2; *p1=*p2; …… } 原来就这么简单!

  14. *p——并非就这么简单 *p的含义与p所指的对象有关 • 如果p被定义成指向普通变量的指针变量,则*p代表该变量的值。 如 int *p,a=5; p=&a;则*p代表变量a的值(5)。 • 如果p被定义成指向某个数组的指针变量,则*p代表该数组中某个元素的值。 如 int *p, a[3]={1,2,3}; p=a;则*p代表数组a中某个元素。

  15. #include <process.h> main() { int a,b=10,*p; system("cls"); p=&b; a=*p+3; printf("a=%d,b=%d\n",a,b); } main() { int *p,a[12]={1,2,3,4,5}; clrscr(); p=a; for ( ;*p<5;p++) printf("%d",*p); } 看看两个例子 结果:a=13,b=10 结果:1234

  16. p指向字符数组时的*p 如果直接用a进行循环,行不行? 在for语句中用*p控制循环,是否适用于数值数组? • 如果p被定义成指向某个字符数组或某个字符串的指针变量,则*p代表某个字符。 如 int *p, a[3]=”abcd”; p=a;*p代表a中的某个字符 main() { char *p,a[12]="abcde"; p=a; for (;*p;p++) printf("%c",*p); } main() { char *p; p="abcde"; for (;*p;p++) printf("%c",*p); }

  17. &与*组合使用时 若 int a, *p; p=&a; 则 &*p = &a = p; *&a = a = *p 妙!&和*可以互相“抵消”。

  18. 小考一下,如何? 以下程序的运行结果是什么? main() { float x,y; int *p; x=3.14; p=&x; y=*p; printf("y=%f\n",y); } 怎么会这样? 把int *p改为float *p后,结果正确:y=3.140000 结果:y=-2621.000000

  19. 指针变量能参加运算吗? • 指针变量和其他变量一样,可以在各种表达式中参加运算。 • 但指针变量和普通变量不同,只能进行以下三种运算: • 赋值运算 • 算术运算 • 指针比较

  20. 指针变量的赋值运算 • 指针变量一般赋值 程序处理时赋值 • 指针变量初始化 变量声明时赋值 main() { int x; int *p1,*p2; p1=&x; p2=p1; printf("%p",p2); } %p以16进制显示指针 main( ) { int a=5,*p=&a; printf(“%d,%d,%d\n”,p,*p,a) } 结果:2000,5,5

  21. 典型错误 【例一】若有定义 char *p,ch; 则不能正确赋值的语句组是 : A) p=&ch scanf("%c",p); B) *p=getchar(); p=&ch; C) p=&ch; *p=getchar(); 【例二】若有定义 char *a,b[30]; 则以下各语句正确的是 : A) a=”abcde”; B)b=”abcde”; C) scanf(“%s”,a); D) scanf(“%s”,b); 指针变量定义后,未指向具体存储单元(实体地址)就使用,此时指针变量所指单元是任意的, 是个”危险指针”。 X X 字符串是一种特殊的实体,存放在内存用户区的常量区。

  22. 为什么未指向实体的指针是“危险指针”? 一个指针未指向任何实体就被使用,属于”内存盗用”!因为该指针将随意指向内存中某一单元,轻则误取或破坏其他实体的值,重则破坏操作系统的工作。 “危险指针”?不要耸人听闻好不好! 一个指针变量被声明后,在没有被赋予某个实体地址之前,如果使用它,不仅可能破坏你的程序,而且可能导致计算机操作系统崩溃,出现灾难性的错误。因为它可能指向内存的任何部分。

  23. 空指针 P256 小姐,我把0号办公室分配给你 院长办公室给我?哼,空头人情! 空指针: int *p; p=NULL; NULL是什么? 在stdio.h中,定义 #define NULL 0 所以 p=NULL; 相当于 p=0; 内存使用常识: 任何C程序的变量在内存中的地址均由操作系统自动分配,不能由编程者通过赋值指定。p=NULL表示p不指向任何变量。 内存的低端只供由操作系统使用(相当于政府机关,普通百姓不能使用)。

  24. 讨论:以下程序中的*p1,*p2 #include <stdio.h> main() { int *p1=NULL,*p2; clrscr(); *p1=100; *p2=200; printf("%d,%d\n",*p1,*p2); } 在指针p指向某个实体的地址之前,不可对*p进行赋值。否则可能发生意想不到的错误(p随便指向某个单元)。 *p1有确定地址,但未指向任何变量 *p2无确定地址,是“危险指针”

  25. 指针变量的算术运算 • 指针只有两种算术运算——加、减 p+5 p++ p-1 p-- • 注意加减运算是以实体为单位而不是以字节为单位。 • 此外,两个指针变量可以相减。即:如果两个指针变量指向同一数组时,两个指针变量值之差是两个指针之间的元素个数。参见P256。 • 但两个指针变量相加并无实际意义。

  26. 以下程序哪个语句执行时会出错? #include <stdio.h> main() { int a[10],*p1=a; clrscr(); a++; p1++; } X

  27. 指针的逻辑比较 P256 • 指针变量指向同一个对象(如数组)的不同单元地址时,才可以进行比较。地址在前者为小。 • 任何指针变量或地址都可以与NULL作相等或不相等的比较。如 if(p==NULL)… #include <stdio.h> #include <string.h> fun(char *w,int n) { char t,*s1,*s2; s1=w; s2=w+n-1; while (s1<s2) { t=*s1++; *s1=*s2--; *s2=t; } } main() { char *p; p="1234567"; fun(p,strlen(p)); puts(p); } 结果:1711717 【注意】对*s1++,因*与++同级,且自右至左结合,所以等价于*(s1++),执行时是先做*s1,后做s1++ 。 P215

  28. **p:多重指针(指向指针的指针)P251 对于 int **p; 定义一个二级指针(指向指针的指针) 存放某个指针变量的地址:等效于 int *(*p) 在引用时,*p是p间接指向的对象的地址。 **p是p间接指向的对象的值。 晕!

  29. 看了例子也许会明白的… 用一个*声明一个变量p int a=5,*p=&a; 使用时,p带*是a的值(“直接取值”)。 用两个*声明一个变量p int a=5,**p,*p1; p1=&a,p=&p1; 使用时p带两个*是a的值 (“两重间接取值),p带一个*是a的地址。 以下程序段的输出是什么? int **pp,*p,a=20,b=30; pp=&p;p=&a;p=&b; printf(“%d,%d\n”,*p,**pp); 对不对? 结果:30,30(多重间接取值)

  30. 指针变量作为函数参数使用 main() { void swap(int *,int *); int a=5,b=3,*p1,*p2; clrscr(); p1=&a,p2=&b; swap(p1,p2); printf("a=%d,b=%d\n",a,b); } void swap(int *x,int *y) { *x=*x-*y; *y=10; printf("x=%d,y=%d\n",*x,*y); } main() { void swap(int,int); int a=5,b=3,*p1,*p2; clrscr(); p1=&a,p2=&b; swap(*p1,*p2); printf("a=%d,b=%d\n",a,b); } void swap(int x,int y) { x=x-y; y=10; printf("x=%d,y=%d\n,",x,y); }

  31. 再看一个例子 • 为了实现: • 在被调函数中改变实体值,然后在主调函数中使用这些改变了的实体值 • 主要技术要点在于: • 主调函数的实参和被调函数对应的形参都必须用地址表示(地址传递) • 用于作实参的地址可以是: • &变量名 &数组元素名(很少使用) • 数组名 • 指针变量名 对应的形参则为: • 数组定义 • 指针定义 #include <stdio.h> fun(int *i) { static int a=1; *i+=a++; } main() { int k=0; fun(&k); fun(&k); printf("%d\n",k); } 结果:3

  32. 二、指针与数组 1、一维数组中的有关规定 #define N 9 main() { int a[10],i,*p; clrscr(); p=a; for(i=0;i<N;i++) scanf("%d",&a[i]); for(i=0;i<N;i++) printf("%d ",a[i]); } • Let’s try… • 运行程序,观看结果 • 在scanf语句中,分别用a+i、p+i和&p[i]代替&a[i]试试; • 在printf语句中,分别用*(a+i)、*(p+i)和p[i]代替a[i]试试; • 在p=a语句中,用&a[0]代替a试试。

  33. 有何感觉? 我明白了,一维数组中p=a时,p和a都可以相互替换。 有一个地方不能替换,知道是什么地方吗? p++不能用a++替换!! 为什么? 因为a是常量,5++即5=5+1是个低级错误!

  34. 注意事项 ① 注意指针变量当前值 P214 例10.6 ② 注意不要超界 ③ 常见表示法: P215 *p++ 等价于*(p++) 先取*p值,然后 p++。※ *(++p) p先自加,然后取*p的值。 *p--与 *(--p) 功能同上。

  35. 小结:C语言的有关规定 对一维数组的数组名a: • 代表数组首地址(数组第一个元素a[0]的地址) 即: a=&a[0] *a=a[0] • a±i 表示右移或左移 i 个元素位置 a+i=&a[i] *(a+i)=a[i] • 如果有指针变量 p=a;或 p=&a[0];则 p 和 a 在程序中实际上可以互相替换使用。 p=&a[0] *p=a[0] p+i=&a[i] *(p+i)=a[i] 切记:p自加或自减不能用a自加或自减替换!!

  36. 试一试看 【例】有int a[10]={0,1,2,3,4,5,6,7,8,9},*p=a,i; 其中0≤i<10,则对a数组元素不正确的引用是。 A)a[p-a] B)*(&a[i]) C)p[i] D)*(*(a+i)) 不可以的。二维数组可要大伤脑筋了! 二维数组可以照此类推吧? 答案:D

  37. 二维数组就没这么简单了 2、二维数组中的有关规定 • 二维数组的地址有”行地址”和”列地址”之分 • 行地址表示是数组中的第几行(不带下标的数组名) 行地址 a a+i a+i表示第i+1行 • 列地址指第几个元素的地址(单下标或双下标+&) 每行的首地址 a[0] a[i] &a[0][0]都是列地址 列地址+1或 列地址-1表示左移或右移一位 • 对行地址取值的结果 如*a、*(a+i) 仍是某行第一个元素的地址,但已由行地址转为列地址了。所以a+i是表示第i+1行的行地址,而*a+1表示第0行第i+1个元素的地址, *(a+i) +j表示&a[i][j],即第i+1行第j+1列的元素地址。

  38. 列地址是怎么回事? • 列地址: a[0] a[i] &a[0][0](单下标或双下标+&) • 使用列地址时,是将整个二维数组看成同一行。 • 遍历数组时用单循环实现(将数组元素排队处理)

  39. 不妨看一个示例 #include <process.h> main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int *p,i,j=0; system("cls"); p=a; /* for (i=0;i<9;i++) */ printf("%d\n",*(a[0]+1)); } • Let’s try… • 1、将a[0]改为 • a[1]、a[2] • &a[0][0] • a 2、将注释标记 去掉

  40. 行地址又是怎么回事呢? • 行地址:a a+i (不带下标的数组名) 对行地址取*运算后转换为列地址,如*a、*(a+i) • 使用行地址时,是将整个二维数组看成i行j列。 • 遍历数组时用双循环实现(将数组元素分行列处理) • 使用行地址时: • a和*a分别表示第0行的行地址和列地址 • a+i 和 *(a+i)分别表示第i行的行地址和列地址 • *(a+i)+j 表示第i行第j个元素的地址(=&a[i][j]) • 行地址本质上是二级地址,通过它取元素值时要多加一次*运算。 • **a表示第0行0列元素值(=a[0][0]); • *(*(a+i))表示第i行第0列元素值(=a[i][0]); • *(*(a+i)+j)表示第i行第j列元素值(=a[i][j])。

  41. 看两个例子 【注意】二维数组中,a[i]、a+i与*(a+i) 等价(P225)。 原因:a+i 不是变量,C规定a[i] 、a+i与*(a+i) 等价(P226)。 #include <process.h> main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int *p,i; system("cls"); p=a; for (i=0;i<3;i++) { printf("%d ",a+i); printf("%d %d %d\n", &a[i][0],&a[i][1],&a[i][2]); } } 改成*(a+i)、a[i]分别试试 再把%d改为%p试试

  42. 看一个例子 #include <process.h> main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int *p,i,j; system("cls"); p=a; for (i=0;i<3;i++) for(j=0;j<3;j++) printf("%d ",*(*(a+i)+j)); } 改成a[i][j]试试

  43. 用简单指针变量指向二维数组时 • 用简单指针变量指向二维数组时: int a[10][10],*p; p=a; p都是列地址性质的指针(姑且称“列指针”)。此时,p可与“排队法”中的a[0]互换使用,但不能与“行列法”中的a互换使用。

  44. 比较一下 1 2 3 4 5 6 7 8 9 #include <process.h> main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int *p,i; system("cls"); p=a; for (i=0;i<9;i++) printf("%d\n",*(p+i)); } 换成*(a[0]+i)试试

  45. 再看下一个例子 【讨论】 如果将输出语句中的*(*(a+i)+j)改为*(*(p+i)+j)可不可以? #include <process.h> main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int *p,i,j; system("cls"); p=a; for (i=0;i<3;i++) for(j=0;j<3;j++) printf("%d ",*(*(a+i)+j)); } 不行! 因为p不是“行指针”! Why?! a改成p试试

  46. 行指针 P229 • 形式: int (*p)[n] • 含义:p为指向含有n个元素的一维数组的指针变量。 P255 • 使用:二维数组可以视为由若干一维数组组成。 • 行指针p是行地址性质的指针。此时,p可与“行列法”中的a互换使用,但不能与“排队法”中的a[0]互换使用。

  47. 行指针是如何使用的? 若 int a[4][5]; int (*p)[5]; p=a;或p=a[0]; 则 (*p)[0]=a[0][0]; (*p)[1]=a[0][1]; (*p)[2]=a[0][2]; …… (*(p+1))[0]=a[1][0]; (*(p+1))[1]=a[1][1]; …… 行指针是一种行地址,可以与二维数组用数组名表示的行地址互换使用。 事实上,有 (*(p+ i))[j]=p[i][j]= * (*(p+ i)+j)=a[i][j];

  48. main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int (*p)[3],i,j; p=a; for(i=0;i<3;i++) for(j=0;j<3;j++) printf("%d ",*(*(p+i)+j)); } 示例一 结果:1 2 3 4 5 6 7 8 9

  49. 示例二 若有以下定义和语句,且0≤i<4,0≤j<3,则不能访问a数组元素的是。 int i, (*p)[3]; int a[ ][3]={1,2,3,4,5,6,7,8,9,10,11,12}; p=a; A)*(*(a+i)+j) B)p[i][j] C)(*(p+i))[j] D)p[j]+j 答案:D (p[j]+j是个地址)

  50. 示例三 main() { char a[3][10]={"abc","123456","ABCDE"}; char (*p)[10]; p=a; printf("%s,%s\n",p+1,*(p+1)); printf("%c,%c,%c\n",*(*(p+1)),*(*(p+2)+1),(*(p+2))[1]); } 结果:123456,123456 1,B,B

More Related