c及c++程序设计教程第6章指针及数组(1)_第1页
c及c++程序设计教程第6章指针及数组(1)_第2页
c及c++程序设计教程第6章指针及数组(1)_第3页
c及c++程序设计教程第6章指针及数组(1)_第4页
c及c++程序设计教程第6章指针及数组(1)_第5页
已阅读5页,还剩47页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、12一、指针的概念一、指针的概念 指针是一种定位内存单元地址的无符号整型数据,简单指针是一种定位内存单元地址的无符号整型数据,简单地说指针是地址的变量。内存单元保存变量的数据状态,地地说指针是地址的变量。内存单元保存变量的数据状态,地址表明变量的存储所在。通过建立指针和变量之间的联系,址表明变量的存储所在。通过建立指针和变量之间的联系,可以访问内存单元,获取或改变内存单元的数据状态可以访问内存单元,获取或改变内存单元的数据状态 long i, j, k ; float x, y, z; double d; &i=0065FD00 &x= 0065FD0C &d=0065

2、FD18 低地址低地址 高地址高地址4字节字节 i j k 4字节字节 x y z d 4字节字节3指针性质:指针性质: 1.指针的值。指针的值本身是无符号的整数,指针的值。指针的值本身是无符号的整数,16位寻址位寻址方式指针的值占两个内存字节,方式指针的值占两个内存字节,32位寻址方式指针的值占四位寻址方式指针的值占四个字节。指针必须初始化个字节。指针必须初始化,才可以进行访问操作运算才可以进行访问操作运算,也允许也允许重新赋值重新赋值;对未初始化的指针进行操作容易导致运行错误对未初始化的指针进行操作容易导致运行错误. 2. 指针的类型属性。指针的类型属性是指针访问内存空指针的类型属性。指针

3、的类型属性是指针访问内存空间的寻址性质,它限定指针的增减以类型步长为单位。不同间的寻址性质,它限定指针的增减以类型步长为单位。不同类型的指针之间不许隐含类型转换。而算术型变量允许。类型的指针之间不许隐含类型转换。而算术型变量允许。 3. 指针的地址属性。指针是地址的变量,因此系统同样指针的地址属性。指针是地址的变量,因此系统同样为其分配内存单元,指针占有的内存相当于一个为其分配内存单元,指针占有的内存相当于一个int型变量占型变量占有的内存。变量的地址是右值,指针的地址也是一个右值。有的内存。变量的地址是右值,指针的地址也是一个右值。4二、一级指针和指针的操作二、一级指针和指针的操作1.一级指

4、针的定义一级指针的定义 一级指针是指向变量或数组元素的指针,一级指针是指向变量或数组元素的指针, 一级指针一级指针即即type*型的指针定义格式为:型的指针定义格式为: type* p, *p1,*pn; 类型类型* 指针名,指针名,*指针名指针名1,*指针名指针名n;指针的初始赋值格式为:指针的初始赋值格式为: p=变量地址变量地址;指针初始化定义语句的格式为:指针初始化定义语句的格式为: type* p = 左值内存区的地址左值内存区的地址; 类型类型* 指针名指针名=变量地址;变量地址; 用于初始化的变量地址是与所定义的指针同类型的地用于初始化的变量地址是与所定义的指针同类型的地址值,指

5、向可读写的内存区。如上语句定义的址值,指向可读写的内存区。如上语句定义的p,p1, p2,pn就就是是type*型指针。型指针。type*型指针专门指向型指针专门指向type型的变量或数组型的变量或数组.52.取地址运算符取地址运算符& &和访问指针运算符和访问指针运算符* * C/C+提供了两个与内存地址相关的单目运算符,一个提供了两个与内存地址相关的单目运算符,一个是取地址运算符是取地址运算符& ,另一个是访问指针运算符另一个是访问指针运算符*。 取地址运算符取地址运算符&的操作数一般为左值,记为的操作数一般为左值,记为: &Lvalue &

6、Lvalue的意义为取左值对应的存储地址,其结果为一的意义为取左值对应的存储地址,其结果为一右值。右值。&(x+y),&(x*y)等是错误的。等是错误的。x+y, x*y是右值。是右值。 左值左值Lvalue为为type型的变量,型的变量,&Lvalue的结果为的结果为type*型的地址。型的地址。 左值左值Lvalue为为type*型的指针,型的指针,&Lvalue的结果为的结果为type*型的地址。型的地址。 简单地说取地址运算进行的是升维处理,把变量升为一简单地说取地址运算进行的是升维处理,把变量升为一级指针,把一级指针升为二级指针,在运算中把左值变为右级

