【8-6,8-7】如你所知,C編譯器為數組配置設定下标時總是從0開始,而且當程式使用下标通路數組元素時,它并不檢查下标的有效性,在這個項目中,你将要編寫一個函數,允許使用者通路“僞數組”,它的下标範圍可以任意指定,并伴以完整的錯誤檢查。下面是你将要編寫的這個函數的原型:
int array_offset(int arrayinfo[],...);
這個函數接受一些用于描述僞數組的維數資訊以及一組下标值,使用這個函數,使用者既可以以向量的形式配置設定記憶體空間,也可以使用mallco配置設定空間,但按照多元數組的形式通路這些空間,這個數組之是以被稱為“僞數組”是因為編譯器以為它是個向量,盡管這個函數允許它按照多元數組的形式進行通路。
這個函數的參數如下:
參數 | 含義 |
arrayinfo | 一個可變長度的整型數組,包含一些關于僞數組的資訊。 arrayinfo[0]指定僞數組具有的維數,它的值必須在1和10之間。 arrayinfo[1]和arrayinfo[2]給出第一維的下限和上限, arrayinfo[3]和arrayinfo[4]給出第2維的下限和上限, 依次類推。 |
... | 參數清單的可變部分可能包含多達10個的整數,用于辨別僞數組中某個特定位置的下标值。你必須使用va_參數通路它們,當函數被調用時,arrayinfo[0]參數将會被傳遞。 |
公式根據下面給出的下标值計算一個數組的位置。變量s1,s2等代表下标參數s1,s2等。變量lo1和hi1代表下标s1的下限和上限,它們來源于arrayinfo參數,其餘各維以此類推。變量loc表示僞數組的目标位置,它用一個距離僞數組起始位置的整型偏移量表示。
對于一維數組:
loc = s1 - lo1
對于二維僞數組:
loc = (s1 - lo1) × (hi2 - lo2 + 1) + s2 - lo2
對于三維僞數組:
loc = [(s1 - lo1) × (hi2 - lo2 + 1) + s2 - lo2] × (hi3 - lo3 + 1) + s3 - lo3
對于四維僞數組:
loc = [[(s1 - lo1) × (hi2 - lo2 + 1) + s2 - lo2] × (hi3 - lo3 + 1) + s3 - lo3] × (hi4 - lo4 + 1) + s4 - lo4
一直到第十維為止,都可以類似的使用這種方法推導出loc的值。你可以假定arrayinfo是個有效的指針,傳遞給array_offset的下标參數也是正确的。對于其他情況你必須進行錯誤檢查,可能出現的一些錯誤有:維的數目不處于1到10之間,下标小于low值,low值大于其對應的high值等,如果檢測到這些或者其他錯誤,函數應該傳回-1.
提示:把下标參數都複制到一個局部數組中,你接着便可以把計算過程以循環的形式編碼,對每一維都使用一次循環。
舉例:假定arrayinfo包含值2,4,6,1,5,-3,3.這些值提示我們所處理的是三維僞數組,第一個下标範圍從4到6,第二個下标範圍從1到5,第三個下标範圍從-3到3,在這個例子中,array_offset被調用時将有三個下标參數傳遞給它,下面顯示了幾組下标值以及它們所代表的偏移量:

