[TOC]html
最近詳細的複習C語言,看到存儲類別的時候總感受一些概念模糊不清,如今認真的梳理一下。C語言的優點之一可以讓程序員恰到好處的控制程序,能夠經過C語言的內存管理系統指定變量的做用域和生存週期,實現對程序的控制。程序員
對象:在C語言中全部的數據都會被存儲到內存中,被存儲的值會佔用必定的物理內存,這樣的一塊內存被稱爲對象,它能夠儲存一個或者多個值,在儲存適當的值時必定具備相應的大小。(C語言對象不一樣於面嚮對象語言的對象)數組
標識符:程序須要一種方法來訪問對象,這就須要聲明變量來實現,例如: int identifier = 1
,在這裏identifier
就是一個標識符,標識符是一個名稱並遵循變量的命名規則。因此在本例中identifier
便是C程序指定硬件內存中的對象的方式並提供了存儲的值的大小「1」。在其它的狀況中 int * pt
、int arr[10]
,pt就是一個標誌符,它指定了儲存地址的變量,可是表達式*p不是一個標誌符,由於它不是一個名稱。arr
的聲明建立了一個可容納10個int
類型元素的對象,該數組的每個元素也是一個對象。併發
做用域:描述程序中可訪問標識符的區域。由於一個C變量的做用域能夠是塊做用域、函數做用域、文件做用域和函數原型做用域。ide
塊做用域:簡單來講塊做用域就是一對花括號括起來的代碼區域。定義在塊中的變量具備塊做用域,範圍是定義處到包含該定義塊的末尾。函數
函數原型做用域:範圍是從形參定義處到函數原型聲明的結束。咱們知道編譯器在處理函數形參時只關心它的類型,而形參的名字一般可有可無。例如:oop
void fun(int n,double m); 一樣能夠聲明爲 void fun(int ,double );
還有一點要注意的是函數體的形參雖然聲明在函數的左花括號以前可是它具備的是塊做用域屬於函數體這個塊。ui
文件做用域:變量的定義在全部函數的外面,從它的定義處到該文件的末尾處都可見稱這個變量擁有文件做用域。因此文件做用域變量也被稱爲全局變量spa
連接:C變量有三種連接屬性:內部連接、外部連接和無連接。具備塊做用域、函數原型做用域的變量都是無連接變量,這就意味這他們屬於定義他們的塊或者函數原型私有。文件做用域變量能夠是外部連接或是內部連接,外部連接能夠在多個文件中使用,內部連接只能定義它的文件單元中使用。線程
指對象在內存中保留了多長時間,做用域和連接描述了對象的可見性。存儲期則描述了標識符訪問對象的生存期。
C對象有4種存儲期:
static
代表的是連接屬性而不是存儲期。以static
聲明的文件做用域變量具備內部連接,不管具備內部連接仍是外部連接,全部的文件做用域變量都具備靜態存儲期。還有一種狀況塊做用域變量也能夠擁有靜態存儲期,把變量聲明在塊中並在變量名前加static
關鍵字,例:
int fun(int num) { static int Index; ... }
在這裏變量Index
就被存儲在靜態內存中,從程序被載入到程序結束都會存在,可是隻有程序進入這個塊中才會訪問它指定的對象。
線程存儲期:用於併發程序設計,一個程序的執行能夠分爲多個線程,具備線程存儲期的變量從被聲明時到線程結束一直存在。以關鍵字_Thread_local
聲明一個對象時,每一個線程都得到該變量的私有備份。
自動存儲期:塊做用域變量一般具備自動存儲期,當程序進入定義這些變量的塊時,會爲這些變量分配內存,當程序離開這個塊時會自動釋放變量佔用的內存,這種作法至關於把自動變量佔用的內存視爲可重複利用的工做區或暫存區。
動態分配存儲期
存儲類別 | 存儲期 | 做用域 | 連接 | 聲明方式 |
---|---|---|---|---|
自動 | 自動 | 塊 | 無連接 | 塊內 |
寄存器 | 自動 | 塊 | 無連接 | 塊內 關鍵字regsiter |
靜態外部連接 | 靜態 | 文件 | 外部 | 全部函數外 |
靜態內部連接 | 靜態 | 文件 | 外部 | 全部函數外 關鍵字static |
靜態無連接 | 靜態 | 塊 | 無 | 塊內 關鍵字static |
自動變量屬於自動識別的變量具備自動存儲期,塊做用域且無連接。能夠顯示的使用auto
關鍵字進行聲明。
注意: auto是存儲類別說明符和C++中的auto用法徹底不一樣
一個變量具備自動存儲期就意味着當程序進入這個塊時變量存在,退出塊時變量消失,原來變量佔用的內存另做他用。
void hiding() { int x = 30; printf("x in outer block: %d at %p\n", x, &x); { x = 77; printf("x in inner block: %d at %p\n", x, &x); } // 塊中內存被釋放隱藏的x恢復 x = 30 printf("x in outer block: %d at %p\n", x, &x); while (x++ < 33) { int x = 100; x++; printf("x in while loop: %d at %p\n", x, &x); } printf("x in outer block: %d at %p\n", x, &x); }
沒有花括號時
void forc() { int n = 8; printf(" Initially, n = %d at %p\n", n, &n); for (int n = 1; n < 3; ++n) printf(" loop 1:n = %d at %p\n", n &n); // 離開循環後原始的你又起做用了 printf("After loop 1:n = %d at %p\n", n &n); for (int n = 1; n < 3; ++n) { printf("loop 2 index n = %d at %p\n", n, &n); // 從新初始化的自動變量,做用域沒有到循環裏的n int n = 6; printf(" loop 2:n = %d at %p\n", n, &n); // 起做用的仍然是循環中的n n++; } // 離開循環後原始的n又起做用了 printf(" loop 2:n = %d at %p\n", n, &n); }
輸出爲
使用關鍵字register,儲存在CPU的寄存器中,存儲在最快的可用內存中。
首先要明確概念靜態變量並非指值不改變的變量,而是指它在內存中的位置不變。具備文件做用域的靜態變量自動具備靜態存儲期。
前面提到咱們能夠建立一個靜態存儲期,塊做用域的局部變量,這種變量和自動變量同樣具備相同的做用域,可是在程序離開塊時並不會消失,
void trystat(); int main() { int count = 1; for (count = 1; count <= 3; count++) { printf("Here comes iteration %d:\n", count); trystat(); } trystat(); return 0; } void trystat() { int fade = 1; static int stay = 1; printf(" fade = %d and stay = %d\n", fade++, stay++); }
輸出:
能夠看出每次離開塊fade變量的值都會被從新的初始化,而stay只是在編譯函數void trystat()
的時候被初始化了一次,在離開本身函數體的塊和for循環塊以後都會遞增,說明stay訪問的對象一直存在並無像自動變量同樣被釋放掉。
具備外部連接、靜態存儲期和文件做用域,屬於該類別的變量屬於外部變量。只須要把變量的聲明放在全部函數的外面就建立了一個外部變量。爲了代表該函數使用了外部變量,須要使用關鍵字extern
來再次申明。若是在一個源文件中使用的外部變量聲明在了另外一個源文件中,則必需要使用extern來申明。
外部變量能夠顯示初始化,若是沒有則會被默認初始化爲0。
具備文件做用域、靜態存儲期和內部連接,在全部函數外用static
來聲明一個變量就是內部連接的靜態變量。
例:
static int val = 1; int main() { ... }
普通的外部變量能夠用於程序中的任意一個函數處,可是內部連接的靜態變量只能用於同一個文件中的函數。均可以使用extern
說明符,在函數中重複任何聲明文件做用域變量並不會改變他們的連接屬性。
例:
int global = 1; static int local_global = 2; int main { extern int global = 1; extern int local_global = 2; ... }
只有在多文件中才能區別內部連接和外部連接的重要性。
總結一下存儲類別的說明符中關鍵字共有六個auto
、register
、_Thread_local
、static
、extern
和typedef
,其中static
和extern
的含義取決於上下文。
函數也有存儲類別,分爲外部函數(默認)和靜態函數。外部函數能夠被其它的文件訪問,而靜態函數只能被定義所在的文件訪問。
例:
double gamma(double); static double beta(int,int); extern double(double,int);
一般使用extern
關鍵字聲明定義在其餘文件中的函數,這樣作是爲了代表當前文件中使用的函數定義在別處。除非使用static
關鍵字,不然通常函數聲明都默認爲extern
。
使用內部連接的靜態變量的函數,隨機函數(僞隨機數)
// 程序 12.7 static unsigned long int next = 1; unsigned int rand0(); int main() { int count = 1; for (count = 0; count < 5; count++) { printf("%d\n", rand0()); } return 0; } unsigned int rand0() { next = next * 1103515245 + 12345; return (unsigned int) (next/65536) % 32768; }
存儲類別有一個共同之處,在肯定使用哪種類型以後,會根據已經規定好的內存管理規則自動選擇其做用域和儲存儲期。如今咱們使用更加靈活的方式庫函數分配和管理內存。
靜態數據在程序載入內存時分配,而自動數據在程序執行時自動分配,在程序離開時銷燬。如今咱們能夠在使用malloc()
函數在程序中動態的分配內存,malloc()
接收一個參數,所須要要內存的字節數,在內存中自動尋找一個空閒的內存塊使用,malloc()
分配內存是匿名的並不會爲分配的內存塊賦名稱,可是動態內存會返回這個內存的首地址,因此使用一個指針類型的變量來接收它,而malloc()
函數可用於返回指向數組的指針、指向結構的指針等,一般使用強制類型轉換將返回的地址轉爲匹配的類型。
例如申請一個可容納30個double類型值的數組
double* pt; pt = (double*)malloc(30*sizeof(double))
注意:pt是數組的首元素地址,按照C語言的用法數組名就是首元素的地址,因此訪問這個數組中元素的方法就能夠這樣表示pt[0]、pt[1]. . .
所以也就有了三種來表示數組的方法:
1)使用常量表達式來表示數組的維度,用數組名來訪問數組的元素 。可使用靜態內存和自動內存
2)聲明變長數組,用變量表達式來表示數組的維度,用數組名訪問數組的元素,具備這種特性的數組只能在自動內存中建立
3)使用malloc()
動態內存來建立一個數組,先聲明一個指針,接收函數返回的地址。可使用指針訪問數組的元素,指針的類型能夠是靜態的或者自動的。
malloc()
函數要和free()配套使用,申請的內存從malloc()
開始到free()
結束。
void dyn_arr() { double* ptd; int max; int number; int i = 0; puts("what is the maxnum number of type double entries!"); if (scanf("%d", &max) != 1) { puts("Number not correctly entered --bye"); exit(EXIT_FAILURE); } ptd = (double*)malloc(max * sizeof(double)); if (ptd == NULL) { puts("Memory allocation failed. GoodBye."); exit(EXIT_FAILURE); } puts("Enter ther values (q to quit)"); while (i < max && scanf("%lf", &ptd[i]) == 1) ++i; printf("Here are your %d enteries:\n", number = i); for (i = 0; i < number; i++) { printf("%7.2f", ptd[i]); if (i % 7 == 6) putchar('\n'); } if (i % 7 != 0) putchar('\n'); puts("Done."); free(ptd); return 0; }
原文出處:https://www.cnblogs.com/TJTO/p/11795786.html