第七弹:C语言基础--标准IO编程详解

目录


知识点1 文件的概述

1 文件的定义

        所谓的文件,指的是程序、文档、图片、音视频等数据的有序集合,对于文件是数据在磁盘中存储的有序集合,称为文件。

2 Linux系统中的文件定义

        在Linux操作系统一切皆文件,主要包含磁盘文件和设备文件

1. 设备文件

        在Linux嵌入式系统中,所有的硬件设备在驱动加载成功的情况下,需要在根文件系统中创建设备节点,提供给应用程序访问,以实现应用程序对硬件设备访问,此时在根文件系统中的设备节点就称为设备文件。

        在Linux设备文件主要包含字符设备文件和块设备文件

        1) 字符设备文件:

                字符设备文件的访问以字节流的方式进行访问,绝大多的设备文件都为字符设备文件;

        2) 块设备文件:

                块设备文件的访问,是以存储块为单位进行访问,一般表示存储设备文件。

2. 磁盘文件

        对于磁盘文件,是存储在外部存储介质中的文件,一般实现有序数据的有效存储。

        在Linux系统中的磁盘文件,主要包含普通的文件(程序、文本、图片、音视频等)、也可以是有多个文件构成目录。对于磁盘文件可以通过相关API接口调入到内存中实现访问。

3 Linux系统中文件的访问

3.1 文件缓冲区概述

        由标准C库所开辟的存储空间,用于数据的临时存储,根据数据的访问的方向可以将文件缓冲分为读文件缓冲区和写文件缓冲区;

        1) 读文件缓冲区:

                用于数据从磁盘中输入到内存中,程序实现文件的读访问过程

                磁盘 --> 读文件缓冲区 --> 内存;

         2) 写文件缓冲区:

                用于数据从内存中输出到磁盘中,程序实现文件的写访问过程。

                内存 --> 写文件缓冲区 --> 磁盘;

3.2 文件缓冲区具体形式

        在Linux操作系统中,对于磁盘文件的访问过程中,具体实现形式有三种:

1. 行缓冲

        所谓的行缓冲,指的是在IO访问过程中,数据在遇到行缓冲标识符'\n',自动刷新缓冲区。

#include <stdio.h>

int main()
{
#if 1
        /* 写入的数据中包含了行标识符('\n'),此时采用的行缓冲形式,所有在遇到行缓冲标识符的时候,自动刷新标识符前面的所有数据*/
        printf("hello\n");    
#else
        printf("hello");
#endif
        while(1);
}

2. 全缓冲

        标准C库为IO操作开辟固定大小的存储空间作为缓冲区,所谓的全缓冲,指的是在IO访问的过程中,只有在满足特定条件情况下才会自动刷新缓冲区。一般默认为缓冲区为满的时候刷新。

        1) 缓冲区不为满,不自动刷新缓冲区

#include <stdio.h>

int main()
{
#if 0
        printf("hello\n");
#else
        /* 写入缓冲区的数据字节数 小于缓冲区的大小,所以缓冲区不为满,不满足刷新条件,也就不会刷新缓冲区的数据到标准输出流中,所以不会显示数据 */
        printf("hello");
#endif
        while(1);
}
/* 程序运行的结果:程序一直运行,但是没有任何数据的输出 */

        2) 缓冲区为满,自动刷新缓冲区

#include <stdio.h>
#include <unistd.h>

int main()
{
        int i = 0;
#if 0
        printf("hello\n");
#else
        printf("hello");
#endif
        while(1) {
                printf("%3d", i++);    /* 循环写入3字节的数据 */
                usleep(5000);
        }
}
/* 程序的运行结果:程序一直运行,每隔一端数据,输出一段数据内容;原因是程序在循环的将数据写入缓冲区中,每隔一段时间缓冲区写满,此时自动刷新缓冲区 */

3) 人为刷新缓冲区

        在标准C库中提供自动刷新缓冲区的API接口

