雙層指標(double pointer or pointer of pointer)

int num=123;
int *pr2 = #
int **pr1; = &pr2;
  • 雙層指標 (double pointer), 這種資料格式很容易和指標陣列、陣列的指標、以及二維陣列三種型態混淆。
    • 會有此不同解釋的差異是因為*運算子的優先權
int* arr1[10];   //長度為10的指標陣列
int* (arr2[10]); //同上,長度為10的指標陣
int (*arr3)[10]; //指標指向長度為10的陣列,若有5列(5個指標)時,等價於int arr3[5][10];
  • 雙層指標基本用法:
int **ppData = NULL;
int *pData = NULL;
int data = 0;

ppData = &pData;
pData = &data;
**ppData = 10; // 在變數 data 內存入數值資料 10
  • 陣列的指標 (pointer to array) 以及二維陣列 (two dimensional array) 的基本用法:
    • 陣列的指標是指有許多指標,每個指標指向固定長度的陣列
/*  pointer to array of 20 integers
 *  這邊*pAry是指向每個row的第一個元素,而每個row有20個元素
 *  因此pAry[i][j] 等於 *(*(pAry+i) + j)
 */

int (*pAry)[20];
int x[10][20];

ppData = x;        // ERROR: can not convert int[20] * to int **
pAry = x;          // OK: same type
pAry[5][10] = 100; // 將數值 100 置於變數 x[5][10] 之內
                   // 等價於 *(*(pAry + 5) + 10) = 100
  • 指標陣列 (pointer array) 的基本用法
    • 指標陣列為型態為指標的陣列
int* ptrAry[20]; // pointer array,即為20個指標的array,如同 double arr[20];
ppData = ptrAry;   // OK: same type
ppData[13] = &data; // 將變數 data 的位址存放到變數 ptrAry[13] 中

cdecl解釋

  • 上述複雜的情形,可使用cdecl程式解釋,首先安裝 sudo apt-get install cdecl

  • 進入cdecl環境中,輸入

cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int

二維陣列和字串陣列

  • 二維陣列和字串陣列是個很類似的東西。
/* names1為字串陣列,names2為二維陣列 */

char *names1[] = {"Hello", "C"}; // sizeof(names1) = 8
char names2[][6] = {"Hello", "C"}; // sizeof (names2) = 12
  • names1是一個字串陣列,簡單的說,它是一個一維陣列,每個元素放的是char *指標,指向字串"Hello"與"C"

    • 重點是,"Hello"與"C"這兩個字串並不存在陣列裡,而是存在記憶體的其他地方。
    • 也可解釋為這個兩個指標所形成的陣列。
  • names2是一個二維陣列,簡單的說,字串是儲存在陣列裡,也由於他是儲存在陣列裡,所以必須考慮到最長的字串長度,雖然第二個元素"C"只有1 byte再加上'\0'共2 byte,"Hello"加上'\0'共6 byte,為了考慮最長字串6 byte,導致names的column長度要宣告到6 byte,很明顯地,這對第一個元素"C"浪費了4 byte,但字串陣列就沒這個缺點。

指標在函數中改方向時必須以double pointer參數傳遞

  • 如要在函式裡面改變變數a的值時,引數必須為 pointer,然後傳入 &a 一樣。
  • 如果要在函式裡面改變pointer p 的值,則引數必須為double pointer,然後傳入 &p。
void foo1(int* q, int& v){
    q = &v;
}

void foo2(int** q, int& v){
    *q = &v;
}

int main(){
    int i = 10, j = 20;
    int *p = &i;
    foo(p, &j);
    printf("*p = %d\n", *p);    //p=10,值沒有改變
    foo2(&p, &j);
    printf("*p = %d\n", *p);    //p=20;
    return 0;
}
  • 在 foo1 裡面,我們有一個 local 的 pointer q。
    • 當呼叫 foo1 時,實際上我們把 p 的內容,拷貝給 q 。所以 q 也指向 i 。
    • 譬如說,&i 的位址假設是 0×1234,p 裡面的內容就是 0×1234。所以呼叫 foo1 的時候,p 的內容被拷給 q,而 p 被丟入 stack 中。
    • 當你改變 q 的值時,p 並沒有受到影響。所以當程式返回之後,p 的值還是 0×1234。