這個題目有點意思,題目說明很長,實際上就是模拟一個多元數組,下标可以為負數,下标範圍有輸入參數決定:
難度系數5顆星,主要是設計的參數比較多,需要花時間了解題意,理清楚各個變量。
1、利用可變參數取出下标值:
va_list arg;
va_start(arg, array_info);
for (...){
index = va_arg(arg, int);
}
2、目前次元的次元内偏移量:
目前次元的下标index - 目前次元的下限
3、整體偏移量:
為了友善了解,我們先舉一個例子:
假設數組array[3][4][5],我們知道這個三維數組實際上也是連續存儲的,如果需要計算array[2][3][4]這個數相對于數組起始位置的偏移,我們應該怎麼計算呢?
我們先看一下這個數組的結構,每個位置都用了位置坐标表示:
{
{
{000,001,002,003,004},
{010,011,012,013,014},
{020,021,022,023,024},
{030,031,032,033,034}
},
{
{100,101,102,103,104},
{110,111,112,113,114},
{120,121,122,123,124},
{130,131,132,133,134}
},
{
{200,201,202,203,204},
{210,211,212,213,214},
{220,221,222,223,224},
{230,231,232,233,234}
}
}
我們可以看到array[2][3][4]表示數組的最後一個元素,那怎麼計算出這個位置相對于數組起始位置的偏移呢?
2表示移動2行,每一行的大小就是 4*5 ,就是移動40個單元;
3表示移動3行,每一行的大小就是5,就是移動15個單元,就移動到了第二維的最後一行;
4表示移動4個單元,就到了最後一行的最後一個元素。
是以最終相對于起始位置的偏移就是40 + 15+ 4 = 59。
這個是數組本身的偏移計算方法,在這個僞數組這裡也是類似的:
目前次元的偏移量 = 上一個次元的偏移量 * 目前次元的大小 + 目前次元的偏移量
其中:目前次元的偏移量 = 目前次元的下标 - 目前次元下限
目前次元的大小 = 目前次元上限 - 目前次元下限 + 1
将題目中給出的公式轉化為代碼:
#include <stdarg.h>
int array_offset(int array_info[], ...)
{
int dimension = array_info[0];
//超出次元範圍直接傳回-1
if(1 > dimension || 10 < dimension){
return -1;
}
int now;//目前次元
int offset;//偏移量
int index;//目前下标
va_list arg;
va_start(arg, array_info);
//每次進兩位,有上下限兩個數
for(now = 1, offset = 0; now <= dimension * 2; now += 2){
//擷取一個下标值
index = va_arg(arg, int);
//和對應的array_info中上下限對比,超過傳回-1;
if(index < array_info[now] || index > array_info[now + 1]){
return -1;
}
//之前一次元的偏移量 乘以 次元 加上目前次元的偏移量
offset = offset * (array_info[now + 1] - array_info[now] + 1) + index - array_info[now];
}
va_end(arg);
return offset;
}
int main()
{
int array_info[] = {3, 4, 6, 1, 5, -3, 3};
printf("%d\n", array_offset(array_info, 4, 1, -3));
printf("%d\n", array_offset(array_info, 4, 1, 3));
printf("%d\n", array_offset(array_info, 5, 1, -3));
printf("%d\n", array_offset(array_info, 4, 1, -2));
printf("%d\n", array_offset(array_info, 4, 2, -3));
printf("%d\n", array_offset(array_info, 6, 3, 1));
return 0;
}
輸出:
0
6
35
1
7
88
上面都是數組下标的表示方法,這裡也可以改成指針表示:
#include <stdio.h>
#include <stdarg.h>
int array_offset(register int *arrayinfo, ...) {
register int ndim;
register int offset;
register int hi, lo;
register int i;
int s[10];
va_list subscripts;
ndim = *arrayinfo++;//維數
if (ndim >= 1 && ndim <= 10) {
//取出坐标到s數組
va_start(subscripts, arrayinfo);
for(i = 0; i < ndim; i+=1) {
s[i] = va_arg(subscripts, int);
}
va_end(subscripts);
//計算每一維的偏移
offset = 0;
for(i = 0; ndim; ndim--,i++) {
//擷取該次元的上下限
lo = *arrayinfo++;
hi = *arrayinfo++;
//如果該次元坐标超出該次元上下限
if (s[i] < lo || s[i] > hi) { return -1;}
//
offset *= (hi - lo + 1 );//上一個offset * 該次元大小,就指向了該次元區間
offset += s[i] - lo; //再加上在該次元内的偏移量
}
return offset;
}
return -1;
}
int main()
{
int array_info[] = {3, 4, 6, 1, 5, -3, 3};
printf("%d\n", array_offset(array_info, 4, 1, -3));
printf("%d\n", array_offset(array_info, 4, 1, 3));
printf("%d\n", array_offset(array_info, 5, 1, -3));
printf("%d\n", array_offset(array_info, 4, 1, -2));
printf("%d\n", array_offset(array_info, 4, 2, -3));
printf("%d\n", array_offset(array_info, 6, 3, 1));
return 0;
}
修改上面問題中的array_offset函數,使它通路以列主序存儲的僞數組,也就是最左邊的下标率先變化。這個新函數,array_offset2,在其它方面應該與原先那個函數一樣。
舉例:假定arrayinfo包含值2,4,6,1,5,-3,3.這些值提示我們所處理的是三維僞數組,第一個下标範圍從4到6,第二個下标範圍從1到5,第三個下标範圍從-3到3,在這個例子中,array_offset被調用時将有三個下标參數傳遞給它,下面顯示了幾組下标值以及它們所代表的偏移量:
現在題目要求變了,以列主序存儲,就是最左邊的下标率先變化,其實就是把上面的下标清單倒着取,即可。
#include <stdio.h>
#include <stdarg.h>
int array_offset2(register int *arrayinfo, ...) {
register int ndim;
register int offset;
register int hi, lo;
register int *sp;
int s[10];
va_list subscripts;
ndim = *arrayinfo++;//維數
if (ndim >= 1 && ndim <= 10) {
//取出坐标到s數組
va_start(subscripts, arrayinfo);
for(offset = 0; offset < ndim; offset+=1) {
s[offset] = va_arg(subscripts, int);
}
va_end(subscripts);
//從後往前計算每一維的偏移
offset = 0;
arrayinfo += (ndim * 2);//指向數組尾部
sp = s + ndim;//指向最後一個下标
while(ndim-- >=1 ) {
hi = *--arrayinfo;
lo = *--arrayinfo;
if(*--sp > hi || *sp < lo) { return -1;}
offset *= (hi-lo + 1);
offset += (*sp -lo);
}
return offset;
}
return -1;
}
int main()
{
int array_info[] = {3, 4, 6, 1, 5, -3, 3};
printf("%d\n", array_offset2(array_info, 4, 1, -3));
printf("%d\n", array_offset2(array_info, 4, 2, -3));
printf("%d\n", array_offset2(array_info, 4, 1, -1));
printf("%d\n", array_offset2(array_info, 5, 1, -3));
printf("%d\n", array_offset2(array_info, 4, 3, -3));
printf("%d\n", array_offset2(array_info, 5, 3, -1));
printf("%d\n", array_offset2(array_info, 6, 1, -3));
printf("%d\n", array_offset2(array_info, 4, 1, -2));
printf("%d\n", array_offset2(array_info, 6, 5, 3));
return 0;
}
這個題目還是有一定的難度,書上面是标注的5顆星難度,這要求熟練掌握C語言的數組和指針才能完成,值得好好研究了解。