C++进阶篇:002.IO操作(外部文件操作)

一、标准IO与文件IO的区别

1.1 IO概念

1> IO:顾名思义就是输入输出,程序与外部设备进行信息交换的过程称为IO操作

2> 最先接触的IO:#include <stdio.h> 标准的缓冲输入输出头文件

1.2 IO的分类

1> 标准IO:使用系统提供的库函数实现

2> 文件IO:基于系统调用完成,每进行一次系统调用,进程会从用户空间向内核空间进行一次切换,当用户空间与内核空间进行切换时,进程就会进入一次挂起状态,从而导致进程执行效率低

3> 文件IO与标准IO的区别:标准IO相比于文件IO而言,提供了缓冲区,用户可以将数据先放入缓冲区中,等到缓冲区时机到了之后,统一进行一次系统调用,将数据刷入内核空间

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4> 标准IO接口:printf/scanf、fopen/fclose、fgetc/fputc、fgets/fputs、fprintf/fscanf、fseek/ftell/rewind

​ 文件IO接口:open/close、read/write、Iseek

二、标准IO

2.1 标准IO实现原理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2 FILE结构体的介绍

1> FILE结构体是系统提供的用于描述一个文件全部信息的结构体

2> FILE结构体的原型

struct FILE
{
 char * _IO_buf_base;       //缓冲区的起始地址
 char * _IO_buf_end;        //缓冲区的终止地址
 
 int _fileno;               //文件描述符,用于进行系统调用
};

3> 特殊的FILE指针,这三个指针,全部都是针对于终端文件而言的,当程序启动后,系统默认打开的三个特殊文件指针

​ stderr: 标准出错指针

​ stdin: 标准输入指针

​ stdout: 标准输出指针

2.3 打开文件:fopen

	#include <stdio.h>     //该函数所在的头文件
	
	FILE *fopen(const char *path, const char *mode);
	功能:打开一个文件,并返回当前文件的文件指针
	参数1:表示文件路径,是一个字符串
	参数2:打开文件的方式,也是一个字符串,字符串必须以以下的字符开头
		r	以只读的形式打开文件,文件光标定位在开头部分
		r+	以读写的形式打开文件,文件光标定位在开头部分
		w	以只写的形式打开文件,如果文件不存在,就创建文件,如果文件存在,就清空,光标定位在开头
		w+	以读写的形式打开文件,如果文件不存在,就创建文件,如果文件存在,就清空,光标定位在开头
		a	以追加(结尾写)的形式打开文件,如果文件不存在就创建文件,光标定位在文件末尾
		a+	以读写的形式打开文件,如果文件不存在就创建文件,如果文件存在,读取操作光标定位在开头,写操作光标定			 位在结尾
	返回值:成功返回打开的文件指针,失败返回NULL并置位错误码
#include "stdio.h"

int main(int argc, const char* argv[])
{
    //定义一个文件指针
    FILE *fp = NULL;
    //以只读的形式打开一个不存在的文件,并将返回结果存入到fp指针中
    //fp = fopen("./file.txt", "r");
    //此时会报错,原因是以只读的形式打开一个不存在的文件,是不允许的

    //以只写的形式打开一个不存在的文件,如果文件不存在就创建一个空的文件,如果文件存在就清空
    fp = fopen("./file.txt", "w");
    if (fp == NULL)
    {
        printf("fopen error\n");
        return -1;
    }
    printf("fopen success\n");
    

    return 0;
}

2.4 关闭文件

       #include <stdio.h>
       
       int fclose(FILE *fp);
       功能:关闭指定的文件
       参数:要关闭的文件指针(由fopen返回的结果)
       返回值:成功执行返回0,失败返回EOF并置位错误码
#include "stdio.h"

int main(int argc, const char *argv[])
{
    //1、定义一个文件指针
    FILE *fp = NULL;
    //以只读的形式打开一个不存在的文件,并将返回结果存入到fp指针中
    //fp = fopen("./file.txt", "r");    
    //此时会报错,原因是以只读的形式打开一个不存在的文件,是不允许的
    //以只写的形式打开一个不存在的文件,如果文件不存在就创建一个空的文件,如果文件存在就清空
    fp = fopen("./file.txt", "w");           
    if(fp == NULL)
   {
        printf("fopen error\n");
        return -1;
   }
    printf("fopen success\n");
    //2、关闭文件
    fclose(fp);
    
    return 0;
}

2.5 关于错误码的问题

1> 概念:当内核提供的函数出错后,内核空间会向用户空间反馈一个错误信息,由于错误信息比较多也比较复杂,系统就给每种不同的错误信息起了一个编号,用一个整数表示,这个整数就是错误码

2> 错误码都是大于或等于0的数字:

#define EPERM            1      /* Operation not permitted */		操作受限
#define ENOENT           2      /* No such file or directory */		没有当前文件或目录
#define ESRCH            3      /* No such process */			   没有进程
#define EINTR            4      /* Interrupted system call */		中断系统调用
#define EIO              5      /* I/O error */
#define ENXIO            6      /* No such device or address */
#define E2BIG            7      /* Argument list too long */
#define ENOEXEC          8      /* Exec format error */
#define EBADF            9      /* Bad file number */
#define ECHILD          10      /* No child processes */
#define EAGAIN          11      /* Try again */
#define ENOMEM          12      /* Out of memory */
#define EACCES          13      /* Permission denied */
#define EFAULT          14      /* Bad address */
#define ENOTBLK         15      /* Block device required */
#define EBUSY           16      /* Device or resource busy */
#define EEXIST          17      /* File exists */
#define EXDEV           18      /* Cross-device link */
#define ENODEV          19      /* No such device */
#define ENOTDIR         20      /* Not a directory */
#define EISDIR          21      /* Is a directory */
#define EINVAL          22      /* Invalid argument */
#define ENFILE          23      /* File table overflow */
#define EMFILE          24      /* Too many open files */
#define ENOTTY          25      /* Not a typewriter */
#define ETXTBSY         26      /* Text file busy */
#define EFBIG           27      /* File too large */
#define ENOSPC          28      /* No space left on device */
#define ESPIPE          29      /* Illegal seek */
#define EROFS           30      /* Read-only file system */
#define EMLINK          31      /* Too many links */
#define EPIPE           32      /* Broken pipe */
#define EDOM            33      /* Math argument out of domain of func */
#define ERANGE          34      /* Math result not representable */

