C語言學習筆記(四)——存儲與文件

目錄

1 存儲類別

2 內存分配

1)malloc()函數

2)calloc()函數

3 文件輸入輸出

1)文件介紹

2)文件通信

3)隨機輸入

4)其他標準I/O函數


1 存儲類別

內存用於存儲程序中的數據,由存儲期、作用域和鏈接表徵。可以用存儲期(storage duration)描述對象,所謂存儲期是指對象在內存中保留了多長時間。用標識符訪問對象,可以用作用域(scope)和鏈接(linkage)描述標識符。

1)作用域

作用域決定程序的哪些部分可以訪問某數據。有塊作用域、函數作用域、函數原型作用域或文件作用域。

  • 定義在塊中的變量具有塊作用域(block scope),塊作用域變量的可見範圍是從定義處到包含該定義的塊的末尾。另外,雖然函數的形式參數聲明在函數的左花括號之前,但是它們也具有塊作用域,屬於函數體這個塊。
  • 函數作用域(function scope)僅用於goto語句的標籤。
  • 函數原型作用域(function prototype scope)用於函數原型中的形參名(變量名)。
  • 變量的定義在函數的外面,具有文件作用域(file scope)。文件作用域變量也稱爲全局變量(globalvariable)

2)鏈接

鏈接描述定義在程序某翻譯單元中的變量可被鏈接的程度。有外部鏈接、內部鏈接或無鏈接。

  • 具有塊作用域、函數作用域或函數原型作用域的變量都是無鏈接變量。
  • 具有文件作用域的變量可以是外部鏈接或內部鏈接。外部鏈接變量可以在多文件程序中使用,內部鏈接變量只能在一個翻譯單元中使用。

如何知道文件作用域變量是內部鏈接還是外部鏈接?可以查看外部定義中是否使用了存儲類別說明符static:

int giants = 5; // 文件作用域,外部鏈接
static int dodgers = 3; // 文件作用域,內部鏈接

3)存儲期

靜態存儲期、線程存儲期、自動存儲期、動態分配存儲期。

  • 對象具有靜態存儲期,那麼它在程序的執行期間一直存在。文件作用域變量具有靜態存儲期。
  • 線程存儲期用於併發程序設計,程序執行可被分爲多個線程。具有線程存儲期的對象,從被聲明時到線程結束一直存在。以關鍵字_Thread_local聲明一個對象時,每個線程都獲得該變量的私有備份。
  • 塊作用域的變量通常都具有自動存儲期。當程序進入定義這些變量的塊時,爲這些變量分配內存;當退出這個塊時,釋放剛纔爲變量分配的內存。
  • 塊作用域變量也能具有靜態存儲期。爲了創建這樣的變量,要把變量聲明在塊中,且在聲明前面加上關鍵字static。

4)存儲類別

C 使用作用域、鏈接和存儲期爲變量定義了多種存儲方案。5種存儲類別:自動、寄存器、靜態塊作用域、靜態外部鏈
接、靜態內部鏈接

自動變量

默認情況下,聲明在塊或函數頭中的任何變量都屬於自動存儲類別。可以顯式使用關鍵字auto。

寄存器變量

寄存器變量儲存在CPU的寄存器中,使用存儲類別說明符register便可聲明寄存器變量。

聲明變量爲register類別與直接命令相比更像是一種請求。編譯器必須根據寄存器或最快可用內存的數量衡量你的請求,或者直接忽略你的請求。

靜態變量

該變量在內存中原地不動,並不是說它的值不變。具有文件作用域的變量自動具有(也必須是)靜態存儲期。

外部變量

外部鏈接的靜態變量具有文件作用域、外部鏈接和靜態存儲期。該類別有時稱爲外部存儲類別。可以在函數中用關鍵字extern再次聲明。

內部鏈接的靜態變量

該存儲類別的變量具有靜態存儲期、文件作用域和內部鏈接。符static定義的變量具有這種存儲類別。

2 內存分配

在確定用哪種存儲類別後,根據已制定好的內存管理規則,將自動選擇其作用域和存儲期。然而,還有更靈活地選擇,即用庫函數分配和管理內存。

1)malloc()函數

該函數接受一個參數:所需的內存字節數。

malloc()函數會找到合適的空閒內存塊,這樣的內存是匿名的,即malloc()分配內存,但是不會爲其賦名。然而,它確實返回動態分配內存塊的首字節地址。因此,可以把該地址賦給一個指針變量,並使用指針訪問這塊內存。因爲char表示1字節,malloc()的返回類型通常被定義爲指向char的指針。如果 malloc()分配內存失敗,將返回空指針。

通常,malloc()要與free()配套使用。free()函數的參數是之前malloc()返回的地址,該函數釋放之前malloc()分配的內存。因此,動態分配內存的存儲期從調用malloc()分配內存到調用free()釋放內存爲止。

