嵌入式养成计划-24-IO进线程----IO,标准IO函数

六十、 IO

下文 下文:文件IO函数

60.1 什么是 IO

  • I : input 输入。 数据从外部存储设备到主存中 。

    • 目的:将辅存中的数据加载到主存中运算执行。
  • O : output 输出 。数据从主存输出到外部存储设备中。

    • 目的:保存数据,防止掉电擦除。
  • IO:本质上就是数据从硬盘到内存,内存到硬盘的流动。
    嵌入式养成计划-24-IO进线程----IO,标准IO函数_第1张图片

60.2 IO函数分类

60.2.1 文件IO函数

  • 文件IO函数是由操作系统提供的基本IO函数,与操作系统绑定,又称之为系统调用函数。
  • Linux与windows的文件IO函数不一样,因为不是一个操作系统。

注意点:

  1. 文件IO函数只有在对应的操作系统上能使用,移植性比较低。
  2. 文件IO涉及到用户空间到内核空间的切换,cpu模式的切换,调用汇编指令等等,是一种耗时的操作。应该尽可能少用文件IO函数,要采用标准IO函数。
  3. 若想要操作内核,只能使用内核提供的对应的系统调用函数。

60.2.2 标准IO函数

  • 标准IO函数根据ANSI标准,对 文件IO函数 进行 二次封装。
    例如:printf , scanf
  • 标准IO函数最终依然会去调用对应的文件IO函数。

举个栗子:

类似于:
scanf()

if(OS == windows)
{
    file_read();      //windows的文件IO函数
}
else if(OS == Linux)
{
    read();         //Linux的文件IO函数;
}

注意:

  1. 在大部分操作系统中标准IO函数均可用,移植性更高
  2. 提高了输入输出的效率
    • 在用户空间中创建一个缓冲区,当缓冲区满或者满足一定条件后,调用文件IO函数,刷新缓冲区。大大减少了对文件IO函数的使用

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第2张图片

60.3 标准IO函数

60.3.1 流和流指针

  • 流:字节流,将数据一个一个的移入或移出缓冲区的形式叫做字节流。
  • 流指针( FILE* ):当要做IO操作的时候,就需要打开一个对应的文件。每打开一个文件,都会在内存中申请一片缓冲区。
    • 管理维护这片缓冲区的变量都存储在FILE结构体中。
    • FILE结构体由操作系统定义好的,我们拿过来用即可。

FILE结构体成员:

1. vi -t

```c
vi -t 命令
	这个命令可以查看系统定义好的 系统定义好的变量、宏、数据类型。
格式:
    vi -t FILE

安装ctags软件后才能使用vi -t
1)sudo apt-get install ctags
2)cd  /usr/include/     
3)sudo ctags -R
4)vim ~/.vimrc中添加 set tags+=/usr/include/tags
```

FILE结构体

```c
1.vi -t FILE
    若有出现选项界面,就输入数字按回车,typedef struct _IO_FILE FILE; 

2.追入struct _IO_FILE
追代码:
    方法1:将光标停留在要追的字符上, 按下ctrl + ]
    方法2:ctrl + 鼠标左键点击要追的内容
返回上层:
    方法1:ctrl + t
    方法2:ctrl + 鼠标右键
```
struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

2. man手册

  • 查函数的原型,功能,返回值

3. 标准流指针

  • 标准流指针是操作系统在main函数启动前,自动为我们申请好的流指针变量。
    FILE* stdin      
    	标准输入(),对应终端文件。即从终端读取数据。
    	scanf getchar gets等函数,默认封装的就是stdin流指针;
    
    FILE* stdout     
    	标准输出(),对应终端文件,行缓冲。即将数据输出到终端。
    	printf putchar puts等函数,默认封装的就是stdout流指针;
    
    FILE* stderr     
    	标准错误输出,对应的终端文件。即将数据输出到终端。无缓冲。 
    	perror,默认封装的就是stderr流指针;
    
    嵌入式养成计划-24-IO进线程----IO,标准IO函数_第3张图片