7、指针,把一级指针升为二级指针,在运算中把左值变为右值。值。6 访问指针运算符访问指针运算符*的操作数的操作数ep仅为地址表达式,访问指仅为地址表达式,访问指针记为针记为 *ep。 如果如果ep为为type*型的地址,型的地址,*ep的结果为的结果为type型的左型的左值;值;ep为为type*型的地址,型的地址,*ep的结果为的结果为type*型的左值,此型的左值,此时访问指针时访问指针*ep的意义为取指针所指的内容或访问指针指向的意义为取指针所指的内容或访问指针指向的存储单元。的存储单元。 如果如果ep是指向二维数组的指针,是指向二维数组的指针,*ep为为type*型的右型的右值,此时值,此

8、时*ep 对应一个寻址计算。对应一个寻址计算。 访问指针运算进行的是降维处理,把二级指针降为一级访问指针运算进行的是降维处理,把二级指针降为一级指针,把一级指针降为变量等。指针,把一级指针降为变量等。7 如下语句定义二个如下语句定义二个double*型的指针型的指针p,q,两个,两个int*型的型的指针指针ip, kp: typedef int* pINT; /声明声明pINT 为为int*型的类型别名型的类型别名 int k; /定义定义int型的变量型的变量k pINT ip, kp=&k; /ip,kp均为均为int*型的指针型的指针, kp指向指向k double * q, x

9、; /仅仅q为为double*型的指针,型的指针,x为为double型的变量型的变量 double *p=&x; /定义定义p为为double*型的指针型的指针,初始化为指向初始化为指向x8 初始化定义语句初始化定义语句double *p=&x;在局部范围可以分解在局部范围可以分解为为double *p; p=&x;,但表达式,但表达式*p=&x则是错误的,则是错误的,*p是是double型的左值,型的左值,&x是是double*型的地址,两边类型不匹型的地址,两边类型不匹配。配。 &k=kp是错误的,是错误的,&k 是是int*型的右值

10、。型的右值。 type*型指针专门指向型指针专门指向type型的变量,型的变量,p=&k,ip=&x是是错误的,错误的,double*型的指针型的指针p不指向不指向int型的变量型的变量k,int*型的型的指针指针ip也不能等于也不能等于double*型的地址型的地址&x 。 k=ip是错误的,是错误的,k 是是int型的数据,型的数据,ip是是int*型的数据。型的数据。虽然指针的值是无符号的整型数,但虽然指针的值是无符号的整型数,但ip的类型属性是的类型属性是int*型。型。*p=*kp将将int型数据赋给型数据赋给double型数据是可以的,反过型数据是可以的,反

11、过来来 *kp=*p将将double型数据截断转换为型数据截断转换为int型数据导致精度损型数据导致精度损失。失。9三、指针与间接变量三、指针与间接变量 1.间接变量间接变量 当当p是是type*型的指针时,型的指针时,*p的值是当前指针所指内存单的值是当前指针所指内存单元的数据状态,可以视元的数据状态,可以视*p是是type型的变量,本书型的变量,本书 称称*p 为为p所所指内存的间接变量。指内存的间接变量。 定义语句定义语句 type v,*p = &v;或函数原型与函数调用或函数原型与函数调用void f (type* p); f(&v); 将指针将指针p关联变量关联变量

12、v,*p是变量是变量v的间接变量。的间接变量。 在有效关联期间对于间接变量在有效关联期间对于间接变量*p的操作就是对于变量的操作就是对于变量v的等价操作。的等价操作。 指针指针p如果未指向任何变量,则不要访问这样的指针。如果未指向任何变量,则不要访问这样的指针。此时对此时对*p的操作导致运行时的错误。的操作导致运行时的错误。 指针必须首先有效的关联,然后才存在对间接变量的合指针必须首先有效的关联,然后才存在对间接变量的合法运算。法运算。10 函数定义函数定义void f (type* p) .中的中的p是定义在堆栈空间是定义在堆栈空间的指针形参。的指针形参。下面代码表示取地址运算符下面代码表示