#include <stdio.h>
int fflush(FILE *stream);
功能:自动刷新stream指定文件流的缓冲区数据。

4) 在程序退出的时候,自动刷新缓冲区中的数据

#include <stdio.h>

int main()
{
    printf("hello");        /* 在整个程序运行结束的时候,自动刷新缓冲区的数据,及时全缓冲区不满也会刷新 */
}

3 不带缓冲区

        不带缓冲也称为无缓冲形式,直接实现内存和磁盘数据的直接交互,此时读数据和写数据都是以字节为单位进行,频繁进行模式的切换,效率会降低。在错误数据输出情况下一般会使用无缓冲形式。

4 磁盘文件分类

4.1 磁盘文件分类说明

        根据磁盘文件的存储编码,可以将文件分为二进制文件和文本文件

1. 文本文件

        所谓的文本文件,指的是数据的存储编码为文本形式存储,具体的编码形式可以ASCII编码、UNICODE编码等形式。

        以ASCII编码为例说明:

                存储数据:567ABC

                        所以数据的存储编码,具体表示数据以字符采用ASCII存储编码形式存储,分别对应字符:'5'、'6'、'7'、'A'、'B'、'C'

                                '5':ASCII存储编码53 (0011 0101)

                                '6':ASCII存储编码54 (0011 0110)

                                '7':ASCII存储编码53 (0011 0111)

                                'A': ASCII存储编码 01000001

                                'B':ASCII存储编码 01000010

                                'C':ASCII存储编码 01000011

        整个数据的存储编码:0011 0101 0011 0110 0011 0111 0100 0001 0100 0010 0100 0011

2. 二进制文件

        所谓的二进制文件,指的是数据的存储编码有数据的内容决定,如果数值数据的存储编码使用整型数据的二进制编码存储,其它数据存储编码使用的文本编码形式存储。

        存储数据:567ABC

                  567以二级制编码形式存储,其存储编码为:0010 0011 0111

                         ABC以其它编码形式存储,以ASCII编码为例说明

                        'A': ASCII存储编码 01000001

                        'B':ASCII存储编码 01000010

                        'C':ASCII存储编码 01000011

                整个数据编码:0010 0011 0111 0100 0001 0100 0010 0100 0011

4.2 文本文件和二进制文件的区别

1. 共同点:

        都可以实现数据文件在磁盘中的存储;

2. 不同点:

        a) 不同编码形式文件的存储,译码方式也不同。

        b) 文本文件定长编码形式(一个数据符号占用1字节数据存储空间),译码简单;二进制文件不定长编码形式,译码困难,二进制文件多种编码形式,需要专用译码器实现数据的译码过程。

        c) 二进制文件空间利用率更高:文本文件定长,数据量和空间成正比;二进制文件往往存储空间比数据量要少。

        d) 文本文件可读性高于二进制文件:文本文件可以直接读取,而二进制文件需要专用译码器读取。

知识点2 标准IO库

        所谓标准IO库,由C库提供给用户实现对磁盘文件访问的函数库,可以实现不同操作系统中文件的访问(移植性高)。

1 FILE文件指针

1.1 FILE指针的概述

        FILE是一个结构体数据类型,用于存储当前所打开文件的相关属性:文件的类型、文件大小、文件存储路径、以及文件的状态等信息。

        实质:在文件打开的时候,自动创建FILE结构体,并将其添加到文件管理的链表中,同时将结构体空间的地址返回给应用程序,应用程序就通过FILE指针实现对文件的访问。

1.2 FILE结构体数据类型的定义

        在标准库头文件中由FILE结果数据类型的定义:

                Linux系统中头文件路径:/usr/include/stdio.h

                使用源程序中需要包含头文件:

                        #include <stdio.h>4

 43 /* Define outside of namespace so the C++ is happy.  */
 44 struct _IO_FILE;        /* 结构体数据类型的声明,表示当前结构体数据类型在其它文件或者位置处完成数据类型的定义*/
 45 
 46 __BEGIN_NAMESPACE_STD
 47 /* The opaque type of streams.  This is the definition used elsewhere.  */
 48 typedef struct _IO_FILE FILE;       /* FILE是结构体数据类型别名,所对应的结构体数据类型名称为struct _IO_FILE */
 49 __END_NAMESPACE_STD

