【Linux】文件系统编程

如果不清楚某个指令的意思,可以输入(man   指令)

查看具体的定义

目录

errno

头文件

文件描述符

打开文件(open)

头文件

函数原型

例子

 读取文件(read)

头文件

函数原型

例子(单纯读取想要的字节长度)

例子(文件有多长读多长并输出)

例子(文件以阻塞方式进行操作) 

例子(文件以非阻塞方式进行操作) 

写文件(write)

头文件

函数原型

例子 

例子(保证数据的可靠写入) 

关闭文件

头文件

函数原型

例子

改变文件偏移量(lseek)

头文件

函数原型

例子

用open等读写结构体类型的例子

 

问题的提出:引入pread与pwrite

方案一

方案二

例子

I/O缓冲(引入fopen与fread)

fopen

头文件

函数原型

举例

fread

头文件

函数原型

 

fwrite

头文件

函数原型

 

fclose

头文件

函数原型

fflush

头文件

函数原型

fseek

头文件

函数原型

ffile的举例

ffile写入读取结构体举例

fgetc()、getc()和getchar()

头文件

函数原型

fputc()、putc()和putchar()

头文件

函数原型

fputc与fgetc举例


errno

当系统调用执行失败,会设置全局整型变量errno的值,以表明具体的出错原因。

头文件

#include <errno.h>

errno是个整型值,如果想查看具体是发生了什么错误可以调用perror:

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{
    FILE *fp;

    fp = fopen("./ff.txt","w+r");
    if(fp == NULL)
    {
        printf("fopen failed,errno is %d\n",errno);
        perror("fopen:");            //perror就会在打印完fopen:之后接着打印错误的原因
        return -1;
    }
}

 

文件描述符

上面代码中的fp即为文件描述符

  • 所有执行I/O操作的系统调用使用文件描述符来表示打开的文件。

  • 文件描述符是一个非负整数。

  • 文件描述符可以表示各种类型的打开的文件。

  • 对文件的操作只要使用文件描述符即可指定所操作的文件。

  • 打开文件,打开成功后,应用程序将获得文件描述符。

 

打开文件(open)

头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

函数原型

int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);

参数:

  • pathname:要打开或创建的文件名称(路径)。
  • flags:标志位,指定打开文件的操作方式。

O_RDONLY:以只读方式打开文件。

O_WRONLY:以只写方式打开文件。

O_RDWR:以读写方式打开文件。

注意:上述三个常量必须指定且只能指定一个。

可选取值(部分):

O_CREAT:如果被打开的文件不存在,则自动创建这个文件。

O_EXCL:如果O_CREAT标志已经使用,那么当由pathname参数指定的文件已经存在时,open返回失败。

O_TRUNC:如果被打开的文件存在并且是以可写的方式打开的,则清空文件原有的内容。

O_APPEND:新写入的内容将被附加在文件原来的内容之后,即打开后文件的读写位置被置于文件尾。

  • mode:指定新文件的访问权限。(仅当创建新文件时才使用该参数)

每个文件有9个权限位(permission bits),这些权限位构成了mode的取值:

返回值:

  • 若成功返回文件描述符,否则返回-1并设置变量errno的值。

例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
    int fd;

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }
}

 

 读取文件(read)

头文件

#include <unistd.h>

函数原型

ssize_t read(int fd, void *buf, size_t count);

参数:

  • fd:要读取的文件的描述符。
  • buf:读取到的数据要放入的缓冲区。
  • count:要读取的字节数。

返回值:

  • 若成功返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1并设置变量errno的值。

注意:

  • 这里的size_t是无符号整型,ssize_t是有符号整型。
  • buf指向的内存空间必须事先分配好。

例子(单纯读取想要的字节长度)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

