Linux系统编程学习笔记(二)C标准I/O库

Linux系统编程学习笔记(二)C标准I/O库

标准I/O

标准IO就是不依赖系统,提供更多的一层抽象,所以是跨平台的。当然也会有自己的包装,包括自带的buffer。

Buffered I/O:Buffered I/O可以减少系统调用的次数,提高性能。每次读一个block的整数倍可以提高效率。

1、Standard I/O:

C语言的标准IO库stdio,提供了跨平台的,user-buffering的方案。

2、文件指针:

标准I/O不直接操作文件描述符,而是使用文件指针来操作文件。
FILE *fp
打开的文件被称为流,输入流、输出流、输入/输出流\

3、打开文件:

#include <stdio.h>  
  
FILE *fopen(const char *path, const char *mode); 

f表示FILE,是标准库的流。

mode描述了如何打开由path代表的文件:
r:打开文件,可读,流的位置在文件的开头
r+:打开文件,可读写,流的位置在文件的开头
w: 打开文件,可写,如果存在,清空。不存在创建,流的位置在文件的开头
w+:打开文件,可读写,如果存在,则清空,如果不存在则创建,流的位置在文件的开头
a:append模式,可写,如果不存在创建。流的位置在文件的末尾。
a+:append模式,可读写,如果不存在创建。流的位置在文件的末尾。\

+就表示可读写。除了append表示在文件末尾写之外,默认都是在文件开头。

例子:

File *fp;  
  
fp = fopen("/etc/manifest","r");  
if(! fp )  
    /* error */  

4、通过文件描述符打开:

#include <stdio.h>  
  
FILE *fdopen(int fd, const char *mode);  

fd就表示文件描述符。

mode的含义和fopen一样,除了w、w+不会清空文件。

例子:

FILE *fp;  
int fd;  
  
fd = open("/home/kidd/map.txt",O_RDONLY);  
if(fd == -1)  
  /* error */  
fp = fdopen(fd,"r");  
if(!fp)  
 /* error */  

这里先是用了系统调用open打开了文件,然后以文件描述符作为参数调用标准库。

一个可读的fd,如果fdopen设置mode是w,会如何?二者不一致会返回NULL,并设置errno为EIVAL。EIVAL表示error是invalid类,即无效文件。

5、关闭流:

fclose可以关闭文件。

#include <stdio.h>  
  
int fclose(FILE *fp);  

buffer和没有写的数据首先被flushed,会被刷新到底下一层的存储位置,这里就是和标准库buffer对应的内核buffer。成功返回0,失败返回EOF。

6、关闭所有的流:

fcloseall关闭当前进程所有的流:

#define _GNU_SOURCE  
  
#include<stdio.h>  
  
int fcloseall(void);  

总是返回0。

7、从流中读:

1)一次读一个字符:

#include <stdio.h>  
  
int fgetc(FILE *fp);  

getc就是get character

返回读取字符转换成了int值,主要由于考虑文件结尾返回EOF,-1不在assci码范围。

int c;  
while((c = fgetc(fp)) != EOF){  
    printf("%c\n",(char)c);  
}  

2)将字符压回流:ungetc:

通过这个函数可以返回流中的字符,如果读超过了需要的字符,可以把这个字符压回。

#include <stdio.h>  
  
int ungetc(int c, FILE *fp);  

成功返回c,失败返回EOF

3)读取一行:

fgets可以读取一行字符串:

#include <stdio.h>  
  
char * fgets(char *str,int size, FILE *stream); 

读取遇到换行或者EOF,成功返回str,失败返回NULL。

例子:

char buf[LINE_MAX];//LINE_MAX in <limits.h>du  
if(!fgets(buf,LINE_MAX,fp)  
    /* error */  

4)读二进制数:

fread:

#include <stdio.h>  
  
size_t fread(void *buf, size_t size, size_t nr, FILE *fp);  

可以读取复杂的二进制数,比如c中的结构体。
读nr个数据,每一个大小为size字节。成功返回读取元素的个数,失败指示错误或者返回EOF,
但是如果不使用ferror()和feof()无法区分。
由于在不同机器的变量大小,对其,补齐,字节序不同,不具有跨机器性。\

例子:

char buf[64];  
size_t nr;  
  
nr = fread(buf,sizeof(buf),1,fp);  
if(nr == 0)   
    /* error */  

8、写入流:

1)写入一个字符:

#include <stdio.h>  
  
int fputc(int c, FILE *fp); 

成功返回c,失败返回EOF。

if( fputc('p',fp) )   
  /* error */  

2)写入一个字符串:

fputs:

#include <stdio.h>  
  
int fputs(const char *str, FILE *stream);  

写入由\0(也就是ascii里面的0,表示字符结尾)结尾的字符串,成功返回非负数,失败返回EOF。

例子:

FILE *fp;  
  
if(!(fp = fopen("journal.txt","a"))  
  /* error */  
if(fputs("The ship is made of wood.\n",fp) == EOF)  
 /*error*/  
if(fclose(fp) == EOF)  
 /*error*/  

3)写入二进制数据:

fwrtie可以将复杂的结构数据存储到文件中:

#include <stdio.h>  
  
size_t fwrite(void *buf,size_t size, size_t nr, FILE *fp);  

将由buf指向的数据写入文件fp中nr个元素,每一个元素的长度是size字节。

9、使用Buffered I/O 例子:

#include <stdio.h>  
  
int main(void){  
    FILE *in,*out;  
    struct pirate{  
        char name[100];  
        unsigned long booty;  
        unsigned int beard_len;  
    }p,blackbeard = {"Edward Teach",950,48};  
      
    out = fopen("data","w");  
  
    if(! out ){  
        perror("fopen");  
        return 1;  
    }  
  
    if(!fwrite(&blackbeard,sizeof(struct pirate),1,out)){  
        perror("fwrite");  
        return 1;  
    }  
  
    if(fclose(out)){  
        perror("fclose");  
        return 1;  
    }  
  
    in = fopen("data","r");  
    if( !in ){  
        perror("fopen");  
        return 1;  
    }  
  
    if(! fread(&p, sizeof(struct pirate),1,in)){  
        perror("fread");  
        return 1;  
    }  
  
    if(fclose(in)){  
        perror("fclose");  
        return 1;  
    }  
  
    printf("name=\"%s\" booty=%lu beard_len=%u\n",p.name,p.booty,p.beard_len);  
      
    return 0;  
}  

10、Seeking a Stream

1)fseek和标准IO lseek类似:

#include <stdio.h>  
  
int fseek(FILE *fp,long offset, int whence); 

whence可以取:SEEK_SET、SEEK_CUR,SEEK_END,和lseek含义一样。

2)fsetpos:

#include <stdio.h>  
  
int fsetpos(FILE *fp,fpos_t *pos);  

和fseek将whence设置成SEEK_SET一样。

3)rewind:

将文件位置重置,相当于fseek(fp,0,SEEK_SET);

#include <stdio.h>  
  
void rewind(FILE *fp);  

rewind 没有返回值,可以通过errno来判断是否成功:

errno = 0;  
rewind(stream);  
if(errno)  
  /* error */  

11、获取当前文件流的位置:

1)ftell:

#include <stdio.h>  
  
long ftell(FILE *stream);  

成功返回位置,失败返回-1,errno被设置。

2)fgetpos:

#include <stdio.h>  
  
int fgetpos(FILE *stream, fpos_t *pos);  

成功返回0,并设置pos,失败返回-1。

12、flush a Stream:

fflush:

#include <stdio.h>  
  
int fflush(FILE *fp);  

所有由fp标示的未写入的数据写入内核的buffer中。
成功返回0,失败返回EOF,并设置errno
write都会写入用户的buffer中,fflush可以将用户buffer的数据,写入内核buffer中,并不能保证写入磁盘。

