使用指针访问数组元素

C++语言中,指针和数组密切相关.特别是在表达式中使用数组名时,该名字会自动 转换为指向数组第一个元素的指针:

int ia[]={0,2,4,6,8};
int *ip=ia;  //ip points to ia[0]

如果希望使指针指向数组中的另一个元素,则可使用下标操作符给某个元素定位,然后用取地址操作符&获取该元素的存储地址:

ip=&ia[4];  //ip points to last element in ia

1.指针的算术操作

与其使用下标操作,倒不如通过指针的算术操作(pointer arithmetic)来获取指定内容的存储地址.指针的算术操作和迭代器的算术操作以相同的方式实现(也具有相同的约束).使用指针的算术操作在指向数组某个元素的指针上加上(或减去)一个整型数值,就可以计算出指向数组另一元素的指针值:

ip=ia;  //ok:ip points to ia[0]
int *ip2=ip+4;  //ok:ip2 points to ia[4],the last element in ia

在指针ip上加4得到一个新的指针,指向数组中ip当前指向的元素后的第4个元素.

通常,在指针上加上(或减去)一个整型数值n等效于获得一个新指针,该新指针指向指针原来指向的元素之后(或之前)的第n个元素.

---------------------------------------------------------------------华丽的分割线-----------------------------------------

注解:指针的算术操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的.如果指针指向一对象,我们还可以在指针上加1从而获取指向相邻的下一个对象的指针.

------------------------------------------------------------------华丽的往下------------------------------------------------

假设数组ia只有4个元素,则在ia上加10是错误的:

//error:ia has only 4 elements,ia+10 is an invalid address
int *ip3=ia+10;

只要两个指针指向同一数组或有一个指向该数组末端的下一单元,C++还支持对这两个指针做减法操作:

ptrdiff_t  n  =ip2-ip;   //ok:distance between the pointers

结果是4,这两个指针所指向的元素间隔为4个对象.两个指针减法操作的结果是标准库类型(library type)ptrdiff_t的数据.与size_t类型一样,ptrdiff_t也是一种与机器相关的类型,在cstddef头文件中定义.size_t是unsigned类型,而ptrdiff_t则是signed整型.

这两种类型的差别体现了它们各自的用途:size_t类型用于指明数组长度,它必须是一个正数;ptrdiff_t类型则应保证足以存放同一数组中两个指针之间的差距,它有可能是负数.例如,ip减去ip2,结果为-4.

允许在指针上加减0,使指针保持不变.更有趣的是,如果一指针具有0值(空指针),则在该指针上加0仍然是合法的,结果得到另一个值为0的指针.也可以对两个空指针做减法操作,得到的结果仍是0.

2.解引用和指针算术操作之间的相互作用

在指针上加一个整型数值,其结果仍然是指针.允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针:

int last=*(ia+4);  //ok:initializes last to 8,the value of ia[4]

这个表达式计算出ia所指向元素后面的第四个元素的地址,然后对该地址进行解引用操作,等价于ia[4].

-----------------------------------------------------------------华丽的分割线----------------------------------------------------

注解:加法操作两边用圆括号括起来是必要的.如果写成:

last=*ia+4;  //ok:last=4,equivalent to ia[0]+4

意味着对ia进行解引用,获得ia所指元素的值ia[0],然后加4.

------------------------------------------------------------华丽退散-------------------------------------------------------

由于加法操作和解引用操作的优先级(precedence)不同,上述表达式中的圆括号是必要的.简单地说,优先级决定了有多个操作符的表达式如何对操作数分组.解引用操作符的优先级比加法操作符高.

与低优先级的操作符相比,优先级高的操作符的操作数先被组合起来操作.如果没有圆括号,解引用操作符的操作数是ia,该表达式先对ia解引用,获得ia数组中的第一个元素,并将该值与4相加.

如果表达式加上圆括号,则不管一般的优先级规则,将(ia+4)作为单个操作数,这是ia所指向的元素后面第四个元素的地址,然后对这个新地址进行解引用.

3.下标和指针

我们已经看到,在表达式中使用数组名时,实际上使用的是指向数组第一个元素的指针.这种用法设计很多方面,当它们出现时我们会逐一指出来.

其中一个重要的应用是使用下标访问数组时,实际上是使用下标访问指针:

int ia[]={0,2,4,6,8};
int i=ia[0];  //ia points to the first element in ia

ia[0]是一个使用数组名的表达式.在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作.只要指针指向数组元素,就可以对它进行下标操作:

int *p=&ia[2];  //ok: p points to the element indexed by 2
int j=p[1];   //ok: p[1] equivalent to *(p+1),
              //    p[1] is the same element as ia[3]
int k=p[-2];  //ok: p[-2] is the same element as ia[0]