int main()
{
    int fd;
    char buf2[5] = {0};

    int ret;

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }

    ret = read(fd,buf2,sizeof(buf2));

    if(ret == -1)
    {
        printf("read failed,error is %d\n",errno);
        perror("read failure is ");
        return -1;
    }
    printf("read success :%s\nread bytes %d\n",buf2,ret);

    while(read(fd,buf2,sizeof(buf2) - 1) > 0)       //-1为了给它预留\0,不然buf2都被占满了,输出的时候%s是遇到\0才会结束的,它就会不停的读直到\0
    {
        printf("%s",buf2);
        memset(buf2,0,sizeof(buf2));                //memset的意思就是把buf2塞满\0,可以理解为\0是本身就有的
    }


    return 0;
}

例子(文件有多长读多长并输出)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

int main()
{
    int fd;
    char buf2[5] = {0};            //用/0把buf2填满

    int ret;

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }

    while(read(fd,buf2,sizeof(buf2) - 1) > 0)       //-1为了给它预留\0,不然buf2都被占满了,输出的时候%s是遇到\0才会结束的,它就会不停的读直到\0
    {
        printf("%s",buf2);
        memset(buf2,0,sizeof(buf2));                //memset的意思就是把buf2塞满\0,可以理解为\0是本身就有的
    }


    return 0;
}

 

使用read()时,可能遇到哪些情况?

  • 返回值等于count
  • 返回一个大于0小于count的值
  • 返回0
  • 阻塞
  • 返回-1errno的值为EINTR
  • 返回-1errno的值为EAGAIN

例子(文件以阻塞方式进行操作) 

ssize_t ret;

while (len != 0 && (ret = read (fd, buf, len)) != 0) 
{
    if (-1 == ret) 
    {
        if (EINTR == errno)
        {    
            continue;
        }
        perror("read");
        break;
    }

    len -= ret;
    buf += ret;
}

//其中:len初始值为要读取的字节数。

例子(文件以非阻塞方式进行操作) 

ssize_t nr;

nr = read (fd, buf, len);

if (-1 == nr) 
{
    if (EINTR == errno)
        goto start;
    if (EAGAIN == errno)
        /* 处理其他事务,在恰当的时候再调用read */
    else
        /* 有错误发生,处理错误 */
}
    
//errno如果是EINTR,可以再次进行读操作;如果是EAGAIN,表示要读取的文件现在没有可供读取的数据。

 

写文件(write)

头文件

#include <unistd.h>

函数原型

ssize_t write(int fd, const void *buf, size_t count);

参数:

  • fd:要写入的文件的描述符。
  • buf:要写入的数据所存放的缓冲区。
  • count:要写入的字节数。

返回值:

  • 若成功返回已写的字节数,出错则返回-1并设置变量errno的值。

例子 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

int main()
{
    int fd;
    char buf[] = "hello world";

    int ret;

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }

    ret = write(fd,buf,sizeof(buf));

    if(ret == -1)
    {
        printf("write failed error = %d\n",errno);
        perror("write failure is ");
    }


    return 0;
}

 

使用write()时,可能遇到哪些情况?

  • 返回值等于count
  • 返回一个大于0小于count的值
  • 阻塞
  • 返回-1errno的值为EINTR
  • 返回-1errno的值为EAGAIN
  • 返回-1errno的值为EBADF
  • 返回-1errno的值为EFAULT
  • 返回-1errno的值为EPIPE
  •  ...

例子(保证数据的可靠写入) 

/*
与读操作一样,在调用write函数向文件写数据时,并不能保证一次写完所提供的全部数据,并且也会有各种各样的异常情况,这就需要采取措施以保证数据的可靠写入。
*/

ssize_t ret;

while (len != 0 && (ret = write (fd, buf, len)) != 0) 
{
    if (-1 == ret) 
    {
        if (EINTR == errno)
        {
            continue;
        }
        perror ("write");
        break;
    }

    len -= ret;
    buf += ret;
}

 

关闭文件

头文件

#include <unistd.h>

函数原型

int close(int fd);

参数:

  • fd:要关闭的文件的描述符。

返回值:

  • 若成功返回0,出错则返回-1

注意:当一个进程终止时,内核会自动关闭它所有打开的文件。 

例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

int main()
{
    int fd;

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }

    close(fd);

    return 0;
}

 

改变文件偏移量(lseek)

lseek系统调用可以改变文件偏移量(File Offset)。文件偏移量是一个整数,表示距文件起始处的字节数。