60.3.2 标准IO函数

fopen     /     fclose         打开/关闭文件
fprintf   /     fscanf         写/读
fputc     /     fgetc
fputs     /     fgets
fwrite    /     fread
fseek                          修改文件偏移(理解为:修改光标位置)
ftell                          获取文件偏移量。

1)fopen

功能:
	打开一个文件;
原型:
	#include 
	FILE *fopen(const char *pathname, const char *mode);
参数:
	char *pathname:指定要打开的文件路径以及名字;
	char *mode:打开方式;
返回值:
	成功,返回FILE*类型指针;
	失败,返回NULL; 更新errno;

mode:

r	以只读的方式打开文件;若要读文件,则从文件开头开始读取;
	若文件不存在,则文件打开失败;      

r+	以读写的方式打开文件,若要读写数据,则从文件开头开始;
	若文件不存在,则文件打开失败; 

w	以写的方式打开文件,若要写入数据,则从文件开头开始;
	若文件不存在,会创建文件;
	若文件存在,则清空文件;
	
w+	以读写的方式打开文件,若要读写数据,则从文件开头开始;
	若文件不存在,会创建文件;
	若文件存在,则清空文件;

a	以写的方式打开文件,
	若文件不存在,会创建文件; 
	若文件存在,若要写入数据,则从文件结尾开始(追加写入);

a+	以读写的方式打开文件,
	若文件不存在,会创建文件;
	若文件存在,则初始位置根据操作来决定:
	读操作:则初始位置在文件开头
	写操作:则初始位置文件结尾;

2)fclose

功能:
	关闭文件,释放内存空间
原型:
	#include 
	int fclose(FILE *stream);
参数;
	FILE *stream:指定要关闭的文件对应的流指针;
返回值:
	成功,返回0;
	失败,返回EOF,更新errno;
	# define EOF (-1)

errno : 错误码。不同的错误,会对应有不同的错误码;

errno 
	错误码,不同的错误,会对应不同的错误码;
errno	
	是包含在stdio.h中的函数的一个返回值;
	若要使用errno,需要包含stdio.h的头文件;

errno中有133种错误。
sudo vim /usr/include/asm-generic/errno-base.h
sudo vim /usr/include/asm-generic/errno.h 

3)perror

功能:
	根据errno打印对应的错误信息;
原型:
	#include 
	void perror(const char *s);
参数:
	char *s:用于提示的字符串;

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第4张图片

4)fprintf

功能:
	将数据格式化输出到指定文件中; ()
原型:
	#include 	
	int fprintf(FILE *stream, const char *format, ...);
参数:
	FILE *stream:指定要将数据写到哪个文件中,就填哪个文件对应的流指针;
	char *format:格式化输出字符串:字符串,转义字符,占位符;
	...:不定参数,不定数据类型,不定数据个数;
返回值:
	成功,返回成功被打印的数据字符数;
	失败,返回负数;    

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第5张图片

5)fscanf

功能:
	从指定文件中格式化获取数据(读);
	fscanf、scanf,使用%s, %d, %f等占位符,默认不获取空格,\n,\t字符
	若想获取上述字符,可以使用%c获取;
原型:
	#include 
	int fscanf(FILE *stream, const char *format, ...);
参数:
	FILE *stream:指定从哪个文件中读取数据,就填哪个文件对应的流指针;
	char *format:格式化输入字符串:字符串,转义字符,占位符;
	...:不定参数,不定数据类型,不定数据个数;
返回值:
	>0, 成功读取到的数据个数;
	=-1,不更新errno,文件读取完毕
	=-1,更新errno,函数运行失败;

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第6张图片

6)fputc

功能:
	向指定文件输出单个字符;()
原型:
	#include 
	int fputc(int c, FILE *stream);
参数:
	int c:指定要输出的字符对应的整型形式,或者字符形式;例如‘a’ 或者97
	FILE *stream:指定要输出到哪个文件中,就文件对应的流指针;