3> 关于错误码的处理函数(strerror、perror)

#include <errno.h>		//错误码所在的头文件,里面定义了一个全局变量
int errno;

//将错误码对应的错误信息转换处理
	#include <string.h>
       
	char *strerror(int errnum);
	功能:将错误码转换为错误信息描述
	参数:错误码
	返回值:错误信息字符串描述
	 
//只打印错误码和对应错误信息的函数
	#include <stdio.h>
	
	void perror(const char* s);
	功能:输出当前错误码对应的错误信息
	参数:提示符号,会原样打印出来,并且会在提示数据后面加上冒号并输出完后,自动换行
	返回值:无	
#include"stdio.h"        //标准的输入输出头文件
#include<errno.h>          //错误码所在的头文件
#include<string.h>         //字符串处理的头文件
int main(int argc, const char *argv[])
{
    //1、定义一个文件指针
    FILE *fp = NULL;
    fp = fopen("./file.txt", "r");           
    if(fp == NULL)
   {
        //printf("fopen error: %d, errmsg:%s\n", errno, strerror(errno));
        //打印错误信息,并输出错误码 2
        perror("fopen error");      //打印当前错误码对应的错误信息
        return -1;
   }
    printf("fopen success\n");
    //2、关闭文件
    fclose(fp);
    
    return 0;
}

2.6 单字符读写:fputc/fgetc

	#include <stdio.h>

	int fgetc(FILE *stream);
	功能:从指定文件中读取一个字符数据,并以无符号整数的形式返回
	参数:文件指针
	返回值:成功返回读取的字符对应的无符号整数,失败返回EOF(-1)并置位错误码
	
	#include <stdio.h>
	
	int fputc(int c, FILE *stream);
	功能:将指定的字符c写入到stream指向的文件中
	参数1:要写入的字符对应的无符号整数
	参数2:文件指针
	返回值:成功返回写入的字符对应的无符号整数,失败返回EOF并置位错误码
#include <stdio.h>

int main(int argc, char* argv[])
{
    //1、定义文件指针
    FILE* fp = NULL;
    if((fp = fopen("./file.txt", "w")) == NULL)     //以只写的形式打开一个文件
    {
        perror("fopen error");
        return -1;
    }

    //使用程序对文件进行读写操作(单个字符进行操作)
    //本次操作的结果是在外部文件中显示Hello,说明每写入一次,文件光标都会向后偏移
    fputc('H', fp);
    fputc('e', fp);
    fputc('l', fp);
    fputc('l', fp);
    fputc('o', fp);

    //能否从该处读取数据?  不可以!因为光标此时在文件结尾处,没有数据可以被读取

    //关闭文件
    fclose(fp);

    //再次以只读的形式,重新打开上一个文件
    if((fp = fopen("./file.txt", "r")) == NULL)     //以只读的形式打开一个文件
    {
        perror("fopen error");
        return -1;
    }

    char ch = 0;    //字符的搬运工,将文件中的字符,搬运到终端上
    while(1)
    {
        ch = fgetc(fp);     //从fp指向的文件中光标位置处读取一个字符,并返回
        if(ch == EOF)
        {
            break;
        }
        printf("%c", ch);      //在终端上打印读取的字符

    }


    //关闭文件
    fclose(fp);


    return 0;
}

练习:

使用fgetc和fputc完成两个文件的拷贝工作,实现指令cp的功能:cp srcfile destfile

#include <stdio.h>

int main(int argc, const char* argv[])
{
    //判断外部传参的个数是否为 3
    if (argc != 3)
    {
        printf("input file error\n");
        printf("usage:./a.out srcfile destfile\n");
        return -1;
    }

    //以只读的形式打开源文件,以只写的形式打开目标文件
    FILE *srcfp = NULL;     //源文件文件指针
    FILE *destfp = NULL;    //目标文件文件指针
    if((srcfp = fopen(argv[1], "r")) == NULL)
    {
        perror("srcfile open error");
        return -1;
    }
    if((destfp = fopen(argv[2], "w")) == NULL)
    {
        perror("destfile open error");
        return -1;
    }

    //将源文件中的内容搬运到目标文件中
    char ch = 0;
    while(1)
    {
        ch = fgetc(srcfp);      //从源文件中读取一个字符
        if (ch == EOF)
        {
            break;              //文件全部读取结束
        }

        fputc(ch, destfp);      //将读取的字符写入到目标文件中
    }    


    //关闭两个文件
    fclose(srcfp);
    fclose(destfp);

    printf("拷贝成功");

    return 0;
}

2.7 字符串读写:fgets/fputs

	int fputs(const char* s, FILE* stream);
	功能:将制定的字符串,写入到指定的文件中
	参数1:要被写入的字符串
	参数2:文件指针
	返回值:成功返回本次写入字符的个数,失败返回EOF
	
	char* fgets(char* s, int size, FILE* stream);
	功能:从stream指向的文件中最多读取 size-1 个字符到s容器中,遇到回车或文件结束会结束一次读取,并且会将回车放入容器,最后自动加上一个字符串结束标识'\0'
	参数1:字符数组容器起始地址
	参数2:要读取的字符个数,最多读取 size-1 个字符
	参数3:文件指针
	返回值:成功返回容器s的起始地址,失败返回NULL
#include <stdio.h>
#include <iostream>
#include <string.h>

using namespace std;