头文件

#include <sys/types.h>
#include <unistd.h>

函数原型

off_t lseek(int fildes, off_t offset, int whence);

参数:

  • whence:必需是以下三个常量之一:

SEEK_SET:将文件偏移量设置在距文件开始处offset个字节。

SEEK_CUR:将文件偏移量设置在其当前值加offset,offset可正可负。

SEEK_END:将文件偏移量设置为文件长度加offset,offset可正可负。

返回值:

  • 如果成功返回文件偏移量,如果失败返回-1。

例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

int main()
{
    int fd;
    char buf[] = "hello world";
    char buf2[5] = {0};

    int ret;

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }

    ret = write(fd,buf,sizeof(buf));

    if(ret == -1)
    {
        printf("write failed error = %d\n",errno);
        perror("write failure is ");
    }

    ret = lseek(fd,0,SEEK_SET);    //写入内容之后文件偏移量不在一开始在末尾了,之后读数据要从一开始读所以要重置文件偏移量

    if(ret == -1)
    {
        printf("lseek failed error = %d\n",errno);
        perror("lseek failure is ");
    }

    ret = read(fd,buf2,sizeof(buf2));

    if(ret == -1)
    {
        printf("read failed,error is %d\n",errno);
        perror("read failure is ");
        return -1;
    }
    printf("read success :%s\nread bytes %d\n",buf2,ret);

    return 0;
}

 

用open等读写结构体类型的例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

typedef struct
{
    char name[10];
    int id;
}student;

int main()
{
    int fd;
    int ret;
    int i;

    student data[5];

    student save;

    for(i = 0; i < 5; i++)
    {
        memset(&data[i],0,sizeof(student));
    }

    for(i = 0; i < 5; i++)
    {
        memcpy(data[i].name,"aa",sizeof("aa"));
        data[i].id = i + 1;
    }

    fd = open("./t.txt", O_RDWR);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }
    printf("open success!\n");

    ret = write(fd,data,sizeof(student) * 5);

    if(ret == -1)
    {
        printf("write failed,error is %d\n",errno);
        perror("write failure is ");
        return -1;
    }
    printf("write success!\n");

    ret = lseek(fd,0,SEEK_SET);
    if(ret == -1)
    {
        printf("lseek failed,error is %d\n",errno);
        perror("lseek failure is ");
        return -1;
    }
    printf("lseek success!\n");

    while(read(fd,&save,sizeof(student)) > 0)
    {
        printf("name is %s,id is %d\n",save.name,save.id);
        memset(&save,0,sizeof(student));
    }

    close(fd);

    return 0;
}

 

问题的提出:引入pread与pwrite

从以上对文件的各种操作中,是不是可以推测出文件描述符和打开的文件是一一对应的关系?

为了搞清楚上述问题,首先需要了解内核如何表示打开的文件         

内核使用三种数据结构表示一个打开的文件:

  • 文件描述符表(每个进程都有)

    (1) 文件描述符标志(file descriptor flags),如:close_on_exec

    (2) 指向一个文件表项(file table entry)的指针。

  • 打开文件表(open file table)

    (1) 文件状态标志(file status flags),如读、写、非阻塞等。

    (2) 当前文件偏移量(file offset)

    (3) 文件访问模式(read-only, write-only, or read-write)

    (4) 指向i-node表项的指针。

  • 文件系统i-node

    (1) 文件类型(regular file, socket, or FIFO)和权限。

    (2) 指向文件表的指针。

    (3) 文件的属性(file size)

有了以上知识后,再看看文件操作

  •  每次完成write后,在文件表项中的当前文件偏移量(file offset)增加所写的字节数。如果当前文件偏移量超过了当前文件长度(file size),则在i-node表项中的文件长度被设置为当前文件偏移量(也就是说文件加长了)

  • 如果用O_APPEND标志打开一个文件,则每次对这种具有添写标志的文件执行写操作时,文件表项中的当前文件偏移量首先被设置为i-node表项中的文件长度,这就使得每次写的数据都添加到文件的当前尾端处。

  • 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i-node表项中的当前文件长度。

  • seek只修改文件表项中的当前文件偏移量,没有进行任何I/O操作。     

