【C/C++】C/C++中的數組是怎么實現的?


  幾乎所有的語言都把數組作為一種固有的數據類型,數組也是我們最常用的數據結構之一。在語言底層,數組是如何實現的呢?本文以抽象數據類型的形式,定義、實現數組。

 

  創建數組,理論上,我們可以使用創建任意維度的數組;但這個多維只是我們“感知”上的多維度,實際上,內存是一種線性存儲單元,不可能實現真正的多維。換言之,多維數組在內存中也是順序的排在一維,占用連續的一段存儲空間。

  以二維數組為例。存儲數組時,可以使用行優先存儲,即先存第一行…再存第二行……當然也可以使用列優先(Fortran語言就采用了列優先)。大多數語言還是行優先的,下面我就以行優先存儲,定義和表示數組。


Def:n維數組的映像函數

        一個數組的各個維度的下標與內存中的存儲單元有着一一對應,這是必然的。這個對應關系稱作映像函數。以三維為例,映像函數是:(這里就不展開一般情況了)

       Loc(i,j,k) = Loc(0,0,0) + ((b2*b3)*i + (b3) * j + i) * L

其中:

        Loc(0,0,0): 基地自

        bi: 為第i維的長度 ;
        L : 地址的加減以元素大小為單位

 

為了方便程序,映像函數顯然可以定義如下:

Loc(j1,j2,j3) = Loc(0,0,0) + sum(ci*ji)

