雙層指標(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。
- 在 foo2 裡面,情況則不一樣。傳入 foo2 裡面的是 p 的位址。所以當你改變 *q 的時候,會改變 p 的指向。
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
- 何謂二維陣列於記憶體中的配置方式?其實陣列存取時的行與列,是為了理解陣列元素的指定存取而想像出來的,索引值正確的意義,是指相對於陣列第一個元素的位移量,例如在一維陣列中的陣列配置與索引意義如下圖所示:
- 即使是二維空間,其在記憶體中也是線性配置的如下:
指標與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;
}