動态内存分配(動态存儲期)
在程序執行并使用該變量的時候分配内存空間,使用完畢立即釋放.
動态内存分配就 是指在程序執行的過程中動态地分配或者回收存儲空間的分配内存的方法。動态内存分配不像數組等靜态内存分配方法那樣需要預先分配存儲空間,而是由系統根據 程序的需要即時分配,且分配的大小就是程序要求的大小。
當程序運行到需要一個動态分配的變量或對象時,必須向系統申請取得堆中的一塊所需大小的存貯空間,用于存貯該變量或對象。當不再使用該變量或對象時,也就是它的生命結束時,要顯式釋放它所占用的存貯空間,這樣系統就能對該堆空間進行再次分配,做到重複使用有限的資源。
在使用數組的時候,總有一個問題困擾着我們:數組應該有多大?在很多的情況下,你并不能确定要使用多大的數組,比如上例,你可能并不知道我們要定義的這個數組到底有多大,那麼你就要把數組定義得足夠大。這樣,你的程序在運行時就申請了固定大小的你認為足夠大的内存空間。
即使你知道你想利用的空間大小,但是如果因為某種特殊原因空間利用的大小有增加或者減少,你又必須重新去修改程序,擴大數組的存儲範圍。這種分配固定大小的内存分配方法稱之為靜态内存分配。但是這種内存分配的方法存在比較嚴重的缺陷,特别是處理某些問題時:在大多數情況下會浪費大量的内存空間,在少數情況下,當你定義的數組不夠大時,可能引起下标越界錯誤,甚至導緻嚴重後果。
我們用動态内存分配就可以解決上面的問題. 所謂動态内存分配就是指在程序執行的過程中動态地分配或者回收存儲空間的分配内存的方法。動态内存分配不象數組等靜态内存分配方法那樣需要預先分配存儲空間,而是由系統根據程序的需要即時分配,且分配的大小就是程序要求的大小。
從以上動、靜态内存分配比較可以知道動态内存分配相對于靜态内存分配的特點:
1、不需要預先分配存儲空間;
2、分配的空間可以根據程序的需要擴大或縮小。
常見的動态内存錯誤:
(1)對NULL指針進行解引用操作
(2)對分配的内存進行操作時越過邊界
(3)釋放并非動态分配的内存
(4)試圖釋放一塊動态分配的内存的一部分以及一塊内存被釋放之後被繼續使用。
說明:
1、動态分配最常見的錯誤就是忘記檢查所請求的内存是否成功分配。
2、動态内存分配的第二大錯誤來源是操作内存時超出了分配内存的邊界。
當你使用free時,可能出現各種不同的錯誤:
1、傳遞給free的指針必須是一個從malloc、calloc或realloc函數返回的指針。
2、傳遞給free函數一個指針,讓它釋放一塊并非動态分配的内存可能導緻程序立即終止或在晚些時候終止。
3、試圖釋放一塊動态分配内存的一部分也有可能引起類似問題。
//實例:動态内存分配實現可變長一維數組
#define _GRT_SECURE_NO_WARNNGS
#include<stdio.h>
#include<stdlib.h>
#include"array.h"//這個頭文件 裡邊包含一個結構表示數組和下列函數的聲明原型
const Block_size = 20;///一次增容20個存儲空間
/*
Array array_creat(int ints_size); //創建一個數組
void array_free(Array *a);//回收空間
int array_size(const Array *a);//查看當前數組大小
int *array_at(Array *a, int index);//訪問數組
void array_inlate(Array *a, int more_size);//增容
*/
int main(void) {
Array a;//表示數組初始值的大小
int i, j,n,m=0;
while (1) {
printf("請輸入你需要多大的數組:\n");
scanf("%d", &n);
a = array_creat(n);//這個可得到a裡邊返回的參數
printf("輸入數據 \n");
for (i = 0; i < n; i ) {
scanf("%d", &j);
*array_at(&a, i) = j;//這個函數相當與是數組 把j的值保存到數組裡邊的元素中去
}
printf("輸出數據:\n");
for (i = 0; i < n; i ) {//遍曆輸出
printf("%d ", a.arrray[i]);
printf("\n");
}
printf("\n");
printf("輸入1初始化數組大小,輸入其他表示退出程序:\n");
scanf("%d", &n);
if (n == 1) {
m = 0;//清零
j = 0;
array_free(&a);//釋放之前的内存
}
else {
exit(0);//退出程序
}
}
return 0;
}
Array array_creat(int ints_size) //創建一個數組
{
Array a;//定義一個數組的結構體
a.size=ints_size; //表示數組的長度
a.arrray = (int *)malloc(sizeof(int)*a.size);//前一個int*是強制類型轉換,後面的表示一個int 是4個字節 總共就是長度乘以
return a;//返回的作用是 讓主函數調用它時,能夠得到它的參數
}
void array_free(Array *a)//回收空間
{
free(a->arrray);
a->arrray = NULL;//讓指針指空 不至于成為野指針
a->size = 0;
}
//封裝
int array_size(const Array *a)//查看當前數組大小
{
return a->size;
}
int *array_at(Array *a, int index)//訪問數組
{
if (index >= a->size) {
//下面的公式是為了算出Block_size的底在哪
//比如130,如果直接加20要加兩次,但是用公式就一次完成
array_inlate(a, (index / Block_size 1)*Block_size - a->size);//在原來的基礎上加20個
}
//返回指針 加括号是為了保持優先級不出錯
return &(a->arrray[index]); //如果返回的是值,那将不能被改變,返回指針就可以進行操作了
}
void array_inlate(Array *a, int more_size)//增容
{
int *p = (int*)malloc(sizeof(int)*(a->size more_size));//重新申請一塊更大的内存 100 20
int i;
for (i = 0; i < a->size; i ) {//把之前數組的内容拷貝到新的數組中去
p[i] = a->arrray[i];
}
free(a->arrray);//把之前的數組釋放
a->arrray = p;//将指針改變指向 重定向
a->size = more_size;//大小加上新增的
}
/*程序演示:
請輸入你需要多大的數組:
5
輸入數據
1 2 3 4 5
輸出數據:
1
2
3
4
5
輸入1初始化數組大小,輸入其他表述退出程序:
1
請輸入你需要多大的數組:
6
輸入數據
1 2 3 4 5 6
輸出數據:
1
2
3
4
5
6
輸入1初始化數組大小,輸入其他表述退出程序:
0
進程1520已退出.返回代碼為 0.
按任意鍵關閉此窗口...
//實例:動态内存分配實現可變長二維數組
#include<stdio.h>
#include<malloc.h>
int main(void)
{
int n, m;
scanf("%d %d", &n, &m);//n=5 m=2 按照自己輸入 來确定二維數組的大小
int **p = (int **)malloc(sizeof(int *) * n);//利用二級指針 申請五行元素
//p是一個二級指針 malloc函數返回一個int* 的類型 sizeof(int*)表示乘以的指針類型的大小
/*、申請m個能夠能夠存放 int* 類型的空間,并将首地址返回給一個二維指針p;
内存可能的分布情況:
int a < -- int *; < -- int **p;
int b < -- int *;
int c < -- int *;
int d < -- int *;
*/
// (int **) 一個*表示強制類型轉換,另一個表示指針 int *
//sizeof(int*),不能少*,一個指針的内存大小,每個元素是一個指針。用指針長度乘以數量 (int*)*n
// 這個p指針的數據類型是個二級指針,它指向的這個空間裡放的是些一級指針
for (int i = 0; i < 5; i )//每行有兩列元素
{
p[i] = (int *)malloc(sizeof(int) * m);//每個元素是int大小 4*m 将元素保存到每一行
//每一個一級指針值的大小 指向一個實際大小的空間
// *(p i) = p[i] 每一次移動表示行的移動
}
//賦值
for (int i = 0; i < n; i )
{
for (int j = 0; j < m; j )
{
p[i][j] =1;
//*(*(p i) j) = p[i][j]
}
}
for (int i = 0; i < n; i )
{
for (int j = 0; j < m; j )
{
//輸出數組每個元素值和地址
printf("%d=%p\t", p[i][j],&p[i][j]);
}
printf("\n");
}
for (int i = 0; i < n; i ) {//按 行 釋放指針
free(p[i]);
}
free(p);//釋放整體
return 0;
}
/*程序演示:
5 2
1=010F44C0 1=010F44C4
1=010F4378 1=010F433C
1=010F4330 1=010F4374
1=010FAB60 1=010FAB64
1=010FAD98 1=010FAB94
進程8432已退出.返回代碼為 0.
按任意鍵關閉此窗口...
const 函數(補充)之前一直把這個關鍵字漏掉了現在補上,const 限定符,它把一個對象轉換成一個常量,C語言中const關鍵字是constant的縮寫,通常翻譯為常量、常數等,有些朋友一看到const關鍵字馬上就想到了常量。事實上在C語言中const功能很強大,它可以修飾變量、數組、指針、函數參數等。
1、修飾變量:
在程序中使用const修飾變量,就可以對變量聲明為隻讀特性,并保護變量值以防被修改。如下:
const int i = 5; 變量i具有隻讀特性,不能夠被更改;若想對i重新賦值,如i = 10;則是錯誤的。
值得注意的是,定義變量的同時,必須初始化。定義形式也可以寫成int const i=5,同樣正确。
此外,const修飾變量還起到了節約空間的目的,通常編譯器并不給普通const隻讀變量分配空間,而是将它們保存到符号表中,無需讀寫内存操作,程序執行效率也會提高。
2、修飾數組
C語言中const還可以修飾數組,舉例如下:
const int array[5] = {1,2,3,4,5};
array[0] = array[0] 1; //錯誤
數組元素與變量類似,具有隻讀屬性,不能被更改;一旦更改,如程序将會報錯。
3、修飾函數參數
const關鍵字修飾函數參數,對參數起限定作用,防止其在函數内部被修改。所限定的函數參數可以是普通變量,也可以是指針變量。舉例如下:
void fun1(const int i)
i ; //對i的值進行了修改,程序報錯
void fun2(const int *p)
(*p) ; //對p指向空間的值進行了修改,程序報錯
保護數組中的元素:
為了避免函數的意圖不是為了修改數組當中的數據内容,那麼在函數原始聲明定義中時應使用關鍵字const,如:
int sum(const a[ ],int n); 這段代碼告訴編譯器,該函數不能修改a所指向的數組中的内容,如果在函數中不小心使用類似a[i] ;的表達式,那麼程序将會報錯。
要注意的是,這樣使用const并不是要求原數組是常量,而是該函數在處理數組時将其視為常量,不可修改,這樣使用const可以保護數組當中的數據不被修改。
4、修飾指針
C語言中const修飾指針要特别注意,共有兩種形式,一種是用來限定指向空間的值不能修改;另一種是限定指針不可更改。舉例說明如下:
int i = 5;
int j = 6;
int k = 7;
const int * p1 = &i; //定義1
int * const p2 =&j; //定義2
上面定義了兩個指針p1和p2。
在定義1中const限定的是 * p1,即其指向空間的值不可改變,若改變其指向空間的值如*p1=20,則程序會報錯;但p1的值是可以改變的,對p1重新賦值如p1=&k是沒有任何問題的。
在定義2中const限定的是指針p2,若改變p2的值如p2=&k,程序将會報錯;但*p2,即其所指向空間的值可以改變,如 * p2=80是沒有問題的,程序正常執行。
關于指針賦值和const需要注意一些規則:
1、把const數據或非const數據的地址初始化為指向const的指針或為其賦值是合法的
2、可以聲明并初始化一個不能指向别處的指針,關鍵是const的位置,這時候,這個指針可以修改它所指向的值,但是隻能指向初始化時設置的地址。
3、在創建指針時還可以使用兩次const,表示該指針既不能修改它所指向的地址,也不能修改它所指向地址上的值
清單:
int a[10];
const double b[10];
const double *p=a; //有效
p=b; //有效
p=&a[3]; //有效
---------------------------
int a[10];
const double b[10];
//隻能将非const數據的地址賦給普通指針 (否則,通過指針就能修改const數組中的值了)
double *p=a //有效
p=b; //無效*
p=&a[3]; //有效
---------------------------
void sum(const double *a,int n);
//此函數可以接受普通數組和const數組名作為參數,因為這兩種參數都可以用來初始化指向const的指針
int a[10];
const double b[10];
sum(a,5);//合法
sum(b,4);//合法
---------------------------
int a[10];
double *const p=a; //p指向數組的開始
p=&a[0]; //不允許,因為該指針不能指向别處
*p=9.9; //可以做,更改a[0]的值
---------------------------
int a[10];
const double *const p=a;
p=&a[0]; //不允許
*p=9.9; //不允許
塊塊指的是一塊數據,是個抽象的概念,和C語言沒有關系,這種抽象的東西,别說其他語言也能用,就是日常生活中也會把東西分塊管理,C語言中沒有對塊進行定義,因為這隻是個抽象的概念,塊可以是内存塊,數據塊,程序塊,哪怕是豆腐塊也能是塊... 意思就是在管理中被劃分為一類的一個基本單位
存儲期:
存儲期這也是變量的特點,它稱為生存期,表示變量在内存中存在的時間的長短。
1、靜态存儲期:在程序編譯時就分配内存空間并保持不變,程序執行結束後才釋放。
2、線程存儲期:thread_local,其聲明後會給每個線程分配一個單獨的私有備份
3、自動存儲期:局部變量通常都自動為auto 存儲期
4、動态存儲期:就是用new 或者malloc分配的内存,如果不主動釋放,在整個程序都占有内存
作用域:
這個是表示變量在哪些範圍内起作用,由鍊接點決定。
1、塊作用域:用{}括起來的,從聲明開始到“}” 結束
2、函數作用域:goto(标識符) 的作用域為整個函數。
3、函數原型作用域:函數聲明開始,函數聲明結束而結束
4、文件作用域:整個文件或者程序
鍊接屬性:表示變量能在哪些範圍内使用.
1、内部鍊接 :隻能在源文件内部使用.
2、外部鍊接 : 能在源文件内部和外部文件中使用.
3、空連接 : 隻能在代碼塊内(函數内部)使用.
限定符:
volatile:
const:
restrict:
_Atomic:
作者:Mr_Li_
另外的話為了幫助大家,輕松,高效學習C語言/C ,我給大家分享我收集的資源,從最零基礎開始的教程到C語言項目案例,幫助大家在學習C語言的道路上披荊斬棘!可以來我粉絲群領取哦~
編程學習書籍分享:
編程學習視頻分享:
整理分享(多年學習的源碼、項目實戰視頻、項目筆記,基礎入門教程)最重要的是你可以在群裡面交流提問編程問題哦!
對于C/C 感興趣可以關注小編在後台私信我:【編程交流】一起來學習哦!可以領取一些C/C 的項目學習視頻資料哦!已經設置好了關鍵詞自動回複,自動領取就好了!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!