Linux编程学习笔记 | Linux IO学习[2] - 标准IO

在上一篇Linux编程学习笔记 | Linux IO学习[1] - 文件IO中,我总结了Linux下的文件IO。文件IO是偏底层的IO操作,在平时的日常工作中,使用文件IO的频率还是比较低的。我们每天使用的 printf() 就不是文件IO,而是另一类IO - 标准IO。在这篇文章中,我将介绍Linux下的标准IO并通过实例来说明如何使用它们。

标准IO库

要使用标准IO库,需要包含头文件 <stdio.h> 。该库为用户创建了一个连接底层系统调用的通用接口,它是ANSI C标准制定的库,因此具有可移植性(文件IO是基于Unix的POSIX标准,不可移植到Windows)。同文件IO类似,它需要先打开一个文件以建立一个访问途径,文件IO的访问途径是通过文件描述符,标准IO的访问途径是流(stream),它被实现为指向结构FILE的指针。

同文件IO类似,在程序启动时也有3个默认的文件流:

标准流 变量或宏 说明
0 stdin 标准输入
1 stdout 标准输出
2 stderr 标准错误输出

标准IO基本操作

标准IO的函数相对文件IO来说要多很多,我这里主要介绍13个标准IO函数。

打开/创建文件流

fopen() 和文件IO中的 open() 类似,用于打开文件流,函数说明如下:

FILE *fopen(const char *restrict pathname, const char *restrict mode);

args:
    const char *restrict pathname: 文件的路径
    const char *restrict mode    : 文件打开的模式
                                    
return:
    返回指向文件的指针,指针不为NULL是成功,指针为NULL是失败

文件打开的模式有以下6种:

1. "r" or "rb"            : 以只读形式打开文(文件必须存在)
2. "w" or "wb"            : 以写方式打开文件并将文件长度截为0或创建一个供写的文件 
3. "a" or "ab"            : 以写方式打开文件并将内容写到文件末或创建一个文件
4. "r+" or "rb+" or "r+b" : 以更新的方式(读/写)打开文件(文件必须存在)
5. "w+" or "wb+" or "w+b" : 以更新的方式(读/写)打开文件并将文件长度截为0或创建一个文件
6. "a+" or "ab+" or "a+b" : 以更新的方式(读/写)打开文件并将更新内容写到文件末或创建一个文件

fopen()open() 不同, fopen() 并不能在创建文件时改变其访问权限。

关闭文件流

fclose() 和文件IO中的 close() 类似,用于关闭文件流,函数说明如下:

int fclose(FILE *stream);

args:
    FILE *stream: 指向被关闭文件的指针 

return:
    关闭文件成功返回0,关闭文件失败返回而EOF

我们来看第一个例子,文件的打开和关闭:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;

    //fp = fopen("stdio.log", "r+");
    fp = fopen("stdio.log", "w+");
    if (fp == NULL) {
        printf("File create fail...\n");
        return -1; 
    } else {
        printf("File create success...\n");
    }
    
    fclose(fp);   

    return 0; 
}

运行结果:
新建一个叫 stdio.log 的文件,并输出
File create success...
如果我们注释掉第8行,去掉第7行的注释,那么将输出
File create fail...

修改文件流读写偏移量

fseek() 和文件IO中的 lseek() 类似,用于修改文件流读写的偏移量,函数说明如下:

int fseek(FILE *stream, long offset, int whence);

args:
    FILE *stream: 指向文件的文件指针
    long offset : 偏移量移动的距离
    int whence  : 偏移量的基址
                    - SEEK_SET 文件开始处
                    - SEEK_CUR 文件当前位置
                    - SEEK_END 文件结束处

return:
    修改偏移量成功返回0, 修改偏移量失败返回-1

whenceSEEK_CURSEEK_END 时, offset 可正负。

写文件流

fwrite()

fwrite() 和文件IO中的 write() 类似,函数说明如下:

size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

args:
    const void *restrict ptr: 写入数据在内存空间存储的地址
    size_t size             : 单个元素的大小
    size_t nitems           : 写入数据元素的个数
    FILE *restrict stream   : 指向写入文件的文件指针 