4.计算数组的超出末端指针

vector类型提供的end操作将返回指向超出vector末端位置的一个迭代器.这个迭代器常用作哨兵,来控制处理vector中元素的循环,类似地,可以计算数组的超出末端指针的值:

const size_t arr_size=5;
int arr[arr_size]={1,2,3,4,5};
int *p=arr;   //ok: p points to arr[0]
int *p2=p+arr_size;   //ok:p2 points on past the end of arr
                      //   use caution--do not dereference!

本例中,p指向数组arr的第一个元素,在指针p上加数组长度即可计算出数组arr的超出末端指针.p加5所得p所指向的元素后面的第五个int元素的地址--------换句话说,p+5指向数组的超出末端的位置.

----------------------------------------------------华丽的分割线---------------------------------------------------------

注解:C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作.而计算数组超出末端位置之后或数组首地址之前的地址都是不合法的.

---------------------------------------------------------华丽,继续-------------------------------------------------------

计算并存储在p2中的地址,与在vector上做end操作所返回的迭代器具有相同的功能.由end返回的迭代器标志了该vector对象的"超出末端位置",不能进行解引用运算,但是可将它与别的迭代器比较,从而判断是否已经处理外vector中所有的元素.同理,p2也只能用来与其他指针比较,或者用做指针算术操作表达式的操作数.对p2进行解引用将得到无效值.对大多数的编译器来说,会把对p2进行解引用的结果(恰好存储在arr数组的最后一个元素后面的内存中的二进制位)视为一个int型数据.

5.输出数组元素

用指针编写以下程序:

const size_t arr_sz=5;
int int_arr[arr_sz]={0,1,2,3,4};
// pbegin points to first element,pend points just after the last
for(int *pbegin=int_arr, *pend=int_arr+arr_sz;pbegin!=pend;++pbegin)
cout<<*pbegin<<' ';  //print the current element

看得出来是依次输出数组的元素.

这段程序使用了一个我们以前没有用过的for循环性质:只要定义的多个变量具有相同的类型,就可以在for循环的初始化语句(init-statement)中同时定义它们.本例在初始化语句中定义了两个int型指针pbegin和pend.

C++允许使用指针遍历数组.和其他内置类型一样,数组也没有成员函数.因此,数组不提供begin和end操作,程序员只能自己给指针定位,使之分别标志数组的起始位置和超出末端位置.可在初始化中实现这两个指针的定位:初始化指针pbegin指向int_arr数组的第一个元素,而指针pend则指向该数组的超出末端的位置.指针pend是标志for循环结束的哨兵.for循环的每次迭代都会使pbegin递增1以指向数组的下一个元素.第一次执行for循环时,pbegin指向数组中的第一个元素;第二次循环,指向第二个元素,依次类推.当处理完数组的最后一个元素后,pbegin再加1则与pend值相等,表示整个数组已遍历完毕.

6.指针是数组的迭代器

下面这一段程序,循环遍历并输出一个string类型的vector的内容:

//equivalent loop using iterators to reset all the element in ivec to 0
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
*iter=0;  //set element to which iter refers to 0

这段程序使用迭代器的方式就像上个程序使用指针实现输出数组内容一样.指针和迭代器的这个相似之处并不是巧合.实际上,内置数组类型具有标准库容器的许多性质,与数组联合使用的指针本身就是迭代器.

习题4.17   一直p1和p2指向同一个数组中的元素,下面语句实现什么功能?

p1+=P2-p1;

当p1和p2具有什么值时这个语句是非法的?

写成这样吧,p1=p1+p2-p1,也就是使得p1指向p2原来所指向的元素.原则上说,只要p1和p2的类型相同,则该语句始终是合法的.只有当p1和p2不是同类型的指针时,该语句才不合法(不能进行减操作)
但是,如果p1和p2不是指向同一个数组中的元素,则这个语句的执行结果可能是错误的.因为-操作的结果类型ptrdiff_t只能保证足以存放同一数组中两个指针之间的间隔,如果p1和p2不是指向同一个数组中的元素,则-操作的结果有可能超出ptrdiff_t类型的表示范围而产生溢出,从而该语句的执行结果不能保证p1指向p2原来所指向的元素(甚至不能保证p1为有效指针).               

习题4;18   编写程序,使用指针把一个int型数组的所有元素设置为0.

int main()
{
 const size_t arr_size=8;
 int int_arr[arr_size]={0,1,2,3,4,5,6,7};
 //pbegin指向第一个元素,pend指向最后一个元素的下一内存位置
 for(int *pbegin=int_arr,*pend=int_arr+arr_size;pbegin!=pend;++pbegin)
  *pbegin=0;  //当前元素置0
     return 0;
}

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值