09_标准IO编程

1 文件的概述

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

1.1 Linux 系统中的文件

1.1.1 文件的基本定义

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

1.磁盘文件

        指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。

2.设备文件

        操作系统将连接到系统中的硬件设备在系统的文件系统中创建设备文件节点,应用程序可以通过系统接口对设备文件节点访问实现对硬件设备的控制。

3.通信文件:管道文件和套接字文件。

1.1.2 文件的访问过程

        文件缓冲区:由标准C库所开辟的内存空间,可以通过标准C库所提供的API接口实现对缓冲区数据的读写访问操作。

1.2 缓冲区的具体形式

        在实现数据操作的过程中,文件的缓冲区访问的形式由三种形式:具体包含行缓冲、全缓冲和无缓冲形式

1.行缓冲

        所谓的行缓冲,指的是文件在IO操作的时候,数据在遇到行缓冲标志('\n')自动刷新缓冲区中的数据。

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

int main()
{
        int i = 0;
        printf("hello ikun\n");        /* 数据会输出,采用的行缓冲的形式,在遇到'\n'的时候会自动刷新 */
        while(1);


}

2.全缓冲

        所谓的全缓冲,指的是文件在IO操作的时候,有固定的缓冲区,在满足特点条件的时候才会刷新缓冲中的数据

1) 当缓冲区为满的时候,自动刷新缓冲区。

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

int main()
{
        int i = 0;
        printf("hello ikun\n");

        while(1) {
                printf("%5d", i++);
                usleep(10000);
                /* 在循环写入数据的过程中,在数据将缓冲区填满自动刷新缓冲区 */

        }
}

2) 人为刷新缓冲区,调用fflush刷新缓冲区

#include <stdio.h>
int fflush(FILE *stream);
功能:人为刷新指定文件流的缓冲区
参数:stream是文件流指针,表示需要刷新缓冲区的文件流。

实例:
#include <stdio.h>
#include <unistd.h>

int main()
{
        int i = 0;
        printf("hello ikun\n");

        while(1) {
                printf("%5d", i++);
                usleep(1000000);
                fflush(stdout);     /* 调用fflush函数,人为刷新缓冲区 */
        }
}

3) 在执行程序正常退出的时候,标准IO库会自动刷新缓冲区中的数据。

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

int main()
{
        int i = 0;
        printf("hello ikun");        /* 程序正常退出,会刷新缓冲区 */


}

3.无缓冲

        所谓的无缓冲,指的是在进行IO操作的时候,直接实现数据的IO操作,将需要写入文件的数据直接写入到文件中,将需要从文件中读取的数据直接从文件中读取。

一般情况错误信息的输出采用无缓冲形式实现。

1.3 磁盘文件的分类

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

1.文本文件:

        数据的编码形式是文本形式,具体编码形式可以是ASCII编码、UNICODE编码等形式。

例如存储数据5678按照ASCII编码形式存储,将字符‘5’、‘6’、‘7’、‘8’的ASCII编码存储,依次

        ‘5’ 存储的ASCII编码53(0011 0101)

        ‘6’ 存储的ASCII编码54(0011 0110)

        ‘7’ 存储的ASCII编码55(0011 0111)

        ‘8’ 存储的ASCII编码56(0011 1000)

2.二进制文件:

        数据的编码形式直接数据内容的二进制编码,主要是数值数据的存储编码,其它数据会按照其它编码形式存储。

        例如存储数据5678按照数据本身的二进制编码存储:0001 0110 0010 1110。

        在实际应用中的图像数据、音频数据、视频数据等都是采用二进制编码形式存储。

3.文本文件和二进制文件的区别

        a.共同点:都可以实现数据文件的存储。

        b.不同点:

                i.编码形式不同,译码不同。

                        1.文本文件是定长编码形式,译码简单。

                        2.二进制文件不定长的编码形式,译码困难,不同的二进制文件译码形式不同,需要专门的译码工具实现。

                ii.空间利用率不同

                        对于二进制按照位表示是数据,而文本文件是按照字节表示数据。所以二进制文件的空间利用率更高。

                iii.可读性

                        文本文件通常使用通用工具可以直接读取,而二进制文件需要专用的工具译码读取。