return:
    实际写入的元素个数,非负整数是成功,-1是失败

fputs()

fputs() 将字符串(不包括 `\0` )写入文件,函数说明如下:

int fputs(const char *restrict s, FILE *restrict stream);

args:
    const char *restrict s: 写入的字符串
    FILE *restrict stream : 指向写入文件的文件指针  

return:
    写入文件的状态,非负整数是成功,EOF是失败

puts()

puts() 将字符串(不包括 `\0` )写入 stdout ,并在行末添加一个换行符,函数说明如下:

int puts(const char *s);

args:
    const char *s: 写入的字符串

return:
    写出到stdio的状态,非负整数是成功,EOF是失败

fputc()

fputc() 将一个字符写入文件,函数说明如下:

int fputc(int c, FILE *stream);

args:
    int char    : 要写入的字符
    FILE *stream: 指向写入文件的文件指针 

return:
    如果没有错误,返回写入的字符,否则返回EOF

putc()

putc()fputc() 基本一样,只不过 putc() 是用宏实现而 fputc 是用函数实现。

int putc(int c, FILE *stream);

args:
    int c       : 要写入的字符
    FILE *stream: 指向写入文件的文件指针 

return:
    如果没有错误,返回写入的字符,否则返回EOF

我们通过例子来看看上面这几个函数的使用方法:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;

    fp = fopen("stdio.log", "w+");
    if (fp == NULL) {
        printf("File create fail...\n");
        return -1; 
    } else {
        printf("File create success...\n");
    }
    
    /* fwrite() function */ 
    char buffer_1[] = "This is fwrite DEMO..."; 
    size_t wr_size = 0; 
    wr_size = fwrite(buffer_1, 1, sizeof(buffer_1), fp); 
    printf("wr_size = %d\n", wr_size); 

    /* fputs() function */ 
    char buffer_2[] = "\nThis is fputs DEMO...\n"; 
    int fputs_status = 0; 
    fputs_status = fputs(buffer_2, fp); 
    printf("fputs_status = %d\n", wr_size); 
    
    /* puts function */
    char buffer_3[] = "This is puts DEMO..."; 
    puts(buffer_3);

    /* fputc function */
    char buffer_4[] = "This is fputc DEMO...\n";
    int ret;
    for (int i = 0; i < sizeof(buffer_4); i++) {
        ret = fputc(buffer_4[i], fp);
        printf("%c", ret);
    }

    /* putc function */
    char buffer_5[] = "This is putc DEMO...\n";
    for (int i = 0; i < sizeof(buffer_5); i++) {
        ret = fputc(buffer_5[i], fp);
        printf("%c", ret);
    }
    
    fclose(fp);   

    return 0; 
}

运行结果:
在生成的 std_io.log 文件中会输出以下内容,其中 @^ 就是 `\0`

This is fwrite DEMO...^@
This is fputs DEMO...
This is fputc DEMO...
^@This is putc DEMO...
^@

注意 fputs 函数并没有输出 `\0` 。在终端会输出:

File create success...
wr_size = 23
fputs_status = 23
This is puts DEMO...
This is fputc DEMO...
This is putc DEMO...

puts 函数直接将字符串输出到 stdio

读文件流

fread()

fread() 和文件IO中的 read() 类似,函数说明如下:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

args:
    void *ptr   : 读取数据存储的内存空间的地址  
    size_t size : 单个元素的大小
    size_t nmemb: 读取数据元素的个数
    FILE *stream: 指向读取文件的文件指针

return:
    实际读取的元素个数,非负整数是成功,-1是失败

fgets()

fgets() 用于读取文件中的字符串,然后将其存储到内存空间,函数说明如下:

char *fgets(char *restrict s, int n, FILE *restrict stream);

args:
    char *restrict s     : 读取后字符串存储的内存空间地址 
    int n                : 最大读取字符数
    FILE *restrict stream: 指向读取文件的文件指针

return:
    如果读取没有错误且没有读入EOF,返回写入的字符串
    如果读取没有错误但读入EOF,返回NULL指针
    如果读取出现错误,返回NULL指针