指標以函數參數傳遞時,仍是copy by value。
  • 在 foo2 裡面,情況則不一樣。傳入 foo2 裡面的是 p 的位址。所以當你改變 *q 的時候,會改變 p 的指向。
使用double pointer接住傳遞來的位址。

Pointer 與 多維陣列

  • array並非pointer,但array可以自動轉型成pointer,這也是array傳進function後變成pointer的理論基礎

  • int a[2][4] = { {1,3,5,7} , {2,4,6,8} };

陣列表示法 連續記憶體表示法 陣列索引值轉換表示法
a[0][0] 1 **a ((a+0)+0)
a[0][1] 3 (a+1) ((a+0)+1)
a[1][0] 2 (a+4) ((a+1)+0)
a[1][3] 8 (a+7) ((a+1)+3)

二維陣列

  • 二維陣列使用陣列名稱與兩個索引值來指定存取陣列元素,這兩個索引值都是由 0 開始。

  • C語言存放二維或維度更高的陣列是以row-major方式儲存(fortran是column major)

    • c compiler在編譯時會將高維的陣列轉回一維。
    • 以二維陣列arr有N_ROW列,N_COL行,則arr[idx][jdx]以row-major轉回一維索引即為idx*N_ROW + jdx.
#include <stdio.h>
#define ROW 5
#define COLUMN 10

int main(void) {
    int maze[ROW][COLUMN];

    for(int i = 0; i < ROW; i++) {
        for(int j = 0; j < COLUMN; j++) {
            maze[i][j] = (i + 1) * (j + 1);
        }
    }

    for(int i = 0; i < ROW; i++) {
        for(int j = 0; j < COLUMN; j++) {
            printf("%d\t", maze[i][j]);
        }
        putchar('\n');
    }

    return 0;
}

// output
1   2   3   4   5   6   7   8   9   10
2   4   6   8   10  12  14  16  18  20
3   6   9   12  15  18  21  24  27  30
4   8   12  16  20  24  28  32  36  40
5   10  15  20  25  30  35  40  45  50
  • 何謂二維陣列於記憶體中的配置方式?其實陣列存取時的行與列,是為了理解陣列元素的指定存取而想像出來的,索引值正確的意義,是指相對於陣列第一個元素的位移量,例如在一維陣列中的陣列配置與索引意義如下圖所示:
對 int 整數陣列來說,每一位移量是 4 個位元組,而指定存取 maze[4],相當於指定存取相對於 maze[0] 四個位移量的記憶體空間。
  • 即使是二維空間,其在記憶體中也是線性配置的如下:
二維陣列將得到的記憶體分為兩個區塊,我們宣告陣列 maze[2[4],表示 maze[0][0] 與 maze[1][0] 相對位移量為 4 (4個元素而非4 bytes),當指定存取 maze[1][3] 時,表示存取的位置是相對於 maze[1][0] 位移 3 個單位。。

指標與const

  • const與指標并用時,會因前後順序不同而有相異的意義,且在C++中的變化更多。

指標與二維陣列

# include <stdio.h>

int main(){
    int a[3][5];
    int (*ptr)[5] = a;  //指向每一個列的開頭元素,每列5個元素。

    printf("a location: %p\n", a);
    printf("a[0][0]: %p\n", &a[0][0]);
    printf(" ptr[0]: %p\n", ptr[0]);
    printf("a[1][0]: %p\n", &a[1][0]);
    printf(" ptr[1]: %p\n", ptr[1]);

    return 0;
}

// output
a location: 0x7ffd8e57cb50
a[0][0]: 0x7ffd8e57cb50
 ptr[0]: 0x7ffd8e57cb50
a[1][0]: 0x7ffd8e57cb64
 ptr[1]: 0x7ffd8e57cb64
 return 0;
}