注意:struct _IO_FILE 结构体数据类型是在标准IO库头文件中定义

        头文件的路径:/usr/include/libio.h

241 struct _IO_FILE {
242   int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
243 #define _IO_file_flags _flags
244 
245   /* The following pointers correspond to the C++ streambuf protocol. */
246   /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
247   char* _IO_read_ptr;   /* Current read pointer */
248   char* _IO_read_end;   /* End of get area. */
249   char* _IO_read_base;  /* Start of putback+get area. */
250   char* _IO_write_base; /* Start of put area. */
251   char* _IO_write_ptr;  /* Current put pointer. */
252   char* _IO_write_end;  /* End of put area. */
253   char* _IO_buf_base;   /* Start of reserve area. */
254   char* _IO_buf_end;    /* End of reserve area. */
255   /* The following fields are used to support backing up and undo. */
256   char *_IO_save_base; /* Pointer to start of non-current get area. */
257   char *_IO_backup_base;  /* Pointer to first valid character of backup area */
258   char *_IO_save_end; /* Pointer to end of non-current get area. */
259 
260   struct _IO_marker *_markers;
261 
262   struct _IO_FILE *_chain;        /* 存储下一个结点的地址:将所有的FILE结构体已链表形式管理 */
263 
264   int _fileno;                    /* 文件描述符:是一个非负整数,系统给已打开文件所分配的编号 */
265 #if 0
266   int _blksize;
267 #else
268   int _flags2;
269 #endif
270   _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
271 
272 #define __HAVE_COLUMN /* temporary */
273   /* 1+column number of pbase(); 0 is unknown. */
274   unsigned short _cur_column;
275   signed char _vtable_offset;
276   char _shortbuf[1];
277 
278   /*  char* _save_gptr;  char* _save_egptr; */
279 
280   _IO_lock_t *_lock;
281 #ifdef _IO_USE_OLD_IO_FILE
282 };

1.3 FILE指针的管理

注意:

        1) 在应用程序中调用fopen打开文件的时候,自动创建FILE结构体存储空间结点,并由系统将结点插入到链表中同一管理,同时会将结点的存储空间以指针形式返回给应用程序。

        2) 应用程序可以通过指针实现对文件的访问。

1.4 FILE指针的分配

        FILE指针的分配,由两种方式:

1. 在进程启动自动分配

        在所有的进程中,都需要实现数据的标准输入、标准输出,以及程序错误的情况下需要将错误信息输出反馈给用户,所以在进程启动过程中,当前进程中自动分配三个特殊的FILE文件指针:

stdin    标准输入流文件流指针        
    是在进程运行过程中,程序可以通过标准输入设备(例如键盘)实现数据的输入;
stdout   标准输出流文件流指针
    是在进程运行过程中,程序可以通过标准输出设备(例如显示屏)实现数据的输出
stderr   标准错误输出文件流指针
    是在进程运行过程中,程序可以将错误消息通过标准输出设备实现错误消息的输出。和stdout的区别(输出内容不同;缓冲形式不同)

2. 在进程启动后,在打开文件的时候自动分配。

        在绝大数情况下,对于文件打开,自动分配,并将FILE结构体空间添加到链表中。

2 文件的IO操作