这里需要注意下该函数将在何时停止读取:
如果读取的字符数量达到 n - 1 ,或读取了换行符,或读取了字符串结束符,只要有一个满足则该函数会停止继续读取。

gets()

gets()stdin 中读取字符串并存放在内存中,函数说明如下:

char *gets(char *s);

args:
    char *s: 读取后字符串存储的内存空间地址  

return:
    如果读取没有错误且没有读入EOF,返回读取的字符串
    如果读取没有错误但读入EOF,返回NULL指针
    如果读取出现错误,返回NULL指针

读取操作将在读入换行符或EOF后结束。

fgetc()

fgetc() 从一个文件读取一个字符,函数说明如下:

int fgetc(FILE *stream);

args:
    FILE *stream: 指向读取文件的文件指针 

return:
    如果读取没有错误且没有读入EOF,返回读取的字符
    如果读取没有错误但读入EOF,返回EOF
    如果读取出现错误,返回EOF

getc()

getc()fgetc() 基本一样,只不过 getc() 是用宏实现而 fgetc() 是用函数实现。

int getc(FILE *stream);

args:
    FILE *stream: 指向读取文件的文件指针 

return:
    如果读取没有错误且没有读入EOF,返回读取的字符
    如果读取没有错误但读入EOF,返回EOF
    如果读取出现错误,返回EOF

说完了读操作的函数,我们也通过一个例子来看看如何使用这些函数:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;
    
    fp = fopen("stdio.log", "r+");
    if (fp == NULL) {
        printf("File open fail...\n");
        return -1; 
    } else {
        printf("File open success...\n");
    }
    
    /* fread() function */ 
    char buffer_1[50]; 
    size_t rd_size = 0; 
    rd_size = fread(buffer_1, 1, 24, fp); 
    printf("rd_size = %d\n", rd_size); 
    printf("fread get: %s\n", buffer_1);

    /* fgets() function */ 
    char buffer_2[50];
    char *fgets_status; 
    fgets_status = fgets(buffer_2, 23, fp); 
    printf("fgets_status = %s", fgets_status); 
    printf("fgets get: %s", buffer_2);
    
    /* gets function */
    char buffer_3[50];
    gets(buffer_3);
    printf("gets get: %s", buffer_3);

    /* fgetc function */
    int ret;
    while ((ret = fgetc(fp)) != EOF)
       printf("%c", ret);

    fclose(fp);   

    return 0; 
}

在编译过程中,编译器会警告:

warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration]
     gets(buffer_3);
     ^~~~
/tmp/cc3YWk3i.o: In function `main':
fread_get.c:(.text+0x11d): warning: the `gets' function is dangerous and should not be used.

因为 gets() 函数过于危险,在C11中的 <stdio.h> 已经不再包含 gets() 函数,因此会出现这个警告。
运行结果:
要读取的文件是之前写函数生成的 stdio.log 文件,终端输出如下。

File open success...
rd_size = 24
fread get: This is fwrite DEMO...
fgets_status = This is fputs DEMO...
fgets get: This is fputs DEMO...
test
gets get: testThis is fputc DEMO...
This is putc DEMO...

总结

这篇文章主要介绍了如何使用几个基本的标准IO函数对文件进行操作,相对于文件IO而言,标准IO使用更方便,并且支持跨平台使用。同时在传输大文件时,标准IO也不比文件IO慢。文中出现的代码都可在我的github上找到。

如果觉得本文对你有帮助,请多多点赞支持,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 2016-02-01 标准io 标准io处理了很多细节,例如缓存分配,优化长度执行io等。 流和file对象 之前...
    千里山南阅读 1,170评论 0 0
  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,128评论 2 34
  • tags:io categories:总结 date: 2017-03-28 22:49:50 不仅仅在JAVA领...
    行径行阅读 2,168评论 0 3
  • 赖玉超看铁打钉~ 2016年2月11日普宁 我媳妇组织同学会去了,是小学同学聚会。 她问我去不? 我也想去,一来我...
    laiyuchao阅读 245评论 0 0
  • 01 听说每一个人都会经历一段受伤的感情才会真正的长大。姐姐在结婚之前遇到了传说中的渣男友,他们是同事,在一起的时...
    宴宴阅读 388评论 0 4