指针与下标计算

差一错误是所有程序员的噩梦!

它看起来是一个琐碎的问题,却非常烦人,许多程序员对它是采取一种“轻蔑”的态度,即使被它虐了千百遍,还是不愿意正视这个问题。

其实,差一问题并不是一个小问题,我们应该对它给予足够的重视。

在《C陷阱与缺陷》中,对这个问题有详细的讨论,这里结合我的理解写一写解决这个问题的方法。

 

许多程序员在遇到差一问题时,常常采用的是“探测法”,一个位置应该写n还是应该写n-1,测试两次就够了,如果是两个位置,就要测试四次,三个位置,要测试八次。。。

其中还可能存在“重叠错误”,当有两个错误时,测试是正常的,当改正了一个错误,反而测试出错。

一次过写一段正确的代码,总比飞快得写一堆糟糕代码,再做一天debug好得多。

 

正确的指针和下标,是计算出来的,不是乱蒙出来的!

 

 

指针运算

在学会计算指针之前,先要正确理解数组和指针。

 

首先,地址就是一个整数,不是什么神奇的东西!

指针是地址变量,数组名是地址常量。

 

举个例子,

int arr[10];

编译器会分配10个整型大小的空间,首地址是arr,假设arr=500,那么这个数组就是这样的:

 

其中能索引到的元素是arr[0]arr[9],arr[n]也就是arr[10]是没有的。

arr[0]有,arr[n]没有,这个叫“不对称边界”,它带来极大便利的同时,也引起了许多错误,不过,这种设计绝对是正确的。

可以用arr+5,表示第5个元素的地址(从0开始),但是不允许arr=arr+5,因为数组名是个常量。这里arr=500,arr+5不等于505,而是等于520,因为在对地址量做加减时,它自动乘了一个sizeof(int)

指针也一样,不同的是指针可以赋值。

 

地址量可以进行下列操作(p表示指针,i表示整型):

p=p+i;    //指针后移i个位置

p=p-i; //指针前移i个位置

i=p1-p2; //两个指针之间的元素个数

 

要指向p1p2中间那个元素,可以用p0=p1+(p2-p1)/2,

p0=(p1+p2)/2似乎也可以,不过编译器不允许,因为p1+p2没有意义。

 

问题解决

C陷阱与缺陷》中,差一问题的解决方法,

一个是理解“不对称边界”,

另一个是“边界计算”。

边界计算,简单举个例子。

在一个位置,不知道应该写n还是n-1,于是可以假设当n=2时,这里应该写1,所以,这个位置应该写n-1

 

“从110有几个数?!”

11个!”

“你给我想清楚!”

11个!”

“那从12有几个数?”

 

就是这样了。

 

解决差一问题的方法绝不止这两种,我们可以在实际问题的过程中,想到各种不同的方法。

 

二分查找

下面以二分查找为例,讲解这个问题。

根据《编程珠玑》里的数据,在100个专业程序员中,90%的程序员写的二分查找是存在bug的。

在没有bug的二分查找代码中,也可能存在元素的重复比较。

 

我写的二分查找是这样的:

int* bs(int* arr,int n,int x){

⓪ if(n==0)return NULL;

① else if(x==arr[n/2])return arr+n/2;

② else if(x<arr[n/2])return bs(arr,n/2,x);

③ else return bs(arr+n/2+1,n-n/2-1,x);

}

元素按从小到大的顺序排,如果找到,返回的是元素的地址,找不到则返回NULL。(如果找到了bug,请告诉我)。

 

我写这段代码的过程是这样的:

先在纸上画出内存模型

  

⓪语句,当数组没有元素了,就返回NULL

①语句,比较x是否等于arr[n/2],(这里arr[n/2]是不是正中间的元素并不重要),如果等于,返回它的地址,arr+n/2

②语句,比较x是否小于arr[n/2],如果小于,应该继续查找的是位于arr[n/2]前面的那一段数组,不包括arr[n/2],因此,这段数组的首地址还是arr,它的最后一个元素是arr[n/2-1],长度是n/2

③语句,应该继续查找的是位于arr[n/2]后面的那一段数组,不包括arr[n/2],因此,这段数组的首地址是arr+n/2+1,那么长度应该怎么计算呢?

1个方法:右边数组长度=原数组长度-左边数组长度-arr[n/2]这个元素

=n-n/2-1

2个方法:右边数组的末地址=原数组的末地址

右边数组的首地址+长度=原数组的首地址+长度

arr+n/2+1+m=arr+n

m=n-n/2-1

所以,右边数组的长度为n-n/2-1n-n/2不一定等于n/2,因为整数除法是向下取整的)

 

待定系数法

我想到的另一个方法,是待定系数法。

以对称矩阵的压缩存储为例,讲解这个方法。

对称矩阵的压缩存储是数据结构考试中的必备题目,在各大考试中频繁露面。

在不同的试题中,具体细节也不同,有的存储上三角,有的存储下三角,有的从0开始,有的从1开始,还有些是01混合。

 

上试题:

一个10阶对称矩阵A,采用行优先顺序压缩存储下三角元素a[1,1]为第一个元素,其存储地址为数组B[0],每个元素占有1个存储地址空间,则a[ i, j ]的地址为_________

解法:

先画出矩阵的一部分


B[t]对应a[ i, j ],即对应方程为,选出四个元素

i=1,j=1时,t=0

i=2,j=1时,t=1

i=3,j=1时,t=3

i=3,j=2时,t=4

 

写成矩阵形式可以更好计算:


a[ i, j ]的地址为

这里需要注意的是,选取元素时不能取同一条直线上的元素,否则在计算时矩阵不满秩,得不到最后结果。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章