2.1 打开文件流-fopen

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:打开文件路径path所指定的文件
FILE *fdopen(int fd, const char *mode);
功能:打开文件描述符fd所指定的文件
FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:重新打开文件
参数:
    path 所指向的字符串,表示需要打开文件的文件名称,可以使用绝对路径和相对路径表示;
    fd   已打开文件的文件描述符;
    mode 使用字符串数据表示,指定文件的打开方式:
        "r"    :以只读方式打开文本文件,设置读文件指针为起始位置;如果文件不存在则打开失败。
        "r+"   :以读写方式打开文本文件,读写文件指针为文件起始位置;如果文件不存在则打开失败。
        "w"    :以只写方式打开文本文件,写文件指针为起始位置;如果文件不存在则创建文件,文件存在则打开文件并将文件内容清零;
        "w+"   :以读写方式打开文本文件,读写文件指针为文件起始位置;如果文件不存在则创建文件,文件存在则打开文件并将文件内容清零;
        "a"    :以只写方式打开文本文件,写文件指针为文件末尾位置;如果文件不存在则创建文件,文件存在则打开文件并保留源文件中的数据,写文件则从文件末尾开始写
        "a+"   :以读写方式打开文本文件;如果文件不存在则创建,文件存在则打开文件并保留源文件中的数据,写文件则从文件末尾开始写,读文件则从文件起始位置开始读
    stream FILE文件流指针,表示重新打开文件流。  
        注意:如果要打开文件为二进制文件,则在文本文件方式中添加符号b          
返回值:
    成功返回所打开文件的FILE指针,失败返回NULL,且修改errno的值(使用perror查看错误消息)。             

2.2 读写文件流

        在标准IO库中,文件流的读写可以根据每次写入数据的数据量分为三种:按字节读写、按行读写、按数据块读写

2.2.1 按字节读写数据

1. 读文件

        函数原型:

#include <stdio.h>
int fgetc(FILE *stream);
功能:从stream指定文件流中读取一个字符数据;
参数:stream表示需要读文件的文件流指针;
返回值:
    读取到字符数据,返回unsigned char数据,转换为int类型数据;
    在读文件失败或者读文件结束则EOF
        EOF实际的数据值是-1;
    注意:对于读文件失败和结束的判断
       #include <stdio.h>

       void clearerr(FILE *stream);    /* 清除stream文件流的错误消息 */

       int feof(FILE *stream);         /* 判断stream文件流是否为文件结尾 */

       int ferror(FILE *stream);       /* 判断stream文件流是否访问错误 */



int getc(FILE *stream);
功能:和fgetc一致
和fgetc区别:
    前者fgetc是函数,而fgetc是标识符宏函数;所以建议在使用的时候使用fgetc。    
int getchar(void);
功能:等价于getc(stdin)

实例:

#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        int ret;
        /* 以只读方式打开已存在的test.txt文本文件 */
        fp = fopen("test.txt", "r");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }   

        printf("fopen test.txt success\n");

        while(1) {
                ret = fgetc(fp);        /* 从fp只写的文件中读取一个字符数据 */
                if (ret == EOF) {       /* 返回值为EOF说明读文件结束或者失败 */
                        if (ferror(fp)) {        /* 判断读文件是否失败 */
                                clearerr(fp);
                                perror("fgetc");
                                break;
                        }
                        if (feof(fp)) {          /* 判断读文件是否结尾 */
                                clearerr(fp);
                                printf("end of file\n");
                                break;
                        }   

                }
                printf("%c", ret);
        }
}

2. 写文件

        函数原型

#include <stdio.h>
int fputc(int c, FILE *stream);
功能:将字符数据c写入到stream流表示的文件中;
参数:
    c表示需要写文件的数据值;
    stream表示需要写文件的流;
返回值:
    成功返回写入文件的unsigned char数据;失败返回EOF;            


int putc(int c, FILE *stream);
功能:等价于fputc(c, stream)功能,采用标识符宏函数实现;    
int putchar(int c);
功能:等价于putc(c, stream);

实例:

#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        int ch;
        int ret;
        /* 以读写方式打开已存在test.txt文件 */
        fp = fopen("test.txt", "r+");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }   

        printf("fopen test.txt success\n");

        while(1) {
                ch = getchar();        /* 从标准输入设备获取字符数据 */
                ret = fputc(ch, fp);    /* 将获取的字符数据写入到fp指向的文件中 */
                if (ret == EOF) {
                        perror("fputc");
                        return -1; 
                }
                fflush(fp);    /* 每次写完一个字符数数据刷新文件缓冲区,否则只有在缓冲写满或者程序正常运行结束才会刷新缓冲区 */
        }
}