如何向文件结尾添加新的内容?可不可以采用这种方式?

lseek(fd, 0L, SEEK_END);

write(fd, buf, 100);

上述代码,什么时候能正常工作?什么时候不能?出错的原因是什么?怎么解决?

方案一

打开文件时设置O_APPEND选项,这就使内核每次对这种文件进行写之前,都将当前文件偏移量设置为该文件的尾端。于是,每次写之前就不需要调用lseek函数了。   

方案二

使用pread()pwrite()

#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);    

调用pread相当于顺序调用lseekread,但是又与这种顺序调用不同,不同之处在于调用pread时,无法中断其定位操作和读操作。pwrite与之类似(lseekwrite)

一般而言,原子操作指的是由多步组成的操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集

例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd;
    int ret;
    int flag = 0;
    char buf2[10] = {0};

    fd = open("./t.txt",O_CREAT | O_RDWR | O_APPEND);

    if(fd == -1)
    {
        printf("open failed,errno is %d\n",errno);
        perror("open failure is ");
        return -1;
    }
    printf("open success!\n");

    while(pread(fd,buf2,sizeof(buf2) - 1,flag) > 0)
    {
        printf("%s",buf2);
        memset(buf2,0,sizeof(buf2));
        flag += sizeof(buf2) - 1;
    }


    return 0;
}

 

I/O缓冲(引入fopen与fread)

C标准库提供了操作文件的标准I/O函数库,与系统调用相比,主要差别是实现了一个跨平台的用户态缓冲的解决方案。

I/O缓冲举例:

printf函数向标准输出写入多个字符,所写入的字符被放在一个用户态的缓冲区中,直到碰到一个换行符,系统才调用write函数将缓冲区中的数据写入标准输出,也就是说,在换行符之前写入的字符并不会立即出现在控制台屏幕上。

为什么要采用这种缓冲机制?

    为了提高系统进行I/O操作的效率!

    系统调用要请求内核的服务,会引发CPU模式的切换,期间会有大量的堆栈数据保存操作,开销比较大。如果频繁地进行系统调用,会降低应用程序的运行效率。有了缓冲机制以后,多个读写操作可以合并为一次系统调用,减少了系统调用的次数,将大大提高程序的运行效率

    所谓的标准I/O函数实际上是对底层系统调用的封装,最终读写设备或文件的操作仍需调用系统I/O函数来完成。

标准I/O函数也是使用文件描述符操作文件?

    标准I/O函数并不直接操作文件描述符,而是使用文件指针文件指针和文件描述符是一一对应的关系,这种对应关系由标准I/O库自己内部维护。文件指针指向的数据类型为FILE型,但应用程序无须关心它的具体内容。

    在标准I/O中,一个打开的文件称为流(stream),流可以用于读(输入流)、写(输出流)或读写(输入输出流)。每个进程在启动后就会打开三个流,分别对应:stdin(标准输入流)stdout(标准输出流)以及stderr(标准错误输出流)

如何根据文件指针进行文件操作呢?

 

fopen

fopen用于打开一个标准I/O

头文件

#include <stdio.h>

函数原型

FILE *fopen(const char *path, const char *mode);

参数:

  • mode

"r""rb":以只读方式打开。

"w""wb":以只写方式打开,并把文件长度截短为零。

"a""ab":以写方式打开,新内容追加在文件尾。

"r+""rb+""r+b":以更新方式打开(读和写)

"w+""wb+""w+b":以更新方式打开,并把文件长度截短为零。

"a+""ab+""a+b":以更新方式打开,新内容追加在文件尾。