返回值:
	成功,返回成功输出的字符对应的整型形式;
	失败,返回EOF
Linux操作系统:
	当用编辑器打开文件保存退出后,会检测文件结尾是否是\n结尾,
	若不是\n结尾,则会自动添加一个\n

Unix	:\r\n结尾
Linux	:\n结尾
windows	:\r结尾

	\r:回车,将光标移动到该行的起始位置 
	\b:回退,将光标往前移动一格。

7)fgetc

功能:
	从指定文件中读取单个字符;
原型:
	#include 
	int fgetc(FILE *stream);
参数:
	FILE *stream:指定要从哪个文件中获取数据;
返回值:
	成功,返回成功获取到的字符对应的整型形式;
	失败或者文件读取完毕,返回EOF

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第7张图片

缓冲区

  1. 全缓冲
    • 操作对象:手动用fopen打开文件后,创建的缓冲区均为全缓冲。
    • 大小:4096bytes = 4k
    // 计算缓冲区大小
    //当只申请不使用的时候,由于操作系统优化,此时不会真正申请缓冲区
    fputc('a', fp);              
    printf("%ld\n", fp->_IO_buf_end - fp->_IO_buf_base);
    
    • 刷新条件
      1. 缓冲区满(要多写一个字符才能直到缓冲区满,此时会将前4096个刷新,第4097个放在缓冲区)
      2. 用fflush函数强制刷新
        功能:
        	刷新输出流指针;
        原型:
           #include 
           int fflush(FILE *stream);
        
      3. fclose关闭流指针
      4. 主函数调用return退出
      5. 程序调用exit函数退出
        功能:
        	退出进程,只要运行到这个函数,就能让程序结束;
        原型:
        	#include 
        	void exit(int status);
        参数:
        	int status:目前随便填一个整数,01,100,1000
  2. 行缓冲
    • 操作对象:标准输入流指针(FILE* stdin),标准输出流指针(FILE* stdout)
    • 大小:1024bytes = 1k
      printf("%ld\n", stdout->_IO_buf_end - stdout->_IO_buf_base);
      
    • 刷新条件
      1. 缓冲区满(要多写一个字符才能直到缓冲区满,此时会将前1024个刷新,第1025个放在缓冲区)
      2. 用fflush函数强制刷新
        功能:
        	刷新输出流指针;
        原型:
        	#include 
        	int fflush(FILE *stream);
        fflush(stdout);
        
      3. fclose关闭流指针
      4. 主函数调用return退出
      5. 程序调用exit函数退出。
        功能:
        	退出进程,目前大家理解为只要运行到这个函数,就能让程序结束;
        原型:
        	#include 
        	void exit(int status);
        参数:
        	int status:目前随便填一个整数,01,100,1000
      6. 遇到’\n’字符刷新
  3. 无缓冲
    • 操作对象:标准错误输出流指针(FILE* stderr)
      perror调用的就是stderr
    • 大小:0 或 1bytes(这1bytes不需要刷新方式刷新),看情况,实际为0,但打印的计算结果是1

9)fputs

功能:
	将字符串输出到指定文件中; 不会自动换行
原型:
	#include 
	int fputs(const char *s, FILE *stream);	不会自动换行
	int puts(const char *s);  会自动换行   
	char str[20]="hello";     puts(str);
参数:
	char *s:指定要输出的字符串的首地址;
	FILE *stream:指定要将数据输出到哪个文件中,填对应的流指针变量;
返回值:
	成功,返回非负数;
	失败,返回EOF

10)fgets

功能:
	从指定文件中获取字符串,
	1. 该函数当停止获取后,会自动在最后一个字符后面补充'\0',确保获取到的是字符串;
		哪怕越界了也会在后面补充'\0'
	3. 会获取空格,且获取空格后,该函数不会停止。
	4. 会获取\n,且获取\n后,该函数停止读取;
原型:
	#include 
	char *fgets(char *s, int size, FILE *stream);