其中:

        i = 1 to n

        cn = L;ci-1 = ci*bi

 

  可見,計算各元素位置的時間相同,存取任意元素時間都是單位O(1)。這叫做“隨機存儲結構” 。


  有了上述定義,就可以實現數組,以及實現數組的基本操作。下面的實現使用了“可變長參數”,后面我可給出了比較詳細的參考資料。下面的注釋非常清楚了。

 

  1 #include<cstdio> 
  2 #include<iostream>
  3 #include<stdlib.h>
  4 #include<stdarg.h> //標准頭文件,提供宏va_start,va_arg,va_end。來使用可變長參數 
  5 #define MAX_DIM 8 //最大維度 
  6 #define ElemType int //數據類型 
  7 using namespace std;
  8 //數組 表示 
  9  typedef struct{
 10      ElemType *base;  //數組的基地址 
 11      int dim;     //維度 
 12      int *bounds;  //維度是可變參數,存各維度的長度 
 13      int *constants; //這里就是上面說的ci 
 14  }Array;
 15 
 16 /*
 17     基本操作如下 
 18 */
 19 void InitArray(Array &A,int dim,...){
 20     /*先判斷維度,以及各維度長度是否合法*/ 
 21     if(dim<1||dim >MAX_DIM) return;
 22     A.dim = dim;
 23     A.bounds = (int*)malloc(dim*sizeof(int)); //bounds存各維度的長度,故開辟空間為dim*dizeof(int)
 24     if(!A.bounds) exit(0);
 25     int elemtotal = 1; //元素總個數,實際上,總個數為各維度長度的乘積 
 26     //先傳一個數組,用來存可變長參數信息表的數組, dim是長度 
 27     va_list argp;
 28     //等同於 char*argp,va_list就是一個指向第一個可變參數的指針 
 29     /*argp指向傳入的第一個可選參數,第二個參數是可變參數之前的第一個參數名,必須有,因為要靠這個去找可變參數在哪里*/  
 30     /*這里把上面得到的字符指針argp,后移動4個字節,就是跳過dim的內存地址 */  
 31     va_start(argp,dim); 
 32     for(int i = 0;i<dim;i++){
 33         A.bounds[i] = va_arg(argp,int);//這里把ap往后跳過4個字節(sizeof(int)大小)指向下一個參數,返回的是當前參數(而非下一個參數)
 34         if(A.bounds[i]<0) return;
 35         elemtotal *= A.bounds[i];
 36     }
 37     va_end(argp);
 38     
 39     A.base = (ElemType*)malloc(elemtotal * sizeof(ElemType));
 40     if(!A.base) exit(0);
 41     
 42     //下面再求映像函數的參數常數ci,有了這個可以很方便地取每一個元素
 43     A.constants = (int*)malloc(dim*sizeof(int));
 44     if(!A.constants) exit(0);
 45     A.constants[dim-1] = 1;//L = 1,指針得增減以元素的大小為單位 
 46     for(int i = dim-2;i>=0;i--){
 47         A.constants[i] = A.bounds[i+1] * A.constants[i+1];
 48     }
 49     return;    
 50 } 
 51 /*銷毀函數*/
 52 void Destory(Array &A){
 53     if(!A.base){
 54         return;
 55     }
 56     free(A.base);A.base = NULL;
 57     if(!A.bounds){
 58         return;
 59     }
 60     free(A.bounds);A.bounds = NULL;
 61     if(!A.constants){
 62         return;
 63     }
 64     free(A.constants);A.constants = NULL;
 65     return;
 66 }
 67 /*求指定元素的地址,即off,求的是相對基地址的偏移*/ 
 68 void  Locate(Array A,va_list ap,int &off){
 69     off = 0;
 70     for(int i = 0;i<A.dim;i++){
 71         //用va_arg去取元素,然后自動后移int位
 72         //va_list ap就是第一個元素的地址 
 73         int ind = va_arg(ap,int);
 74         if(ind<0||ind>=A.bounds[i]) return; //是否合法 
 75         //求地址的公式! 
 76         off += ind * A.constants[i];
 77     }
 78     //return off;
 79 }
 80 
 81 void Value(Array A,ElemType &e,...) {
 82     //A是n維數組 e 是要去取得元素,...是n個下標
 83     
 84     //創建取不定參數的指針 
 85     va_list ap;
 86     //初始化,通過va_start() 實現 
 87     va_start(ap,e);
 88     
 89     int off;
 90     //off存放元素e地址 
 91     Locate(A,ap,off);
 92     //取元素,返回e
 93     if(off <= 0) return; //這其實也判斷了下標是否合法 
 94     //關閉 
 95     va_end(ap);
 96     e = *(A.base+off);
 97 }
 98 //賦值語句 
 99 void Assign(Array &A,ElemType e,...){
100     //A是n維數組,e是元素變量,...是下標
101     //即把指定下標的元素賦值成e
102     va_list ap;
103     va_start(ap,e);
104     int off;
105     Locate(A,ap,off);
106     if(off <= 0)    return;
107     *(A.base + off) = e;
108     va_end(ap);
109     return;
110 }
111 
112 int main(){
113     //測試
114     
115     Array A;
116     InitArray(A,3,3,3,3);
117     Assign(A,10,0,0,1);
118     Assign(A,2,1,1,1);
119     int e = 1000;
120     Value(A,e,1,1,1);
121     printf("e1:%d\n",e);
122     int e2 = 99;
123     Value(A,e2,0,0,1);
124     printf("e2:%d\n",e2);
125     printf("成功...");
126 }

 

 

上面用到的可變參數以及stdarg頭文件的比較好的學習信息:

https://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html

總結使用stdarg的步驟:

    va_start() va_arg() va_end() va_list 的使用: 
    <Step 1> 在調用參數表之前,定義一個 va_list 類型的變量,(假設va_list 類型變量被定義為ap);
    <Step 2> 然后應該對ap 進行初始化,讓它指向可變參數表里面的第一個參數,這是通過 va_start 來實現的,第一個參數是 ap 本身,第二個參數是在變參表前面緊挨着的一個變量,即“...”之前的那個參數;
    <Step 3> 然后是獲取參數,調用va_arg,它的第一個參數是ap,第二個參數是要獲取的參數的指定類型,然后返回這個指定類型的值,並且把 ap 的位置指向變參表的下一個變量位置;
    <Step 4> 獲取所有的參數之后,我們有必要將這個 ap 指針關掉,以免發生危險,方法是調用 va_end,他是輸入的參數 ap 置為 NULL,應該養成獲取完參數表之后關閉指針的習慣。說白了,就是讓我們的程序具有健壯性。通常va_start和va_end是成對出現。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM