2022-2023-1 20201324《信息安全系统设计与实现(上)》第9章

1 I/O库函数

系统调用是文件操作的基础,但它们只支持数据块的读/写。用户程序希望以最适合应用程序的逻辑单元读/写文件,如行、字符、结构化记录等,而系统调用不支持这些逻辑单元。I/O库函数则能够满足用户这一要求

2 I/O库函数与系统调用

在Unix/Linux中,I/O库函数建立在系统调用的基础上

  • 系统调用函数:open() read() write() lseek() close()
  • I/O库函数:fopen() fread() fwrite() fseek() fclose()

fopen()依赖于open(),fread()依赖于read(),等等

3 I/O库函数的算法

(1)fread算法

在第一次调用fread()时,FILE结构体的缓冲区是空的,fread()使用保存的文件描述符fd发出一个

n=read(fa, fbuffer,BLKSIZE);

系统调用,用数据块填充内部的fbuf[]。然后,它会初始化 fbuf[]的指针、计数器和状态变量,以表明内部缓冲区中有一个数据块。接着,通过将数据复制到程序的缓冲区,尝试满足来自内部缓冲区的fread()调用。如果内部缓冲区没有足够的数据,则会再发出一个read()系统调用来填充内部缓冲区,将数据从内部缓冲区传输到程序缓冲区,直到满足所需的字节数(或者文件无更多数据)。将数据复制到程序的缓冲区之后,它会更新内部缓冲区的指针、计数器等,为下一个fread()请求做好准备。然后,它会返回实际读取的数据对象数量。
在随后的每次fread()调用中,它都尝试满足来自FILE结构体内部缓冲区的调用。当缓冲区变为空时,它就会发出read()系统调用来重新填充内部缓冲区。因此,fread()一方面接受来自用户程序的调用,另一方面向操作系统内核发出read()系统调用。

(2)fwrite算法

fwrite()算法与fread()算法相似,只是数据传输方向不同。最开始,FILE结构体的内部缓冲区是空的。在每次调用fwrite()时,它将数据写入内部缓冲区,并调整缓冲区的指针、计数器和状态变量,以跟踪缓冲区中的字节数。如果缓冲区已满,则发出write()系统调用,将整个缓冲区写入操作系统内核。

(3)fclose算法

若文件以写的方式被打开,fclose()会先关闭文件流的局部缓冲区。然后,它会发出一个close(fd)系统调用来关闭FILE结构体中的文件描述符。最后,它会释放FILE结构体,并将FILE指针重置为NULL。

4 使用I/O库函数或系统调用

对于以BLKSIZE为单位的读/写数据,使用系统调用比I/O库函数更高效。

5 I/O库模式

open()中的模式参数可以指定为:"r"、"w"、"a",分别代表读、写、追加。
每个模式字符串可包含一个+号,表示同时读写,或者在写入、追加情况下,如果文件不存在则创建文件。

  • "r+":表示读/写,不会截断文件。
  • "w+":表示读/写,但是会先截断文件;如果文件不存在,会创建文件。
  • "a+":表示通过追加进行读/写;如果文件不存在,会创建文件。

6 字符模式I/O

(1)字符模式I/O

int fgetc(FILE *fp);            //get a char from fp, cast to int
int ungetc(int c, FILE *fp);    //push a previously char got by fgetc() back to stream
int fput(int c, FILE *fp);      //put a char to fp

注意,fgetc()返回的是整数,而不是字符。这是因为它必须在文件结束时返回文件结束符。文件结束符通常是一个整数-1,将它与文件流中的任何字符区分开。
对于fp=stdin或stdout,可能会使用c=getchar(); putchar(c);来代替。对于运行时效来说,getchar()和putchar()通常不是getc()和 putc()的缩小版本。相反,可以将它们实现为宏,以避免额外的函数调用。

(2)字符模式

/* file copy using getc(), putc() */
  #include <stdio.h> 
  FILE*fp,*gp; 
  int main(){
     int c; /* for testing EoF */ 
     fp=fopen("source.txt","r"); 
     gp=fopen("target.txt","w");
     while((c=getc(fp)) != EOF )
         putc(c,gp);
     fclose(fp);
     fclose(gp);
  }