2 标准IO操作

2.1 文件指针

        文件指针,在标准IO操作中,用来标识已经打开的文件,并用来对打开文件的读写等访问操作。在文件打开成功的时候分配,在不需要对文件访问的时候去做释放。

1.FILE指针的认识

        对于文件指针是FILE结构体指针,是在标准C库中所定义的一个结构体。

        a.在使用的时候需要包含头文件:

                #include<stdio.h>

        b.在结构体中,主要存储文件的相关属性:主要包含文件缓冲区大小、文件的状态、文件当前位置等信息。

        c.结构体具体定义:

                /user/include/stdio.h文件

 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
 50 #if defined __USE_LARGEFILE64 || defined __USE_POSIX \
 51     || defined __USE_ISOC99 || defined __USE_XOPEN \
 52     || defined __USE_POSIX2
 53 __USING_NAMESPACE_STD(FILE)
 54 #endif

/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 };

d.程序(进程)中FILE指针的管理

        在一个进程中,可能打开多个文件,每一个文件都会分配一个FILE指针,而在一个进程中的多个FILE指针通过链表来管理。系统就可以通过链表实现多进程中所有的FILE指针的操作。

2.Linux系统中的进程启动

        在Linux系统进程启动的时候,会自动分配三个FILE指针,标识:

stdin    标准输入文件流指针        功能实现检测标准输入文件的数据,并给到应用程序    scanf
stdout   标准输出文件流指针        功能实现标准输出文件的数据给终端设备             printf
stderr   标准错误输出文件流指针    功能实现标准错误文件的数据给到终端设备            perror

2.2文件流的打开和关闭

2.2.1 打开文件流-fopen

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:使用文件的路径打开文件流
参数:
    参数1:path指针,指向的需要打开文件的路径名称
    参数2:mode以字符串设置文件的打开方式
        "r"    以只读方式打开已存在文件,设置读文件位置为文件的起始位置;
        "r+"   以读写方式打开已存在文件,设置读文件位置为文件的起始位置。
        "w"    以只写方式打开文件,文件存在清除文件中数据,不存在创建文件。
        "w+"   以读写方式打开文件,文件存在清除文件中数据,不存在创建文件。
        "a"    以只写方式打开文件,文件存在将写位置设置在文件的末尾(追加数据),不存在创建文件
        "a+"
   以读写方式打开文件,文件存在将写位置设置在文件的末尾(追加数据),不存在创建文件
        直接使用以上6种方式表示操作的文件为文本文件,如果操作二进制文件则以上方式中添加字符'b'表示:"rb"、"r+b"、……
        在文件创建的时候,文件的访问权限默认为:S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
| S_IROTH | S_IWOTH (0666)        
返回值:成功返回文件的FILE指针,失败返回NULL
实例:只读打开已存在的test.txt文件
#include <stdio.h>

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

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

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

        return 0;
}

FILE *fdopen(int fd, const char *mode);
功能:通过已打开文件的文件描述符打开文件。
参数:
    参数1:fd表示已经打开文件的文件描述符;
    参数2:mode表示打开文件的方式,同fopen中参数mode一致。
返回值:
    成功返回文件流指针,失败返回NULL。
实例:
#include <stdio.h>
#include <unistd.h>

int main()
{
        FILE *fp;

        fp = fdopen(1, "w+");    /* 打开标准输出流文件描述符(1)的文件 */
        if (fp == NULL) {
                perror("fdopen error");
                return -1; 
        }

        while(1) {
                if (EOF == fputs("hello kunkun\n", fp)) {    /* 在向文件流fp中写入数据 */
                        perror("fputs error");
                        return -1; 
                }
                sleep(1);
        }

        fclose(fp);

        return 0;
}



FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:重新打开文件流指针
参数:
    参数1:path表示需要重新打开文件的名称;
    参数2:mode文件重新打开的方式,参数设置和fopen中的mode参数一致。
    参数3:stream是文件流指针,表示需要重新打开的文件流指针。
返回指针:
    成功返回重新打开文件的文件流指针,失败返回NULL
实例:
#include <stdio.h>

int main()
{
        FILE *fp;
        FILE *fp1;
        /* 读写打开文件test.txt,文件存在清除源文件中的数据,文件不存在创建文件 */
        fp = fopen("test.txt", "w+");
        if (fp == NULL) {
                perror("fopen error");
                return -1; 
        }

        if (EOF == fputs("hello ikun\n", fp)) {
                perror("fputs error");
                return -1; 
        }
        /* 读写方式重新打开文件test.txt,文件存在保留文件中的数据并将写指针移动文件末尾,文件不存在创建文件 */
        fp1 = freopen("test.txt", "a+", fp);
        if(fp1 == NULL) {
                perror("freopen error");
                return -1;
        }   
            
        if (EOF == fputs("hello kunkun\n", fp)) {
                perror("fputs error");
                return -1;
        }


        if (EOF == fclose(fp1)) {
                perror("fclose error");
                return -1;
        }
}

2.2.2 关闭文件流-fclose

#include <stdio.h>
int fclose(FILE *stream); 
功能:刷新文件流缓冲区,在关闭整个文件流指针。
参数:stream文件流指针。
返回值:成功返回0,失败返回EOF(是一个标识符常量,值为-1)且修改errno的值.
实例:
#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        /* 打开文件test.txt得到文件流指针fp*/
        fp = fopen("test.txt", "r");
        if (fp == NULL) {
                perror("fopen error");
                return -1; 
        }   

        printf("fopen test.txt success\n");
        /* 刷新文件流fp的缓冲区,在关闭文件流指针fp */         
        if (EOF == fclose(fp)) {    
                perror("fclose error");
                return -1;
        }

        return 0;
}

2.3 文件流的读写访问

        在标准IO中,对于文件流的读写有多种方式实现,具体包含每次读写一个字符、每次读写一行数据、直接读写。

2.3.1 读写一个字节数据

  1. 读一个字符

           a.读函数的原型

#include <stdio.h>
int fgetc(FILE *stream);
参数:stream文件指针,表示读取数据的文件流
返回值:成功返回读取到的unsigned char类型数据,失败和文件结束返回EOF 


int getc(FILE *stream);
参数:stream文件指针,表示读取数据的文件流
返回值:成功返回读取到的unsigned char类型数据,失败和文件结束返回EOF
和fgetc函数的区别:
    共同点:都可以从指定的文件流中读取1个字节的数据;
    不同点:fgetc是函数,而getc是宏函数;
        1. fgetc是在程序执行的时候来调用,会入栈和出栈的做数据保护;getc是在程序预处理阶段做替换。
     在使用过程中尽量使用前者实现。                            
int getchar(void);
功能:等价于getc(stdin);

b.循环读数据实例

#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        int ret;
        /* 以只读形式打开文件 */
        fp = fopen(argv[1], "r");
        if (fp == NULL) {
                perror("fopen error");
                return -1; 
        }

        while(1) {
                ret = fgetc(fp);    /* 每次读一个字符 */
                if (ret == EOF) {
                        if (feof(fp)) {    /* 判断读文件是否结束 */
                                printf("end-of-file\n");
                                break;
                        }    

                        if (ferror(fp)) {    /* 判读读文件是否出错 */
                                printf("fgetc error\n");
                                return -1;
                        }
                }
                printf("%c", ret);    /* 将读取到的字符标准输出显示 */
        }

        /* 关闭文件流 */
        if (EOF == fclose(fp)) {
                perror("fclose error");
                return -1;
        }

        return 0;
}

2.通过EOF对文件结束和错误的判断