13、取地址运算符&与访问指针运算符与访问指针运算符*的简的简单单例子:例子: float x, y=1, z=2,*p; /定义三个定义三个float型的变量型的变量x, y, z,一个一个float*型的指针型的指针p p=&y; /&y 取变量取变量y的地址,的地址,p指向指向y x=*p; /*p 取地址取地址p中的内容中的内容, 等价于等价于x=y; p=&z; /&z 取变量取变量z的地址,的地址,p指向指向z *p=x; /*p 访问指针指向的变量,等价于访问指针指向的变量,等价于z=x; *p=x+y; /*p 访问指针指向的变量,等价于访问指

14、针指向的变量,等价于z=x+y; 11 注意:注意: *p=x改变间接变量的值,改变间接变量的值,p=&z或或p+=j,p+等改变指针等改变指针p本身的值。本身的值。 访问指针运算符访问指针运算符*与取地址运算符与取地址运算符&可看作不具有交换可看作不具有交换律的互逆运算符。即:律的互逆运算符。即: Lvalue = *(&Lvalue); p = &(*p); 指针的引用是指针的别名。指针的引用是指针的别名。 指针用右值地址初始化,指针的引用由同类型的左值指指针用右值地址初始化,指针的引用由同类型的左值指针初始化。对指针引用的操作是对相关指针的等价操作。针初始

15、化。对指针引用的操作是对相关指针的等价操作。 声明指针引用的语句为:声明指针引用的语句为: 类名类名*& 指针引用名指针引用名=指针名指针名; type* & rp = p;12 例例指针作为函数的形参,指针形参可改变其指向的实指针作为函数的形参,指针形参可改变其指向的实参变量。参变量。 #include typedef long type,*ptype; void f (type* x, ptype y) *x+ = 1;*y+ = 2; void main () long a=1,*pa=&a; / 定义定义a为为long型变量型变量, pa为为long*型指针,指

16、向型指针,指向a。 type b=2,&rb=b; /定义定义b为为long型变量型变量,声明声明rb为为b的引用的引用 long*& qa=pa; /声明引用声明引用qa为指针为指针pa的别名的别名13 ptype pb (&b), &qb=pb; /定义指针定义指针pb初始化初始化&b, 声明引用声明引用qb为指针为指针pb的别名的别名 printf (%d, %dt, *pa,*pb); f (pa, qb); /函数调用函数调用f(pa,qb);相当于相当于*pa+=1; *pb+=2; *qa+ =1; rb+ = 2; f (&a,

17、&b); /虚实结合导致虚实结合导致x=&a,y=&b printf (%d, %dt, a,b); /输出输出:1,2 4,8 函数调用函数调用f (&a, &b)导致指针形参导致指针形参x,y分别指向分别指向a, b,即:,即:*x,*y分别是分别是a,b的间接变量,对的间接变量,对*x,*y的操作就是对相应变量的操作就是对相应变量a,b的等价操作。类似的分析适用于的等价操作。类似的分析适用于f (pa, qb)。142.空指针空指针: 整数整数0可以初始化指针,空指针就是其值为可以初始化指针,空指针就是其值为0的指针,的指针,含义为该指针不表示指向

18、,而是表示指针的一种约束状态,含义为该指针不表示指向,而是表示指针的一种约束状态,常用于判断指针的开关或返回指针的函数成功与否的标志。常用于判断指针的开关或返回指针的函数成功与否的标志。 #define NULL 0 /C+中中NULL定义为定义为0 double* lp=0; /声明声明lp为为double*类型的指针,初始化为空指针。类型的指针,初始化为空指针。或或 double* lp = NULL ; 指针指针lp的值为零,该指针此时尚未与同类型变量的地址的值为零,该指针此时尚未与同类型变量的地址发生关联,也不指向系统的其它地址特别是重要数据区的地发生关联,也不指向系统的其它地址特别是

19、重要数据区的地址。将址。将0直接赋值给指针等价于指针赋值以直接赋值给指针等价于指针赋值以NULL。 指针只能指向编译器为变量或函数入口分配的内存空指针只能指向编译器为变量或函数入口分配的内存空间,指针仅在指向内存空间之后才可以进行访问指针运算。间,指针仅在指向内存空间之后才可以进行访问指针运算。变量的值可由用户随意指定。变量的值可由用户随意指定。 15 不能将非不能将非0整数赋给指针即试图自行分配非零地址值是整数赋给指针即试图自行分配非零地址值是非法的。非法的。 如下非法指定指针初始值的语句是编程时的大忌如下非法指定指针初始值的语句是编程时的大忌: long * p=10; p=0 x4060

20、1234; p=0 x6500ffde; char *q; scanf (%d, &q); /scanf (%d, &q)表示从屏幕读取值送入表示从屏幕读取值送入q占有的内存单元占有的内存单元 char *s; scanf (%s, s); / scanf (“%s”, s)表示从屏幕读取文本串送入表示从屏幕读取文本串送入s指向的内存区指向的内存区 指针指针s没有初始化,没有初始化,scanf (%s, s)中对于指针中对于指针s的操的操作是危险的。作是危险的。 指针的初始化或者由初始化定义语句或赋值表达式或函指针的初始化或者由初始化定义语句或赋值表达式或函数调用虚实结合完成。

21、数调用虚实结合完成。16 指针的初始值追根逆源一般地须与指针的初始值追根逆源一般地须与 : 取地址运算符;取地址运算符; 一维数组名一维数组名a; 函数名;函数名; new运算符;运算符; 内存分配函数;内存分配函数; 字符串常数;字符串常数; 多维数组下标表达式等相联系:多维数组下标表达式等相联系: pointer = &Lvalue; pointer = a+j ; pointer = function; pointer = new typesize; pointer = (type*)malloc (N*sizeof (type); pointer=字符串常数字符串常数; poi

22、nter=dk+i;17四、一维数组四、一维数组 1.一维数组的定义一维数组的定义 数组的作用域和生存期与变量的规则相同。静态的一维数组的作用域和生存期与变量的规则相同。静态的一维数组的定义格式为:数组的定义格式为: type a N; 类型类型 数组名数组名 整型常数整型常数 ; 整型常数整型常数N用于指出数组的大小。用于指出数组的大小。 type表示表示int, float,double类型以及结构名和类类型名类型以及结构名和类类型名等。数组名遵循标识符的命名规定。等。数组名遵循标识符的命名规定。 数组的大小在编译时必须静态确定,除非建立动态堆内数组的大小在编译时必须静态确定,除非建立动态

23、堆内存数组。存数组。例如:例如: int n=8; int an; 是不允许的,是不允许的,而而 const int n=8;int an; 是可以的。是可以的。 18 方括号方括号 称为下标运算符,具有左结合性。称为下标运算符,具有左结合性。a j 称为下称为下标表达式,方括号标表达式,方括号 中的整型表达式中的整型表达式j为下标。为下标。一维数组一经定义有如下性质:一维数组一经定义有如下性质: a. 数组的元素个数,整型常数数组的元素个数,整型常数N给出数组的元素个数。给出数组的元素个数。 b. 数组的第数组的第j+1个元素表示为个元素表示为aj, 数组元素数组元素aj为左为左值,下标值,

24、下标j合理范围为合理范围为 0 N-1。aj可以当作变量一样使用。可以当作变量一样使用。a j 相当于访问指针形式相当于访问指针形式(*(a+j)。例如:。例如: a1=a0*(*(a+2) 等价于等价于 *(a+1)=*a*a2 /建议优先采用建议优先采用aj 下标表达式格式下标表达式格式 c.数组元素的类型由数组元素的类型由type指出,每个元素占住的字节数指出,每个元素占住的字节数为为n = sizeof (type) = sizeof (a j )。 19 d. 数组名数组名a携带两个信息:数组的首地址和数组的大携带两个信息:数组的首地址和数组的大小。首地址是小。首地址是type*型的

25、右值地址,型的右值地址,a为第为第1个元素的地址即个元素的地址即&a0,a+j指向指向a j 即关系式即关系式a + j = &a j 为真。为真。 数组大小为数组大小为: sizeof (a)= N *sizeof (*a)个字节。个字节。 N= sizeof (a)/ sizeof (a0)。 e.一维数组在内存中的每一元素是按照升序方向依次一维数组在内存中的每一元素是按照升序方向依次连续存放的。连续存放的。20 例如:例如:数组定义语句数组定义语句 char a8; long b2; short c4; int d8; 定义了数组定义了数组a,b,c,d,分别拥有,分别拥

26、有8,2,4,8个元素,个元素,a,b,c共共各分配各分配8字节存储单元,定义语句中的字节存储单元,定义语句中的8,2和和4用于界定数组用于界定数组的维数,最后一个元素各自是的维数,最后一个元素各自是 a7,b1,c3,d7。 a数组和数组和d数组都有数组都有8个元素但占有的内存空间是不一样个元素但占有的内存空间是不一样的。数组名的。数组名a,b,c,d依次是依次是char*,long*,short*,int*型的右值型的右值地址。地址。a8,b2,c4,d9是合法的表达式,但导致越界。是合法的表达式,但导致越界。 定义语句分配唯一的一片内存定义语句分配唯一的一片内存,下标表达式下标表达式a

27、i ,b j , ck,dn等用于访问或操作内存,编译器不检查下标等用于访问或操作内存,编译器不检查下标i,j,k,n是否越界。是否越界。 左值变量和数组元素占有内存单元,而右值地址用于寻左值变量和数组元素占有内存单元,而右值地址用于寻址计算系统并未分配内存。址计算系统并未分配内存。212.一维数组的初始化一维数组的初始化 定义数组时对数组的元素同时指定初始值称为数组的初定义数组时对数组的元素同时指定初始值称为数组的初始化始化,其格式为其格式为: type a N = initialList ; 类型类型 数组名数组名数组大小数组大小 = 初始化列表初始化列表; 初始化列表中的数据用逗号分隔,

28、初始化列表中的数据用逗号分隔, 其中的数据是可以转其中的数据是可以转换换type类型的常数表达式,类型的常数表达式,C+中也可以是求值的函数调中也可以是求值的函数调用。用。 方括号中的数组大小可省略,仅存在初始化列表才可以方括号中的数组大小可省略,仅存在初始化列表才可以略去数组的大小,此时系统根据初始化列表的个数确定数组略去数组的大小,此时系统根据初始化列表的个数确定数组的大小。的大小。 例如例如: float x=2, y=3; float b = y-x, 2, 3,4,x+y,6,7,8 ; 其中作为其中作为int型的常数型的常数2,3,4,6,7,8转换为转换为float型的数据。型的

29、数据。22 上面数组的定义与初始化在局部范围中几乎等价于下面上面数组的定义与初始化在局部范围中几乎等价于下面的两步即首先定义然后由赋值语句赋值:的两步即首先定义然后由赋值语句赋值: float b8; /定义一个维数为定义一个维数为8的的float型的一维数组型的一维数组 b 0 = 3-2; b 1 = 2; b 2 = 3; b 3 = 4; b 4 = 2+3; b 5 = 6; b 6 = 7; b 7 = 8; 允许初始化列表的个数少于数组的大小。此时前面的元允许初始化列表的个数少于数组的大小。此时前面的元素按初始化列表的顺序赋以初始值,后面缺省的元素为素按初始化列表的顺序赋以初始值

30、,后面缺省的元素为0,初始化列表的个数不可以大于数组的大小。初始化列表的个数不可以大于数组的大小。 23 初始化列表的个数小于数组的大小,这称为数组的定义初始化列表的个数小于数组的大小,这称为数组的定义与初始化列表截断赋值。与初始化列表截断赋值。 float b 4 = 1,1*2 ; /数组的定义与初始化列表截断赋值数组的定义与初始化列表截断赋值 数组的定义与初始化列表截断赋值等价于:数组的定义与初始化列表截断赋值等价于: float b4 = 1,2,0,0 ;类似地:类似地:int a2=0*2; int a2=0; int a2=0,0;即前面的元素具有指定的初始值,其余的元素为零。即

31、前面的元素具有指定的初始值,其余的元素为零。243.一维数组的内存映像和指针加减关系运算一维数组的内存映像和指针加减关系运算 type aN=1111,222,333,.,666,.,999; / 设设type为算术类型,为算术类型,N是已知的正数是已知的正数 type *p= a; 一级指针指向一维数组一级指针指向一维数组 一维数组的内存映像一维数组的内存映像确保指针或下标不要越界确保指针或下标不要越界n=sizeof (type)指针向后移动指针向后移动j个位置个位置 p=a; p+= j; 元素的地址元素的地址 hhhh hhhh+1n hhhh+2n : hhhh +jn : hhhh

32、 +Nn -n 一维数组元素一维数组元素 a0 a1 a2 : aj : aN-1 元素的值元素的值 1111 2222 3333 : 6666 : 999925 不同类型的指针不允许不同类型的指针不允许(,=,=,=,!=)关系比较运关系比较运算,同类型的指针可以,比较的是指针的地址大小。算,同类型的指针可以,比较的是指针的地址大小。 通常对指向同一数组元素的指针执行关系运算,相当于通常对指向同一数组元素的指针执行关系运算,相当于比较数组元素的下标。两个指针指向同一元素则相等关系比较数组元素的下标。两个指针指向同一元素则相等关系=比较为真,否则为假;比较为真,否则为假; 指向不同的元素不相等

33、关系指向不同的元素不相等关系!=比较为真。指针可以与比较为真。指针可以与0比较相等关系比较相等关系=和不相等关系和不相等关系!=。 pq为真表示为真表示p指向内存的低地址,指向内存的低地址,q指向内存的高地指向内存的高地址。址。26 仅当指针指向数组或实参数组占有的内存空间时,指针仅当指针指向数组或实参数组占有的内存空间时,指针才进行加减运算,否则访问指针时导致运行错误。才进行加减运算,否则访问指针时导致运行错误。 其规则为:其规则为: 加法操作中仅允许一个操作数为定位内存的地址表达加法操作中仅允许一个操作数为定位内存的地址表达式,另一个操作数为整型操作数,例如式,另一个操作数为整型操作数,例

34、如 p+j 或或 j+p 。 减法操作中允许操作数减法操作中允许操作数1为定位地址的指针,操作数为定位地址的指针,操作数2为整型操作数,为整型操作数,例如例如: p-j。 指针型的操作数指针型的操作数p给出变量的存储位置,整型操作数给出变量的存储位置,整型操作数j存存放着相对于该存储位置的偏移。放着相对于该存储位置的偏移。 表示指针表示指针p从当前位置前移或后移从当前位置前移或后移j个类型步长。个类型步长。27 一维数组在内存中的每一元素是按照升序方向依次连续一维数组在内存中的每一元素是按照升序方向依次连续存放的。存放的。 令令n= sizeof (type),a的地址系统确定为的地址系统确定

35、为hhhh,则,则 a+j的地址值为的地址值为 hhhh+j*n,同样地,同样地 p=a; p+= j; 导致导致p的值为的值为hhhh+j*n。 type*型指针位移步长增量为型指针位移步长增量为sizeof(type),p+向前移向前移动一个位移步长即动一个位移步长即p从当前位置向后移动从当前位置向后移动n个字节,也就是值个字节,也就是值向下一个元素。向下一个元素。 减法操作中允许两个操作数为指向同一类型数据(通常减法操作中允许两个操作数为指向同一类型数据(通常是数组元素)的指针,相减的结果为有符号的整数,通常表是数组元素)的指针,相减的结果为有符号的整数,通常表示两个存储位置之间的元素个

36、数。示两个存储位置之间的元素个数。 28 同类型的两个指针同类型的两个指针p2与与p1相减结果根据公式相减结果根据公式k=(p2-p1)/类型步长类型步长确定。确定。 两个指针相加两个指针相加p1+p2是非法的操作运算。是非法的操作运算。 当当p是指针时对于是指针时对于p+=j, p-=j型的复合赋值运算,表达式型的复合赋值运算,表达式j仅允许是整型,整数仅允许是整型,整数j存放着相对于存储位置的偏移,表示存放着相对于存储位置的偏移,表示指针指针p从当前位置前移或后移从当前位置前移或后移j个类型步长。个类型步长。 p+j或或p-j或或p+=j, p-=j的类型属性是地址表达式的类型属性是地址表

37、达式p的类型的类型属性。属性。例如:例如: long b10; long *lp=b; lp+=9; lp-=2; 29 关系式关系式 type v; type* p=&v; 表示表示p当前关联一个当前关联一个离散变量,在指针离散变量,在指针p未进行新的关联时不要对未进行新的关联时不要对p执行加减运执行加减运算,此时导致对离散变量的越界。算,此时导致对离散变量的越界。 指针指针p关联到数组时确保指针或下标不要越界。关联到数组时确保指针或下标不要越界。 指针的加减本身不引起错误,在访问越界的指针时才导指针的加减本身不引起错误,在访问越界的指针时才导致错误。致错误。 越界的含义是指在没有定

38、义的内存空间,指针进行了形越界的含义是指在没有定义的内存空间,指针进行了形如如pk或或*p的寻址访问计算。的寻址访问计算。 30 假设假设p是一个是一个type*型的地址型的地址, k是一个整数,如下四个是一个整数,如下四个表达式是相等的:表达式是相等的: pk *(p+ k) *( k +p) kp 它们均表示距基地址它们均表示距基地址p偏移偏移k*sizeof(type)个字节的地个字节的地址位置中的元素,为左值。址位置中的元素,为左值。 如下四个地址表达式也是等价的如下四个地址表达式也是等价的: &pk p+k k+p &kp都是指向数组的元素都是指向数组的元素pk的存储

39、位置,为右值。的存储位置,为右值。31 例例指针自增运算访问数组元素指针自增运算访问数组元素 指针自增或自减运算时,系统把指针直接向前或向后移指针自增或自减运算时,系统把指针直接向前或向后移动一个单位,以指向相邻的另一个存储单元。动一个单位,以指向相邻的另一个存储单元。 指针后置运算的机会最为频繁,后置运算表达式指针后置运算的机会最为频繁,后置运算表达式L+的的结果是变化前的值,然后才使左操作数的值变动结果是变化前的值,然后才使左操作数的值变动1个单位,个单位,结果为右值表达式。结果为右值表达式。 如先用下面的语句定义如先用下面的语句定义int型的变量型的变量 y,z,以及,以及int*型的型

40、的指针指针 r, p为:为: int y, z,*r, *p; 32 设指针已经指向数组的一片内存空间,加减运算不引起设指针已经指向数组的一片内存空间,加减运算不引起越界。分析一下有代表性的四个表达式语句:越界。分析一下有代表性的四个表达式语句: r = p+; y = *p+; z = (*p)+; *p+ = *r+; 表达式语句表达式语句r=p+;相当于下面的几个表达式语句:相当于下面的几个表达式语句: int* ptemp = p; p+ = 1; r = ptemp; 确切地确切地r = p+;可等价地分解为可等价地分解为 r = p; p+;表达式表达式r = p+完成两个运算,完

41、成两个运算,r 指向指向p原先的位置原先的位置, p指向下一个元指向下一个元素的存储单元。素的存储单元。33 表达式表达式y=*p+是非常洗练的,根据优先级可知后自是非常洗练的,根据优先级可知后自增运算符增运算符+的优先级别高于访问指针运算符的优先级别高于访问指针运算符*的优先级别的优先级别,因因此此y = *p+等价于等价于y = *(p+)。 相当于下面的几个步骤:相当于下面的几个步骤: int* ptemp = p; p+ = 1; y = *ptemp; 或者说或者说y=*p+;等价于等价于 y=*p; p+;,因此,因此y首先得首先得到的是指针原先位置的内容。到的是指针原先位置的内容

42、。 然后指针然后指针p发生了改变,移动到下一个元素的位置。发生了改变,移动到下一个元素的位置。34 表达式表达式z=(*p)+由于使用了圆括号,因此相当于下面由于使用了圆括号,因此相当于下面几个步骤:几个步骤: int temp=(*p); (*p) + = 1 ; z= temp; 或者说或者说z=(*p)+;等价于等价于 z=*p; (*p)+;,此时,此时z得到的是得到的是指针原先位置的单元内容指针原先位置的单元内容, 指针指针p没有变化。没有变化。 原先位置的单元内容则发生了增值原先位置的单元内容则发生了增值1的变化。的变化。z=(*p)+就是就是z=p0+。 表达式表达式*p+=*r

43、+等价于等价于*(p+)=*(r+)。这个表达式。这个表达式构成的语句构成的语句: *p+=*r+;相当于相当于 *p=*r; r+; p+; 即右操作数指针原先指向的单元内容赋值给左操作数指即右操作数指针原先指向的单元内容赋值给左操作数指针原先指向的单元。然后两个指针同时向后移动一个类型步针原先指向的单元。然后两个指针同时向后移动一个类型步长。长。 35# include void main (void) int a =1,2,3,4; int y, z,*r, *p=a; if (r=p+); printf (r=%p,a=%p,p=%pn,r,a,p); y = *p+; printf

44、(a0=%d,a1=%d,%pt,*r,y,p); if (z = (*p)+); printf (a2=%d,a2=%d,%pt,z,a2,p); *p+ = *r+; printf (a2=%d,a3=%d,%pn,a2,*p,p); / r=0065FDE8,a=0065FDE8,p=0065FDEC36 以下表达式是容易产生副作用的,一般应回避。例如典以下表达式是容易产生副作用的,一般应回避。例如典型的例子是型的例子是: a i = i+;, a i = i+; 可分解可分解 a i = i; i+;也可分解也可分解 int k = i; i+; a i =k; 。 数组下标数组下标i

45、取旧值还是新值是不确定的。因此不要在循取旧值还是新值是不确定的。因此不要在循环条件表达式中采用上面复杂的表达式,例如:环条件表达式中采用上面复杂的表达式,例如: for(; *p=*r+; ) r i =i;与与 while (*p=*r+) y = *p+;之类语句幕后分解次序是不确定的。之类语句幕后分解次序是不确定的。37下标表示下标表示a0a1a2a3a4a5a6下标表示下标表示q1q2q3q4q5q6q7元素的值元素的值 11 223344556677访问指针访问指针*p*(p+1)*(p+2)*(p+3)*(p+4)*(p+5)*(p+6)地址偏移地址偏移pp+1p+2p+3p+4p

46、+5p+6地址偏移地址偏移&a0&a1&a2&a3&a4&a5&a6地址的值地址的值hhhhhhhh+4hhhh+8hhhh+12hhhh+16hhhh+20hhhh+24下标表示法与访问指针形式下标表示法与访问指针形式(32位寻址位寻址)38 下标表达式下标表达式a0表示表示a的第一个元素的值,类似地表达的第一个元素的值,类似地表达式式a5给出的是偏移给出的是偏移a为为5个位置的元素或该数组的第个位置的元素或该数组的第6个元个元素。素。 上面的下标表达式的索引有两种方式:上面的下标表达式的索引有两种方式: 一种是数组名的索引一种是数组名

47、的索引ak; 一种是指针名一种是指针名(q=a-1)的索引的索引qk+1。 数组名数组名a和指针名和指针名q,p都是同类型都是同类型(long*型型)的地址表达的地址表达式,这是它们值得强调的共同特点。这种共同的特点是两种式,这是它们值得强调的共同特点。这种共同的特点是两种索引方式存在并且可以互换的前提。差别在于数组名索引方式存在并且可以互换的前提。差别在于数组名a是右是右值,指针值,指针p,q是左值。是左值。 39 a0总是索引数组总是索引数组a的第一个元素,而的第一个元素,而q0,p0索引的索引的内存数据根据内存数据根据q,p的指向确定。数组定义语句例如的指向确定。数组定义语句例如: lo

48、ng a7;分配一片大小为分配一片大小为4*7=28字节的内存区。占有字节的内存区。占有2或或4字节内存字节内存的指针索引数组的内存。的指针索引数组的内存。 在在p=a期间期间pk等价于等价于ak,即对即对pk的操作就是对的操作就是对ak的等价操作。的等价操作。 如果如果p是形参,是形参,p通过通过pk或或*p方式访问内存,导致直接方式访问内存,导致直接操作相应的实参数组。操作相应的实参数组。 404.4.函数的数组形参和指针形参函数的数组形参和指针形参 在函数形参中的数组仅占有在函数形参中的数组仅占有2或或4字节的堆栈空间,在被字节的堆栈空间,在被调函数体中并未分配额外的内存。它实际访问的存

49、储单元由调函数体中并未分配额外的内存。它实际访问的存储单元由相应的实参数组提供,形参在函数中直接操作相应的实参数相应的实参数组提供,形参在函数中直接操作相应的实参数组。数组形参通过虚实结合得到实参的基准定位地址,直接组。数组形参通过虚实结合得到实参的基准定位地址,直接使用实参数组的内存单元。使用实参数组的内存单元。 实参数组由数组定义语句实参数组由数组定义语句 type aN, dr c ; type* pa N;或或new运算符或运算符或malloc函数等分配内存。一函数等分配内存。一个函数实现存在两种变通的等价的函数原型:个函数实现存在两种变通的等价的函数原型: long f (long

50、p ,.); long f (long *p,.);41 形如形如“long p ”的形参为数组形参或出现在形参中的的形参为数组形参或出现在形参中的数数组。组。 形如形如“long *p”的形参为指针形参。的形参为指针形参。 数组形参和指针形参在入口的情形无任何差异,两者都数组形参和指针形参在入口的情形无任何差异,两者都视为视为long*型的表达式。型的表达式。 函数定义函数定义long f (long *p,.) ;.;可等价地改写为可等价地改写为 long f (long p ,.) 函数体代码不变函数体代码不变 两个函数原型具有同样的类型抽象形式两个函数原型具有同样的类型抽象形式long

51、 f(long*) 或或long f (long )。概念上数组形参要求实参数组的内存分。概念上数组形参要求实参数组的内存分配,而指针形参表明定位内存地址的基准特性。配,而指针形参表明定位内存地址的基准特性。42例例数组形参的大小形同虚设,数组形参的大小形同虚设,p是指针变量是指针变量,而而a表表示数组首元素的右值地址示数组首元素的右值地址 #include void f (double p 8) printf (sizeof (p)=%d; , sizeof (p); p+; void main (void) double a 8; f (a); printf (sizeof (a)=%dn

52、, sizeof (a); /输出:输出:sizeof(p)=4; sizeof (a)=6443例例求内存空间连续求内存空间连续n个元素的和个元素的和 #include long sum ( long x ,int n); long sum(long 8,int); /函数原型中的函数原型中的8不起作用不起作用 void main() / sum(a,n) 求区间求区间1,2,3,4的和的和10 long a = 1,2,3,4; const int n = sizeof (a)/ sizeof (*a); / sum(&a2,n-2) 求区间求区间3,4的和的和7 long *q=

53、&a1; /函数调用函数调用sum(a,n+1),sum(q,n)导致越界导致越界 printf (%d%, %d, %dn, sum(a,n),sum(q,n-1), sum(&a2,n-2); /输出:输出:10,9,744 long sum(long *p,int n) /函数定义为一级指针形参函数定义为一级指针形参 long s=0 ; /p+指向指向long型数组的下一个元素型数组的下一个元素 for (int i=0; in; p+, i+) s+=*p; /*p得到该位置元素的值得到该位置元素的值 return s; /s+=*p累积地加上这个元素累积地加上这个元

54、素 /sum返回以入口指针定位的其后返回以入口指针定位的其后n个元素的和个元素的和45 说明:说明:入口形参入口形参long p 和和long* p是等价的是等价的,即前即前面求和的函数可以等价地改写为面求和的函数可以等价地改写为: long sum (long p , int n) long s=0 ; for (int i=0; in; p+, i+) s+=*p; return s; for (int i=0; in; p+, i+) s+=*p;可以改为可以改为: for (int i=0; in; i+) s+ = pi;但前者效率高。但前者效率高。 long*型的形参要求匹配的实参是型的形参要求匹配的实参是long*型的右值地址表型的右值地址表达式。达式。 数组名数组名a指针指针q和和 &a2是是long*型的表达式,满足虚实型的表达式,满足虚实结合类型匹配要求。结合类型匹配要求。46 函数调用函数调用sum(q,n-1) 在虚实结合时导致在虚实结合时导致q的值赋给形参的值赋给形参p,此时形参,此时形参p即指向即指向q所指的内存空间,在所指的内存空间,在sum函数中关于函数中关于形参形参p的操作,就

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论