Linux 程序设计学习笔记----ANSI C 文件I/O管理

转载请注明出处:http://blog.csdn.net/suool/article/details/38129201

问题引入

文件的种类

根据数据存储的方式不同,可以将文件分为文本文件和二进制文件.具体的区别和关系如下:

文本文件与二进制文件在计算机文件系统中的物理存储都是二进制的,也就是在物理存储方面没有区别都是01码,这个没有异议,他们的区别主要在逻辑存储上,也就是编码上。

文本文件格式存储时是将值作为字符然后存入其字符编码的二进制文本文件用‘字符’作为单位来表示和存储数据,比如对于1这个值,文本文件会将其看做字符‘1’然后保存其ASCII编码值(这里假定是ASCII编码),这样在物理上就是0x31这个二进制值,而若是二进制保存1,则直接保存其二进制值,比如如果程序中是处理1为整数则保存的二进制值就是 0x00000001 (4字节)。

当然如果程序本来就是按字符保存的 也就是 char ch =‘1‘ ;则二进制保存后值就是其ASCII码,因为该变量的二进制本来就是其ASCII码。可以总结出二进制文件就是值本身的编码,那么就是不定长的编码了,因为值本身就是不等字节的,如整数4个字节那么保存在二进制文件就是这四个字节的原生二进制值。

综上,可以知道文本文件与二进制文件就是编码方式不一样而已,而这个是用户行为,把一个数据以什么样的编码(字符还是值本身)存入文件是由用户主动选择的,也就是写入的接口选择,如果以二进制接口方式写入文件那么就是一个二进制文件,如果以字符方式写入文件就是一个文本文件了。既然有写入时候的编码也就会有读出的编码,只有两个编码对应才能读出正确的结果,如用记事本打开一个二进制文件会呈现乱码的,这里稍微提一下后缀名,后缀名并不能确定其是否就是文本文件,二进制文件也可以是txt后缀名,后缀名只是用来关联打开程序,给用户做备注用的,与文件的具体编码没有关系

可以使用字符接口读写二进制文件,只需要做些处理即可,所以所谓的二进制文件,文本文件主要体现在读写方式这里。此外windows有一个明显的区别是对待文本文件读写的时候,会将换行 \n自动替换成 \r\n。

最后文本文件和二进制文件主要是windows下的概念,UNIX/Linux并没有区分这两种文件,他们对所有文件一视同仁,将所有文件都看成二进制文件。

文件操作方式

根据应用程序对文件的访问方式不同,可以分为带缓冲区的文件操作和非缓冲文件操作,具体区别简单介绍如下:

缓冲文件操作:高级文件操作,将在用户空间中自动为正在使用的文件开辟内存缓冲区,遵循ANSI C标准的I/O函数就是缓冲文件操作

非缓冲文件操作:低级文件操作,如果需要,只能由用户自己在程序中为每个文件设置缓冲区,遵循POSIX标准的系统调用I/O函数就是这种类型.

具体的区别见:http://www.360doc.com/content/11/0521/11/5455634_118306098.shtml

ANSI C库函数为了实现他的特性,采用了流的概念,在流的实现中,缓冲区是最重要的单元,根据需要可以分为全缓冲,行缓冲,无缓冲.

下面就具体讲一下关于ANSI  C的流和文件I/O相关的方法和函数.

问题解析

关于流及其功能

在linux系统中,系统默认为每个进程打开了三个文件,即是每个进程可以默认操作三个流:标准输入流,输出流,错误流.

在系统源文件中的宏定义如下:

文件流的主要功能是:

1‘格式化内容:实现不同输入输出格式转换.

2‘缓冲功能:将数据读写集中,从而减少系统调用次数

文件流指针

在应用编程层面上,程序对流的操作主要是体现在文件流指针FILE上,操作一个文件之前,需要使用fopen打开文件,之后返回该文件的文件流指针与该文件关联,所有针对该文件的读写操作都是通过文件指针完成.下面是在应用层能够访问的FILE的结构体,因此结构体成员可以在用户空间访问:

struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
..............
  int _fileno;                         / 文件描述符
#if 0
}