可以用 malloc()創建一個數組。

double * ptd;
ptd = (double *) malloc(30 * sizeof(double));

使用malloc(),程序可以在運行時才確定數組大小。如果內存分配失敗,可以調用  exit()函數結束程序,EXIT_SUCCESS(或者,相當於0)表示普通的程序結束, EXIT_FAILURE 表示程序異常中止。

2)calloc()函數

和malloc()類似,在ANSI之前,calloc()也返回指向char的指針;在ANSI之後,返回指向void的指針。如果要儲存不同的類型,應使用強制類型轉換運算符。

calloc()函數接受兩個無符號整數作爲參數(ANSI規定是size_t類型)。第1個參數是所需的存儲單元數量,第2個參數是存儲單元的大小(以字節爲單位)。

free()函數也可用於釋放calloc()分配的內存。

3 文件輸入輸出

1)文件介紹

文件(file)通常是在磁盤或固態硬盤上的一段已命名的存儲區。C把文件看作是一系列連續的字節,每個字節都能被單獨讀取。

  • C提供兩種文件模式:文本模式和二進制模式。

文本文件:文件最初使用二進制編碼的字符(例如, ASCII或Unicode)表示文本。

二進制文件:文件中的二進制值代表機器語言代碼或數值數據或圖片或音樂編碼。

C 提供兩種訪問文件的途徑:二進制模式和文本模式。在二進制模式中,程序可以訪問文件的每個字節。而在文本模式
中,程序所見的內容和文件的實際內容不同。程序以文本模式讀取文件時,把本地環境表示的行末尾或文件結尾映射爲C模式。

MS-DOS用\r\n組合表示文本文件換行。以文本模式打開相同的文件時,C程序把\r\n“看成”\n。但是,以二進制模式打開該文件時,程序能看見這兩個字符。

  • I/O的兩個級別

處理文件訪問的兩個級別。底層I/O(low-level I/O)使用操作系統提供的基本I/O服務。標準高級I/O(standard  high-level  I/O)使用C庫的標準包和stdio.h頭文件定義。C標準只支持標準I/O包。

  • 標準文件

C程序會自動打開3個文件,它們被稱爲標準輸入(standard input)、標準輸出(standard output)和標準錯誤輸出(standard error output)。

標準輸入是系統的普通輸入設備,通常爲鍵盤;

標準輸出和標準錯誤輸出是系統的普通輸出設備,通常爲顯示屏。

2)文件通信

重定向

程序可以通過兩種方式使用文件。第 1 種方法是,顯式使用特定的函數打開文件、關閉文件、讀取文件、寫入文件。

第2種方法是,設計能與鍵盤和屏幕互動的程序,通過不同的渠道重定向輸入至文件和從文件輸出。

重定向的一個主要問題與操作系統有關,與C無關。重定向輸入讓程序使用文件而不是鍵盤來輸入,重定向輸出讓程序輸出至文件而不是屏幕。

echo_eof < words
<符號是UNIX和DOS/Windows的重定向運算符。該運算符使words文件與stdin流相關聯,把文件中的內容導入echo_eof程序。echo_eof程序本身並不知道(或不關心)輸入的內容是來自文件還是鍵盤,它只知道這是需要導入的字符流,所以它讀取這些內容並把字符逐個打印在屏幕上,直至讀到文件結尾。

echo_eof>mywords
>符號是第2個重定向運算符。它創建了一個名爲mywords的新文件,然後把echo_eof的輸出重定向至該文件中。

標準I/O

int main(int argc, char *argv [])
{
int ch;      // 讀取文件時,儲存每個字符的地方
FILE *fp;  // “文件指針”
unsigned long count = 0;

if (argc != 2)
{
printf("Usage: %s filename\n", argv[0]);
exit(EXIT_FAILURE);
}

if ((fp = fopen(argv[1], "r")) == NULL)
{
printf("Can't open %s\n", argv[1]);
exit(EXIT_FAILURE);
}

while ((ch = getc(fp)) != EOF)
{
putc(ch, stdout); // 與 putchar(ch); 相同
count++;
}

fclose(fp);
printf("File %s has %lu characters\n", argv[1], count);
return 0;
}

解釋:

  • 1. exit()

根據ANSI  C的規定,在最初調用的main()中使用return與調用exit()的效果相同。因此,在main(),下面的語句:return 0;和下面這條語句的作用相同:exit(0);

如果main()在一個遞歸程序中,exit()仍然會終止程序,但是return只會把控制權交給上一級遞歸,直至最初的一級。然後return結束程序。return和exit()的另一個區別是,即使在其他函數中(除main()以外)調用exit()也能結束整個程序。

  • 2. 使用fopen()函數打開文件