练习:

 1. 实现文件的拷贝;

         1) 打开源文件(只读方式打开已存在的文件),打开文件方式:"r"

        2) 打开目标文件(可写方式打开;不存在需要创建,存在则打开源文件中同时清除文件内容),打开方式:"w"

        3) 循环读取源文件中的数据内容,并写入到目标文件中,直到读到数据EOF结束。

#include <stdio.h>

int main(int argc, char *argv[])
{
        FILE *src_fp;
        FILE *dest_fp;
        int ch; 
        /* 告诉使用者如何执行程序,方式错误的告诉使用者 */
        if (argc != 3) {
                fprintf(stderr, "Usge: %s <src_file> <dest_file>\n", argv[0]);
                return -1; 
        }
        /* 以只读方式打开已存在的需要拷贝数据的源文件 */
        src_fp = fopen(argv[1], "r");
        if (src_fp == NULL) {
                perror("fopen->src");
                return -1; 
        }   
        /* 以只写的方式打开目标文件,如果目标文件不存在则创建文件,存在则清除源文件中的数据 */
        dest_fp = fopen(argv[2], "w");
        if (dest_fp == NULL) {
                perror("fopen->dest");
                return -1; 
        } 
        /* 循环从源文件中读取一个字节数据并写入到目标文件中 */
        while(1) {
                ch = fgetc(src_fp);        /* 读一个字节 */
                if (ch == EOF) {
                        if (feof(src_fp)) {
                                break;
                        }

                        if (ferror(src_fp)) {
                                perror("fgetc");
                                return -1;
                        }
                }
                if (fputc(ch, dest_fp) == EOF) {    /* 写一个字节 */
                        perror("fputc");
                        return -1;
                }
        }
        return 0;
}

2. 统计一个文件中内容的行数

        1) 以可读方式打开源文件,同时需要保留源文件中的数据

        2) 从起始位置每次读取一字节数据,判断是否为换行标识'\n',如果是则为1行结束行数+1;

#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        int line;
        int ret;
    
        if (argc != 2) {
                fprintf(stderr, "Usge: %s <file_name>\n", argv[0]);
                return -1; 
        }

        fp = fopen(argv[1], "r");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }

        line = 0;                
        while(1) {
                ret = fgetc(fp);
                if (ret == EOF) {
                        if (ferror(fp)) {
                                clearerr(fp);
                                perror("fgetc");
                                break;
                        }   
                        if (feof(fp)) {
                                clearerr(fp);
                                printf("end of file\n");
                                break;
                        }

                }

                if (ret == '\n')
                        line++;
        }

        printf("line = %d\n", line);
}
2.2.2 按行读写数据

1. 读数据

        函数原型

char *fgets(char *s, int size, FILE *stream);
参数:
    s指针,所指向的地址为所读取数据所存储空间的起始地址;
    size表示s指针所指向连续存储空间的字节数;
        如果所读行数据的字节数 < size,读取完整行数据内容;
        如果所读取行数据的字节数 >= size, 读取数据字节数为size-1,并将最后一个字节使用'\0'填充;
            综述:在读数据的过程中,不会超过存储空间的上限,存储空间的访问不会越界,数据安全。
    stream:表示需要读的文件指针。
返回值:
    成功返回s起始地址;
    失败或者读文件结束返回NULL。
    
char *gets(char *s);
功能:从标准输入流读取字符串数据,在读的过程中只有遇到换行符标识才结束,没有做越界处理。
    可能存在每次读取有效数据量大于s指向的所开辟的连续存储空间,此时导致数据越界访问,需要谨慎使用。

实例:

#include <stdio.h>

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

        fp = fopen("test.txt", "r");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }   

        printf("fopen test.txt success\n");

        while(1) {
                p = fgets(buf, sizeof(buf), fp);
                if (p == NULL) {
                        if (ferror(fp)) {
                                clearerr(fp);
                                perror("fgets");
                                break;
                        }   
                        if (feof(fp)) {
                                clearerr(fp);
                                printf("end of file\n");
                                break;
                        }

                }
                printf("%s", buf);
        }
}

2. 写文件

#include <stdio.h>
int fputs(const char *s, FILE *stream);
功能:将s指针所指向的字符串数据写入到stream文件指针流中;遇到'\0'结束。
参数:
    s 指向需要写文件的数据起始地址;
    stream标识写文件的文件指针。
返回值:
    成功返回写入文件状态,1
    失败返回EOF

int puts(const char *s)
功能:将指针s所指向的连续字符串数据写入到标准输出流中。

实例

#include <stdio.h>

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

        fp = fopen("test.txt", "a");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }

        fgets(buf, sizeof(buf), stdin);

        ret = fputs(buf, fp);
        if (EOF == ret) {
                perror("fputs");
                return -1; 
        }

        printf("ret = %d\n", ret);
}
2.2.3 读写一段数据

        不管是以字节为单位,还是以行为单位读写数据,效率相对较低,可以采用一段数据的读写方式实现。

1. 函数原型

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
    ptr:数据内容连续存储空间起始地址;所读取文件中的数据元素类型可以是任意数据类型
    size:所读/写每一个数据元素的字节数;
    nmemb:读/写元素的个数;
    stream:需要读/写文件的文件指针;
返回值:
    成功返回所读/写数据的元素个数;
    失败和读文件结束返回0
注意:可以实现文本数据的读写,也可以是二进制文件的读写。

2. 文本文件读写应用实例

#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *src_fp;
        FILE *dest_fp;
        int ret;
        char buf[256];

        src_fp = fopen(argv[1], "r");
        if (src_fp == NULL) {
                perror("fopen->src_file");
                return -1; 
        }   

        dest_fp = fopen(argv[2], "w");
        if (dest_fp == NULL) {
                perror("fopen->dest_file");
                return -1; 
        }
        while(1) {
                ret = fread(buf, 1, sizeof(buf), src_fp);
                if (ret == 0) {
                        if (feof(src_fp))
                                break;
                        if (ferror(src_fp)) {
                                perror("fread");
                                return -1;
                        }
                }
                ret = fwrite(buf, 1, ret, dest_fp);
                if (ret == 0) {
                        perror("fwrite");
                        return -1;
                }
        }
}

3. 二进制文件读写实例

#include <stdio.h>

struct Stu {
        int id;
        char name[32];
        int score;
};

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

        struct Stu stu1 = {1, "huihui", 99};
        struct Stu stu2;

        fp = fopen(argv[1], "w+b");    /* 打开文件的时候,方式中添加符号b表示所打开文件为二进制文件 */
        if (fp == NULL) {
                perror("fopen->file");
                return -1; 
        }   

        ret = fwrite(&stu1, sizeof(struct Stu), 1, fp);
        printf("ret = %d\n", ret);
        printf("%d-%s-%d\n", stu1.id, stu1.name, stu1.score);

        rewind(fp);    /* 设置文件读写流位置为起始位置 */

        ret = fread(&stu2, sizeof(struct Stu), 1, fp);
        printf("%d-%s-%d\n", stu2.id, stu2.name, stu2.score);
        printf("ret = %d\n", ret);
}

2.3 关闭文件流

        在已打开文件流访问结束的时候,需要将文件流关闭做资源回收处理。

#include <stdio.h>

int fclose(FILE *stream);

功能:将缓冲区的数据刷新到stream流中,同时释放文件流资源。

注意:如果在文件访问结束后,用户未调用fclose函数刷新文件流和关闭资源的情况下

        1) 可以用户调用缓冲区刷新处理函数:

#include <stdio.h>

int fflush(FILE *stream);