功能:读取source.txt中的内容,写入target.txt

(3)书本习题

练习9.2

编写一个C程序。将文本文件中的字母由小写转换为大写

#include <stdio.h>

FILE *fp, *gp;

int main(int argc, char *argv[])
{
    char ch;
    fp = fopen("source.txt", "r"); 
    gp = fopen("target.txt", "w"); 
    if(fp==NULL || gp==NULL){
		printf("Failed to open.\n");
		return 0;
    }
    while ((ch = fgetc(fp)) != EOF)
    {
        if(ch>='a'&&ch<='z')
		ch-=32;
        fputc(ch,gp);
    }
    fclose(fp); fclose(gp);
    return 0;
}

练习9.3

编写一个C程序。计算文本文件的行数

#include <stdio.h>

FILE *fp;

int main(int argc, char *argv[])
{
    char ch;
  	int lines=0;
	fp=fopen("source.txt","rb");
  	if(fp)
    {
      while((ch = fgetc(fp)) != EOF)
      {
        if(ch=='\n')
          lines++;
      }
      printf("%d\n",lines);
      fclose(fp);
    }
    return 0;
}

一开始我的运行结果是0,后来发现是因为写成了while(ch = fgetc(fp) != EOF) ,没有给ch = fgetc(fp) 加括号,导致结合的优先级出现错误

(4)行模式I/O

char *fgets(char *buf, int size,FILE*fp) :从fp中读取最多为一行(以\n结尾)的字符。
int fputs(char *buf,FILE*fp) :将buf中的一行写人fp中。

#include <stdio.h> 
FILE *fp,*gp;
char buf[256]; 
char *s="this is a string";
int main(){
    fp = fopen("sre","r"); 
    gp = fopen("dest","w");
    fgets(buf,256,fp); // read a line of up to 255 chars to buf
    fputs(buf,gp);	// write line to destination file
}

(5)格式化I/O

①格式化输入:(FMT=格式字符串)

scanf(char*FMT, &items);	// from stdin
fscanf(fp,char *FMT, &items);	// from file stream

②格式化输出:

printf(char *FMT, items);	// to stdout
fprintf(fp,char *FMT, items);	// to file stream

(6)内存中的转换函数

sscanf(buf,FMT,&items);	// input from buf[ ] in memory
sprintf(buf,FMT,items);	// print to buf[ ] in memroy

sscanf和sprintf并非I/O函数,而是内存中的数据转换函数

(7)其他I/O库函数

  • fseek()、ftell()、rewind():更改文件流中的读/写字节位置。
  • feof()、ferr()、fileno():测试文件流状态。
  • fdopen():用文件描述符打开文件流。
  • freopen():以新名称重新打开现有的流。
  • setbuf()、setvbuf():设置缓冲方案。
  • popen():创建管道,复刻子进程来调用sh。

7 文件流缓冲

  • 无缓冲
  • 行缓冲
  • 全缓冲
    通过fopen()创建文件流之后,在对其执行任何操作之前,用户均可发出一个
    setvbuf(FILE *stream, char *buf, int node, int size)
    调用来设置缓冲区(buf)、缓冲区大小(size)和缓冲方案(mode),它们必须是以下一个宏:
  • IONBUF:无缓冲
  • IOLBUF:行缓冲
  • IOFBUF:全缓冲
    此外,还有其他的setbuf()函数,是setvbuf()的变体。对于行缓冲流或全缓冲流,可用fflush(stream)立即清除流的缓冲区。

运行后报错。照网上教程添加头文件后成功运行

8 变参函数

目前,C语言和C++会强制执行类型检查,但这两种语言仍然允许参数数量可变的函数。这些函数必须至少使用一个参数进行声明,后跟3个点,如

int func(int m, int n ...)  //n = last specified parameter

在函数内部,可以通过C语言库宏访问参数:

void va_start(va_list ap,last); // start param list from last parameter
type va_arg(va_list ap, type);  // type = next parameter type
va_end(va_list ap);             // clear parameter list
posted @ 2022-09-11 16:18  20201324徐源  阅读(37)  评论(0编辑  收藏  举报