第1個參數是待打開文件的名稱,第 2 個參數是一個字符串,指定待打開文件的模式。程序成功打開文件後,fopen()將返回文件指針(file pointer),其他I/O函數可以使用這個指針指定該文件。

如果使用任何一種"w"模式(不帶x字母)打開一個現有文件,該文件的內容會被刪除,以便程序在一個空白文件中開始操作。然而,如果使用帶x字母的任何一種模式,將無法打開一個現有文件。

  • 3. getc()和putc()函數

與getchar()和putchar()函數類似。所不同的是,要告訴getc()和putc()函數使用哪一個文件。

  • 4. 文件結尾

從文件中讀取數據的程序在讀到文件結尾時要停止。getc()函數在讀取一個字符時發現是文件結尾,它將返回一個特殊值EOF。爲了避免讀到空文件,應該使用入口條件循環(不是do while循環)進行文件輸入。

fp = fopen("wacky.txt", "r");

while (( ch = getc(fp)) != EOF)
{
putchar(ch); //處理輸入
}
  • 5. fclose()函數

fclose(fp)函數關閉fp指定的文件,必要時刷新緩衝區。如果成功關閉,fclose()函數返回0,否則返回EOF。

  • 6. 標準文件指針

stdio.h頭文件把3個文件指針與3個標準文件相關聯,C程序會自動打開這3個標準文件。

3)隨機輸入

fseek(fp, 0L, SEEK_END);       /* 定位到文件末尾 */
last = ftell(fp);

for (count = 1L; count <= last; count++)
{
fseek(fp, -count, SEEK_END);   /* 回退   */
ch = getc(fp);
if (ch != CNTL_Z && ch != '\r') /* MS-DOS 文件 */
putchar(ch);
}

putchar('\n');
fclose(fp);

fseek()函數,便可把文件看作是數組,在 fopen()打開的文件中直接移動到任意字節處。

第1個參數是FILE指針,指向待查找的文件,fopen()應該已打開該文件。

第2個參數是偏移量(offset)。該參數表示從起始點開始要移動的距離。該參數必須是一個long類型的值,可以爲正(前移)、負(後移)或0(保持不動)。

第3個參數是模式,該參數確定起始點。

下面是調用fseek()函數的一些示例,fp是一個文件指針:

fseek(fp, 0L, SEEK_SET); // 定位至文件開始處
fseek(fp, 10L, SEEK_SET); // 定位至文件中的第10個字節
fseek(fp, 2L, SEEK_CUR); // 從文件當前位置前移2個字節
fseek(fp, 0L, SEEK_END); // 定位至文件結尾
fseek(fp, -10L, SEEK_END); // 從文件結尾處回退10個字節

ftell()函數的返回類型是long,它返回的是當前的位置。

fgetpos()和 fsetpos()。這兩個函數不使用 long 類型的值表示位置,它們使用一種新類型:fpos_t(代表file  position  type,文件定位類型)。fpos_t類型不是基本類型,它根據其他類型來定義。fpos_t  類型的變量或數據對象可以在文件中指定一個位置,它不能是數組類型,除此之外,沒有其他限制。

4)其他標準I/O函數

  1. fprintf()和fscanf()函數的工作方式與printf()和scanf()類似,區別在於前者需要用第1個參數指定待處理的文件。
  2. fgets()函數。它的第1個參數和gets()函數一樣,也是表示儲存輸入位置的地址(char * 類型);第2個參數是一個整數,表示待輸
  3. 入字符串的大小 ;最後一個參數是文件指針,指定待讀取的文件。
  4. int ungetc()函數把c指定的字符放回輸入流中。如果把一個字符放回輸入流,下次調用標準輸入函數時將讀取該字符。
  5. int fflush(FILE *fp);                                                                                                                                                                   調用fflush()函數引起輸出緩衝區中所有的未寫入數據被髮送到fp指定的輸出文件。這個過程稱爲刷新緩衝區。
  6. int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);                                                                            setvbuf()函數創建了一個供標準I/O函數替換使用的緩衝區。在打開文件後且未對流進行其他操作之前,調用該函數。
  7. read()和 fwrite函數用於以二進制形式處理數據:                                                                                                              size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb,FILE * restrictfp);fwrite()函數把二進制數據寫入文件。指針ptr是待寫入數據塊的地址,它的第1個參數不是固定的類型。。size表示待寫入數據塊的大小(以字節爲單位),nmemb表示待寫入數據塊的數量。和其他函數一樣,fp指定待寫入的文件。
  8. 如果標準輸入函數返回  EOF,則通常表明函數已到達文件結尾。然而,出現讀取錯誤時,函數也會返回EOF。feof()和ferror()函數用於區分這兩種情況。當上一次輸入調用檢測到文件結尾時,feof()函數返回一個非零值,否則返回0。當讀或寫出現錯誤,ferror()函數返回一個非零值,否則返回0。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章