功能:将缓冲区的数据刷新到stream流中。

        2)在程序结束的时候,由系统自动完成文件流缓冲区数据刷新,同时释放文件流资源数据。

        3)在使用的过程中,需要注意程序的严谨性,建议在文件流不在访问的时候,需要手动调用fclose关闭文件流指针。

2.4 文件流定位

        在数据读写的过程中,不一定每次从文件的起始位置和末尾位置进行数据的读写访问,需要根据 当前的需求进行读取指针的偏移,完成读写文件指针的定位

1. 函数原型

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:设置文件流读写指针位置
参数:
    stream 需要设置的文件流指针;
    offset 表示偏移量,可以是正数(向后偏移),也可以是负数(向前偏移)
    whence 表示基准量
        SEEK_SET:文件起始位置
        SEEK_CUR:文件当前位置
        SEEK_END:文件末尾                  

返回值:
     成功返回0,失败返回-1,且修改errno的值。
long ftell(FILE *stream);
功能:获取当前位置相对于起始位置的偏移量(当前位置之前的字节数)。
void rewind(FILE *stream);
功能:等价于fseek(stream, 0L, SEEK_SET);

2. 获取当前文件的大小实例

#include <stdio.h>

int main(int argc, char **argv)
{
        int ret;
        FILE *fp;
        long lenth;

        fp = fopen(argv[1], "r");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }
        /* 将文件读指针定位到文件末尾 */
        ret = fseek(fp, 0, SEEK_END);
        if (ret == -1) {
                perror("fseek");
                return -1; 
        }

        lenth = ftell(fp);    /* 获取偏移量,字节数(文件的大小) */
        printf("lenth = %ld\n", lenth);

        rewind(fp);
}

2.5 freopen重定向文件流

FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:
    将stream文件流指针重定向到path所表示的文件中

实例:

#include <stdio.h>

int main()
{
        FILE *fp;

        printf("aasdfghjkl\n");
        /* 重新打开stream文件指针:最终实现stdout文件指针重定向到文件printlog.txt中 */
        fp = freopen("printlog.txt", "a+", stdout);
        if (fp == NULL) {
                perror("freopen");
                return -1; 
        }   

        /* 写入到标准输出流中的数据,实际会写入发文件printlog.txt中 */  
        printf("1234567\n");
        printf("zxcvbnm,\n");
}

2.6 文件流数据格式输入和输出

1. 函数原型

int fprintf(FILE *stream, const char *format, ...);
功能:实现格式化数据输出到文件中;
int fscanf(FILE *stream, const char *format, ...);
功能:将文件中数据格式化输入到内存变量中

2. 实例

#include <stdio.h>

int main()
{
        FILE *fp;
        int tem = 27; 
        int hum = 65; 
        int light = 987;

        fp = fopen("test.txt", "w+");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }
        /* 实现将程序中数据格式化输出到文件流中 */
        fprintf(fp, "tem=%d,hum=%d,light=%d", tem, hum, light);    
        fflush(fp);
        rewind(fp);
        /* 实现将文件流中的数据格式化输入到程序中 */
        fscanf(fp, "tem=%d,hum=%d,light=%d", &light, &tem, &hum);
        printf("tem=%d,hum=%d,light=%d", tem, hum, light);
        fclose(fp);
}

3 练习

1. 分别使用单个字节读写、行读写和块数据读写方式实现文件的拷贝程序;

2. 实现错误日志文件

        1) 每间隔1s获取当前错误信息按行写入文件errlog.txt中

        2) 每一行显示 当前行号(从1开始计数) 错误发生的时候(例如:2024/3/10 17:50:24)

                1 2024/3/10 17:50:24

                2 2024/3/10 17:50:25

                ……

        3) 错误日志可以通过Ctrl+c键退出,

        4) 下一次启动程序,错误消息行号在上一次末尾行序号递增

分析:

1. 当前错误发生的时间

        1) time函数获取

