1
一、指针的概念二、一级指针和指针的操作三、指针与间接变量四、一维数组
2
一、指针的概念指针是一种定位内存单元地址的无符号整型数据,简单地说指针是地址的变量。内存单元保存变量的数据状态,地址表明变量的存储所在。通过建立指针和变量之间的联系,
可以访问内存单元,获取或改变内存单元的数据状态
long i,j,k ; float x,y,z; double d;
&i=0065FD00 &x= 0065FD0C &d=0065FD18
低地址 高地址
4字节 i j k 4字节 x y z d 4字节
3
指针性质:
1.指针的值。指针的值本身是无符号的整数,16位寻址方式指针的值占两个内存字节,32位寻址方式指针的值占四个字节。指针必须初始化,才可以进行访问操作运算,也允许重新赋值 ;对未初始化的指针进行操作容易导致运行错误,
2,指针的类型属性。指针的类型属性是指针访问内存空间的寻址性质,它限定指针的增减以类型步长为单位。不同类型的指针之间不许隐含类型转换。而算术型变量允许。
3,指针的地址属性。指针是地址的变量,因此系统同样为其分配内存单元,指针占有的内存相当于一个 int型变量占有的内存。变量的地址是右值,指针的地址也是一个右值。
4
二、一级指针和指针的操作
1.一级指针的定义一级指针是指向变量或数组元素的指针,一级指针即 type*型的指针定义格式为:
type* p,*p1,…,*pn;
类型 * 指针名,*指针名 1,…* 指针名 n;
指针的初始赋值格式为,p=变量地址 ;
指针初始化定义语句的格式为:
type* p = 左值内存区的地址 ;
类型 * 指针名 =变量地址;
用于初始化的变量地址是与所定义的指针同类型的地址值,指向可读写的内存区。如上语句定义的 p,p1,p2,pn就是 type*型指针。 type*型指针专门指向 type型的变量或数组,
5
2.取地址运算符 &和访问指针运算符 *
C/C++提供了两个与内存地址相关的单目运算符,一个是取地址运算符 &,另一个是访问指针运算符 *。
取地址运算符 &的操作数一般为左值,记为,
&Lvalue
&Lvalue的意义为取左值对应的存储地址,其结果为一右值。 &(x+y),&(x*y)等是错误的。 x+y,x*y是右值。
左值 Lvalue为 type型的变量,&Lvalue的结果为 type*
型的地址。
左值 Lvalue为 type*型的指针,&Lvalue的结果为
type**型的地址。
简单地说取地址运算进行的是升维处理,把变量升为一级指针,把一级指针升为二级指针,在运算中把左值变为右值。
6
访问指针运算符 *的操作数 ep仅为地址表达式,访问指针记为 *ep。
如果 ep为 type*型的地址,*ep的结果为 type型的左值; ep为 type**型的地址,*ep的结果为 type*型的左值,此时访问指针 *ep的意义为取指针所指的内容或访问指针指向的存储单元。
如果 ep是指向二维数组的指针,*ep为 type*型的右值,此时 *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;
//仅 q为 double*型的指针,x为 double型的变量
double *p=&x;
//定义 p为 double*型的指针,初始化为指向 x
8
初始化定义语句 {double *p=&x;}在局部范围可以分解为 {double *p; p=&x;},但表达式 *p=&x则是错误的,*p是
double型的左值,&x是 double*型的地址,两边类型不匹配。
&k=kp是错误的,&k 是 int*型的右值。
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型数据是可以的,反过来 *kp=*p将 double型数据截断转换为 int型数据导致精度损失。
9
三、指针与间接变量
1.间接变量当 p是 type*型的指针时,*p的值是当前指针所指内存单元的数据状态,可以视 *p是 type型的变量,本书 称 *p 为 p所指内存的间接变量。
定义语句 [ type v,*p = &v;]或函数原型与函数调用
[void f (type* p); f(&v);] 将指针 p关联变量 v,*p是变量 v
的间接变量。
在有效关联期间对于间接变量 *p的操作就是对于变量 v
的等价操作。
指针 p如果未指向任何变量,则不要访问这样的指针。
此时对 *p的操作导致运行时的错误。
指针必须首先有效的关联,然后才存在对间接变量的合法运算。
10
函数定义 void f (type* p) {...}中的 p是定义在堆栈空间的指针形参。
下面代码表示取地址运算符 &与访问指针运算符 *的简单 例子:
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 访问指针指向的变量,等价于 z=x+y;
11
注意:
*p=x改变间接变量的值,p=&z或 p+=j,p++等改变指针
p本身的值。
访问指针运算符 *与取地址运算符 &可看作不具有交换律的互逆运算符。即:
Lvalue = *(&Lvalue); p = &(*p);
指针的引用是指针的别名。
指针用右值地址初始化,指针的引用由同类型的左值指针初始化。对指针引用的操作是对相关指针的等价操作。
声明指针引用的语句为:
类名 *& 指针引用名 =指针名 ;
type* & rp = p;
12
[例 ]指针作为函数的形参,指针形参可改变其指向的实参变量。
#include <stdio.h>
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*型指针,指向 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,%d\t",*pa,*pb);
f (pa,qb);
//函数调用 f(pa,qb);相当于 *pa+=1; *pb+=2;
*qa+ =1; rb+ = 2; f (&a,&b);
//虚实结合导致 x=&a,y=&b
printf ("%d,%d\t",a,b);
} //输出,1,2 4,8
函数调用 f (&a,&b)导致指针形参 x,y分别指向 a,b,即:
*x,*y分别是 a,b的间接变量,对 *x,*y的操作就是对相应变量
a,b的等价操作。类似的分析适用于 f (pa,qb)。
14
2.空指针,
整数 0可以初始化指针,空指针就是其值为 0的指针,
含义为该指针不表示指向,而是表示指针的一种约束状态,
常用于判断指针的开关或返回指针的函数成功与否的标志。
#define NULL 0 //C++中 NULL定义为 0
double* lp=0;
//声明 lp为 double*类型的指针,初始化为空指针。
或 double* lp = NULL ;
指针 lp的值为零,该指针此时尚未与同类型变量的地址发生关联,也不指向系统的其它地址特别是重要数据区的地址。将 0直接赋值给指针等价于指针赋值以 NULL。
指针只能指向编译器为变量或函数入口分配的内存空间,指针仅在指向内存空间之后才可以进行访问指针运算。
变量的值可由用户随意指定。
15
不能将非 0整数赋给指针即试图自行分配非零地址值是非法的。
如下非法指定指针初始值的语句是编程时的大忌,
long * p=10;
p=0x40601234;
p=0x6500ffde;
char *q; scanf ("%d",&q);
//scanf ("%d",&q)表示从屏幕读取值送入 q占有的内存单元
char *s; scanf ("%s",s);
// scanf (“%s”,s)表示从屏幕读取文本串送入 s指向的内存区指针 s没有初始化,scanf ("%s",s)中对于指针 s的操作是危险的。
指针的初始化或者由初始化定义语句或赋值表达式或函数调用虚实结合完成。
16
指针的初始值追根逆源一般地须与,
取地址运算符;?一维数组名 a;?函数名;
new运算符;?内存分配函数;?字符串常数;
多维数组下标表达式等相联系:
pointer = &Lvalue;? pointer = a+j ;
pointer = function;
pointer = new type[size];
pointer = (type*)malloc (N*sizeof (type));
pointer="字符串常数 ";
pointer=d[k]+i;
17
四、一维数组
1.一维数组的定义数组的作用域和生存期与变量的规则相同 。 静态的一维数组的定义格式为:
type a [N]; 类型 数组名 [ 整型常数 ];
整型常数 N用于指出数组的大小。
type表示 int,float,double类型以及结构名和类类型名等。数组名遵循标识符的命名规定。
数组的大小在编译时必须静态确定,除非建立动态堆内存数组。 例如,{int n=8; int a[n]; } 是不允许的,
而 {const int n=8;int a[n];} 是可以的。
18
方括号 [ ]称为下标运算符,具有左结合性。 a[ j ]称为下标表达式,方括号 [ ]中的整型表达式 j为下标。
一维数组一经定义有如下性质:
a,数组的元素个数,整型常数 N给出数组的元素个数。
b,数组的第 j+1个元素表示为 a[j],数组元素 a[j]为左值,下标 j合理范围为 0~ N-1。 a[j]可以当作变量一样使用。
a[ j ]相当于访问指针形式 (*(a+j))。例如:
a[1]=a[0]*(*(a+2) 等价于 *(a+1)=*a*a[2]
//建议优先采用 a[j] 下标表达式格式
c.数组元素的类型由 type指出,每个元素占住的字节数为 n = sizeof (type) = sizeof (a[ j ])。
19
d,数组名 a携带两个信息:数组的首地址和数组的大小。首地址是 type*型的右值地址,a为第 1个元素的地址即
&a[0],a+j指向 a [ j ]即关系式 a + j == &a[ j ]为真。
数组大小为,
sizeof (a)= N *sizeof (*a)个字节。
N= sizeof (a)/ sizeof (a[0])。
e.一维数组在内存中的每一元素是按照升序方向依次连续存放的。
20
例如,数组定义语句
{char a[8]; long b[2]; short c[4]; int d[8]; }
定义了数组 a,b,c,d,分别拥有 8,2,4,8个元素,a,b,c共各分配 8字节存储单元,定义语句中的 8,2和 4用于界定数组的维数,最后一个元素各自是 a[7],b[1],c[3],d[7]。
a数组和 d数组都有 8个元素但占有的内存空间是不一样的。数组名 a,b,c,d依次是 char*,long*,short*,int*型的右值地址。 a[8],b[2],c[4],d[9]是合法的表达式,但导致越界。
定义语句分配唯一的一片内存,下标表达式 a[ i ],b[ j ],
c[k],d[n]等用于访问或操作内存,编译器不检查下标 i,j,k,n
是否越界。
左值变量和数组元素占有内存单元,而右值地址用于寻址计算系统并未分配内存。
21
2.一维数组的初始化定义数组时对数组的元素同时指定初始值称为数组的初始化,其格式为,
type a[ N ] = { initialList };
类型 数组名 [数组大小 ] = {初始化列表 };
初始化列表中的数据用逗号分隔,其中的数据是可以转换 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型的数据。
22
上面数组的定义与初始化在局部范围中几乎等价于下面的两步即首先定义然后由赋值语句赋值:
float b[8];
//定义一个维数为 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;
允许初始化列表的个数少于数组的大小。此时前面的元素按初始化列表的顺序赋以初始值,后面缺省的元素为 0,
初始化列表的个数不可以大于数组的大小。
23
初始化列表的个数小于数组的大小,这称为数组的定义与初始化列表截断赋值。
float b[ 4 ] = { 1,1*2 };
//数组的定义与初始化列表截断赋值数组的定义与初始化列表截断赋值等价于:
float b[4 ] = { 1,2,0,0 };
类似地:
int a[2]={0*2}; int a[2]={0}; int a[2]={0,0};
即前面的元素具有指定的初始值,其余的元素为零。
24
3.一维数组的内存映像和指针加减关系运算
type a[N]={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 +Nn -n
一维数组元素
a[0]
a[1]
a[2]

a[j]

a[N-1]
元素的值
1111
2222
3333

6666

9999
25
不同类型的指针不允许 (<,>,>=,<=,==,!=)关系比较运算,同类型的指针可以,比较的是指针的地址大小。
通常对指向同一数组元素的指针执行关系运算,相当于比较数组元素的下标。两个指针指向同一元素则相等关系 ==
比较为真,否则为假;
指向不同的元素不相等关系 !=比较为真。指针可以与 0
比较相等关系 ==和不相等关系 !=。
p<q为真表示 p指向内存的低地址,q指向内存的高地址。
26
仅当指针指向数组或实参数组占有的内存空间时,指针才进行加减运算,否则访问指针时导致运行错误。
其规则为:
加法操作中仅允许一个操作数为定位内存的地址表达式,另一个操作数为整型操作数,例如 p+j 或 j+p 。
减法操作中允许操作数 1为定位地址的指针,操作数 2
为整型操作数,例如,p-j。
指针型的操作数 p给出变量的存储位置,整型操作数 j存放着相对于该存储位置的偏移。
表示指针 p从当前位置前移或后移 j个类型步长。
27
一维数组在内存中的每一元素是按照升序方向依次连续存放的。
令 n= sizeof (type),a的地址系统确定为 hhhh,则 a+j
的地址值为 hhhh+j*n,同样地 { p=a; p+= j; }导致 p的值为
hhhh+j*n。
type*型指针位移步长增量为 sizeof(type),p++向前移动一个位移步长即 p从当前位置向后移动 n个字节,也就是值向下一个元素。
减法操作中允许两个操作数为指向同一类型数据(通常是数组元素)的指针,相减的结果为有符号的整数,通常表示两个存储位置之间的元素个数。
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的类型属性是地址表达式 p的类型属性。 例如:
long b[10];
long *lp=b;
lp+=9;
lp-=2;
29
关系式 {type v; type* p=&v; } 表示 p当前关联一个离散变量,在指针 p未进行新的关联时不要对 p执行加减运算,此时导致对离散变量的越界。
指针 p关联到数组时确保指针或下标不要越界。
指针的加减本身不引起错误,在访问越界的指针时才导致错误。
越界的含义是指在没有定义的内存空间,指针进行了形如 p[k]或 *p的寻址访问计算。
30
假设 p是一个 type*型的地址,k是一个整数,如下四个表达式是相等的:
p[k]? *(p+ k)?*( k +p)?k[p]
它们均表示距基地址 p偏移 k*sizeof(type)个字节的地址位置中的元素,为左值。
如下四个地址表达式也是等价的,
&p[k]? p+k? k+p? &k[p]
都是指向数组的元素 p[k]的存储位置,为右值。
31
[例 ]指针自增运算访问数组元素指针自增或自减运算时,系统把指针直接向前或向后移动一个单位,以指向相邻的另一个存储单元。
指针后置运算的机会最为频繁,后置运算表达式 L++的结果是变化前的值,然后才使左操作数的值变动 1个单位,
结果为右值表达式。
如先用下面的语句定义 int型的变量 y,z,以及 int*型的指针 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++完成两个运算,r 指向 p原先的位置,p指向下一个元素的存储单元。
33
表达式 y=*p++是非常洗练的,根据优先级可知后自增运算符 ++的优先级别高于访问指针运算符 *的优先级别,因此 y = *p++等价于 y = *(p++)。
相当于下面的几个步骤:
int* ptemp = p;
p+ = 1;
y = *ptemp;
或者说 {y=*p++;}等价于 { y=*p; p++;},因此 y首先得到的是指针原先位置的内容。
然后指针 p发生了改变,移动到下一个元素的位置。
34
表达式 z=(*p)++由于使用了圆括号,因此相当于下面几个步骤:
int temp=(*p); (*p) + = 1 ; z= temp;
或者说 {z=(*p)++;}等价于 { z=*p; (*p)++;},此时 z得到的是指针原先位置的单元内容,指针 p没有变化。
原先位置的单元内容则发生了增值 1的变化。 z=(*p)++
就是 z=p[0]++。
表达式 *p++=*r++等价于 *(p++)=*(r++)。这个表达式构成的语句,*p++=*r++;
相当于 { *p=*r; r++; p++; }
即右操作数指针原先指向的单元内容赋值给左操作数指针原先指向的单元。然后两个指针同时向后移动一个类型步长。
35
# include<stdio.h>
void main (void)
{ int a[ ]={1,2,3,4};
int y,z,*r,*p=a;
if (r=p++);
printf ("r=%p,a=%p,p=%p\n",r,a,p);
y = *p++;
printf ("a[0]=%d,a[1]=%d,%p\t",*r,y,p);
if (z = (*p)++);
printf ("a[2]=%d,a[2]=%d,%p\t",z,a[2],p);
*p++ = *r++;
printf ("a[2]=%d,a[3]=%d,%p\n",a[2],*p,p);
}
// r=0065FDE8,a=0065FDE8,p=0065FDEC
36
以下表达式是容易产生副作用的,一般应回避。例如典型的例子是,
a[ i ] = i++;,{ a [ i ] = i++; }
可分解 {a [ i ] = i; i++;}
也可分解 {int k = i; i++; a [ i ]=k; }。
数组下标 i取旧值还是新值是不确定的。因此不要在循环条件表达式中采用上面复杂的表达式,例如:
{ for(; *p=*r++; ) r [ i ]=i;}
与 { while (*p=*r++) y = *p++;}
之类语句幕后分解次序是不确定的。
37
下标表示 a[0] a[1] a[2] a[3] a[4] a[5] a[6]
下标表示 q[1] q[2] q[3] q[4] q[5] q[6] q[7]
元素的值 11 22 33 44 55 66 77
访问指针 *p *(p+1) *(p+2) *(p+3) *(p+4) *(p+5) *(p+6)
地址偏移 p p+1 p+2 p+3 p+4 p+5 p+6
地址偏移 &a[0] &a[1] &a[2] &a[3] &a[4] &a[5] &a[6]
地址的值 hhhh hhhh+4 hhhh+8 hhhh+12 hhhh+16 hhhh+20 hhhh+24
下标表示法与访问指针形式 (32位寻址 )
38
下标表达式 a[0]表示 a的第一个元素的值,类似地表达式 a[5]给出的是偏移 a为 5个位置的元素或该数组的第 6个元素。
上面的下标表达式的索引有两种方式:
一种是数组名的索引 a[k];
一种是指针名 (q=a-1)的索引 q[k+1]。
数组名 a和指针名 q,p都是同类型 (long*型 )的地址表达式,这是它们值得强调的共同特点。这种共同的特点是两种索引方式存在并且可以互换的前提。差别在于数组名 a是右值,指针 p,q是左值。
39
a[0]总是索引数组 a的第一个元素,而 q[0],p[0]索引的内存数据根据 q,p的指向确定。数组定义语句例如,
{ long a[7];}
分配一片大小为 4*7=28字节的内存区。占有 2或 4字节内存的指针索引数组的内存。
在 p=a期间 p[k]等价于 a[k],即对 p[k]的操作就是对 a[k]
的等价操作。
如果 p是形参,p通过 p[k]或 *p方式访问内存,导致直接操作相应的实参数组。
40
4.函数的数组形参和指针形参在函数形参中的数组仅占有 2或 4字节的堆栈空间,在被调函数体中并未分配额外的内存。它实际访问的存储单元由相应的实参数组提供,形参在函数中直接操作相应的实参数组。数组形参通过虚实结合得到实参的基准定位地址,直接使用实参数组的内存单元。
实参数组由数组定义语句 {type a[N],d[r] [c] ;
type* pa [N];}或 new运算符或 malloc函数等分配内存。一个函数实现存在两种变通的等价的函数原型:
long f (long p[ ],...); long f (long *p,...);
41
形如,long p[ ]”的形参为数组形参或出现在形参中的数组。
形如,long *p”的形参为指针形参。
数组形参和指针形参在入口的情形无任何差异,两者都视为 long*型的表达式。
函数定义 long f (long *p,...) {;...;}可等价地改写为
long f (long p[ ],...) {函数体代码不变 }
两个函数原型具有同样的类型抽象形式 long f(long*)
或 long f (long [ ])。概念上数组形参要求实参数组的内存分配,而指针形参表明定位内存地址的基准特性。
42
[例 ]数组形参的大小形同虚设,p是指针变量,而 a表示数组首元素的右值地址
#include<stdio.h>
void f (double p [8])
{ printf ("sizeof (p)=%d; ",sizeof (p)); p++; }
void main (void)
{ double a [8];
f (a);
printf ("sizeof (a)=%d\n",sizeof (a));
}
//输出,sizeof(p)=4; sizeof (a)=64
43
[例 ]求内存空间连续 n个元素的和
#include<stdio.h>
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(&a[2],n-2) 求区间 {3,4}的和 7
long *q=&a[1];
//函数调用 sum(a,n+1),sum(q,n)导致越界
printf ("[%d%,%d,%d]\n",sum(a,n),sum(q,n-1),
sum(&a[2],n-2));
}
//输出,[10,9,7]
44
long sum(long *p,int n)
//函数定义为一级指针形参
{ long s=0 ;
//p++指向 long型数组的下一个元素
for (int i=0; i<n; p++,i++) s+=*p;
//*p得到该位置元素的值
return s; //s+=*p累积地加上这个元素
}
//sum返回以入口指针定位的其后 n个元素的和
45
说明,入口形参 "long p[ ]"和 "long* p"是等价的,即前面求和的函数可以等价地改写为,
long sum (long p[ ],int n)
{ long s=0 ; for (int i=0; i<n; p++,i++) s+=*p;
return s;
}
for (int i=0; i<n; p++,i++) s+=*p;
可以改为,for (int i=0; i<n; i++) s+ = p[i];但前者效率高。
long*型的形参要求匹配的实参是 long*型的右值地址表达式。
数组名 a指针 q和 &a[2]是 long*型的表达式,满足虚实结合类型匹配要求。
46
函数调用 sum(q,n-1) 在虚实结合时导致 q的值赋给形参
p,此时形参 p即指向 q所指的内存空间,在 sum函数中关于形参 p的操作,就是对于 q指向内存空间的等价操作。
指针 p在被调函数中的变化根据数值形参传递机制对于实参指针 q没有影响,可以放心地借用指针形参提供的堆栈单元进行指针对实参数组空间的遍历。
而不必在当前函数体中另外定义局部指针,占用多余的内存。
47
[例 ]将数组 反序排放为反序排放的关系为 s[0]和 s[n-1]互换,s[1]和 s[n-2]互换,
s[i]和 s[n-1-i]互换,..。
互换变量 a和 b的代码记为,
swap (a,b)? { t=a; a=b; b=t; }
则互换数组元素 s[ i ],s[ j ]为,
{ t= s[ i ]; s[ i ] = s[ j ]; s[ j ] = t; }
互换间接变量 *p和 *e记为,
swap(p,e)? { t=*p; *p=*e; *e=t; }
p和 e是指向内存变量的指针。如果 p=&a,e=&b,则
swap (p,e)等效于 swap (a,b)。
s s s sn n0 1 2 1,,, s s s sn n1 2 1 0,,,,?
48
如果 p = &s [ i ],e = &s [ j ],则 swap (p,e)等效于
swap (s [ i ],s[ j ])。
当 n=5,互换 2次为 swap (s[0],s[4]),swap (s[1],s[3])。
当 n=6,互换 3次,swap (s[0],s[5]),swap(s[1],s[4]),
swap(s[2],s[3])
无论奇数或偶数互换的次数可以用 n/2表示。
p指向第一个元素 (p=&s[0];),e指向最后一个元素
(e = &s [n-1];),swap (p,e)表示互换 s[0]和 s[n-1]。
p向后移一个单位 (p++;),e向前移一个单位 (--e;),此时 swap (p,e)表示互换 s [1]和 s [n-2]。
p向后移 e向前移同时访问指针并进行数据互换,直到两指针相遇为止。
49
数组逆序存放的程序如下:
s++
s++ --e
--e
s[0] s[1],.,s[i],.,s[n-1-i],.,s[n-2] s[n-1]
swap (p,e)
图利用指针逆序一个数组
50
#include<stdio.h>
void swap (long s[ ],int n,int i)
{ long t = s [ i ] ; s[ i ] = s [n-1-i]; s [n-1-i] = t;}
void inverse (long p[ ],long *e)
{ for (; p<e; p++,--e )
{ long t=*p; *p=*e; *e=t;}
}
void main() //数组形参与指针形参是等价的
{ long s[ ] = {1,2,3,4,5,6,7,8,9};
const int n = sizeof (s)/sizeof (s [0]);
int i ; for( i=0; i< n/2; i++)
swap (s,n,i);
for ( i=0; i<n; i++) printf ("%d,",s [ i ]);
inverse (s,&s [n-1]);
51
long *p=&s[0];
for (i=0; i<n; i++,p++) printf ("%d;",*p);
}
//输出结果,9,8,7,6,5,4,3,2,1,1;2;3;4;5;6;7;8;9;
数组的有效范围是从 a[0]到 a[N-1],但 C/C++支持正和负下标。
负下标必须落在数组界限内,不然结果是不可预料的。例如,对于
{int a[7]={1,2,3,4,5,6,7}; int *p = &a[3];}
p初始化为 a中间一个元素的地址,p[-2]相当于索引
[1]。而 a[-1]产生运行错误。
52