注:字母b表示文件是一个二进制文件而不是文本文件。

 下面的解释清楚一点:

  • r:打开只读文件,该文件必须存在。
  • r+:打开可读写的文件,该文件必须存在。
  • w:打开只写文件,若文件存在则文件长度清0,即该文件内容会消失;若文件不存在则建立该文件。
  • w+:打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失;若文件不存在则建立该文件。
  • a:以附加的方式打开只写文件,若文件不存在,则会建立该文件;如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
  • a+:以附加方式打开可读写的文件,若文件不存在,则会建立该文件;如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。

  上述的字符串都可以再加一个b字符,如rb、w+b或ab+等组合,加入b字符用来告诉函数库打开的文件为二进制文件,而非纯文字文件。 

返回值: 

  • 执行成功返回一个非空的FILE*指针,失败时返回NULL

举例

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{
    FILE *fp;

    fp = fopen("./ff.txt","w+r");
    if(fp == NULL)
    {
        printf("fopen failed,errno is %d\n",errno);
        perror("fopen:");
        return -1;
    }

    fclose(fp);

    return 0;
}

 


fread

fread()用于从一个文件流里读取数据。

头文件

#include <stdio.h>

函数原型

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

参数:

  • ptr:数据从文件流stream读到ptr指向的数据缓冲区里。
  • size:指定每个数据记录的长度。
  • nmemb:给出要传输的记录的个数。

返回值:

  • 成功读到数据缓冲区里的记录的个数(不是字节数)

 


fwrite

fwrite()从指定的数据缓冲区里取出数据记录,并把它们写到输出流中。

头文件

#include <stdio.h>

函数原型

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

参数:

  • 参考fread

返回值:

  • 成功写入的记录个数。

 


fprintf

如果要输入数字的话,直接用fprintf(FILE *stream,"%2d....",num);这个方法也可以输入字符串等

fwrite()是数据块输入方式并且默认是二进制的。所以,打开txt文件时,里面全是乱码。fwrite(&num,sizeof(int),1,f1);  //fwrite这样不行!!
fprintf(),则可以输入txt,并且打开也是输入的数。

 


fclose

fclose()函数用于关闭指定的文件流。

头文件

#include <stdio.h>

函数原型

int fclose(FILE *fp);

 


fflush

fflush()用于把文件流里所有未写出的数据立即写出。使用这个函数可以确保在程序继续执行之前重要的数据都已经被写到磁盘上。调用fclose函数隐含执行了一次fflush操作,所以不必在调用fclose之前调用fflush

头文件

#include <stdio.h>

函数原型

int fflush(FILE *stream);  

 


fseek

fseek()用于在文件流里为下一次读写操作指定位置。

头文件

#include <stdio.h>

函数原型

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

参数:

  • offset与whence参数与lseek函数完全一样。 

返回值:

  • 返回整数:0表示成功,-1表示失败并设置errno指出错误。

 


ffile的举例

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{
    FILE *fp;
    int ret;
    char buf[] = "hello world!";
    char buf2[100] = {0};

    fp = fopen("./ff.txt","w+r");
    if(fp == NULL)
    {
        printf("fopen failed,errno is %d\n",errno);
        perror("fopen:");
        return -1;
    }

    ret = fwrite(buf,sizeof(char),strlen(buf),fp);
    if(ret != strlen(buf))
    {
        printf("fwrite failed errno is %d\n",errno);
        perror("fwrite:");
        return -1;
    }

    fflush(fp);

    fseek(fp,0,SEEK_SET);

    ret = fread(buf2,sizeof(char),sizeof(buf2)/sizeof(char),fp);
    if(ret <= 0)
    {
        printf("fread failed errno is %d\n",errno);
        perror("fread:");
    }

    fclose(fp);

    return 0;
}

 

ffile写入读取结构体举例

#include <stdio.h>
#include <errno.h>
#include <string.h>

typedef struct 
{
    char name[10];
    int id;
}student;