#include <stdio.h>
void clearerr(FILE *stream);    /* 清除stream流的文件结束和失败的状态 */
int feof(FILE *stream);
         /* 判断文件是否结束 */
int ferror(FILE *stream);
       /* 判断是否出错 */ 
int fileno(FILE *stream);
       /* 得到stream流文件描述符 */

3.写一个字符

        a.写数据函数原型

#include <stdio.h>
int fputc(int c, FILE *stream);
参数:
    参数1:c表示需要写入文件的字符数据;
    参数2:stream文件指针,表示需要写入的文件流。
返回值:成功返回写入文件流字符数据,失败返回EOF                
int putc(int c, FILE *stream);
参数:
    参数1:c表示需要写入文件的字符数据;
    参数2:stream文件指针,表示需要写入的文件流。
返回值:成功返回写入文件流字符数据,失败返回EOF 
和fputc的区别:
    前者fputc是函数,putc是宏函数。
int putchar(int c);
功能:等价于putc(stdout);

b.写数据的实例

#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        int c;
        /* 以读写方式打开文件 */
        fp = fopen(argv[1], "r+");
        if (fp == NULL) {
                perror("fopen error");
                return -1; 
        }
        
        while(1) {
                c = fgetc(stdin);    /* 从标准输入流读取字符数据 */
                if (EOF == fputc(c, fp)) {    /* 将字符数据写入到文件流中 */
                        perror("fputc error");
                        return -1; 
                }
                fflush(fp);        /* 刷新文件流 */
        }
        /* 关闭文件流 */
        if (EOF == fclose(fp)) {
                perror("fclose error");
                return -1;
        }

        return 0;
}

2.3.2 每次读写一行数据

  1. 读一行数据内容

                a.函数的原型

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
参数:
    参数1:s是缓冲空间的起始地址,存储读取到数据内容;
    参数2:size表示s指向缓存空间的大小;
        1) 读取数据的字节数小于size-1,遇到换行符'\n'结束,并且在换行符的后添加结束标志'\0';
        2) 最多能够读取数据的字节数等于size-1,在后面添加'\0'结束。
    参数3:stream文件指向,需要读文件的文件流。
返回值:
    成功返回缓存空间的起始地址;
    返回为NULL表示读失败或者结束。
    
#include <stdio.h>
char *gets(char *s);
功能:是从标准输入流stdin中读取数据内容,不会检测缓存空间大小,会根据输入数据的多少存储。
    存在的问题:实际输入数据的字节数 > s指针所指向的空间的大小,导致访问越界。

        b.实例

#include <stdio.h>

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

    fp = fopen(argv[1], "r");    /* 以只读方式打开文件 */
    if (fp == NULL) {
        perror("fopen");
        return -1;
    }

    while(1) {
        /* 每次读取一行数据,如果该行数据的字节数>128,读取127字节,否则读取到'\n'结束 */
        if (NULL == fgets(buf, sizeof(buf), fp)) {
            if (feof(fp))
                break;
            if (ferror(fp)) {
                printf("fgets buf error\n");
                return -1;
            }
        }
        printf("%s\n", buf);
    }
    
    if (EOF == fclose(fp)) {
        perror("fclose");
        return -1;
    }

    return 0;
}

2.写一行数据

        a.函数原型

#include <stdio.h>
int fputs(const char *s, FILE *stream);
参数:
    参数1:s指针表示的是缓存空间起始地址,空间存储的是需要写入文件的数据内容;
    参数2:stream文件指针,文件标识。
返回值:
    成功返回写入文件的字节数,失败返回EOF                
int puts(const char *s);
参数:s表示需要写入标准输出流文件的缓存数据起始地址。

        b.实例:

#include <stdio.h>

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

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

    while(1) {
        fgets(buf, sizeof(buf), stdin);
        if (EOF == fputs(buf, fp)) {
            perror("fputs error");
            return -1;
        }
        fflush(fp);
    }
    if (EOF == fclose(fp)) {
        perror("fclose error");
        return -1;
    }

    return 0;
}