参数:
	char *s:将获取到的数据存储到该指针指向的内存空间中;
	int size:最多获取size-1个字节; 
	FILE *stream:从哪个文件中获取,填对应的流指针;
返回值:
	成功,返回char*s存储的地址;
	失败或者文件读取完毕,返回NULL,且没有数据被读取出来;

有下列代码,若从终端输入12345后按下回车。请问下列可能出现的结果为:B D_。

char buf[6];
fgets(buf, 7, stdin);
printf("%s\n", buf);
A. 12345         B. 12345\n     C.12345\n乱码        D. 段错误

ps: 该函数当停止获取后,会自动在最后一个字符后面补充'\0',确保获取到的是字符串;越界也会补充

11)fwrite

功能:
	向指定文件中输出数据,(); 会将数据转换成二进制输出输出。
	
	二进制数据:
		将数据拆成一个一个的字节,将对应的字符形式写入到文件中
原型:
	#include 
	size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
	void *ptr:指定要输出的数据首地址,void*:可以输出任意类型数据;
	size_t size:每个数据的字节数
	size_t nmemb:指定要输出的数据个数;	
		注意点:
		真实输出的数据总大小为:size*nmemb
		例如要输出int arr[3]这个数组: size		nmemb
									4			3
									12			1
									1			12
	FILE *stream:指定要将数据输出到哪个文件,就填哪个文件对应的流指针;
返回值:
	成功,返回成功输出的数据个数;
	失败,返回小于指定要输出的数据个数或者等于0

是0的话,则会以^@的形式表示,有些编辑器可能会以NULL形式表示

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第8张图片

12)fread

功能:
	从指定的文件中读取二进制数据,转换成对应的数据类型;(以什么形式写入,就以什么形式读取)
注意:
	fread函数不会自动补充\0字符,若要\0字符,则需要手动添加。
原型:
	#include 
	size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
	void *ptr:指定用于存储读取到的数据的内存空间首地址,void*:可以读取任意类型数据;
	size_t size:每个数据的字节数
	size_t nmemb:指定要读取的数据个数;
		注意点:
		真实读取的数据总大小为:size*nmemb
	FILE *stream:指定要从哪个文件中读取数据,就填哪个文件对应的流指针;
返回值:
	成功,返回成功读取的数据个数;
	失败获取读取到文件结尾,返回小于指定要读取的数据个数或者等于0

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第9张图片

13)fseek

功能:
	修改文件偏移量;
原型:
	#include 
	int fseek(FILE *stream, long offset, int whence);
参数:
	FILE *stream:指定要修改哪个文件的偏移量,填对应的流指针;
	long offset	:距离whence的偏移量,
		若想要往文件开头方向偏移,填负数, 
		若想往文件结尾方向偏移,填正数.
	int whence:
		SEEK_SET	文件开始位置, 
		SEEK_CUR	文件当前位置, 
		SEEK_END	文件结尾位置;
返回值:
	成功,返回0;
	失败,返回-1,更新errno;
将偏移量修改到文件开头:        
	void rewind(FILE *stream); ===>等价于fseek(fp, 0, SEEK_SET);
当偏移量在文件开头的时候,能否继续往前偏移。		不能
当偏移量在文件结尾的时候,能否继续往后偏移。		能

以w或者w+形式打开文件,在文件结尾往后偏移n个字节后,写入数据,此时操作系统会自动给这个n个字节补充0,即补充^@
在这里插入图片描述
以a或者a+形式打开文件,在文件结尾往后偏移n个字节后,写入数据。此时会找到文件最后一个非0数据,在该数据后面写入数据
在这里插入图片描述

14)ftell

功能:
	获取文件当前位置到文件开头的偏移量(字节为单位);
原型:
	#include 
	long ftell(FILE *stream);   
参数:
	FILE *stream:指定要获取哪个文件的偏移量;
返回值:
	文件当前位置到文件开头的偏移量(字节为单位);
	
	//将偏移量修改到文件结尾
	fseek(fp, 0, SEEK_END);
	
	//获取文件结尾位置到文件开头的偏移量,即文件大小
	long size = ftell(fp);
	printf("size = %ld\n", size);