#include <time.h>
time_t time(time_t *tloc);
功能:获取计算机元年(1970-01-01 00:00:00)到当前时间所经历的时间,单位为s
当tloc不为空,此时的时间可以通过形参返回,同时通过返回值返回。
当tloc为空,此时的时间只能通过返回值返回。

2) 时间转换函数

struct tm *localtime(const time_t *timep);
功能:将s为单位的时候,转换为结构体存储时间
struct tm {
               int tm_sec;    /* Seconds (0-60) */
               int tm_min;    /* Minutes (0-59) */
               int tm_hour;   /* Hours (0-23) */
               int tm_mday;   /* Day of the month (1-31) */
               int tm_mon;    /* Month (0-11) */
               int tm_year;   /* Year - 1900 */
               int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
               int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
               int tm_isdst;  /* Daylight saving time */
           };
实例:
tm = localtime(&tim);
printf("%d-%d-%d %d:%d:%d\n", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);

2. 下一次运行数据是之前文件内容基础上追加数据。

        统计之前的行序号,读源文件中的数据统计行数;

        文件打开方式“a+”;

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

int main()
{
#if 0
        time_t tim;
        struct tm *tm;

        printf("%d\n", time(&tim));
        printf("%d\n", tim);


        tm = localtime(&tim);
        printf("%d-%d-%d %d:%d:%d\n", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
#else
        FILE *fp;
        char buf[128];
        int line;
        time_t tim;
        struct tm *tm;
        /* 以追加读写方式打开错误日志文件 */
        fp = fopen("errlog.txt", "a+");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }

        line = 0;
        while(1) {
                if (NULL == fgets(buf, sizeof(buf), fp)) {
                        if (ferror(fp)) {
                                perror("fgets");
                                clearerr(fp);
                                return -1;
                        }
                        if (feof(fp))
                                break;
                }
                if (buf[strlen(buf)-1] == '\n') {
                        printf("buf:%s\n", buf);
                        line++;
                }
        }
        printf("line = %d\n", line);
        while(1) {
                time(&tim);
                tm = localtime(&tim);
                fprintf(fp, "%d %d-%d-%d %d:%d:%d\n",\
                        ++line, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, \
                        tm->tm_hour, tm->tm_min, tm->tm_sec);
                fflush(fp);
                sleep(1);
        }

#endif
}

3. 实现歌词文件的解析,将歌词的时间和内容存储到结构体数组中

字符串数据切割函数,切割字符串数据,将指定符号替换为\0。

#include <string.h>
char *strtok(char *str, const char *delim);
str:要切割的源字符串数据,
delim:切割分隔符,使用\0替换。

歌词解析并存储到结构体数组中

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

struct Time {
        int min;
        int s;
        int ms; 
        char *name;
};

int main()
{
        FILE *fp;
        int ret;
        int len;
        char *buf;
        struct Time mytim[16];
        int i;
        int count;
    
        fp = fopen("test.lrc", "r");
        if (fp == NULL) {
                perror("fopen");
                return -1; 
        }


        ret = fseek(fp, 0L, SEEK_END);
        if (ret == -1) {
                perror("fseek");
        }
        len = ftell(fp);
        rewind(fp);

        buf = malloc(len);
        if (buf == NULL) {
                perror("malloc");
                return -1;
        }

        ret = fread(buf, 1, len, fp);

        printf("%s\n", buf);

        char *p;

        i = 0;
        p = strtok(buf, "\n");
        while(p != NULL) {
                printf("p:%s\n", p);
                count = 0;
                while(*p == '[') {
                        sscanf(p, "[%d:%d.%d]", &mytim[i].min, &mytim[i].s, &mytim[i].ms);
                        p += 11;
                        i++;
                        count ++;
                }
                for (count; count > 0; count--) {
                        printf("%s\n", p);
                        mytim[i-count].name = p;
                }
                p = strtok(NULL, "\n");

        }

        for (count = 0; count < i; count ++) {
                printf("%d:%d.%d %s\n", mytim[count].min, mytim[count].s, mytim[count].ms, mytim[count].name);
        }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值