3.fgets/fputs和gets/puts的区别

        a.相同点:在标准IO中实现数据按行的读写。

        b.不同点:

                I.前者是函数在程序执行过程中调用处理,后者是宏函数在程序预处理阶段做替换。

                II.前者会自动做缓存空间的越界判断处理,后者不会。

               III.前者可以指文件流的读写,后者只能实现标准输入输出流的读写

2.3.3 直接读写文件流

        1.函数原型

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件流中读取数据
参数:
    参数1:ptr表示数据缓存空间起始地址,存储读取到的数据;
    参数2:size每一个数据元素空间的大小;
    参数3:nmemb表示读取数据的元素个数;
    参数4:stream指针表示读取数据的文件流。
返回值:
    成功返回读取数据的字节数,失败和结束返回0.
    
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:向文件流中写入数据
参数:
    参数1:ptr表示数据缓存空间起始地址,存储需要写入的数据;
    参数2:size每一个数据元素空间的大小;
    参数3:nmemb表示要写入数据的元素个数;
    参数4:stream指针表示需要写入数据的文件流    
返回值:
    成功返回写入文件的字节数,失败返回0

        2.实例:

#include <stdio.h>

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

    if (argc != 3) {
        printf("Usge: %s <src_file> <dst_file>\n", argv[0]);
        return -1;
    }
    src_fp = fopen(argv[1], "r");
    if (src_fp == NULL) {
        perror("fopen->src_file");
        return -1;
    }
    dst_fp = fopen(argv[2], "w");
    if (dst_fp == NULL) {
        perror("fopen->dst_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)) {
        printf("fread error\n");
        return -1;
            }
        }
        ret = fwrite(buf, 1, ret, dst_fp);
        if (ret == 0) {
            return -1;
        }
    }

    fclose(dst_fp);
    fclose(src_fp);
}

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);
功能:获取文件流的当前位置
参数:stream文件流指针
返回值:从文件起始位置到当前的字节数

void rewind(FILE *stream);
功能:等价于:fseek(stream, 0, SEEK_SET);也就是设置为文件位置为文件起始位置。

        2.实例

#include <stdio.h>

int main(int argc, char **argv)
{
    int ret;
    FILE *fp;
    char buf[128];
    /* 打开文件 */
    fp = fopen(argv[1], "r");
    if (fp == NULL) {
        perror("fopen error");
        return -1;
    }
    
    ret = fseek(fp, 0, SEEK_END);    /* 设置文件的位置为结束位置 */
    printf("ret = %d\n", ret);
    /* 获取文件的当前位置,返回文件起始到当前的字节数 */
    printf("%ld\n", ftell(fp));    /* 由于前面设置文件的结束位置,所以相当于获取文件的大小 */
    /* 设置文件位置为起始位置:等价于:fseek(fp, 0, SEEK_SET); */
    rewind(fp);

    if (NULL == fgets(buf, sizeof(buf), fp)) {
        if (feof(fp))
            return 0;
        if (ferror(fp)) {
            printf("fgets buf error\n");
            return -1;
        }
    }
    printf("->%s<-\n", buf);
    if (EOF == fclose(fp)) {
        perror("fclose error");
        return -1;
    }

    return 0;
}

3.可以创建空洞文件

#include <stdio.h>

int main()
{
    FILE *fp;
    fp = fopen("test.txt", "r+");
    if (fp == NULL) {
        perror("fopen error");
        return -1;
    }

    fseek(fp, 1024, SEEK_SET);        /* 创建空洞文件:占用磁盘空间,数据无效 */
    fwrite("kunkun", 1, 7, fp);       
    rewind(fp);                        /* 定位文件流 */
    fwrite("hello", 1, 5, fp);        /* 写数据 */
    fclose(fp);
}

        对于空洞文件,相当于先开辟足够的空间用于数据的存储,后写入的数据可能在前面,但是不影响前面所写入的数据内容。