int main()
{
    FILE *fp;
    int ret;
    int i;
    student data[5];
    student save;

    fp = fopen("./ff.txt","w+b");
    if(fp == NULL)
    {
        printf("fopen failed,errno is %d\n",errno);
        perror("fopen:");
        return -1;
    }

    for(i = 0; i < 5; i++)
    {
        memset(&data[i],0,sizeof(student));
        memcpy(data[i].name,"aa",strlen("aa"));
        data[i].id = i + 1;
    }

    ret = fwrite(data,sizeof(student),sizeof(data)/sizeof(student),fp);
    if(ret != sizeof(data) / sizeof(student))
    {
        printf("fwrite failed errno is %d\n",errno);
        perror("fwrite:");
        return -1;
    }

    fflush(fp);

    fseek(fp,0,SEEK_SET);

    while(fread(&save,sizeof(student),sizeof(save) / sizeof(student),fp) > 0)
    {
        printf("name is %s,id is %d\n",save.name,save.id);
        memset(&save,0,sizeof(save));
    }

    fclose(fp);

    return 0;
}

 


fgetc()、getc()和getchar()

    fgetc()从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件结尾或出现错误时,返回EOF。  getc( )与fgetc( )作用相同,但getc( )为宏定义,非真正的函数调用getchar()相当于getc(stdin)。具体解释看课件。。

头文件

#include <stdio.h>

函数原型

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

成功则将字符有unsigned char转为int返回

 


fputc()、putc()和putchar()

fputc()把一个字符写到一个输出文件流中,它返回写入的值,如果失败,则返回EOF。类似fgetc()getc()putc()的作用也相当于fputc(),但它可能被实现为一个宏。putchar()相当于putc(c, stdout),它把单个字符写到标准输出。

头文件

#include <stdio.h>

函数原型

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

注意:putchargetchar都是把字符当作int类型而不是char类型来使用的,这就允许文件结尾EOF取值为-1

 


fputc与fgetc举例

#include <stdio.h>


int main()
{
    int i;
    FILE *fp;
    fp = fopen("getc.txt","w+");
    if(fp == NULL)
    {
        printf("open error!\n");
        return -1;
    }

    for(i = 0; i < 100; i++)
    {
        if(fputc(i,fp) == EOF)
        {
            perror("fputc:");
            return -1;
        }
    }

    fseek(fp,0,SEEK_SET);

    while((i = fgetc(fp)) != EOF)
    {
        printf("%3d",i);
    }

    fseek(fp,0,SEEK_SET);

    while((i = fgetc(fp)) != EOF)
    {
        printf("%c ",i);
    }

    close(fp);
    return 0;
}

 


fgets和fputs

fgets( )用来从参数stream所指的文件内读入字符并存到参数s所指的内存空间,直到出现换行字符、读到文件尾或者已读了size-1个字符为止,最后会加上NULL作为字符串结束

头文件

#include <stdio.h>

函数原型

char * fgets(char * s,int size,FILE * stream);

fgets( )若成功则返回s指针;返回NULL则表示有错误发生

 


fprintf

fprintf( )会根据参数format字符串来转换并格式化数据,然后将结果输出到参数stream指定的文件中,直到出现字符串结束(\0)为止

头文件

#include <stdio.h>

函数原型

int fprintf(FILE * stream, const char * format,……);

关于参数format字符串的格式请参考printf( )。成功则返回实际输出的字符数,失败则返回-1,错误原因存于errno中

    fprintf(fp, "%d %s", num, name);

 


一些复习小结:

read,write和fread,fwrite区别?

前者系统调用,后者是I/O库函数。前者返回文件描述符,后者返回文件指针。前者要包含多个头文件,后者只要一个stdio基本能解决

为什么之后要引入后者?(系统调用和库函数的区别?)

后者实现了缓冲机制。系统调用要请求内核的服务,会引发CPU模式的切换,开销比较大。如果频繁地进行系统调用,会降低应用程序的运行效率。有了缓冲机制以后,多个读写操作可以合并为一次系统调用,减少了系统调用的次数,将大大提高程序的运行效率

creat函数怎么用,相当于使用xxx模式的open函数

open的flags参数几个含义,fopen打开方式参数的含义,lseek三个参数分别是

各函数返回值是什么

open成功返回文件描述符,失败返回-1

read,write返回读写长度

lseek成功返回当前读写位置,失败返回-1

close成功返回0,失败返回EOF

fopen成功返回文件指针,失败返回NULL

fread,fwrite成功返回读取到的数据个数

fseek与lseek返回参数不一样!成功返回0,失败返回-1

