630 likes | 806 Views
C 语言程序设计. 第十章 指针. 莆田学院 《C 语言程序设计 》 精品课程组 2005 年 5 月 制作. 目录. 一、指针概述 二、指针与数组 三、指针与函数 作业. 一、指针概述. 1 .地址 ( address ) P201 如果在程序中定义了一个 “ 实体 ” ( 变量、数组、函数 …… ),编译时系统就要给这些实体分配内存单元。 分配规则:. 什么是内存单元“地址”?.
E N D
C语言程序设计 第十章 指针 莆田学院《C语言程序设计》精品课程组 2005年5月 制作
目录 • 一、指针概述 • 二、指针与数组 • 三、指针与函数 • 作业
一、指针概述 1.地址(address)P201 如果在程序中定义了一个“实体”(变量、数组、函数 ……),编译时系统就要给这些实体分配内存单元。 分配规则:
什么是内存单元“地址”? • 内存单元是以字节为单位,每个字节都有一个编号(即“地址”)。如果将内存比作一个旅馆,内存单元就好比“床位”,而实体则好比“旅客”。这些“旅客”(实体)中,有单人型(char)、夫妇型(int)、家庭型(float,long,double等),还有团体型(数组等)。每个“实体”占用的内存单元是不同的。如: char a;int b;float c;int d[3];int max( )
内存单元与地址 地址 main() { char a; int b; float c; int d[3]; int max( ); …… } 通常我们关心的不是各个内存单元的具体地址值,而是每个实体的“起始地址”。
如何表示实体地址? • 实体地址表示法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:间接访问(指针) 适合于地址运算(加减等)
2.指针(pointer) • 实体地址的一种表示法(便于编程处理)。 • 指针是一种特殊的数据类型——存放的是某个实体的地址值。 实际上我们在C程序中用到的并不是代表地址的“指针”,而是另有所指啊! 那为什么不就叫“地址”呢?! • 变量的“指针” 变量在内存单元的占用的地址(首地址)
3.指针变量 P202 是不是说地址有”整型”, ”字符型”, ”实型”之分? • 存放“指针”(地址值)的特殊变量。 • 定义方法: 类型标识符*变量名 如 int *a; char *b; float *c; 通常在C语言中,所谓“指针”就是指“指针变量”。 从现在开始,我们所说的“指针”除非另加说明,否则均表示“指针变量”。
为什么要使用指针变量? C程序中访问(读写)变量有两种方式: • 直接访问(按名单预留的座位入座) 利用实体名访问变量。访问变量的过程—— 变量(实体)名→定义时分配的地址→变量值 好比“先坐再买票”看电影:来一个观众,分配一个空位给他去坐,并且还要在纸上记一个某人坐在哪里。这种方式对用户来说很方便(“直接就座”),但对系统来说,“找某人”就极不方便(间接:查名字→座号)。 • 间接访问 (先买票,后按号入座) 把变量地址先存放在“指针”中,再通过“指针”访问变量。 好比先买票(票—指针,座号—地址),再“按号入座”看电影。这种方式对用户来说属于“间接就座”,便对系统查找来说就很直接,且便于处理。尤其对于数组(团体),可通过指针简单自加或自减,对整个数组进行处理。 习惯用语: 若指针变量p存放了变量a的地址,我们称“p指向a”。
指针变量——不要谈”指”色变 • 指针是C语言学习中的一大难点。 • 难——难在概念。 main() { int a,*p1,*p2=&a; a=100; p1=p2; *p1=*p2; …… } 学了半天,我还是一头雾水
首先——搞定*p • 请看以下变量声明语句 int a,*p1; char b,*p2; a,b普通变量(存放某个数值或字符) p1,p2 指针变量(存放某个实体的地址) 如果是int *a,p1; char *b,p2; 变量声明时,如果变量名前带 *号,表示该变量是个指针变量
注意——不同的*p 讨论: 程序中引用变量时, 对指针变量p, 不带*号引用表示? 带*号引用表示? 以下程序中哪些语句是错误的? main() { int a,*p; a=3; p=3; /*或者 p=a;*/ p=&a; *p=a; /*或者*p=3*/ } 两个特殊的运算符 &变量名 取该变量的地址 * 指针变量名 取该地址处存放的值
有关*p的小结 • 变量声明时,*p表示定义了一个用来存放变量地址而非数据(数值、字符等)的指针变量。 • 程序中引用时,*p表示取指针变量p所指变量的值。 main() { int a,*p1,*p2=&a; a=100; p1=p2; *p1=*p2; …… } 原来就这么简单!
*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中某个元素。
#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
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); }
&与*组合使用时 若 int a, *p; p=&a; 则 &*p = &a = p; *&a = a = *p 妙!&和*可以互相“抵消”。
小考一下,如何? 以下程序的运行结果是什么? 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
指针变量能参加运算吗? • 指针变量和其他变量一样,可以在各种表达式中参加运算。 • 但指针变量和普通变量不同,只能进行以下三种运算: • 赋值运算 • 算术运算 • 指针比较
指针变量的赋值运算 • 指针变量一般赋值 程序处理时赋值 • 指针变量初始化 变量声明时赋值 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
典型错误 【例一】若有定义 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 字符串是一种特殊的实体,存放在内存用户区的常量区。
为什么未指向实体的指针是“危险指针”? 一个指针未指向任何实体就被使用,属于”内存盗用”!因为该指针将随意指向内存中某一单元,轻则误取或破坏其他实体的值,重则破坏操作系统的工作。 “危险指针”?不要耸人听闻好不好! 一个指针变量被声明后,在没有被赋予某个实体地址之前,如果使用它,不仅可能破坏你的程序,而且可能导致计算机操作系统崩溃,出现灾难性的错误。因为它可能指向内存的任何部分。
空指针 P256 小姐,我把0号办公室分配给你 院长办公室给我?哼,空头人情! 空指针: int *p; p=NULL; NULL是什么? 在stdio.h中,定义 #define NULL 0 所以 p=NULL; 相当于 p=0; 内存使用常识: 任何C程序的变量在内存中的地址均由操作系统自动分配,不能由编程者通过赋值指定。p=NULL表示p不指向任何变量。 内存的低端只供由操作系统使用(相当于政府机关,普通百姓不能使用)。
讨论:以下程序中的*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无确定地址,是“危险指针”
指针变量的算术运算 • 指针只有两种算术运算——加、减 p+5 p++ p-1 p-- • 注意加减运算是以实体为单位而不是以字节为单位。 • 此外,两个指针变量可以相减。即:如果两个指针变量指向同一数组时,两个指针变量值之差是两个指针之间的元素个数。参见P256。 • 但两个指针变量相加并无实际意义。
以下程序哪个语句执行时会出错? #include <stdio.h> main() { int a[10],*p1=a; clrscr(); a++; p1++; } X
指针的逻辑比较 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
**p:多重指针(指向指针的指针)P251 对于 int **p; 定义一个二级指针(指向指针的指针) 存放某个指针变量的地址:等效于 int *(*p) 在引用时,*p是p间接指向的对象的地址。 **p是p间接指向的对象的值。 晕!
看了例子也许会明白的… 用一个*声明一个变量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(多重间接取值)
指针变量作为函数参数使用 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); }
再看一个例子 • 为了实现: • 在被调函数中改变实体值,然后在主调函数中使用这些改变了的实体值 • 主要技术要点在于: • 主调函数的实参和被调函数对应的形参都必须用地址表示(地址传递) • 用于作实参的地址可以是: • &变量名 &数组元素名(很少使用) • 数组名 • 指针变量名 对应的形参则为: • 数组定义 • 指针定义 #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
二、指针与数组 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试试。
有何感觉? 我明白了,一维数组中p=a时,p和a都可以相互替换。 有一个地方不能替换,知道是什么地方吗? p++不能用a++替换!! 为什么? 因为a是常量,5++即5=5+1是个低级错误!
注意事项 ① 注意指针变量当前值 P214 例10.6 ② 注意不要超界 ③ 常见表示法: P215 *p++ 等价于*(p++) 先取*p值,然后 p++。※ *(++p) p先自加,然后取*p的值。 *p--与 *(--p) 功能同上。
小结: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自加或自减替换!!
试一试看 【例】有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
二维数组就没这么简单了 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列的元素地址。
列地址是怎么回事? • 列地址: a[0] a[i] &a[0][0](单下标或双下标+&) • 使用列地址时,是将整个二维数组看成同一行。 • 遍历数组时用单循环实现(将数组元素排队处理)
不妨看一个示例 #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、将注释标记 去掉
行地址又是怎么回事呢? • 行地址: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])。
看两个例子 【注意】二维数组中,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试试
看一个例子 #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]试试
用简单指针变量指向二维数组时 • 用简单指针变量指向二维数组时: int a[10][10],*p; p=a; p都是列地址性质的指针(姑且称“列指针”)。此时,p可与“排队法”中的a[0]互换使用,但不能与“行列法”中的a互换使用。
比较一下 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)试试
再看下一个例子 【讨论】 如果将输出语句中的*(*(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试试
行指针 P229 • 形式: int (*p)[n] • 含义:p为指向含有n个元素的一维数组的指针变量。 P255 • 使用:二维数组可以视为由若干一维数组组成。 • 行指针p是行地址性质的指针。此时,p可与“行列法”中的a互换使用,但不能与“排队法”中的a[0]互换使用。
行指针是如何使用的? 若 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];
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
示例二 若有以下定义和语句,且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是个地址)
示例三 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