2.5 格式文件流数据

        1.格式化数据到文件流指针

#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
参数:
    参数1:stream文件流指针,
    参数2:format是一个可变参数,使用和printf的参数一致。

        2.从文件流中格式化读数据

int fscanf(FILE *stream, const char *format, ...);
参数:
    参数1:stream文件流指针
    参数2:format可变参数

        3.实例

#include <stdio.h>

int main()
{
        FILE *fp;

        int tem = 34; 
        int hum = 65; 
        int light = 1234;

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

        fprintf(fp, "tem:%d, hum:%d, light:%d\n", tem, hum, light);    /* 格式化输入到文件流中 */
        rewind(fp);
        fscanf(fp, "tem:%d, hum:%d, light:%d\n", &light, &tem, &hum);    /* 从文件流中格式化到变量存储 */
        printf("tem = %d, hum = %d, light = %d\n", tem, hum, light);

        fclose(fp);
}

练习:

        实现文件拷贝:读取一个文件的数据到另外一个文件中,新的文件内容和源文件的内容一致。

        a.打开源文件:必须存在,只需要读数据:"r"

1. 打开源文件:必须存在,只需要读数据:"r"
2. 打开目标文件,如果存在清除原来的内容数据,如果不存在需要创建文件。需要写数据:"w"
3. 循环读取源文件数据,并写入到目标文件中
while(1) {
    /* 从源文件中每次读取一个字符 */
    /* 将成功读取的字符写入到目标文件中 */
}
4.关闭文件流

        b.统计文件中的行数:循环读取文件中的数据,遇到'\n',将行数+1

        c.有以下文件mytime.txt,内容如下:

1 2023-3-11 17:22:23
2 2023-3-11 17:22:24
3 2023-3-11 17:22:25
4 2023-3-11 17:22:26
5 2023-3-11 17:22:27
6 2023-3-11 17:26:30        /* 在下一次运行的时候,行序号递增,时间记录当前时间。 */

程序运行的时候,每个1s将当前时间写入到文件中。可以使用Ctrl+c退出程序,

在下次程序运行的时候,在原数据后添加新的数据,行序号依次递增,时间记录当前时间。

可以参考的函数:

1. man 3 sleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
2. man 2 time
#include <time.h>
time_t time(time_t *tloc);
功能:获取当前时间(以秒s为单位,并且从计算机元年开始计算的时间)
参数:
    tloc可以为NULL,通过返回值返回时间,可以使用time_t 指针,需要开辟空间表示。
    
3. man 3 localtime
struct tm *localtime(const time_t *timep);
将时间秒转换为具体时间的结构体信息
参数:
    参数:timep表示需要转换时间的地址;
返回值:
    成功时间结构体指针,失败返回NULL。    
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 */
           };
4. man 3 fprintf
int fprintf(FILE *stream, const char *format, ...);

实例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <time.h>


int main(int argc, char **argv)
{
        FILE *fp;
        int line;
        char buf[128];
        time_t tloc;
        struct tm *tm_p;

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

        line = 0;
        while(1) {
                if (NULL == fgets(buf, sizeof(buf), fp)) {
                        if (feof(fp))
                                break;
                        if (ferror(fp)) {
                                printf("ferror\n");
                                return -1;
                        }
                }
                if (buf[strlen(buf)-1] == '\n')
                        line++;
        }

        printf("line = %d\n", line);

        while(1) {
                time(&tloc);
                tm_p = localtime(&tloc);
                if (tm_p == NULL) {
                        printf("localtime error\n");
                        return -1;
                }
                fprintf(fp, "%3d %4d-%2d-%2d %2d:%2d:%2d\n", ++line, \
                        tm_p->tm_year+1900, tm_p->tm_mon+1, tm_p->tm_mday, \
                        tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec);
                fflush(fp);
                sleep(1);
        }
        fclose(fp);
}

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值