動態內存分配【重點 難點】
傳統數組的缺點:
1. 數組長度必須事先制定,且只能是常整數,不能是變量
例子:
int a[5]; //ok
int len =5; int a[len]; //error 不能是變量
2. 傳統形式的定義的數組,該數組的內存程序員無法手動釋放它,是由系統自動釋放的
在一個函數運行期間,系統爲該函數中數組所分配的存儲空間會一直存在,
直到該函數運行完畢時,數組的空間纔會被系統釋放。
3. 數組的長度一旦定義,其長度就不能再更改 a[5] 其中5就表示數組長度
數組的長度不能在函數運行的過程總動態的擴充或縮小
4. A函數定義的數組,在A函數運行期間可以被其他函數使用,但A函數運行完畢之後,
A函數中的數組將無法再被其他函數使用
傳統方式定義的數組,不能跨函數使用
爲什麼需要動態分配內存
動態數組很好的解決了傳統數組的四個缺陷
傳統數組也叫靜態數組
動態內存分配的舉例_動態數組的構造
malloc函數的使用:malloc 是分配內存的意思
malloc是memory(內存) allocate(分配)的縮寫
#include <stdio.h>
#include <malloc.h> //不能省
int main(void)
{
int i = 5; //靜態分配了 4個字節 //5行
int *p = (int *)malloc(4); //6行
/*
1.要使用malloc函數,必須添加malloc.h的頭文件
2.malloc函數只有一個形參,並且形參是整型
3.形參 (4) 表示請求系統爲本程序分配4個字節
4.malloc函數只能返回第一個字節的地址
5.6行最終分配了8個字節,P變量佔了4個字節,p所指向的內存也佔了4個字節
6.p 本身所佔 的4個內存是靜態分配的,p 所指向的 4個內存是動態分配的
*/
*p = 5; //*p 代表的就是一個int變量,只不過*p這個整型變量的內存分配方式和5行的分配方式不同
free(p); //表示把p所指向的內存給釋放掉,p本身的內存是靜態的,不能由程序員手動釋放,
//p本身的內存只能在p變量所在的函數運行終止時由系統自動釋放
printf("hello!\n");
return 0;
}
解析:
(int *)是將malloc請求的字節強制轉換爲整型變量,按整型變量所佔字節數來劃分地址。
(第一個字節地址只有值,而沒有類型,只有強制轉換後纔會給加上所屬轉換的類型。)
malloc後面的括號中必須是一個值,而這個值必須爲整數,無論請求的字節數是多少,比如 malloc(200)
但是最終返回的都是第一個字節的地址,如果只知道第一個字節地址,但是並不知道第一個字節地址指向的變量
最終會佔用幾個字節,所以需要在前面進行強制的變量類型轉換,告訴程序最後按什麼標準來劃分地址。以便明
白第一個字節地址到底指向的是一個什麼類型的變量。最終表示請求來的字節數按第一個字節地址所指向的變量
類型所佔的字節數來劃分,比如(int *)是4個字節的整型變量類型,那麼最後就會按4個字節來進行劃分。
如果按 malloc(200) 來看:
char * 佔1字節 200個變量
int * 佔4字節 50個變量
double * 佔8字節 25個變量
int *p = (int *)malloc(4)
完了之後,請求來的4個字節的首地址就會給p 而通過強制類型轉換,也知道了轉換後的類型。
#include <stdio.h>
#include <malloc.h>
void f(int *q)
{
//*p = 200; //error
//q = 200; //error
//**q = 200; //error 因爲*q已經是個整型變量了,所以前面不能再加*號
* q =200;
// free(q); //是把q所指向的內存釋放掉,必須註釋掉,否則會導致第17行代碼會出錯
}
int main(void)
{
int *p = (int *)malloc(sizeof(int)); //動態分配int類型的字節數4個字節,sizeof(int)的返回值是int
*p = 10;
printf("%d\n",*p); //10
f(p); //p是int *類型
printf("%d\n",*p); //200 //第17行
return 0;
}
解析:
因爲f(p)調用上面的f函數,而這個括號內p存放的是動態內存分配而來的第一個字節的地址,那麼將這個地址傳遞給q,那麼
void f(int *q)中q的值就是這個地址,跟p等同,而*q=200 是給*q這個整型變量賦值200,即改變了*q 的值,也相當於改變了
*p的值爲200,所以最終*p的輸出值也爲200
而如果free(q);生效,則因爲最終q指向的是sizeof申請來的整型的4個字節,所以如果生效,則會釋放掉申請來4字節的內存。
從而導致出錯。
動態一維數組的構造:
-------------------------------
#include <stdio.h>
#include <malloc.h>
int main(void)
{
int a[5]; //如果int佔4個字節的話,則本數組總共包含有20個字節,每個4個字節爲一個元素,從a[0]開始,後面依次類推。
int len;
int * pArr;
int i;
printf("請輸入你要存放的元素的個數:\n");
scanf("%d",&len);
pArr = (int *)malloc(4 * len); //動態的構造了一個數組,因爲是整型指針變量,一個元素佔4個字節,而len是元素個數
for (i=0;i<len;++i) //對動態一維數組進行操作
scanf("%d",&pArr[i]); //對動態一維數組進行賦值
printf("一維數組的內容是:\n");
for (i=0;i<len;++i) //對動態一維數組進行循環輸出
printf("%d\n",pArr[i]);
free(pArr); //釋放掉pArr所指向的動態數組分配的內存
return 0;
}
解析:
動態數組在使用上是跟靜態數組的使用方式是一樣的,只是構造的時候不一樣而已。
因爲pArr是整型指針變量佔4個字節,所以動態內存分配後,指向的是內存分配的20個字節中的前4個字節,如果pArr+1 則表示
指向的是第二個4個字節(即第5個字節到第8個字節),而不是在前面4字節的基礎上+1成爲5個字節。因爲每4個字節爲一部分。
由幾個字節爲一部分,需要由pArr的指針變量類型來決定。
pArr = (int *)malloc(4 * len);等同於int pArr[0]、int pArr[1]、int pArr[2]、int pArr[3]、int pArr[4]
即int pArr[5]
動態的構造了一個一維數組,該數組的數組名是pArr,數組長度是len,單個元素如上所示。
上面靜態的a[5]有靜態的20字節的內存
下面pArr動態申請的有20個字節的內存
由上可知:
a[0]是第一個元素,佔用了20個字節的前4個字節
那麼:pArr[0]同樣也是佔用了動態分配的20字節的前4個字節
又pArr[0]等同於 *(pArr+0)
所以例如:a[2]就等同於pArr[2]
那麼如何動態的增加數組的長度呢:
可以使用realloc函數:realloc(aArr,100) 括號裏面,前面表示數組名,後面表示要擴充的字節數容量上限。
如果pArr當初指向的是50個字節的內存,那麼運行的時候就會擴充到100.前50個字節的數據會保留。
如果pArr當初指向的是150個字節的內存,那麼運行的時候就縮小到100,後50個字節的數據會丟失。
#include <stdio.h>
#include <malloc.h>
void f(int *q)
{
*q = 10;
}
//void g(int **p)
//{
//}
int main(void)
{
int *p = (int *)malloc(4); //動態請求4字節 p指向的是(int*)
printf("*p = %d\n",*p); //輸出的是垃圾數字
f(p);
//g(&p); //p是int*,&p是int**
printf("*p = %d\n",*p);
return 0;
}
程序運行的結果爲:*p = 10
---------------------------
#include <stdio.h>
#include <malloc.h>
void f(int **q)
{
**q = **q + 30;
}
int main(void)
{
int i;
int *p;
printf("請輸入一個數字:\n");
scanf("%d",&i);
p = (int *)malloc(4); //動態請求4個字節的內存 //第13行
p = &i;
f(&p);
printf("i = %d\n",i);
// free(p); error,此處不能添加,否則會有內存報錯。
return 0;
}
程序運行的結果爲:i = 輸入的任何數+30後的結果
如果將第13行取消,程序依然可以運行,只是卻變成了由系統自行分配的靜態內存。
--------------------------------------------------------------------------
靜態內存和動態內存的區別比較:
靜態內存是由系統自動分配,由系統自動釋放
靜態內存是在棧中分配的
動態內存是由程序員手動分配,手動釋放,如果忘了釋放,就會出現內存泄漏,內存越用越少,最後就會死機。
動態內存是在堆分配的 堆就是堆排序
-----------------------------------------------
跨函數使用內存的問題:
靜態變量不能跨函數使用內存:
#include <stdio.h>
void f(int **q)
{
int i = 5;
//**q等價於*p *q等價於p q和**q都不等價於p
//*q = i; error,因爲*q = i; 等價於 p = i 這樣寫是錯誤的,而p中存放的是地址不是變量
*q = &i; // p = &i
}
int main(void)
{
int *p;
f(&p); //第12行
printf("%d\n",*p); //本句語法沒有問題,但邏輯上有問題。
//不能讀取不屬於自己的內存,在規則上講 //第13行
return 0;
}
程序的運行結果爲:5 .
解析:
此程序在語法上是沒有問題的,但是在現實中邏輯上是有問題的,不能這樣去寫程序。
程序在運行到第12行的時候,實際上f函數的靜態內存已經由系統自動釋放掉了,
所以這個時候第13行的p指向的i變量就不存在了,換句話說就是無法訪問了。
由此可見靜態變量當被終止的時候就不能夠被其他函數使用了。
----------------------------------------------------------------------------------
動態內存可以跨函數使用:
#include <stdio.h>
#include <malloc.h>
void f(int **q)
{
*q = (int *)malloc(sizeof(int)); //sizeof(數據類型) 返回值是該數據類型所佔的字節數
// 等價於 p = (int *)malloc(sizeof(int));
// q = 5; error
// *q = 5; //p=5; error
**q = 5; //*p = 5;
}
int main(void)
{
int *p;
f(&p);
printf("%d\n",*p); // 第15行
return 0;
}
運行結果爲:5
解析:
因爲是動態分配的內存 而p指向的是malloc(sizeof(int))中的int的4個字節的內存,而最終此程序並沒有手動釋放內存的
free(q),且動態內存是在堆裏面進行分配的不存在出棧,所以在最後第15行,仍然可以訪問之前動態分配的內存。
----------------------------------------------------------------------------------------------------------------