fclose成功返回0,失败返回EOF

fdopen函数怎么用,将文件描述符转为文件指针,还有一个参数mode表示打开文件方式要前后一致

fread,fwrite参数含义和位置,文件指针放在第四个参数

fread,fwrite和fprintf,fscanf区别?

文件的输入输出是一对对存在进行使用的,fread与fwrite、fprintf与fscanf。fread,fwrite以二进制方式读写,fprintf,fscanf以字符方式读写。注意这里是指方式,并不是读取或写入文件的格式,这两个概念不同。

C语言把文件看作一个字符(字节)的序列,即由一个一个字符(字节)的数据顺序组成。根据数据的组织形式,可分为ASCII文件和二进制文件。ASCII文件又称为文本(text)文件,它的每个字节放一个ASCII代码,代表一个字符。二进制文件是把内存中的数据按其在内在中的存储形式原样输出到磁盘上存放。

由于fprintf写入时,对于整数来说,一位占一个字节,比如1,占1个字节;10,占2个字节;100,占3个字节,10000,占5个字节
所以文件的大小会随数据的大小而改变,对大数据空间占用很大。
而fwrite是按二进制写入,所以写入数据所占空间是根据数据类型来确定,比如int的大小为4个字节(一般32位下),那么整数10所占空间为4个字节,100、10000所占空间也是4个字节。所以二进制写入比格式化写入更省空间。

 

但我还是不懂,明明说fwrite是二进制方式写,我写了个字符a进txt文件,而且文件打开方式不论是加了b还是没加,那个txt文件永远显示字符a而不是a的二进制?

例子:用fwirte和fprintf分别写入字符a,不管b加不加,在txt文件中都显示a。用fwrite和fprintf写入数字22,不管b加不加,前者显示方框,后者显示22。

所以我的理解是,txt文件会将二进制转换为字符,因为所有计算机存储的方式都是二进制,不管你输入的是字符还是啥,最后存储在计算机里都是二进制,不可能说会有字符或者int型之说。txt只是拥有一个将二进制转化为字符的功能。所以char以二进制存进去,读出来还是char。int以二进制读进去,txt不知道它是二进制,所以读出来是乱码,但fprintf呢它就把int先转为char(人类读着是char),再存进去,中间也有把char变为二进制给txt,txt显示的时候把二进制变为txt,就是char型的可读22。

 

简单说下文本文件和二进制文件的区别吧
文本文件是指显示出来的内容上是字符形式的;二进制文件是指显示出来的内容上是二进制的
fopen 生成的是文本文件还是二进制文件不是由文件名和扩展决定的,而是由第二个参数用t(文本)还是b(二进制)决定的。用fwrite可以写二进制,也可以写文本。二进制和文本只是格式不同而已。fread 只是按块读入文件,并不是说,只能读二进制,控制文件的读写, 实际上是有open 系统调用控制

文件打开时选择以二进制方式读写和不选有什么区别?

windows下有区别:

  • 使用二进制方式进行读文件时,会原封不动的读出全部的内容,写文件的时候,会把内存缓冲区的内容原封不动的写到文件中。
  • 使用文本方式进行读文件时,会将回车换行符号CRLF(0x0D OxOA)全部转换成单个的换行符号LF(OxOA),写文件的时候,会将换行符号LF( OxOA)全部转换成回车换行符号CRLF(0x0D OxOA)。
  • 所以建议以文本方式写的最好用文本方式读,以二进制方式写的最好用二进制方式读(我觉得可能跟数据所存的文件是文本文件还是二进制文件关系不大?)

linux平台下没有区别:

在linux平台下进行文件读写时,文本模式和二进制模式没有区别。在文件读写时,调用fopen,无论以文本模式还是二进制模式打开文件,之后在进行文件读取和写入,其结果都是一样的。因此,在linux平台下,以二进制方式创建的文件和写入的文件,在以后的访问中,使用二进制或者文本方式均可以进行正确的读写;同理,以文本方式创建的文件和写入的文件,使用二进制或者文本模式均可以进行正确的读写。

 

 

 

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值