上面那个结构体中包含了I/O库为管理该流所需要的所有信息.

下面是打印了一个打开的文件流指针指针成员信息的示例程序,在此程序中,使用标准的C库函数实现了文件的复制操作,在复制过程中,实时打印当前的读写位置和缓冲区信息.

代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define prt(CONTENT,MSG) printf(CONTENT":\t%p\n",MSG)

int main(int argc, char *argv[])
{
    FILE *fp_src,*fp_des;                                // 文件读写指针
    char buffer[10],buffer1[128];                        // 文件读写缓冲区
    int i;
    if((fp_src=fopen(argv[1],"r+"))== NULL)	             // 从第一个命令行文件参数,读文件
    {
        perror("open1");
        exit(EXIT_FAILURE);
    }
    if((fp_des=fopen(argv[2],"w+"))== NULL)              // 从第二个命令行文件参数,写文件
    {
        perror("open2");
        exit(EXIT_FAILURE);
    }
    setvbuf(fp_src,buffer1,_IOLBF,128);                  // 显式设置缓冲区的位置和类型
    do
    {
        /*
         * 以下的prt内容均是属于提示性内容,是读取应用层所能访问的FILE结构体得到的.
         */
        prt("src_IO_read_ptr",fp_src->_IO_read_ptr);     // 源文件读位置,source
        prt("_IO_read_end",fp_src->_IO_read_end);
        prt("_IO_read_base",fp_src->_IO_read_base);

        prt("src_IO_write_ptr",fp_src->_IO_write_ptr);
        prt("_IO_write_base",fp_src->_IO_write_base);
        prt("_IO_write_end",fp_src->_IO_write_end);

        prt("_IO_buf_base",fp_src->_IO_buf_base);        // 源文件缓冲区位置
        prt("_IO_buf_end",fp_src->_IO_buf_end);

        memset(buffer,'\0',10);
        i = fread(buffer,1,10,fp_src);                   // 读
        fwrite(buffer,1,i,fp_des);                       // 写

        prt("des_IO_read_ptr",fp_des->_IO_read_ptr);
        prt("des_IO_write_ptr",fp_des->_IO_write_ptr);   // 目标文件写位置

    }while(i==10);
    fclose(fp_src);
    fclose(fp_des);                                      // 关闭文件读写
}

运行结果如下:

最终成功复制.

缓冲区类型

刚刚说了三种缓冲区类型,他们依然在系统源代码文件有所定义.

对于标准流ANSI C有如下的要求:

1.标准输入输出:当且仅当不涉及交互作用设备时,他们才是全缓冲的

2.标准错误:绝不会是全缓冲的

下面是用于测试缓冲区类型的示例程序.

代码:

#include <stdio.h>

void pr_stdio(const char *, FILE *);
int main(void)
{
    FILE    *fp;
    fputs("enter any character\n", stdout);
    if(getchar()==EOF)
        printf("getchar error");
    fputs("one line to standard error\n", stderr);
    pr_stdio("stdin",  stdin);                    // test for standard input stream
    pr_stdio("stdout", stdout);                   // test for standard output stream
    pr_stdio("stderr", stderr);                   // test for standard error stream

    if ( (fp = fopen("/etc/motd", "r")) == NULL)  // 普通文件
        printf("fopen error");
    if (fgetc(fp) == EOF)
        printf("getc error");
    pr_stdio("/etc/motd", fp);
    return(0);
}
void pr_stdio(const char *name, FILE *fp)
{
    printf("stream = %s, ", name);
    if (fp->_flags & _IO_UNBUFFERED)              // 无缓冲
        printf("unbuffered");
    else if (fp->_flags & _IO_LINE_BUF)           // 行缓冲
        printf("line buffered");
    else
        printf("fully buffered");                 // 全缓冲
    printf(", buffer size = %ld\n", fp->_IO_buf_end-fp->_IO_buf_base);
}

结果如下:

指定缓冲区类型,通过sretbuf函数.其中三种缓冲的定义如下:

一个示例代码如下:

/* Example show usage of setbuf() &setvbuf() */
#include<stdio.h>
#include<error.h>
#include<string.h>
int main( int argc , char ** argv )
{
    int i;
    FILE * fp;
    char msg1[]="hello,wolrd\n";
    char msg2[] = "hello\nworld";
    char buf[128];

    //open a file and set nobuf(used setbuf).and write string to it,check it before close of flush the stream
    if(( fp = fopen("no_buf1.txt","w")) == NULL)
    {
        perror("file open failure!");
        return(-1);
    }
    setbuf(fp,NULL);                                    // set the buff NULL
    memset(buf,'\0',128);
    fwrite( msg1 , 7 , 1 , fp );                        // write message to file
    printf("test setbuf(no buf)!check no_buf1.txt\n");
    printf("now buf data is :buf=%s\n",buf);            // test buff

    printf("press enter to continue!\n");
    getchar();
    fclose(fp);

    //open a file and set nobuf(used setvbuf).and write string to it,check it before close of flush the stream
    if(( fp = fopen("no_buf2.txt","w")) == NULL)
    {
        perror("file open failure!");
        return(-1);
    }
    setvbuf( fp , NULL, _IONBF , 0 );
    memset(buf,'\0',128);                                // clear the buff
    fwrite( msg1 , 7 , 1 , fp );
    printf("test setvbuf(no buf)!check no_buf2.txt\n");

    printf("now buf data is :buf=%s\n",buf);

    printf("press enter to continue!\n");
    getchar();
    fclose(fp);

    //open a file and set line buf(used setvbuf).and write string(include '\n') to it,
    //
    //check it before close of flush the stream
    if(( fp = fopen("l_buf.txt","w")) == NULL)
    {
        perror("file open failure!");
        return(-1);
    }
    setvbuf( fp , buf , _IOLBF , sizeof(buf) );
    memset(buf,'\0',128);
    fwrite( msg2 , sizeof(msg2) , 1 , fp );
    printf("test setvbuf(line buf)!check l_buf.txt, because line buf ,only data before enter send to file\n");

    printf("now buf data is :buf=%s\n",buf);
    printf("press enter to continue!\n");
    getchar();
    fclose(fp);

    //open a file and set full buf(used setvbuf).and write string to it for 20th time (it is large than the buf)
    //check it before close of flush the stream
    if(( fp = fopen("f_buf.txt","w")) == NULL)
    {
        perror("file open failure!");
        return(-1);
    }
    setvbuf( fp , buf , _IOFBF , sizeof(buf) );
    memset(buf,'\0',128);
    fwrite( msg2 , sizeof(msg2) , 1 , fp );
    printf("test setbuf(full buf)!check f_buf.txt\n");

    printf("now buf data is :buf=%s\n",buf);    // check data of the current buff
    printf("press enter to continue!\n");
    getchar();

    fclose(fp);

}

result:

ANSI C 文件I/O操作

1.打开关闭文件三个函数:

fopen()

fclose()

fflush()  // 刷新流

具体代码见stdio.h源代码

2.读写文件

字符单位

1.字符读

从流中读取

从stdin中:

/* Read a character from stdin.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int getchar (void);

2.字符写

/* Write a character to STREAM.

   These functions are possible cancellation points and therefore not
   marked with __THROW.

   These functions is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int fputc (int __c, FILE *__stream);
extern int putc (int __c, FILE *__stream);

/* Write a character to stdout.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int putchar (int __c);

行单位

行读出&行写入

/* Write a string to STREAM.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int fputs (const char *__restrict __s, FILE *__restrict __stream);

/* Write a string, followed by a newline, to stdout.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int puts (const char *__s);

块单位

读写

/* Read chunks of generic data from STREAM.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern size_t fread (void *__restrict __ptr, size_t __size,
		     size_t __n, FILE *__restrict __stream) __wur;
/* Write chunks of generic data to STREAM.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern size_t fwrite (const void *__restrict __ptr, size_t __size,
		      size_t __n, FILE *__restrict __s);

文件流检测

即是检测文件是否已经读写到末尾或者出错.,使用feof函数.

对于ascii码文件,可以通过是否=EOF判断

对于二进制文件,则需要使用feof来判断是否结束,结束返回1.否则返回0.

ferror函数判断是否出错,无错返回0.

文件流定位

ftell()函数返回流的当前读写位置距离文件开始的字节数

fseek()函数修改当前读写位置

rewind()函数重置读写位置到开头.

/* Seek to a certain position on STREAM.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int fseek (FILE *__stream, long int __off, int __whence);
/* Return the current position of STREAM.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern long int ftell (FILE *__stream) __wur;
/* Rewind to the beginning of STREAM.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern void rewind (FILE *__stream);

实例应用

为实现磁盘文件的复制,在ANSI C 的标准下,首先需要将源文件和目标文件建立联系以读的方式打开源文件,以写的方式打开目标文件,先将源文件的数据集中写到缓冲区,然后从内存写入目标文件所在的磁盘.

其中最重要的是判断文件的是否结束.一种方式是使用feof函数,另一种方式是使用读操作的返回值,比如,每次读128字节,只有读到结束位置的时候毒的字节数会小于128,如果出错则返回-1.

一种实现代码如下:

#include<stdio.h>
int main(int argc,char *argv[])
{
    FILE *fp=NULL;
    char ch;
    if(argc<=1)
    {
        printf("check usage of %s \n",argv[0]);
        return -1;
    }
if((fp=fopen(argv[1],"r"))==NULL)			//以只读形式打开argv[1]所指明的文件
    {
        printf("can not open %s\n",argv[1]);
        return -1;
    }
    while ((ch=fgetc(fp))!=EOF)			   //把已打开的文件中的数据逐字节的输出到标准输出stdout
        fputc(ch,stdout);
    fclose(fp);						       //关闭文件
    return 0;
}

具体结果就不演示了.可以自行编译验证.

ElSE

流的格式化输入和输出操作

其实就是几个函数的讲解,这个部分很多资料,所以不在讲了,具体的可以自己google或者看源代码了.

1.

printf()和scanf(0函数.

2.

fprintf()函数和fscanf()函数

3.sprintf函数

4.sscanf()函数

下面贴一下一个使用sscanf获取cpu频率的示例程序:

#include <stdio.h>
#include <string.h>
float get_cpu_clock_speed ()
{
      FILE* fp;
      char buffer[1024];
      size_t bytes_read;
      char* match;
      float clock_speed; 

      fp = fopen ("/proc/cpuinfo", "r");
      bytes_read = fread (buffer, 1, sizeof (buffer), fp);
      fclose (fp);
      if (bytes_read == 0 || bytes_read == sizeof (buffer))
      	return 0;
      buffer[bytes_read] = '\0';
      match = strstr (buffer, "cpu MHz");			//匹配
      if (match == NULL)
      	return 0;
      sscanf (match, "cpu MHz : %f", &clock_speed);	//读取
      return clock_speed;
}
int main (void)
{
      printf ("CPU clock speed: %4.0f MHz\n", get_cpu_clock_speed ());
      return 0;
}

result:

reference

关于二进制和文本文件 http://www.cnblogs.com/whutzhou/p/3215210.html

linux高级程序设计 李宗德

NEXT

POSIX 文件及目录管理

普通文件\连接文件及目录文件属性管理

转载请注明出处:http://blog.csdn.net/suool/article/details/38129201

Linux 程序设计学习笔记----ANSI C 文件I/O管理

时间: 2024-10-25 19:14:20

Linux 程序设计学习笔记----ANSI C 文件I/O管理的相关文章

Linux 编程学习笔记----ANSI C 文件I/O管理

转载请注明出处:http://blog.csdn.net/suool/article/details/38129201 问题引入 文件的种类 依据数据存储的方式不同,能够将文件分为文本文件和二进制文件.详细的差别和关系例如以下: 文本文件与二进制文件在计算机文件系统中的物理存储都是二进制的,也就是在物理存储方面没有差别都是01码,这个没有异议.他们的差别主要在逻辑存储上,也就是编码上. 文本文件格式存储时是将值作为字符然后存入其字符编码的二进制,文本文件用'字符'作为单位来表示和存储数据,比方对

Linux 程序设计学习笔记----动手编写makefile文件

Befroe Beginning. 之前定了暑假的plan ,关于Linux的书籍现在在看的是ALP和Linux高级程序设计(杨宗德)第三版.在计划中的是Linux高级环境编程. 现在开始关于Linux程序设计的第一篇学习笔记. 本来打算把名字写成教程,不过觉得自己完全是新手在自学,还是写学习笔记比较负责和适合. 希望可以一起学习进步. 引入 首先我们假设这样一个场景.我们有一个程序包含了三个文件,分别是源码文件main_plus,c和function_plus.c以及头文件mydefine_p

Linux 程序设计学习笔记----POSIX 文件及目录管理

转载请注明:http://blog.csdn.net/suool/article/details/38141047 问题引入 文件流和文件描述符的区别 上节讲到ANSI C 库函数的实现在用户态,流的相应资源也在用户空间,但无论如何实现最终都需要通过内核实现对文件的读写控制.因此fopen函数必然调用了对OS的系统调用.这一调用在LINUX下即为open, close, read, write等函数.这些都遵循POSIX标准. so,在linux系统中是如何通过POSIX标准实现对文件的操作和目

Linux 程序设计学习笔记----Linux下文件类型和属性管理

转载请注明出处:http://blog.csdn.net/suool/article/details/38318225 部分内容整理自网络,在此感谢各位大神. Linux文件类型和权限 数据表示 文件属性存储结构体Inode的成员变量i_mode存储着该文件的文件类型和权限信息.该变量为short int类型. 这个16位变量的各个位功能划分为: 第0-8位为权限位,为别对应拥有者(user),同组其他用户(group)和其他用户(other)的读R写W和执行X权限. 第9-11位是权限修饰位,

Linux 程序设计学习笔记----文件管理系统

本文部分整理自网络 Linux下文件系统管理 1.VFS文件系统概述 linux采用VFS来管理文件系统,而且linux设计的原则之一就是everything is file.因此文件管理系统是linux设计最核心的体现. VFS的全称是Virtual File System (虚拟文件系统). 总体上说 Linux 下的文件系统主要可分为三大块:一是上层的文件系统的系统调用,二是虚拟文件系统 VFS(Virtual Filesystem Switch),三是挂载到 VFS 中的各实际文件系统,

Linux 程序设计学习笔记----命令行参数处理

转载请注明出处.http://blog.csdn.net/suool/article/details/38089001 问题引入----命令行参数及解析 在使用linux时,与windows最大的不同应该就是经常使用命令行来解决大多数问题.比如下面这样的: 而显然我们知道C语言程序的入口是mian函数,即是从main函数开始执行,而main函数的原型是: int main( int argc, char *argv[] ); int main( int argc, char **argv );

Linux程序设计学习笔记----网络通信编程API及其示例应用

转载请注明出处, http://blog.csdn.net/suool/article/details/38702855. BSD Socket 网络通信编程 BSD TCP 通信编程流程 图为面向连接的Socket通信的双方执行函数流程.使用TCP协议的通信双方实现数据通信的基本流程如下 建立连接的步骤 1.首先服务器端需要以下工作: (1)调用socket()函数,建立Socket对象,指定通信协议. (2)调用bind()函数,将创建的Socket对象与当前主机的某一个IP地址和TCP端口

Linux 程序设计学习笔记----进程管理与程序开发(下)

转载请注明出处:http://blog.csdn.net/suool/article/details/38419983,谢谢! 进程管理及其控制 创建进程 fork()函数 函数说明具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html 返回值:Upon successful completion, fork() shall return 0 to the child process and shall re

Linux 程序设计学习笔记----终端及串口编程基础之概念详解

转载请注明出处,谢谢! linux下的终端及串口的相关概念有: tty,控制台,虚拟终端,串口,console(控制台终端)详解 部分内容整理于网络. 终端/控制台 终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念. 1.终端 一台主机,连很多终端,终端为主机提供了人机接口,每个人都通过终端使用主机的资源. 终端有字符哑终端和图形终端两种. 控制台是另一种人机接口, 不通过终端与主机相连, 而是通过显示卡-显示器和键盘接口分别与主机相连, 这是人控制主机的第一人机接口.