13、错误和文件结尾:

一些标准的I/O比如fread,没有提供区分error和EOF的机制,所以检查所给的流确定遇到错误还是文件结尾很有用。标准I/O提供了两个接口:

1)ferror:

#include <stdio.h>  
  
int ferror(FILE *fp);  

返回非0表示设置了错误,0表示没有。

2)feof

#include <stdio.h>  
  
int feof(FILE *fp);  

测试流是否设置了EOF,返回非零表示设置,0表示没有。

3)清除错误和EOF设置:

#include<stdio.h>  
  
void clearerr(FILE *fp);  

例子:

if(ferror(f))  
    printf("Error on f!\n");  
if(feof(f))  
    printf("EOF on f!\n");  
clearerr(f);  

14、获得相关的文件描述符:

#include <stdio.h>  
  
int fileno(FILE *fp);  

成功返回相关的文件描述符,失败返回-1
混用标准I/O和IO系统调用并不是值得推荐的。

15、控制buffer:

a、标准I/O实现了三种用户Buffer:

1)Unbuffered:
没有缓存,数据直接交给内核。stderr默认使用这种方式。

2)Line-buffered:
buffer缓存一行,当遇到换行符则提交给内核。stdout默认使用这种方式。

3)Block-buffered
与文件相关的I/O默认都是Block-buffered。

b、设置流的buffer类型:

setvbuf:

#include <stdio.h>  
  
int setvbuf(FILE *stream, char *buf, int mode, size_t size); 

mode:
_IONBF: 没有buffer
_IOLBF: Line-buffered
_IOFBF: block-buffered/full buffered
除了_IONBF外,其他的两个,都需要用户提供一个size自己的buf。
提供的buffer在文件关闭的时候仍然要可见,否则显式的关闭操作会失败。\

下面例子存在bug:

#include <stdio.h>  
  
int main(void){  
    char buf[BUFSIZ];  
      
    setvbuf(stdout,buf,_IOFBF,BUFSIZ);  
    printf("Arrr!\n");  
  
    return 0;  
}  

程序退出的时候会隐式的关闭文件,导致失败。可以在上面程序return之前加上显式的close关闭stdout,或者使用全局的buf。

16、格式化I/O:

1)使用可变参数族:

#include <stdio.h>  
  
//正确返回输出的字节数,错误返回-1  
int printf(const char *restrict format, ...);  
int fprintf(FILE *restrict fp, const char * restrict format,...);  
  
//调用者需要确保buf足够大,不会溢出,  
int sprintf(char *restrict buf,const char * restrict format,...);  
//显示指定了buf的大小,多出来的会被抛弃  
int snprintf(char *restrict buf, size_t n, const char *restrict format,...);  
//正确返回输出到数组buf中的字节数,错误返回-1。  

2)使用va_list族:

va_list在<stdarg.h>中定义。

#include <stdio.h>  
#include <stdarg.h>  
  
int vprintf(const char *restrict format, va_list arg);  
int vfprintf(FILE *restrict fp, const char * restrict format,va_list arg);  
int vsprintf(char *restrict buf,const char * restrict format,va_list arg);  
int vsnprintf(char *restrict buf, size_t n, const char *restrict format,va_list arg);  

17、格式化输入:

1)使用可变参数族:

#include <stdio.h>  
  
int scanf(const char *restrict format,...);  
int fscanf(FILE * restrict fp, const char * restrict format,...);  
int sscanf(const char *restrict buf, const char *restrict format,...);  

2)使用va_list族

#include <stdio.h>  
#include <stdarg.h>  
  
int vscanf(const char *restrict format,va_list arg);  
int vfscanf(FILE * restrict fp, const char * restrict format,va_list arg);  
int vsscanf(const char *restrict buf, const char *restrict format,va_list arg);  

参考:

  1. 《Linux system programming》
  2. 《Unix system programming》
  3. 《Advanced Programming in the Unix Environment》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值