int main(int argc, const char* argv[])
{
    //打开文件
    FILE* fp = NULL;
    if((fp = fopen("./file.txt", "w")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //向文件中写入多个字符
    char wbuf[128] = "";    //定义一个字符数组

    while(1)
    {
        cin >> wbuf;    //从终端输入一个字符串
        //将上述字符串写入文件中
        fputs(wbuf, fp);
        fputc('\n', fp);

        if (strcmp(wbuf, "quit") == 0)      //strcmp:比较两个字符串是否一致,如果相等返回0
        {
            break;
        }

    }




    //关闭文件
    fclose(fp);

    //以只读的形式再次打开文件
    if((fp = fopen("./file.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    char rbuf[128] = "";
    while(1)
    {
        //从文件中读取数据到rbuf容器中
        char* res = fgets(rbuf, sizeof(rbuf), fp);
        if(res == NULL)
        {
            break;      //说明文件读取结束
        }

        printf("rbuf = %s\n", rbuf);


    }

    //关闭文件
    fclose(fp);



    return 0;
}

练习:

使用fgets和fputs完成两个文件的拷贝

#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    //判断外部是否传入两个文件
    if (argc != 3)
    {
        printf("input file error\n");
        printf("usage:./a.out srcfile destfile\n");
        return -1;
    }

    //以只读的形式打开源文件,以只写的形式打开目标文件
    FILE* srcfp = NULL;
    FILE* destfp = NULL;
    if ((srcfp = fopen(argv[1], "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }
    if ((destfp = fopen(argv[2], "w")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义搬运工
    char buf[128] = "";
    while(1)
    {
        char* ptr = fgets(buf, sizeof(buf), srcfp);     //将数据从源文件中读取下来
        if(ptr == NULL)
        {
            break;
        }
        fputs(buf, destfp);     //将从源文件中读取的数据写入到目标文件中
    }



    //文件关闭
    fclose(srcfp);
    fclose(destfp);

    printf("拷贝成功\n");


    return 0;
}

2.8 关于标准IO的缓冲区问题

1> 缓冲区分为三种:

​ 行缓存(IOLBF):和终端文件相关的缓冲区叫做行缓存,行缓冲区的大小为1024字节,对应的文件指针:stdin, stdout

​ 全缓存(IOFBF):和外界文件相关的缓冲区叫做全缓存,全缓冲区的大小为4096字节,对应的文件指针:fp

​ 不缓存(IONBF):和标准出错相关的缓冲区叫不缓存,不缓存的大小为0字节,对应的文件指针:stderr

#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    //如果缓冲区没有被使用时,求出大小为0,只有被至少使用了一次后,缓冲区的大小就被分配了
    printf("行缓存的大小为:%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);     //行缓存的大小:0
    printf("行缓存的大小为:%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);     //行缓存的大小:1024

    printf("行缓存的大小为:%d\n", stdin->_IO_buf_end - stdin->_IO_buf_base);       // 0
    int num = 0;
    //scanf("%d", &num);
    cin >> num;
    printf("行缓存的大小为:%d\n", stdin->_IO_buf_end - stdin->_IO_buf_base);       // 1024


    //验证全缓存的大小为4096
    FILE* fp = NULL;
    if ((fp = fopen("./aa.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }
    //未使用全缓存时,大小为0
    printf("全缓存的大小为:%d\n", fp->_IO_buf_end - fp->_IO_buf_base);     // 0
    fgetc(fp);      //从文件中读取一个字符
    printf("全缓存的大小为:%d\n", fp->_IO_buf_end - fp->_IO_buf_base);     // 4096


    //验证不缓存的大小为0
    //未使用不缓存时大小为0
    printf("不缓存的大小为:%d\n", stderr->_IO_buf_end - stderr->_IO_buf_base);     // 0
    //使用一次不缓存
    perror("error");
    printf("不缓存的大小为:%d\n", stderr->_IO_buf_end - stderr->_IO_buf_base);     // 0
    //关闭文件
    fclose(fp);


    return 0;
}

效果图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2> 缓冲区的刷新时机

​ 缓冲区刷新函数fflush

	#include <stdio.h>

	int fflush(FILE *stream);
	功能:刷新给定的文件指针对应的缓冲区
	参数:文件指针
	返回值:成功返回0,失败返回EOF并置位错误码

​ 行缓存的刷新时机:

#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    /*1、验证缓冲区如果没有达到刷新时机,就不会将数据进行刷新
    printf("hello world\n");      //在终端上打印输出一个hello world,没有到缓冲区的刷新时机,就不会输出数据

    perror("error");            //在终端打印输出错误信息

    while(1);                   //阻塞程序,不让进程结束
    */

    /*2、当程序结束后,会刷新行缓冲区
    printf("hello world");
    */

    /*3、当遇到换行时,会刷新行缓存
    printf("hello world\n");
    while(1);
    */

    /*4、当输入输出发生切换时,也会刷新缓存
    int num = 0;
    printf("请输入>>>");        //向标准输出缓冲区中写入一组数据,没有换行符号
    scanf("%d", &num);          // cin >> num
    */

    /*5、当关闭行缓存对应的文件指针时,也会刷新行缓存
    printf("hello world");      //向标准输出缓冲区中写入数据,没有换行
    fclose(stdout);             //关闭标准输出指针

    while(1);
    */

    /*6、使用fflush函数手动刷新缓冲区时,行缓存会被刷新
    printf("hello world");
    fflush(stdout);
    while(1);
    */

    //7、当缓冲区满了后,会刷新行缓存,行缓存大小:1024字节
    for (int i = 0; i < 1025; i++)
    {
        printf("A");
    }
    while(1);


    
    return 0;
}   

​ 全缓存的刷新时机:

#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    //打开一个文件
    FILE *fp = NULL;
    if((fp = fopen("./aa.txt", "r+")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    /*1、当缓冲区刷新时机未到时,不会刷新全缓存
    fputs("hello world", fp);       //将字符串写入到文件中
    while(1);
    */

    /*2、当遇到换行时,也不会刷新全缓存
    fputs("hello world\n", fp);     //将字符串写入到文件中
    while(1);
    */

    /*3、当程序结束后,会刷新全缓存
    fputs("hello world 你好 星球!\n", fp);
    */

    /*4、当输入输出发生切换时,
    fputs("I love China\n", fp);       //向文件中输出一个字符串
    fgetc(fp);                      //从文件中读取一个字符,主要是让输入输出发生切换
    */
   
    /*5、当关闭缓冲区对应的文件指针时,也会刷新全缓存
    fputs("北京欢迎你", fp);
    fclose(fp);         //刷新文件指针
    while(1);
    */

    /*5、当手动刷新缓冲区对应的文件指针时,也会刷新全缓存
    fputs("上海欢迎你", fp);
    fflush(fp);         //刷新文件指针
    while(1);
    */

    //7、当缓冲区满了后,再向缓冲区中存放数据时,会刷新全缓存
    for (int i = 0; i < 4097; i++)
    {
        fputc('A', fp);     //循环将单字符写入文件中
    }
    while(1);





    return 0;
}

​ 对于不缓存的刷新数据,只要放入数据,立马刷新

#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    perror("a");        //向标准出错中放入数据

    fputs("A", stderr); //向标准出错缓冲区中写入一个字符A

    while(1);

    return 0;
}

2.9 格式化读写:fprintf/fscanf

int fprintf(FILE *stream, const char *format, ...);
功能:向指定的文件中输出一个格式串
参数1:文件指针
参数2:格式串,可以包含格式控制符,%d(整数)、%s(字符串)、%f(小数)、%lf(小数)
参数3:可变参数,输出项列表,参数个数由参数2中的格式控制付的个数决定
返回值:成功返回输出的字符个数,失败返回EOF并置位错误码

int fscanf(FILE *stream, const char *format, ...);
功能:从指定的文件中以指定的格式读取数据,放入程序中
参数1:文件指针
参数2:格式串,可以包含格式控制符,%d(整数)、%s(字符串)、%f(小数)、%lf(小数)
参数3:可变参数,输入项地址列表,参数个数由参数2中的格式控制符的个数决定
返回值:成功返回读入的项数,失败返回EOF并置位错误码
#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    //向标准输出文件指针中写入数据
    fprintf(stdout, "%d %lf %s", 520, 3.14, "I love Xingqiu\n");

    //从标准输入中读取数据到程序中来
    int num = 0;
    fscanf(stdin, "%d", &num);      //从标准输入缓冲区中以%d的格式读取一个整数
    printf("num = %d\n", num);      // 1314

    //对外部文件进行格式化读写
    FILE* fp = NULL;
    if((fp = fopen("./usr.txt", "w")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //向文件中写入数据
    fprintf(fp, "%s %d", "admin", 123456);      //将两个不同的数据存放到文件中


    //关闭文件
    fclose(fp);

    //再次以只读的形式打开文件
    if((fp = fopen("./usr.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    char usrName[20] = "";      //存放用户名
    int pwd = 0;                //存放密码
    fscanf(fp, "%s %d", usrName, &pwd);      //从外部文件中分别读取一个字符串和一个整数放入程序中的userName和pwd中


    //关闭文件
    fclose(fp);

    printf("usrName = %s, pwd = %d\n", usrName, pwd);


    return 0;
}

练习:

使用fprintf和fscanf完成注册和登录功能,要求做个小菜单,三个功能,功能1是注册,用户输入账号和密码后,将其存放到外部文件中;功能2是登录,用户输入登录账号和密码后,如果跟文件中匹配,则提示登陆成功,如果全部不匹配,则提示登录失败;功能0,·表示退出。菜单可以循环调用

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

using namespace std;

//定义注册函数
int do_register()
{
    //定义存储注册账户和密码的容器
    char reg_name[20] = "";
    char reg_pwd[20] = "";

    //输入注册账号和密码
    printf("请输入注册账户:");
    fgets(reg_name, sizeof(reg_name), stdin);
    reg_name[strlen(reg_name) - 1] = '\0';      //将字符串最后的换行符换成结束符号
    printf("请输入注册密码: ");
    fgets(reg_pwd, sizeof(reg_pwd), stdin);
    reg_pwd[strlen(reg_pwd) - 1] = '\0';

    //将账号和密码写入文件
    FILE* wfp = NULL;
    if((wfp = fopen("./usr.txt", "a")) == NULL)     //以追加的方式打开文件(防止第二次打开时内容被清空)
    {
        perror("fopen error");
        return -1;
    }
    fprintf(wfp, "%s %s\n", reg_name, reg_pwd);
    //关闭文件
    fclose(wfp);

    printf("注册成功\n");

    return 0;
}


//定义登录功能
int do_login()
{
    //定义容器存储登录账号和密码
    char log_name[20] = "";
    char log_pwd[20] = "";
    char name[20] = "";     //存储从文件中读取下来的账户
    char pwd[20] = "";      //存储从文件中读取下来的密码

    //提示并输入登录的账号和密码
    printf("请输入登录账户:");
    fgets(log_name, sizeof(log_name), stdin);
    log_name[strlen(log_name) - 1] = '\0';      //将字符串最后的换行符换成结束符号
    printf("请输入登录密码: ");
    fgets(log_pwd, sizeof(log_pwd), stdin);
    log_pwd[strlen(log_pwd) - 1] = '\0';

    //打开文件
    FILE* rfp = NULL;
    if((rfp = fopen("./usr.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //将登录账户和密码跟文件中所有账户和密码进行匹配
    while(1)
    {
        //将每一行的账户和密码读取出来
        int res = fscanf(rfp, "%s %s", name, pwd);
        if(res == EOF)      //读取完成
        {
            break;
        }

        //将读取出来的账号和密码跟登录账号和密码进行匹配
        if(strcmp(name, log_name) == 0 && strcmp(pwd, log_pwd) == 0)
        {
            printf("登录成功\n");
            //关闭文件
            fclose(rfp);
            return 1;
        }
    }
    
    //关闭文件
    fclose(rfp);

    printf("登录失败,请重新登录\n");

    return 0;
}



/*******************主程序********************/
int main(int argc, const char *argv[])
{
    int menu = 0;       //存储菜单选项变量

    while(1)
    {
        system("clear");        //创建子进程调用终端指令
        printf("\t\t========XXX 登录界面========\n");
        printf("\t\t========1、注册========\n");
        printf("\t\t========2、登录========\n");
        printf("\t\t========0、退出========\n");

        printf("请输入功能选项:");
        scanf("%d", &menu);
        getchar();      //吸收一下scanf留下的回车

        switch(menu)
        {
            case 1:
            {
                //注册
                do_register();
            }
            break;

            case 2:
            {
                //登录
                int res = do_login();
                if(res == 1)
                {
                    printf("登录成功后的界面\n");
                    //省略10000行代码
                }
            }
            break;

            case 0:exit(EXIT_SUCCESS);
            default:printf("您输入的功能有误,请重新输入!!!");
        }
        printf("请输入任意键按回车清屏\n");
        while(getchar() != '\n');
    }

    return 0;
}

2.10 格式串转字符串存入字符数组中:sprintf/snprintf

1> 目前所学的函数中printf是向终端打印一个格式串,fprintf是向外部文件中打印一个格式串

2> 有时候,想要将多个不同类型的数据,组成一个字符串放入字符数组中,此时我们就可以使用sprintf或snprintf

	int sprintf(char *str, const char *format, ...);
	功能:将指定的格式串转换为字符串,放入字符数组中
	参数1:字符数组的起始地址
	参数2:格式串,可以包含多个格式控制符
	参数3:可变参数
	返回值:成功返回转换的字符个数,失败返回EOF
	
	对于上述函数而言,用一个小的容器去存储一个大的转换后的字符串时,会出现指针越界的段错误,为了安全起见,
	引入了snprintf
	
	int snprintf(char *str, size_t size, const char *format, ...);
	功能:将格式串中最多size-1个字符转换为了字符串,存放到字符串中
	参数1:字符数组的起始地址
	参数2:要转换的字符个数,最多为size-1
	参数3:格式串,可以包含多个格式控制符
	参数4:可变参数
	返回值:如果转换的字符小于size时,返回值就是成功转换字符的个数,如果大于size则只转换size-1个字符,返回值就	是size,失败返回EOF

2.11 模块化读写(fread/fwrite)

	#include <stdio.h>
	
	size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
	功能:从stream指向的文件中,读取nmemb项数据,每一项的大小为size,将整个结果放入ptr指向的容器中
	参数1:容器指针,是一个void*类型,表示可以存储任意类型的数据
	参数2:要读取数据每一项的大小
	参数3:要读取数据的项数
	参数4:文件指针
	返回值:成功返回nmemb,就是成功读取的项数,失败返回小于项数的值,或者是0
	
	size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
	功能:向stream指向的文件中写入nmemb项数据,每一项的大小为size,数据的起始位置为ptr
	参数1:要写入数据的起始地址
	参数2:每一项的大小
	参数3:要写入的总项数
	参数4:文件指针
	返回值:成功返回nmemb,就是成功写入的项数,失败返回小于项数的值,或者是0

1> 字符串的读写

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

using namespace std;
int main(int argc, const char *argv[])
{
    //定义文件指针,以只写的形式打开
    FILE* fp = NULL;
    if((fp = fopen("./test.txt", "w")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义要存储的字符串
    char wbuf[128] = "";
    while(1)
    {
        //提示从终端上输入字符串
        printf("请输入>>>");
        fgets(wbuf, sizeof(wbuf), stdin);   //从标准输入中读取数据
        wbuf[strlen(wbuf) - 1] = '\0';      //将换行换成字符串结束标志

        //判断写入的是否为 quit
        if(strcmp(wbuf, "quit") == 0)
        {
            break;
        }

        //将字符串写入文件
        fwrite(wbuf, strlen(wbuf), 1, fp);  //将字符串以每个字符串为单位写入文件,每次写一项
        fwrite("\n", strlen("\n"), 1, fp);  //每输入一个字符串,加上换行
        //刷新缓冲区
        fflush(fp);
        printf("录入成功\n");
    }


    //关闭文件
    fclose(fp);
    
    //再次以只读的形式打开文件
    if((fp = fopen("./test.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    char rbuf[10] = "";
    int res = fread(rbuf, 1, sizeof(rbuf), fp);   //从文件中读取一个字符串
    fwrite(rbuf, 1, res, stdout);       //向标准输出中写入数据


    //关闭文件
    fclose(fp);


    return 0;
}

2> 整数的读写

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

using namespace std;
int main(int argc, const char *argv[])
{
    //定义文件指针,以只写的形式打开
    FILE* fp = NULL;
    if((fp = fopen("./test.txt", "w")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义要写入文件中的数据
    int num = 16;
    //将数据写入文件中
    fwrite(&num, sizeof(num), 1, fp);   //将num中的数据写入fp指向的文件中


    //关闭文件
    fclose(fp);
    
    //再次以只读的形式打开文件
    if((fp = fopen("./test.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义接收数据的变量
    int key = 0;
    //从文件中读取数据
    fread(&key, sizeof(key), 1, fp);

    //关闭文件
    fclose(fp);

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


    return 0;
}

3> 结构体的读写

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

using namespace std;

//定义一个学生信息
class Stu
{
public:
    char name[20];
    int age;
    double score;
};


int main(int argc, const char *argv[])
{
    //定义文件指针,以只写的形式打开
    FILE* fp = NULL;
    if((fp = fopen("./test.txt", "w")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义三个学生
    Stu s[3] = {{"张三", 18, 98}, \
                {"李四", 20, 88}, \
                {"王五", 16, 95}};
    //将三个学生信息写入文件中
    fwrite(s, sizeof(Stu), 3, fp);


    //关闭文件
    fclose(fp);
    
    //再次以只读的形式打开文件
    if((fp = fopen("./test.txt", "r")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义一个对象,接收读取的结果
    Stu temp;

    //从文件中读取一个学生的信息
    fread(&temp, sizeof(Stu), 1, fp);


    //将读取的数据展示出来
    printf("name:%s, age:%d, score:%.2lf\n", temp.name, temp.age, temp.score);


    //关闭文件
    fclose(fp);

    return 0;
}

2.12 关于文件光标:fseek/ftell/rewind

	int fseek(FILE *stream, long offset, int whence);
	功能:移动文件光标位置,将光标从指定位置处进行前后偏移
	参数1:文件指针
	参数2:偏移量
		>0:表示从指定位置向后偏移n个字节
		<0:表示从指定位置向前偏移n个字节
		=0:表示在指定位置处不偏移
	参数3:偏移的起始位置
		SEEK_SET:文件起始位置
		SEEK_CUR:文件指针当前位置
		SEEK_END:文件结束位置
	返回值:成功返回0,失败返回-1并置位错误码

	long ftell(FILE *stream);
	功能:获取文件指针当前的偏移量
	参数:文件指针
	返回值:成功返回文件指针所在的位置,失败返回-1并置位错误码
	eg:fseek(fp, 0, SEEK_END);		//将光标定位在结尾
	    ftell(fp);					//该函数的返回值,就是文件大小

	void rewind(FILE *stream);
	功能:将文件光标定位在开头,类似于:fseek(fp, 0, SEEK_SET);
	参数:文件指针
	返回值:无
#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std;

//定义一个学生信息
class Stu
{
public:
    char name[20];
    int age;
    double score;
};


int main(int argc, const char *argv[])
{
    //定义文件指针,以读写的形式打开
    FILE* fp = NULL;
    if((fp = fopen("./test.txt", "w+")) == NULL)
    {
        perror("fopen error");
        return -1;
    }

    //定义三个学生
    Stu s[3] = {{"张三", 18, 98}, \
                {"李四", 20, 88}, \
                {"王五", 16, 95}};
    //将三个学生信息写入文件中
    fwrite(s, sizeof(Stu), 3, fp);

    //求出文件的大小
    printf("此时文件的大小为:%ld\n", ftell(fp));       //文件大小

    //将光标移动到开头位置
    //fseek(fp, 0, SEEK_SET);

    //将光标直接定位到第二个学生信息前,但是此时光标在最后
    //fseek(fp, sizeof(Stu), SEEK_SET);     //将光标从开头后移一个学生空间的内容
    fseek(fp, -sizeof(Stu) * 2, SEEK_CUR);  //将光标从当前位置向前偏移两个学生空间的内容


    //定义一个对象,接收读取的结果
    Stu temp;

    //从文件中读取一个学生的信息
    fread(&temp, sizeof(Stu), 1, fp);


    //将读取的数据展示出来
    printf("name:%s, age:%d, score:%.2lf\n", temp.name, temp.age, temp.score);


    //关闭文件
    fclose(fp);

    return 0;
}

补充知识:

1、关于追代码工具ctags的使用

1、安装ctags:sudo yum install ctags
2、进入 /usr/include 目录下,执行sudo ctags -R
3、该目录下会多出一个tags索引文件
4、追相关内容的指令:vi -t 内容
	继续向后追内容:ctrl + ]
	退出追代码工具: 底行模式输入q,回车

2、vscode中关于自定义片段

1、在vscode中使用组合键ctrl+shift+p调出控制面板
2、输入 user snippets 回车
3、输入自定义片段文件名称,打开自定义文件
4、输入以下程序
"Simple C++ Program": {
	"prefix": "main",
	"body": [
		 "#include<iostream>",
		"using namespace std;",
		"int main(int argc, const char *argv[])",
		"{",
		"   $1",
		"   return 0;",
		"}"
	],
	"description": "Simple C++ program template"
 }

3、补充一个头文件中可以包含多个头文件内容

1、创建一个头文件,例如 myhead.h
2、将能用到的所有头文件都放入该文件中
3、将myhead.h文件,移动到根目录下的usr目录中的include目录下
		sudo mv myhead.h /usr/include
4、常用的头文件
	#include<iostream>		//C++标准输出输入流
	#include<string>		//C++字符串类对象
	#include<iomanip>		//C++的格式化输入输出
	#include<stdio.h>		//C语言的输入输出头文件
	#include<math.h>		//C语言的数学函数头文件
	#include<string.h>		//C语言字符串处理头文件
	#include<stdlib.h>		//C语言库头文件

三、文件IO

​ 就是通过系统调用(内核提供的函数)实现,只要使用了文件IO接口,进程就会从用户空间向内核空间进行一次切换。标准IO的实现中,也是在内部调用了文件IO操作,该操作效率较低,因为没有缓冲区的概念。文件IO常用的概念:open、close、read、write、lseek

3.1 文件描述符

1> 文件描述符的本质是一个大于等于0的整数,在使用fopen函数打开文件时,就会产生一个用于操作文件的句柄,这就是文件描述符

2> 在一个进程中,能够打开的文件描述符是有限制的,一般是1024个,[0, 1023],可以通过指令ulimit -a查看,如果要更改这个限制,可以通过指令 ulimit -n 数字,进行更改

3> 文件描述符的使用原则一般是最小未分配原则(如果已经用了0,1,2,5,6,7,8,那么下次分配的就应该是3)

4> 特殊的文件描述符:0、1、2,这三个文件描述符在一个进程启动时就默认打开了,分别表示标准输入、标准输出、标准出错

#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
    //分别输出标准输入、标准输出、标准出错文件指针对应的文件描述符
    printf("stdin->_fileno = %d\n", stdin->_fileno);        // 0
    printf("stdout->_fileno = %d\n", stdout->_fileno);      // 1
    printf("stderr->_fileno = %d\n", stderr->_fileno);      // 2

    return 0;
}

效果图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.2 打开文件: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);
	功能:打开或创建一个文件,并返回该文件的文件描述符,返回文件描述符的规则是最小未分配原则
	参数1:要打开的文件路径
	参数2:打开模式
		以下三种方式必须选其一:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)
		以下的打开方式可以有零个或多个,跟上述的方式一起用位或运算符连接到一起
		O_CREAT:用于创建文件,如果文件不存在,则创建文件,如果文件存在,则打开文件,如果flag中包含了该模式,则		 函数的第三个参数必须要加上
		O_APPEND:以追加的形式打开文件,光标定位在结尾
		O_TRUNC:清空文件
		O_EXCL:常跟O_CREAT一起使用,确保本次要创建一个新文件,如果文件已经存在,则open函数报错
		eg:
			"w":O_WRONLY | O_CREAT | O_TRUNC
			"w+":O_RDWR | O_CREAT | O_TRUNC
			"r":O_RDONLY
			"r+":O_RDWR
			"a":O_WRONLY | O_CREAT | O_APPEND
			"a+":O_RDWR | O_CREAT | O_APPEND
	参数3:如果参数2中的flag中有O_CREAT时,表示创建新文件,参数3必须给定,表示新创建的文件的权限
	
		如果当前参数给定了创建的文件权限,最终的结果也不一定就是参数3的值,系统会用你给定的参数3的值,与系			统的umask取反的值进行位与运算后,才是最终创建文件的权限(mode & ~umask)
		
		当前终端的umas的值,可以通过指令umask来查看,一般默认为0022,表示当前进程所在的组中对该文件没有写			权限,其他用户对该文件也没有写权限
		
		当前终端的umask的值是可以更改的,通过指令:umask 数字进行更改,这种方式只对当前终端有效
		普通文件的权限一般为:0644,表示当前用户没有可执行权限,当前组中其他用户和其他组中的用户都只有读权限
		目录文件的权限一般为:0755,表示当前用户具有可读、可写、可执行权限,当前组中其他用户和其他组中的用户都		 没有可写权限
		
		注意:如果不给权限,那么当前创建的权限会是一个随机值
		
	返回值:成功返回打开文件的文件描述符,失败返回-1并置位错误码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.3 文件关闭:close

       #include <unistd.h>
       int close(int fd);
       功能:关闭文件描述符对应的文件
       参数:文件描述符
       返回值:成功返回0,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
    //1、定义文件描述符,对于文件IO而言,句柄就是文件描述符
    int fd = -1;
    //以只读的形式创建文件,如果文件不存在则创建文件
    //如果创建文件时没有给权限,则该文件的权限是随机权限
    //如果创建文件时,给定了文件的权限,则文件最终的权限是 给定的 mode&~umask
    if((fd = open("./tt.txt", O_WRONLY|O_CREAT, 0644)) == -1)
   {
        perror("open error");
        return -1;
   }
    printf("open success fd = %d\n", fd);          //3,由于0、1、2已经被使用,所以该
数为3
    //关闭文件
    close(fd);         //关闭fd引用的文件
    return 0;
}

3.4 读写操作:read、write

	#include <unistd.h>
	
	ssize_t read(int fd, void *buf, size_t count);
	功能:从fd文件描述符引用的文件中读取count个字符,放入buf对应的容器中
	参数1:已被打开文件对应的文件描述符
	参数2:容器的起始地址
	参数3:要读取的字符个数
	返回值:成功返回读取的字符个数,这个个数可能会小于count的值,失败返回-1并置位错误码
	
	
	ssize_t write(int fd, const void *buf, size_t count);
	功能:将buf容器中的count个数据,写入到fd引用的文件中
	参数1:已被打开文件的文件描述符
	参数2:要写入的数据的起始地址
	参数3:要写入数据的个数
	返回值:成果返回写入的字符个数,这个个数可能小于count,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
    //1、定义文件描述符,对于文件IO而言,句柄就是文件描述符
    int fd = -1;
    //以只读的形式创建文件,如果文件不存在则创建文件
    //如果创建文件时没有给权限,则该文件的权限是随机权限
    //如果创建文件时,给定了文件的权限,则文件最终的全显示给定的 mode & ~umask
    if((fd = open("./tt.txt", O_WRONLY | O_CREAT, 0644)) == -1)
    {
        perror("open error");
        return -1;
    }

    printf("open success fd = %d\n", fd);       // 3,因为0、1、2已经被使用

    //对数据进行读写操作
    char wbuf[128] = "hello world";
    //将上述字符串写入文件中
    write(fd, wbuf, strlen(wbuf));

    //关闭文件
    close(fd);      //关闭fd引用的文件

    //再次以只读的形式打开文件
    if((fd = open("./tt.txt", O_RDONLY | O_CREAT, 0644)) == -1)
    {
        perror("open error");
        return -1;
    }


    printf("open success fd = %d\n", fd);   // 3,因为之前的文件关闭时,3被回收

    //定义接受数据容器
    char rbuf[5] = "";
    int res = read(fd, rbuf, sizeof(rbuf));     //从文件中读取数据放入rbuf中

    write(1, rbuf, res);    //向1号文件描述符中写入数据,之前读取多少,现在写多少

    //关闭文件
    close(fd);

    return 0;
}

3.5 关于光标的操作:lseek

	#include <sys/types.h>
	#include <unistd.h>
	
	off_t lseek(int fd, off_t offset, int whence);
	功能:移动光标的位置,并返回光标现在所在的位置
	参数1:文件描述符
	参数2:偏移量
		>0:表示从指定位置向后偏移n个字节
		<0:表示从指定位置向前偏移n个字节
		=0:表示在指定位置处不偏移
	参数3:偏移的起始位置
		SEEK_SET:文件起始位置
		SEEK_CUR:文件指针当前位置
		SEEK_END:文件结束位置
	返回值:光标现在所在的位置
	注意:lseek = fseek + ftell(类似)
#include<myhead.h>
int main(int argc, const char *argv[])
{
    //1、定义文件描述符,对于文件IO而言,句柄就是文件描述符
    int fd = -1;
    //以读写的形式创建文件,如果文件不存在则创建文件,如果文件存在则清空文件
    //如果创建文件时没有给权限,则该文件的权限是随机权限
    //如果创建文件时,给定了文件的权限,则文件最终的全显示给定的 mode & ~umask
    if((fd = open("./tt.txt", O_RDWR | O_CREAT, 0644)) == -1)
    {
        perror("open error");
        return -1;
    }

    printf("open success fd = %d\n", fd);       // 3,因为0、1、2已经被使用

    //对数据进行读写操作
    char wbuf[128] = "hello world";
    //将上述字符串写入文件中
    write(fd, wbuf, strlen(wbuf));

    //此时文件光标在文件的末尾位置

    //需求是:读取文件中的 world
    //lseek(fd, 6, SEEK_SET);     //从文件开头位置向后偏移6个字节
    lseek(fd, -5, SEEK_END);      //从文件结束位置向前偏移5个字节

    //定义接受数据容器
    char rbuf[5] = "";
    int res = read(fd, rbuf, sizeof(rbuf));     //从文件中读取数据放入rbuf中

    write(1, rbuf, res);    //向1号文件描述符中写入数据,之前读取多少,现在写多少

    //关闭文件
    close(fd);

    return 0;
}

练习:将一个bmp格式的图片信息读取出来,并将该图片的相关内容(颜色)进行更改

如何将windows下的文件,复制到linux系统的Centos下

1、在windows下调出cmd窗口
2、输入指令:
	scp 要拷贝的文件路径 linux下的用户名@ip地址:linux下存放的地址路径
	eg: scp C:\Users\鹏程万里\Desktop\wukong.bmp zpp@192.168.31.88:/home/zpp/IO/day2
#include<myhead.h>
int main(int argc, const char *argv[])
{
    //以读写的形式打开文件
    int fd = -1;
    if((fd = open("./flower.bmp", O_RDWR)) == -1)
    {
        perror("open error");
        return -1;
    }

    //获取文件的大小
    printf("文件大小为: %ld\n", lseek(fd, 0, SEEK_END));        //输出的就是文件的大小
    
    //定义变量存储文件大小
    int pic_size = 0;
    lseek(fd, 2, SEEK_SET);     //将文件从起始位置向后偏移两个字节,跳过文件类型
    read(fd, &pic_size, 4);     //将文件头的3-6字节的内容读取出来
    printf("pic_size = %d\n", pic_size);        //文件的大小

    //将文件光标向后偏移54字节,跳过文件头和信息头
    lseek(fd, 54, SEEK_SET);
    //定义1像素的颜色,颜色规律是蓝绿红,取0-255(程度)
    unsigned char color[3] = {0, 255, 0};       //定义一个绿色
    for(int i = 0; i < 100; i++)                //以像素为单位遍历行数,遍历前100行
    {
        for(int j = 0; j < 684; j++)
        {
            //此时的(i, j)定位的就是一个像素点,是一个三字节为单位的颜色点
            write(fd, color, sizeof(color));        //将当前光标所在位置的像素点变成绿色
        }
    }


    //关闭文件
    close(fd);

    return 0;
}

课后练习:

使用文件IO来完成两个文件的拷贝,可以完成两个图像的拷贝工作
K_SET); //从文件开头位置向后偏移6个字节
lseek(fd, -5, SEEK_END); //从文件结束位置向前偏移5个字节

//定义接受数据容器
char rbuf[5] = "";
int res = read(fd, rbuf, sizeof(rbuf));     //从文件中读取数据放入rbuf中

write(1, rbuf, res);    //向1号文件描述符中写入数据,之前读取多少,现在写多少

//关闭文件
close(fd);

return 0;

}


练习:将一个bmp格式的图片信息读取出来,并将该图片的相关内容(颜色)进行更改

如何将windows下的文件,复制到linux系统的Centos下

1、在windows下调出cmd窗口
2、输入指令:
scp 要拷贝的文件路径 linux下的用户名@ip地址:linux下存放的地址路径
eg: scp C:\Users\鹏程万里\Desktop\wukong.bmp zpp@192.168.31.88:/home/zpp/IO/day2


```c++
#include<myhead.h>
int main(int argc, const char *argv[])
{
    //以读写的形式打开文件
    int fd = -1;
    if((fd = open("./flower.bmp", O_RDWR)) == -1)
    {
        perror("open error");
        return -1;
    }

    //获取文件的大小
    printf("文件大小为: %ld\n", lseek(fd, 0, SEEK_END));        //输出的就是文件的大小
    
    //定义变量存储文件大小
    int pic_size = 0;
    lseek(fd, 2, SEEK_SET);     //将文件从起始位置向后偏移两个字节,跳过文件类型
    read(fd, &pic_size, 4);     //将文件头的3-6字节的内容读取出来
    printf("pic_size = %d\n", pic_size);        //文件的大小

    //将文件光标向后偏移54字节,跳过文件头和信息头
    lseek(fd, 54, SEEK_SET);
    //定义1像素的颜色,颜色规律是蓝绿红,取0-255(程度)
    unsigned char color[3] = {0, 255, 0};       //定义一个绿色
    for(int i = 0; i < 100; i++)                //以像素为单位遍历行数,遍历前100行
    {
        for(int j = 0; j < 684; j++)
        {
            //此时的(i, j)定位的就是一个像素点,是一个三字节为单位的颜色点
            write(fd, color, sizeof(color));        //将当前光标所在位置的像素点变成绿色
        }
    }


    //关闭文件
    close(fd);

    return 0;
}

课后练习:

使用文件IO来完成两个文件的拷贝,可以完成两个图像的拷贝工作

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