下文连接:下文:文件IO函数

小作业 :

  1. 使用fgetc和fputc实现文件拷贝,例如将1.c的内容拷贝给2.c
手动创建一个usr.txt文件,其中存储用户的账户密码,
一行一个账户密码,中间用空格隔开。
例如:
     zhangsan  aaaa
     lisi  bbbb
     wangwu cccc
需求如下:
从终端获取一个账户,密码。判断该账户密码是否正确
若账户不存在,则输出账户不存在
若账户存在,密码错误,则输出密码错误
若账户密码均正确,则输出登录成功。
计算一个文件的大小
计算一个文件有几行。
封装成函数

我写的:
1.

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    FILE *fp1 = fopen("lalala.txt", "r");
    if (NULL == fp1)
    {
        perror("fopen fp1:");
    }
    FILE *fp2 = fopen("2.txt", "w");
    if (NULL == fp2)
    {
        perror("fopen fp2:");
    }

    char c;
    while (1)
    {
        c = fgetc(fp1);
        if (EOF == c)
        {
            break;
        }
        fputc(c, fp2);
    }

    fclose(fp1);
    fclose(fp2);
    return 0;
}

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第10张图片

#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    /*
    FILE *fp =fopen("usr.txt","w+");
    fprintf(fp,"zhangsan aaaa\n");
    fprintf(fp,"lisi bbbb\n");
    fprintf(fp,"wangwu cccc\n");
    fclose(fp);
    */

    FILE *fp = fopen("usr.txt", "r");
    if (NULL == fp)
        perror("fopen");

    char input_name[64] = "";
    char input_pwd[64] = "";
    printf("请输入用户名:");
    scanf("%s", input_name);
    printf("请输入密码:");
    scanf("%s", input_pwd);

    char name[64] = "", pwd[64] = "", temp[64];

    int flag_name = 1, flag_pwd = 1;

    while (1)
    {
        memset(name, 0, 64);
        memset(pwd, 0, 64);
        //  获取name
        fscanf(fp, "%s", temp);
        strcpy(name, temp);

        //  获取pwd
        memset(temp, 0, 64);
        fscanf(fp, "%s", temp);
        strcpy(pwd, temp);

        //  比较
        if (!strcmp(input_name, name))
        {
            flag_name = 0;
            if (!strcmp(input_pwd, pwd))
            {
                flag_pwd = 0;
                break;
            }
        }
        if (0 == *temp)
            break;
    }
    if (flag_name)
    {
        printf("账户不存在\n");
        return 0;
    }
    else if (flag_pwd)
    {
        printf("密码错误\n");
        return 0;
    }
    printf("登录成功\n");

    fclose(fp);

    return 0;
}

嵌入式养成计划-24-IO进线程----IO,标准IO函数_第11张图片

#include 
#include 
#include 

int get_size(char *s);
int get_line(char *s);

int main(int argc, const char *argv[])
{
    char *s = "usr.txt";
    printf("size = %d\n", get_size(s));
    printf("line = %d\n", get_line(s));

    return 0;
}
int get_size(char *s)
{
    FILE *fp = fopen(s, "r");
    if (NULL == fp)
        perror("fopen");
    char c;
    int count = 0;
    while (1)
    {
        c = fgetc(fp);
        if (EOF == c)
        {
            break;
        }
        ++count;
    }
    fclose(fp);
    return count;
}
int get_line(char *s)
{
    FILE *fp = fopen(s, "r");
    if (NULL == fp)
        perror("fopen");
    char c;
    int count = 0;
    while (1)
    {
        c = fgetc(fp);
        if ('\n' == c)
        {
            ++count;
        }
        else if (EOF == c)
        {
            ++count;
            break;
        }
    }
    fclose(fp);
    return count;
}

你可能感兴趣的:(C/C++,IO进程线程,c++,c语言)