函數參數必須指定column的長度

void foo1(int x[][]) { // 編譯成功,但Compiler不知如何翻譯, 還是用int *x自己計算地址比較好
    x[2][2] = 0; // 編譯錯誤!, Compiler不知如何翻譯
}
void foo2(int x[][3]) { // Compiler知道如何翻譯
    x[1][1] = 0; // Compiler知道要翻成*(x+1*3+1)
}
void foo3(int x[2][3]) { // Compiler知道如何翻譯
    x[1][1] = 0; // Compiler知道要翻成*(x+1*3+1)
}
void foo4(int x[3][]) { // 編譯錯誤
}
void foo5(int *x) { // 反正只能傳pointer
    *(x+1*3+1) = 0;
}
int main() {
    int m[2][3];
    int *p;
    int k[4][4];
    foo2(m);
    foo2(p); // Compiler warning, incompatible pointer type
    foo3(k); // Compiler warning, incompatible pointer type
    foo5(m); // Compiler warning, incompatible pointer type
    foo5(p);
    foo5((int *)m); // 強迫轉型, Compiler就不會抱怨了
}

動態配置二維陣列

  • 非連續記憶體: 缺點是memory fragment。
const int sizex = 2;
const int sizey = 3;
int x, y;
int **ia = (int **)malloc(sizey * sizeof(void *));
for(y = 0; y != sizey; ++y)
    ia[y] = (int *)malloc(sizex * sizeof(int *));

// operations

for(y = 0; y != sizey; ++y)
    free(ia[y]);
free(ia);
  • 連續記憶體
//此版本仍然釋放二次,不完善
const int sizex = 3;
const int sizey = 2;
int x, y;
int **ia = (int **)malloc(sizey * sizeof(void *));

// 將第二個陣列所需的記憶體一次malloc()
int *iax = (int *)malloc(sizey * sizex * sizeof(int *));
for(y = 0; y != sizey; ++y, iax+=sizex)
    ia[y] = iax;
...
free(ia[0]);
free(ia);
const int sizex = 3;
const int sizey = 2;
int x, y;
int **ia = (int **)malloc(sizey * sizeof(void *) + sizey * sizex * sizeof(int *));
int *iax = (int*)(ia + sizex);
for(y = 0; y != sizey; ++y, iax+=sizex)
     ia[y] = iax;
...
free(ia);

二維陣列非連續配置記憶體

  • C語言可以使用array style的方式將二維陣列傳入函數,可以同時提供row size與column size,也可省略row size只提供column size。
  • C語言可以將二維陣列用pointer如傳遞一維陣列方式傳入函數,在某些場合使用一維陣列操作方式可能更加方便。
  • 當C語言的二維陣列以pointer型態傳入函數時,必須要使用包含column size的一維陣列的pointer。
// 因為n_row與n_col都是變數,所以必須使用此方式才能傳進function
void sub( int ** matrix, int row_size, int col_size );

/* 如果n_row與n_col為定值時,可使用以下的函式簽名
   void sub(int matrix[][n_col]);
   or void sub(int(*matrix)[n_col]);
   注意仍不可指定n_row之值
*/
int main() {
        int x, y;
        scanf( "%d %d", &x, &y );

        int **matrix = calloc( y, sizeof( int * ) );
        for( int i = 0; i < y; ++i ) {
                matrix[i] = calloc( x, sizeof( int ) );
        }
        // do something ...
        sub( matrix, x, y );
        // do something ...
        for( int i = 0; i < y; ++i ) {
                free( matrix[i] );
        }
        free( matrix );
        return 0;
}

results matching ""

    No results matching ""