Linux操作系統—標準IO庫(2)(2015-8-4)
分類:Linux操作系統
打開一個流後,可採用三種不同類型的非格式化I/O對其進行讀,寫操作。
1. 每次讀取一個字符的I/O
2. 每次一行的I/O。以換行符標示一行的終止
3. 二進制I/O。每次I/O操作讀或寫一定數量的對象,而每個對象具有指定的長度。
前兩者可以說是基於字符和行的I/O。後者叫做二進制I/O。
基於字符和行的I/O
字符I/O
讀字符
使用下面的三個函數可以一次讀取一個字符
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
對於這些函數的區別,作爲初學者,我不想太糾結,這不是重點。等以後使用熟練再深究也不遲。暫時先記着這個:函數getchar等同於getc(stdin)(這就意味着getchar是特定地從stdin中讀取數據)。
參數stream表示已打開並準備從其中讀取數據的流。成功時,三個函數均返回所讀取的字符,出錯或已到達文件尾端時返回EOF。
&esmp; 下面來講一講非常有意思東西。大家可能發現了,這三個函數返回的是字符,然而它們的函數類型卻是int型的,這是怎麼回事呢?
這三個函數以unsigned char類型轉換爲int的方式返回下一個字符。說明爲不帶符號的理由是,即使所讀字節最高位爲1也不會返回負數。要求整形返回值的理由是,這樣可以返回已發生錯誤或已達到文件尾端的指示值EOF(-1)。
小插曲:回送字符
當讀一個輸入流時,經常會用到回送字符操作。回送字符操作通常用於需要根據下一個字符的值來決定如何處理當前字符的情況。處理這種情況時,需要在讀出下一個字符以後,能將其送回流緩衝區,以便下一次輸入時再返回該字符。
標準I/O庫提供了ungetc函數以支持字符回送操作。該函數的原型如下。
#include <stdio.h>
int ungetc(int c, FILE *stream);
說明:參數c是要送回流中的字符,stream是所操作的流。成功時返回c,失敗時返回EOF。送到流中的字符以後又可以從流中讀出,但讀出的順序與送回的順序相反。回送的字符,不一定必須是上一次讀到的字符,但是不能回送EOF。但已到達文件尾端時,扔可以回送一個字符。下次讀將返回該字符,再次讀時才返回EOF。之所以能這樣做的原因是一次成功的ungetc調用會清除該流的文件結束指示。
判斷流結束或出錯
在大多數的FILE對象的實現中都爲每個流保持了兩個標誌:文件結束標誌和文件出錯標誌。feof函數和ferror函數分別根據這兩個標誌來判斷流是否結束或着流是否出錯。因爲到達文件尾端和出錯時,三個字符讀取函數的返回值都是EOF,所以應當通過調用feof和ferror函數區分這兩種情況。(看到這,終於明白了,這兩個函數的存在是爲了分辨,當這三個函數返回的是EOF時,到底是因爲流結束還是因爲是出錯了)。這兩個函數的原型如下:
#include <stdio.h>
int feof(FILE *stream);
int ferror(FILE *stream);
說明:參數stream爲要判斷是否已到達文件尾或出錯的流。當流已到達文件尾時,feof返回非0值,否則返回0值。當流出錯時,ferror返回非0,否則返回0。
那麼,如何清除FILE對象中的文件結束標誌和出錯標誌呢?可以使用clearerr函數,該函數的原型如下:
#include <stdio.h>
void clearerr (FILE *stream);
寫字符
使用下列字符可以一次寫入一個字符
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
與字符輸入函數一樣,putchar(c)與putc(c, stdout)等價。這些函數的每一次調用會使當前讀寫位置向文件尾部移動一個字符,即每次寫入的位置是上一次寫入位置之後的一個字節。
成功時,三個函數均返回寫入的字符,出錯時返回EOF。
實踐篇
初學者嘛,盡情地實踐,有了想法就把它實現出來,儘量把學到的東西都練習一遍。
目標一:從標準輸入中讀取字符並將其保存在名爲data.txt的文件中
這個並不難,可以很輕鬆地寫出來。注意在命令行中,按下Ctrl + D可以向輸入流中產生EOF值。
/*
* Name : IO001.c
* Author : LazyBone1994
* Date : 2015-8-4 18:00
*
*/
#include <stdio.h>
#include <stdlib.h>
/* 從標準輸入中讀取字符並將其保存在名爲data.txt的文件中 */
int main(int argc, char *argv[])
{
FILE *fp;
int c;
if ((fp = fopen("data.txt", "w")) == NULL){ /* 無法創建文件 */
printf("Cannot create data.txt file!\n");
exit(-1);
}
while ((c = getchar()) != EOF) /* 從標準輸入讀取字符 */
fputc(c, fp);
fclose(fp); /* 關閉流 */
return 0;
}
查看一下結果唄。實現了目標
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o IO001 IO001.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./IO001
Hello World !
This is a sample test.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
data.txt IO001 IO001.c IO001.c~ IO001.C~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ cat data.txt
Hello World !
This is a sample test.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$
初學者就要盡情地嘗試。改造一下,改哪裏?將getchar語句改掉改成一個等效的語句。
/*
* Name : IO001.c
* Author : LazyBone1994
* Date : 2015-8-4 18:00
*
*/
#include <stdio.h>
#include <stdlib.h>
/* 從標準輸入中讀取字符並將其保存在名爲data.txt的文件中 */
int main(int argc, char *argv[])
{
FILE *fp;
int c;
if ((fp = fopen("data.txt", "w")) == NULL){ /* 無法創建文件 */
printf("Cannot create data.txt file!\n");
exit(-1);
}
while ((c = getc(stdin)) != EOF) /* 從標準輸入讀取字符 */
fputc(c, fp);
fclose(fp); /* 關閉流 */
return 0;
}
看看結果如何。實現了目標。
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gedit IO001.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o IO001 IO001.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
IO001 IO001.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./IO001
Hello, My CSDN ID is LazyBone1994.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
data.txt IO001 IO001.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ cat data.txt
Hello, My CSDN ID is LazyBone1994.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$
目標二:從標準輸入中構造文本data.txt,並實現copyFile函數複製data.txt文件
按照順序來寫吧,如下:
/*
* Name : IO002.c
* Author : LazyBone1994
* Date : 2015-8-5 11:23
*/
#include <stdio.h>
#include <stdlib.h>
#define ERROR -2
#define OK 1
/* source爲要被複制的文件的指針,filename爲復件的文件名 */
int copyFile(FILE *source, char *filename)
{
char c;
FILE *newFile = NULL;
if (source == NULL){
printf("Source file error!\n");
return ERROR;
}
if (filename == NULL){
printf("Warning:The new file will use the default name.\n");
filename = "a.txt";
}
newFile = fopen(filename, "w"); /* 打開文件的另外兩個是fdopen和freopen,它們在這裏並不適用 */
if (newFile == NULL){
printf("Cannot create new file.\n");
return ERROR;
}
while ((c = getc(source)) != EOF){ /* 可用fgetc替換,另一個函數getchar在這並不適用 */
putc(c, newFile); /* 可用fputc替換,另一個函數putchar在這並不適用 */
}
fclose(newFile);
return OK;
}
/* 從標準輸入中構造文本data.txt,並實現copyFile函數複製data.txt文件 */
int main(int argc, char *argv[])
{
int flag;
char c;
char filename[33];
FILE *fp;
printf("Creating data.txt begins...\n");
if ((fp = fopen("data.txt", "w")) == NULL){
printf("Cannot create file data.txt.\n");
exit(-1);
}
printf("Please input the content of data.txt(end with Ctrl + D):\n");
while ((c = getchar()) != EOF){
fputc(c, fp);
}
fclose(fp);
fp = fopen("data.txt","r");
printf("Create file data.txt is done.\n");
printf("Copy new file is begining...\n");
printf("Please input new file's name(end with Ctrl + D):");
fgets(filename, 33, stdin);
putchar('\n');
flag = copyFile(fp, filename);
if (flag == OK)
printf("Copy new file is done.\n");
else
printf("Copy new file failed.\n");
return 0;
}
修改了好久(沒辦法比較菜),終於能行了,還算比較友好的界面提示。查看結果了。
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o IO002 IO002.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./IO002
Creating data.txt begins...
Please input the content of data.txt(end with Ctrl + D):
Hello everybody!
This is all the words that will be copied later.
Create file data.txt is done.
Copy new file is begining...
Please input new file's name(end with Ctrl + D):newFile.txt
Copy new file is done.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
data.txt IO002 IO002.c IO002.c~ newFile.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ cat newFile.txt
Hello everybody!
This is all the words that will be copied later.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$
怎麼說呢?當所有的輸入都不爲空的時候,確實能夠複製成功。但是但你輸入的新文件的名稱爲空時,複製的新文件的名字將會出現亂碼。爲什麼?因爲filename這個指針從來不爲空(即使你不輸入它)所以函數中的if (filename == NULL)
就成了無用的了,實際的文件名在你不輸入的情況下取的是垃圾值。怎麼修改呢?暫時先不想,往下繼續。想好的朋友可以告訴我。
發現自己的C語言功底實在是菜。特別是在字符串,數組,指針,傳指針調用結合在一起的情況下,就懵了。得好好惡補。
行I/O
讀行
使用以下兩個函數可以從流中一次讀取一行文本。
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
各參數和返回值含義如下:
1. s:輸入緩存
2. size:輸入緩存大小
3. stream:流文件指針
4. 返回值
- 成功時返回指向緩存的指針
- 失敗時或處於文件尾端時返回NULL,同時設置errno
fgets從指定的流讀,而gets從標準輸入讀。每調用一次fgets會使當前讀寫位置向文件尾部移動所讀取到的字符數,以保證每次讀取的是上一次讀取位置之後的位置。
對於fgets,必須指定緩存的長度size。次函數一直讀取到下一個換行符爲止,但是不超過size - 1個字符,讀入的字符被送入緩存。該緩存總是以null字符結尾。如果該行(包括最後一個換行符)的字符數超過size - 1,則只返回一個不完整的行,而且緩存仍以null字符結尾。對fgets的下一次調用會繼續讀取該行剩餘的字符。
gets並不招人待見,因爲使用它非常不安全,而且在新的C語言標準中它已經被踢出去了,只是爲了向後兼容,使用它仍可以通過編譯。記住一點:fgets會將讀取到的換行符送入緩存,而gets並不保存換行符。
寫行
使用下面兩個函數可向流中一次寫入一行文本。
#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);
這兩個函數的各參數和返回值的含義如下:
1. s:待輸出的字符串,應以null終止
2. stream:流文件指針
3. 返回值
- 成功時返回非負數
- 失敗時返回EOF(-1)
函數fputs將一個以null符終止的字符寫到指定的流,終止符null本身並不寫出。但是,puts在輸出指定的字符串後又附加地將一個換行符寫到標準輸出。
實踐篇
目標一:反覆從標準輸入中讀取行並將其保存在一個文本中
代碼如下嘍
/*
* Name : IO003.c
* Author : LazyBone1994
* Date : 2015-8-6 15:35
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUFFSIZE 81
void DeleteCharEnter(char *s)
{
if (s == NULL)
return ;
for (; (*s) != '\0'; s++){
if (*s == '\n')
*s = '\0';
}
}
/* 反覆從標準輸入中讀取行並將其保存在一個文本中 */
int main(int argc, char *argv[])
{
FILE *fp = NULL;
char filename[33], str[BUFFSIZE];
printf("Please input the filename(end with Enter):");
fgets(filename, 33, stdin);
DeleteCharEnter(filename);
if ((fp = fopen(filename, "w")) == NULL){
puts("Error : Cannot open file for writing.");
exit(-1);
}
printf("Please input the file content:\n"); /* 輸入quit結束 */
while (strcmp(fgets(str, BUFFSIZE, stdin), "quit\n") != 0){
fputs(str, fp);
}
fclose(fp);
return 0;
}
編譯並運行
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o IO003 IO003.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./IO003
Please input the filename(end with Enter):NewFIle
Please input the file content:
Hello World!
I am LazyBone1994.
quit
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
IO003 IO003.c NewFIle
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ cat NewFIle
Hello World!
I am LazyBone1994.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$
目標二:從文件中讀取字符,並將其顯示在標準輸出中
就利用剛纔生成的NewFile文件吧,代碼如下嘍
/*
* Name : IO004.c
* Author : LazyBone1994
* Date : 2015-8-6 16:23
*/
#include <stdio.h>
#include <stdlib.h>
#define BUFFSIZE 80
/* 從文件中讀取字符,並將其顯示在標準輸出中 */
int main(int argc, char *argv[])
{
char buf[BUFFSIZE];
FILE *fp;
fp = fopen("NewFIle", "r");
if (fp == NULL){
puts("Error : Cannot open file for reading.");
exit(-1);
}
while((fgets(buf, BUFFSIZE, fp)) != NULL){
fputs(buf, stdout);
}
fclose(fp);
return 0;
}
編譯並運行:
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
IO004 IO004.c NewFIle
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./IO004
Hello World!
I am LazyBone1994.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$