注:部落格中内容主要來自《狄泰軟體學院》,部落格僅當私人筆記使用。
測試環境:Ubuntu 10.10
GCC版本:4.4.5
一、指向指針的指針
1) 指針的本質是變量
2) 指針會占用一定的記憶體空間
3) 可以定義指針的指針來儲存指針變量的位址值
int main()
{
int i = 0;
int* p = NULL; //p指向int類型變量
int** pp = NULL;
pp = &p;
*pp = &i; //*pp=p=&i
return 0;
}
二、問題
1) 為什麼需要指向指針的指針?
- 指針在本質上也是變量
- 對于指針也同樣存在傳值調用與傳址調用
執行個體分析
重置動态空間大小
34-1.c
#include <stdio.h>
#include <malloc.h>
/*
p:申請的記憶體位址(指向指針的指針,傳址操作)
size:申請的記憶體大小
new_size:實際需要的記憶體大小
*/
int reset(char** p, int size, int new_size) //動态記憶體過大,減小記憶體浪費
{
int ret = 1;
int len = 0; //存儲新記憶體和輸入記憶體二者長度最小值
char* pt = NULL; //指向新申請的記憶體
char* tmp = NULL; //存儲新申請的記憶體(tmp = pt)
char* pp = *p; //pp指針存儲輸入的記憶體p位址
if( (p != NULL) && (new_size > 0) )
{
//申請新記憶體
pt = (char*)malloc(new_size);
//盡量不要操作原始資料,為了防止資料丢失,用一個變量tmp存儲,後續操作tmp
tmp = pt;
//對比新申請的記憶體和要修改的記憶體大小,找出二者最小值
len = (size < new_size) ? size : new_size;
int i = 0; //循環計數
for(i=0; i<len; i++)
{ //複制資料。任何指向這塊記憶體位址的指針,解引用(*)都會擷取新值
*tmp++ = *pp++;
}
free(*p); //釋放掉輸入的**p記憶體
*p = pt; //pt和tmp最初指向的都是新申請記憶體其實位置,上邊操作tmp修改了記憶體資料,而pt仍然存儲記憶體的起始位址,隻要将pt指派給*p就能使用新的記憶體
}
else
{
ret = 0;
}
return ret;
}
int main()
{
char* p = (char*)malloc(5);
printf("%p\n", p);
if( reset(&p, 5, 3) )
{
printf("%p\n", p);
}
free(p);
return 0;
}
操作:
1) gcc 34-1.c -o 34-1.out編譯正确,列印結果:
0x8de2008
0x8de2018
分析: 原來申請的記憶體被修改。 修改指針指向的資料:(安全操作指針指向的資料) 1. 定義臨時指針變量tmp 2. 指向資料指針p指派給tmp 3. 通過操作tmp修改p指向記憶體中的資料,防止丢失指向記憶體起始位址的指針(用p記錄) 4. 修改資料完畢,将p傳遞給使用者。(p指向存儲資料記憶體的起始位址)
三、二維數組與二級指針
1) 二維數組在記憶體中以一維的方式排布
2) 二維數組中的第一維是一維數組
3) 二維數組中的第二維才是具體的值
4) 二維數組的數組名可看做常量指針
由上圖可知:二維數組,記憶體與一維數組方式存儲,都是連續線性存儲。
小提示:
三維數組,一維中存儲的是二維數組。
執行個體分析(很重要的例子)
周遊二維數組
34-2.c
#include <stdio.h>
#include <malloc.h>
//列印資料:效率O(n)
void printArray(int a[], int size)
{
int i = 0;
//列印數組a的大小,結果隻列印指針大小,數組退化為指針
printf("printArray: %d\n", sizeof(a));
for(i=0; i<size; i++)
{
printf("%d\n", a[i]); //記憶體都是線性表示
}
}
int main()
{
int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int* p = &a[0][0]; //擷取二維數組首位址
int i = 0;
int j = 0;
for(i=0; i<3; i++) //一維
{
for(j=0; j<3; j++) //二維
{
printf("%d, ", *(*(a+i) + j)); //*(a+i) ==> a[i],*(a[i] + j) ==>a[i][j] 數組方式可讀 //性好
}
printf("\n"); //列印3個換行
}
printf("\n");
printArray(p, 9);
return 0;
}
操作:
1) gcc 34-2.c -o 34-2.out編譯正确,列印結果:
0, 1, 2,
3, 4, 5,
6, 7, 8,
printArray: 4
0
1
2
3
4
5
6
7
8
分析:
1. 記憶體都是線性表示。
四、數組名
1) 一維數組名代表數組首元素的位址
int a[5] a的類型為int*
2) 二維數組名同樣代表數組首元素的位址
int m[2][5] m的類型為int(*)[5]
結論:
1、二維數組名可以看做是指向數組的常量指針
2、二維數組可以看做是一維數組(記憶體分布是線性的)
3、二維數組中的每個元素都是同類型的一維數組
執行個體分析
如何動态申請二維數組
34-3.c
#include <stdio.h>
#include <malloc.h>
//行指針存儲的是每一列的首位址
int** malloc2d(int row, int col) //行列 傳回二維指針
{
int** ret = NULL;
if( (row > 0) && (col > 0) )
{
int* p = NULL;
ret = (int**)malloc(row * sizeof(int*)); //行
p = (int*)malloc(row * col * sizeof(int)); //列
if( (ret != NULL) && (p != NULL) )
{
int i = 0;
//填充順序:在ret[0]中填充列記憶體。以此類推ret[1]、ret[2]……
for(i=0; i<row; i++)
{
//p + i * col ==> 每次随着i的改變指向新的一列記憶體首位址
ret[i] = p + i * col; //ret[i]是解引用了一次,一級指針
}
}
else
{
free(ret);
free(p);
ret = NULL;
}
}
return ret;
}
//從外向内釋放記憶體(這裡是先釋放列,在釋放行)
void free2d(int** p)
{
if( *p != NULL )
{
free(*p);
}
free(p);
}
int main()
{
int** a = malloc2d(3, 3);
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("%d, ", a[i][j]);
}
printf("\n");
}
free2d(a);
return 0;
}
操作:
1) gcc 34-3.c -o 34-3.out編譯正确,列印結果:
0, 0, 0,
0, 0, 0,
0, 0, 0,
五、小結:
1) C語言中隻支援一維數組
2) C語言中的數組大小必須在編譯器就作為常數确定
3) C語言中的數組元素可是任何類型的數組
4) C語言中的數組的元素可以是另一個數組