LV.4 文件IO

1.标准IO

1.1标准IO介绍

课程目标

文件的概念和类型(了解)
如何理解标准IO(了解)
流(FILE)的含义(了解)
流的缓冲类型(熟练)

文件基础

  1. 文件:一组有关数据的有序集合
  2. 文件类型
    1. 常规文件r
    2. 目录文件d
    3. 字符设备文件c
    4. 块设备文件b
    5. 管道文件p
    6. 套接字文件s
    7. 符号链接文件l
  3. IO的概念
    I input 输入设备 比如键盘鼠标都是Input设备
    O output 输出设备 比如显示器
    优盘,网口,既是输入也是输出

UNIX基础知识-系统调用和库函数

系统调用和库函数
1. 系统调用:系统调用就是操作系统提供的接口函数
2. 库函数: 如果我们把系统调用封装成库函数就可以起到隔离的作用,提供程序的可移植性。Printf就是库函数然后调用了系统调用才在显示器上显示字符。
介于应用代码和和系统调用之间,实现了解耦,相同库函数可以有不同的系统调用实现。
系统调用和库函数

标准I/O – 介绍

标准I/O由ANSI C标准定义
主流操作系统上都实现了C库
标准I/O通过缓冲机制减少系统调用,实现更高的效率

标准I/O – 流

FILE
标准IO用一个结构体类型来存放打开的文件的相关信息
标准I/O的所有操作都是围绕FILE来进行
流(stream)
FILE又被称为流(stream),就是数据的流,在程序中就是一个结构体。
文本流/二进制流
命令
man 2 name 查系统调用
man 3 name 查库文件调用

标准I/O – 文本流和二进制流

Windows
二进制流: 换行符 ←→ ‘\n’
文本流: 换行符 ←→ ‘\r’ ‘\n’
Linux
换行符 ←→ ‘\n’

标准I/O – 流的缓冲类型

缓冲区的概念

为了减少操作IO设备的次数,提高运行效率,在内存里面设置的缓冲区
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作,缓冲区写满后会自动刷新
行缓冲
当在输入和输出中遇到换行符(‘\n’)时,进行I/O操作
当流和一个终端关联时,典型的行缓冲,行缓冲区大小1024B
无缓冲
数据直接写入文件,流不进行缓冲

缓冲区实验:

#include <stdio.h>
#include <unistd.h>
int main(int argc,char*argv[]){

    int i=0;
    for(i=0;i<1025;i++){
       printf("a");

    }

//    printf("hello world\n");

    while(1){
	sleep(1);
    }

}

Sleep函数:是释放cpu给其他应用程序使用的库函数。使用的头文件是#include <unistd.h>

标准I/O –stdin,stdout,stderr

标准I/O预定义3个流,程序运行时自动打开
在这里插入图片描述

标准I/O – 小结

标准IO
流的缓冲类型
stdin / stdout / stderr
stdin/stdout 默认是行缓冲
stderr没有缓冲
写代码显示缓冲区大小

1.2文件打开和关闭的概念

课程目标

文件的打开(熟练)
文件的关闭(熟练)
小结

标准I/O – 打开文件

  1. 文件的打开:占用资源

  2. 文件的关闭:释放资源

  3. 函数,打开一个标准IO流。

     FILE *fopen(const char *path,const char *mode);	
     path 默认是当前文件,mode 是模式
     成功返回流指针;出错返回NULL
     fopen() 创建的文件访问权限是0666(rw-rw-rw-)
     Linux系统中umask设定会影响文件的访问权限,其规则为(0666 & ~umask),此外真正建文件时的权限会受到umask 值影响,实际权限是mode-umaks 
    

标准I/O – fopen – mode参数

mode参数
在这里插入图片描述

标准I/O – fopen – 示例

  1. 文件打开代码
#include <stdio.h>
int main(int argc,  char *argv[])
{
   FILE *fp;
   if ((fp = fopen(“test.txt”, “r+)) == NULL) {
       printf(“fopen  error\n”);
       return -1;
   }
   ……
   return  0;
 }

标准I/O – fopen – 新建文件权限

fopen() 创建的文件访问权限是0666(rw-rw-rw-)
Linux系统中umask设定会影响文件的访问权限,其规则为(0666 & ~umask)
Root用户是 022 普通用户是002
用户可以通过umask函数或者命令修改相关设定
如果希望umask不影响文件访问权限,该如何设定?

linux目录权限与文件权限的区别
在Linux系统中,目录最大的权限是777,文件最大的权限是666,因为基于安全原因,新建的文件不允许有执行权限,所以从文件的权限位来看,文件比目录少了执行(x)权限。
Linux系统文件的默认权限和特殊权限

标准I/O – 处理错误信息

extern int errno;
void perror(const char *s);
char *strerror(int errno);

errno 存放错误号,由系统生成
perror先输出字符串s,再输出错误号对应的错误信息
strerror根据错误号返回对应的错误信息

void perror(const char*s);
打印错误错误信息
成功则打印成功
一般加在错误的分支
perror先输出字符串s,再输出错误号对应的错误信息
char *strerror(int errno);
头文件<string.h>
errno 参数可以直接写系统变量
strerror(errno);
error 需要引入头文件<errno.h>
errno 存放错误号,由系统生成
strerror根据错误号返回对应的错误信息

标准I/O – 错误信息处理 – 示例1

#include <stdio.h>

int main(int argc,  char *argv[])
{
   FILE *fp;
    
   if ((fp = fopen(“test.txt”, “r+)) == NULL) {
       perror(“fopen”);
       return -1;
   }
//  ……
//fopen: No such file or directory 

标准I/O – 错误信息处理 – 示例2

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc,  char *argv[])
{
   FILE *fp;
   if ((fp = fopen(“test.txt”, “r+)) == NULL) {
       printf(“fopen: %s\n”, strerror(errno));
       return -1;
   }
//	   ……
//  fopen: No such file or directory 

标准I/O – 关闭文件

int fclose(FILE *stream);
fclose()调用成功返回0,失败返回EOF(-1),并设置errno
流关闭时自动刷新缓冲中的数据并释放缓冲区,比如:常规文件把缓冲区内容写入磁盘
当一个程序正常终止时,所有打开的流都会被关闭
fclose()函数的入参stream必须保证为非空,否则出现段错误。

标准I/O – 小结

fopen
fclose

标准I/O – 思考和练习

程序中能够打开的文件或流的个数有限制,如何测试?

思路:循环打开流,成功则计数器累加,直到出错为止
答案:1021 + stdin + stdout + stderr = 1024

代码

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

int main(int argc,char *argv[]){
    FILE *fp;
    int fpret;
    fp = fopen("1.txt","r");
    if(fp==NULL){
        //printf("Open file Failed\n");
        perror("fopen");
        printf("fopen:%s\n",strerror(errno));              

    }else{
	printf("Open file success\n");
       // perror("open");
       fpret = fclose(fp);
       if(fpret==0){
            printf("file close sucess\n");
       }else{
            perror("fclose");
       }
    }  
}

2.标准IO的读写

2.1读写单个字符

课程目标

按字符输入(熟练)
按字符输出(熟练)
小结

标准I/O – 读写流

流支持不同的读写方式:
读写一个字符:fgetc()/fputc()一次读/写一个字符
读写一行:fgets()和fputs()一次读/写一行
读写若干个对象:fread()/fwrite() 每次读/写若干个对象,而每个对象具有相同的长度

标准I/O – 按字符输入

下列函数用来输入一个字符:
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream); //宏
int getchar(void);执行时会阻塞,等待键盘输入
成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1),
getchar()等同于fgetc(stdin)
getc和fgetc区别是一个是宏一个是函数

标准I/O – fgetc – 示例

 int  ch;
  ch = fgetc(stdin);//stdin 是一个系统定义变量,代表标准输入,函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
  printf(%c\n”, ch);

  FILE  *fp;
  int  ch, count = 0;
  if ((fp  =  fopen(argv[1], “r”)) == NULL)  { //打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
    perror(“fopen”);  return  -1;
  }
  while ((ch  =  fgetc(fp)) != EOF) {  
    count++; 
  }
  printf(“total  %d bytes\n”, count);

标准I/O – 按字符输出

下列函数用来输出一个字符:
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

成功时返回写入的字符;出错时返回EOF
putchar©等同于fputc(c, stdout)

注意事项:
1返回和输入参数都是int类型
2遇到这种错误:Bad file descriptor, 很可能是文件打开的模式错误(只读模式去写,只写模式去读)

标准I/O – fputc – 示例

  fputc(‘a’, stdout);
  putchar(‘\n’);

  FILE  *fp;
  int  ch;
  if ((fp  =  fopen(argv[1], “w”)) == NULL)  { 
     perror(“fopen”);  return  -1;
  }
  for(ch = ‘a’; ch <=‘z’; ch++) {  
     fputc(ch, fp); 
  }

标准I/O – 小结

fopen
fgetc
fputc

标准I/O – 思考和练习

如何利用fgetc / fputc实现文件的复制?
1 通过命令行参数传递源文件和目标文件名
2 通过fgetc返回值判断是否读到文件末尾

2.2按行读写文件

课程目标

按行输入(熟练)
按行输出(熟练)
小结

标准I/O – 按行输入

下列函数用来输入一行:
#include <stdio.h>
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);
最多只能读size-1 个有效字符,末位填充’\0’,不满则自动添加换行符。

成功时返回s,到文件末尾或出错时返回NULL
遇到’\n’或已输入size-1个字符时返回,总是包含’\0’

注意事项:
1 gets函数已经被淘汰,因为会导致缓冲区溢出
2 fgets 函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于size-1 后面会添加换行符。

标准I/O – fgets – 示例

  #define  N  6

  char buf[N];
  fgets(buf, N, stdin);
  printf(%s”, buf);

标准I/O – 按行输出

下列函数用来输出字符串:
#include <stdio.h>
int puts(const char *s);
int fputs(const char *s, FILE *stream);

成功时返回非负整数;出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加’\n’
fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’

标准I/O – fputs – 示例

  puts(“hello  world”);
  
  FILE  *fp;
  char buf[] = “hello world”;
  if ((fp  =  fopen(argv[1], “a”)) == NULL)  { 
     perror(“fopen”);  
     return  -1;
  }
  fputs(buf, fp);

注意:输出的字符串中可以包含’\n’,也可以不包含

标准I/O – 思考和练习

如何统计一个文本文件包含多少行?

1 fgetc ?
2 fgets ?

效率低
如何判断读取了一行?

2.3 对象输入输出(二进制读写)

文本文件和二进制的区别:
存储的格式不同:文本文件只能存储文本。

计算机内码概念:文本符号在计算机内部的编码(计算机内部只能存储数字0101001…,所以所有符号都要编码)

标准I/O – 按对象读写

下列函数用来从流中读写若干个对象:
#include <stdio.h>

  1. size_t fread(void *ptr, size_t size, size_t n, FILE *fp);
    ptr指向要读取的数组中首个对象的指针
    size每个对象的大小(单位是字节)
    n要读取的对象个数
    fp输入流

  2. size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
    成功返回读写的对象个数;出错时返回EOF既可以读写文本文件,也可以读写数据文件效率高

注意事项:
文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
解决办法:移动指针(后面讲解)到文件头;关闭文件,重新打开

标准I/O – fread/fwrite – 示例

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

struct student{
   char name[16];
   int age;
   char sex[8];
};

int main(int argc,char *argv[]){
   FILE *fp;
   size_t ret;
   
   struct student stu;
   struct student stu2;

   fp=fopen("1.bin","w");
   if(fp==NULL){
      perror("fopen");
      return 0;
   }
   strcpy(stu.name,"zhangsan");
   stu.age = 49;
   strcpy(stu.sex,"male");
   
   ret = fwrite(&stu,sizeof(stu),1,fp);
   if(ret ==-1){
      perror("fwrite");
      goto end;

   }else{
     printf("write struct student success!\n");
   }
   fclose(fp);

   fp=fopen("1.bin","r");
   if(fp==NULL){
      perror("fopen");
      return 0;
   }
 
   ret = fread(&stu2,sizeof(stu),1,fp);
   if(ret ==-1){
      perror("fread");
      goto end;
   }
  
printf("name=%s,age=%d,sex=%s\n",stu2.name,stu2.age,stu2.sex);

end:
   fclose(fp);

}

fread.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[]){
   FILE *fp;
   char *buff;   
   size_t ret;
   fp=fopen("1.txt","r");
   if(fp==NULL){
      perror("fopen");
      return 0;
   }
   
   buff=(char*)malloc(100);
   if(buff==NULL){
      return 0;

   }

   ret = fread(buff,10,1,fp);
   if(ret==-1){
       perror("fread");
       goto end;
   }
   
   printf("buf=%s\n",buff); 

end:
   free(buff);
   fclose(fp);

}

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

struct student{
   char name[16];
   int age;
   char sex[8];
};

int main(int argc,char *argv[]){
   FILE *fp;
   size_t ret;
   
   struct student stu;
   struct student stu2;

   fp=fopen("1.bin","w");
   if(fp==NULL){
      perror("fopen");
      return 0;
   }
   strcpy(stu.name,"zhangsan");
   stu.age = 49;
   strcpy(stu.sex,"male");
   
   ret = fwrite(&stu,sizeof(stu),1,fp);
   if(ret ==-1){
      perror("fwrite");
      goto end;

   }else{
     printf("write struct student success!\n");
   }
   fclose(fp);

   fp=fopen("1.bin","r");
   if(fp==NULL){
      perror("fopen");
      return 0;
   }
 
   ret = fread(&stu2,sizeof(stu),1,fp);
   if(ret ==-1){
      perror("fread");
      goto end;
   }
  
printf("name=%s,age=%d,sex=%s\n",stu2.name,stu2.age,stu2.sex);

end:
   fclose(fp);

}

标准I/O – 小结

fgetc / fputc
fgets / fputs
fread / fwrite

标准I/O – 思考和练习

如何利用fread / fwrite实现文件的复制?
通过命令行参数传递源文件和目标文件名
通过fread返回值判断是否读到文件末尾
  1. 格式化输入输出
    1. int fprintf(FILE *stream, const char *format, …);
    2. int sprintf(char *str, const char *format, …);
    3. int fscanf(FILE *stream, const char *format, …);
    4. int sscanf(const char *str, const char *format, …);

3.流刷新定位、格式化输入输出

3.1文件流的刷新和定位

课程目标

流的刷新(熟练)
流的定位(熟练)
检测流结束和出错(了解)

标准I/O – 刷新流

#include <stdio.h>
int fflush(FILE *fp);

成功时返回0;出错时返回EOF
将流缓冲区中的数据写入实际的文件
Linux下只能刷新输出缓冲区,输入缓冲区丢弃
如果是输出到屏幕使用fflush(stdout)

定位流 – ftell/fseek/rewind

#include <stdio.h>
long ftell(FILE *stream);
long fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);

ftell() 成功时返回流的当前读写位置,出错时返回EOF
fseek()定位一个流,成功时返回0,出错时返回EOF
whence参数:SEEK_SET/SEEK_CUR/SEEK_END
SEEK_SET 从距文件开头 offset 位移量为新的读写位置

	- SEEK_SET 从距文件开头 offset 位移量为新的读写位置
	- SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
	- SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量

offset参数:偏移量,可正可负
** 打开a模式 fseek无效**

rewind()将流定位到文件开始位置
读写流时,当前读写位置自动后移

示例一(在文件末尾追加字符’t’)

FILE *fp = fopen(“test.txt”, “r+”);
fseek(fp, 0, SEEK_END);
fputc(‘t’, fp);

	  FILE *fp = fopen(“test.txt”, “r+);
	  fseek(fp, 0, SEEK_END);
	  fputc(‘t’, fp);
示例二(获取文件长度,最大小于2G)
FILE  *fp;
if ((fp = fopen(“test.txt”, “r+)) == NULL) {
  perror(“fopen”);
  return  -1;
}
fseek(fp, 0, SEEK_END);
printf(“length is %d\n”, ftell(fp));

标准I/O – 判断流是否出错和结束

#include  <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);

ferror()返回1表示流出错;否则返回0
feof()返回1表示文件已到末尾;否则返回0

标准I/O – 小结

fflush
ftell
fseek

3.2格式化输出和输入

标准I/O – 格式化输出

#include  <stdio.h>
int printf(const char *fmt,);
int fprintf(FILE *stream, const char *fmt,);
int sprintf(char *s, const char *fmt,);

成功时返回输出的字符个数;出错时返回EOF
使用起来很方便,强烈推荐!

标准I/O – 格式化输出 – 示例

//以指定格式 “年-月-日” 分别写入文件和缓冲区
int  year, month, date;
FILE *fp;
char buf[64];
year = 2014; month = 10; date = 26;
fp = fopen(“test.txt”, “a+);
fprintf(fp,%d-%d-%d\n”, year, month, date);
sprintf(buf,%d-%d-%d\n”, year, month, date);

格式化输出,用于解析日志文件及解析字符串

标准I/O – 格式化输入

int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

重点掌握sprintf 和sscanf

标准I/O – 小结

fprintf
sprintf
fscanf
sscanf

标准I/O – 思考和练习

每隔1秒向文件test.txt中写入当前系统时间,格式如下:

1, 2014-10-15 15:16:42
2, 2014-10-15 15:16:43
该程序无限循环,直到按Ctrl-C中断程序
每次执行程序时,系统时间追加到文件末尾,序号递增

1, 2014-10-15 15:16:42
2, 2014-10-15 15:16:43
3, 2014-10-16 11:35:07
4, 2014-10-16 11:35:08

标准I/O – 思考和练习– 提示

time()用来获取系统时间(秒数)
time_t time(time_t *seconds) 1970.1.1 0:0:0
localtime()将系统时间转换成本地时间
struct tm *localtime(const time_t *timer)
struct tm {
   int tm_sec;         /* 秒,范围从 0 到 59                */
   int tm_min;         /* 分,范围从 0 到 59                */
   int tm_hour;        /* 小时,范围从 0 到 23                */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31                    */
   int tm_mon;         /* 月份,范围从 0 到 11                */
   int tm_year;        /* 自 1900 起的年数                */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6                */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365                    */
   int tm_isdst;       /* 夏令时                        */    
};

注意:
   int tm_mon;        获取的值要加1是正确的月份
   int tm_year;        获取的值加1900是正确的年份

获取文件内的所有行数量:
   while(fgets(buf,32,fp)!=NULL){

          if(buf[strlen(buf)-1] =='\n'){  //注意判断是否是一行结束
               linecount++;
          }


}

写完文件记得fflush ,写到磁盘里面去。
标准IO磁盘文件的缓冲区一般为4096
注意和标准输出的全缓冲区别,标准输出是1024

sleep()实现程序睡眠
以何种方式打开流?
流的缓冲类型对文件写入的影响

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

int main(int argc, char *argv[])
{
	time_t tm;
	struct tm *tmm;
	int lc =1;
	char buf[32];


	FILE *fp = fopen("time.txt","a+");

	if(fp ==NULL){
		perror("fopen");
		return 0;
	}


	//get line number 
	while((fgets(buf,32,fp))!= NULL){
		if(buf[strlen(buf)-1]=='\n'){
			lc++;
		}
	}


	//printf time to FILE
	while(1){
		tm = time(NULL);
		printf("time:%d\n",(int)tm);
		tmm = localtime(&tm);
		fprintf(fp,"%03d   %d-%02d-%02d  %02d:%02d:%02d\n",lc,tmm->tm_year+1900,tmm->tm_mon+1,tmm->tm_mday,tmm->tm_hour,tmm->tm_min,tmm->tm_sec);
		fflush(fp);
		lc++;
		sleep(1);
	}

	fclose(fp);

	return 0;
}

时间类型长度32位二进制

4.文件IO

4.1 文件IO 的打开和关闭

课程目标

如何理解文件IO(了解)
文件描述符的含义(了解)

文件I/O-介绍(系统IO、系统调用)

没有缓冲机制
什么是文件I/O?
posix(Portable Operating System Interface ,缩写为 POSIX,可移植操作系统接口)定义的一组函数
不提供缓冲机制,每次读写操作都引起系统调用
核心概念是文件描述符
访问各种类型文件
Linux下, 标准IO基于文件IO实现
在这里插入图片描述

文件io和标准io的区别
1.使用标准不一样,文件IO遵循POSIX(可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ))标准,只能在遵循POSIX标准的类UNIX环境下使用。标准IO遵循ANSI标准(美国国家标准学会(American National Standard Institute,ANSI)),只要重新编译就可以在不同的环境下运行。
2.文件IO属于系统文件只能在linux下进行系统调用,可移植性差。标准IO属于C库(意思是C语言环境),可以在不同的操作系统下移植使用, 主流操作系统上都实现了C库。
3.文件IO使用文件描述符(fd:起的编号),标准IO使用文件流指针(结构体:包含文件的各种数据)。
在Linux系统中,一切设备都看作文件。而每打开一个文件,就有一个代表该打开文件的文件描述符。程序启动时默认打开三个I/O设备文件:标准输入文件stdin,标准输出文件stdout,标准错误输出文件stderr,分别得到文件描述符 0, 1, 2。	
4.文件IO不带缓冲,执行效率低;标准IO带缓冲,执行效率高。
缓冲区是标准IO在文件IO基础上封装出来的一片用于存放数据的地址,一般存放不着急的数据。等缓冲区满或程序员手动刷新这片地址时,系统会调用一次文件IO读走缓冲区中的数据。			
缓冲区分为三类:全缓冲,行缓冲,无缓冲。			
全缓冲一般是对文件的操作,缓冲区大小为4096个字节。行缓冲有两个:标准输入和标准输出。缓冲区大小为1024个字节。无缓冲一般用于标准错误输出,用于比较着急的数据,实际不会进入缓冲区,直接调用文件IO执行。			
手动刷新缓冲区的函数:fflush
头文件:#include <stdio.h>
函数: int fflush(FILE *stream);
功能: 刷新缓冲区,将保存在缓冲区的数据显示到终端上
参数: 目标文件流指针
返回值:成功返回 0 ;失败返回 EOF(-1)			
刷新缓冲区的条件:
全缓冲:缓冲区满;程序结束;手动刷新。
行缓冲:缓冲区满;程序结束;手动刷新;遇到 ‘\n’ ;当标准输入和标准输出一方要使用缓冲区时,正在使用的一方需要让出缓冲区,给需要用的一方使用。
5.文件IO属于系统调用,可以访问不同类型的文件,如普通文件,设备文件(open时不能create),管道文件,套接字文件等;标准IO属于C库只能访问普通文件。

文件I/O-文件描述符

每个打开的文件都对应一个文件描述符。
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。
文件描述符从0开始分配,依次递增。
文件IO操作通过文件描述符来完成。

英文:缩写fd(file descriptor)
是0-1023的数字,表示文件。
0, 1, 2 的含义 标准输入,标准输出,错误

文件I/O-open

open函数用来创建或打开一个文件:
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
成功时返回文件描述符;出错时返回EOF
打开文件时使用两个参数
创建文件时第三个参数指定新文件的权限,(只有在建立新文件时有效)此外真正建文件时的权限会受到umask 值影响,实际权限是mode-umaks
可以打开设备文件,但是不能创建设备文件(创建设备mknode 驱动部分会讲)
在这里插入图片描述

O_NONBLOCK和O_NDELAY所产生的结果都是使I/O变成非搁置模式(non-blocking),在读取不到数据或是写入缓冲区已满会马上return,而不会搁置程序动作,直到有数据或写入完成。
它们的差别在于设立O_NDELAY会使I/O函式马上回传0,但是又衍生出一个问题,因为读取到档案结尾时所回传的也是0,这样无法得知是哪中情况;因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。
不过需要注意的是,在GNU C中O_NDELAY只是为了与BSD的程序兼容,实际上是使用O_NONBLOCK作为宏定义,而且O_NONBLOCK除了在ioctl中使用,还可以在open时设定。

文件IO和标准的模式对应关系

文件IO和标准的模式对应关系

umask :用来设定文件或目录的初始权限,文件和目录的真正初始权限
文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限

文件I/O – open – 示例1

以只写方式打开文件1.txt。如果文件不存在则创建,如果文件存在则清空:

int  fd;

if ((fd  = open("1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
    perror("open");
    return  -1;
}

文件I/O – open – 示例2

以读写方式打开文件1.txt。如果文件不存在则创建,如果文件存在则报错:

int  fd;

if ((fd  = open(1.txt”, O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) {
    if (errno == EEXIST) { 
       perror(“exist error”);
    } else {
       perror(“other error”);
    }
}

文件I/O – close

close函数用来关闭一个打开的文件:

 #include  <unistd.h>
 int  close(int fd);

成功时返回0;出错时返回EOF
程序结束时自动关闭所有打开的文件
文件关闭后,文件描述符不再代表文件

文件I/O – 小结

文件描述符
open
close

容易出错点:
求字符串长度使用sizeof,对二进制数据使用strlen
printf 的字符最后没有’\0’

4.2文件IO的读写定位

文件I/O – read

read函数用来从文件中读取数据:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

成功时返回实际读取的字节数;出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区
count不应超过buf大小(实际字符串练习中发现,buf写满的情况下,实际能读到buf-1个字节的数据,然后会换行,count应比buf大小小一个,留出一个字符串结束字符

文件I/O – read – 示例

  • 从指定的文件(文本文件)中读取内容并统计大小
int  main(int argc, char *argv[]) {
{
    int  fd, n, total = 0;
    char  buf[64];
    if (argc < 2) {
       printf(“Usage : %s <file>\n”, argv[0]);  return -1;
    }
    if ((fd  = open(argv[1], O_RDONLY)) < 0) {
           perror(“open”);  return -1;
    }
    while ((n = read(fd, buf, 64)) > 0) {
        total += n;
    }
}

文件I/O – write

write函数用来向文件写入数据:

 #include  <unistd.h>
 ssize_t  write(int fd, void *buf, size_t count);

成功时返回实际写入的字节数;出错时返回EOF
buf是发送数据的缓冲区
count不应超过buf大小(count 和 buffer 的实际大小必须一致,不够也得填满,否则出现乱码

文件I/O – write – 示例

  • 将键盘输入的内容写入文件,直到输入quit
    int  fd;
    char  buf[20];

    if ((fd  = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
           perror(“open”);  return -1;
    }
    while (fgets(buf, 20, stdin) != NULL) {
        if (strcmp(buf, “quit\n”) == 0) break;
        write(fd, buf, strlen(buf));
    }

文件IO – lseek( 定位)

  • lseek函数用来定位文件:
#include  <unistd.h>
 off_t  lseek(int fd, off_t offset, int whence);

成功时返回当前的文件读写位置;出错时返回EOF
参数offset和参数whence同fseek完全一样
- offset参数:偏移量,可正可负
- 打开a模式 fseek无效
- whence参数:SEEK_SET/SEEK_CUR/SEEK_END
- SEEK_SET 从距文件开头 offset 位移量为新的读写位置
- SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
- SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量

文件I/O – 小结

read
write
lseek

作业

使用文件IO实现“每隔1秒向文件1.txt写入当前系统时间,行号递增”

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#define LEN 27

int main(int argc, char *argv[])
{
	time_t tm;
	struct tm *tmm;
	int lc =1;
	char buf[LEN];
	char buf2[LEN]={0};


	int	fd = open("time.txt",O_RDWR|O_CREAT|O_APPEND, 0666);

	if(fd < 0){
		perror("fopen");
		return -1;
	}


	//get line number 
	//每次读LEN个,读够则行数++
	while((read(fd,buf2,LEN))> 0){
			lc++;
	}

	//printf time to FILE
	while(1){
		tm = time(NULL);
		//输出到控制台
		tmm = localtime(&tm);
		printf("%03d   %d-%02d-%02d  %02d:%02d:%02d\n",lc,tmm->tm_year+1900,tmm->tm_mon+1,tmm->tm_mday,tmm->tm_hour,tmm->tm_min,tmm->tm_sec);
		//输出到缓冲
		sprintf(buf,"%03d   %d-%02d-%02d  %02d:%02d:%02d\n",lc,tmm->tm_year+1900,tmm->tm_mon+1,tmm->tm_mday,tmm->tm_hour,tmm->tm_min,tmm->tm_sec);
		//缓冲输出到文件
		write(fd,buf,27);
		lc++;
		sleep(1);
	}

	close(fd);

	return 0;
}


5.目录操作和库的使用

5.1目录的读取

课程目标

读取目录(熟练)
修改文件访问权限(熟练)
获取文件属性(熟练)

打开目录 – opendir

opendir函数用来打开一个目录文件:
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd); 使用文件描述符,要配合open函数使用
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针;出错时返回NULL

读取目录 – readdir

readdir函数用来读取目录流中的内容:
#include <dirent.h>
struct dirent *readdir(DIR *dirp);

struct dirent是用来描述目录流中一个目录项的结构体类型
包含成员char d_name[256] 参考帮助文档
成功时返回目录流dirp中下一个目录项;
出错或到末尾时时返回NULL

关闭目录 – closedir

closedir函数用来关闭一个目录文件:
#include <dirent.h>
int closedir(DIR *dirp);

成功时返回0;出错时返回EOF

打印指定的目录下所有文件名称

int  main(int argc, char *argv[]) {
{
    DIR *dirp;
    struct dirent *dp;
    if (argc < 2) {
       printf(“Usage : %s <directory>\n”, argv[0]);  return -1;
    }
    if ((dirp  = opendir(argv[1])) == NULL) {
           perror(“opendir”);  return -1;
    }
    while ((dp = readdir(dirp)) != NULL) {
        printf(%s\n”, dp->d_name);
    }
    closedir(dirp);
}

修改文件访问权限 – chmod/fchmod

chmod/fchmod函数用来修改文件的访问权限:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);

成功时返回0;出错时返回EOF
root和文件所有者能修改文件的访问权限
示例: chmod(“test.txt”, 0666);
成功时返回0;出错时返回EOF
注意:在vmware和windows共享的文件夹下,有些权限不能改变。

5.2文件属性获取

获取文件属性 – stat/lstat/fstat

stat/lstat/fstat函数用来获取文件属性:
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);

成功时返回0;出错时返回EOF
如果path是符号链接stat获取的是目标文件的属性;而lstat获取的是链接文件的属性

文件属性 – struct stat

struct stat是存放文件属性的结构体类型:
mode_t st_mode; 类型和访问权限
uid_t st_uid; 所有者id
uid_t st_gid; 用户组id
off_t st_size; 文件大小
time_t st_mtime; 最后修改时间

Stat 结构体

struct stat {
    dev_t         st_dev;       //文件的设备编号
    ino_t         st_ino;       //节点
    mode_t        st_mode;      //文件的类型和存取的权限
    nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1
    uid_t         st_uid;       //用户ID
    gid_t         st_gid;       //组ID
    dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号
    off_t         st_size;      //文件字节数(文件大小)
    unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)
    unsigned long st_blocks;    //块数
    time_t        st_atime;     //最后一次访问时间
    time_t        st_mtime;     //最后一次修改时间
    time_t        st_ctime;     //最后一次改变时间(指属性)
};

文件类型 – st_mode

通过系统提供的宏来判断文件类型:

表示描述
S_IFMT0170000文件类型的位遮罩
S_ISREG(st_mode)0100000是否常规文件
S_ISDIR(st_mode)0040000是否目录
S_ISCHR(st_mode)0020000是否字符设备
S_ISBLK(st_mode)0060000是否块设备
S_ISFIFO(st_mode)0010000是否FIFO文件
S_ISLNK(st_mode)0120000是否链接文件
S_ISSOCK(st_mode)0140000是否SOCKET文件

文件访问权限 – st_mode

通过系统提供的宏来获取文件访问权限:

表示描述权限
S_IRUSR00400bit:8所有者有读权限
S_IWUSR002007所有者拥有写权限
S_IXUSR001006所有者拥有执行权限S_IRGRP
S_IWGRP000204群组拥有写权限
S_IXGRP000103群组拥有执行权限
S_IROTH000042其他用户拥有读权限
S_IWOTH000021其他用户拥有写权限
S_IXOTH000010其他用户拥有执行权限

练习 – 获取并显示文件属性1

以下面格式打印指定文件的主要信息:
$ ./a.out test.c
-rw-r–r-- 317 2014-11-08 test.c
调用lstat函数获取文件的属性
从stat结构体中获取相应信息并输出

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

int main (int argc,char **argv){

   struct stat buf;
   int ret;
   ret = stat("chmod_t.c",&buf);
   if(ret<0){
      perror("stat");
      return 0;

   }
   if(S_ISREG(buf.st_mode)){
       printf("-");
   }
   if(S_ISDIR(buf.st_mode)){
       printf("d");
   }
   if(S_ISCHR(buf.st_mode)){
       printf("c");
   }
   if(S_ISBLK(buf.st_mode)){
       printf("b");
   }
   if(S_ISFIFO(buf.st_mode)){
       printf("p");
   }
   if(S_ISSOCK(buf.st_mode)){
       printf("s");
   }
   
//   printf(" ");
   int i;
   for(i=8;i>=0;i--){
       if(buf.st_mode & (1<<i)){
          switch(i%3){
          case 2:
              printf("r");
              break;
          case 1:
              printf("w");
              break;
          case 0:
              printf("x");
              break;
          }
       }else{
           printf("-");
       }


   }
   
   printf(" %d",(int)buf.st_size);

   struct tm *t;
   t = localtime(&buf.st_ctime);
   printf(" %d-%d-%d %d:%d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min);

   printf(" chmod_t.c\n");
}

练习 – 获取并显示文件属性2

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char *argv[]){

    int fd;
    int ret;
    char buf[32] = "hello world";
    char buf2[32]={0};
    fd = open("test.txt",O_RDWR | O_CREAT|O_APPEND, 0666);
    if(fd<0){
       printf("open file err\n");
       return 0;

    }
    printf("sucess,fd=%d\n",fd);

    ret=write(fd,buf,strlen(buf));
    if(ret<0){
       perror("write");
       goto END;
    }
    printf("write count=%d\n",ret);
    
    lseek(fd,0,SEEK_SET);
     
    ret = read(fd,buf2,32);
    if(ret<0){
        perror("read");
        goto END;
    }
    buf2[31]=0;
    printf("read buf2=%s\n",buf2);

END:
   close(fd);
}


小结

opendir
readdir
closedir
lstat/stat

5.3静态库的使用

课程目标:

库的概念(了解)
静态库(了解)
静态库创建(熟练)
链接静态库(熟练)

库的概念

库是一个二进制文件,包含的代码可被程序调用
标准C库、数学库、线程库……
库有源码,可下载后编译;也可以直接安装二进制包
Linux 库位置:/lib /usr/lib
库是事先编译好的,可以复用的代码。
在OS上运行的程序基本上都要使用库。使用库可以提高开发效率。
Windows和Linux下库文件的格式不兼容
Linux下包含静态库和共享库

静态库的特点

编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已包含代码,运行时不再需要静态库
程序运行时无需加载库,运行速度更快
占用更多磁盘和内存空间
静态库升级后,程序需要重新编译链接

静态库的创建

库名lib开头
缀名是.a

步骤
1.编写库文件代码 vi xxxx.c
2.编译成.o目标文件 gcc -c xxxx.c -Wall
3.创建静态库 ar -rsv libxxxx.a xxxx.o
4.查看库中符号信息 nm libxxxx.a
5.链接静态库 gcc -o test xxxx.c -L. -lxxxx
6.运行:./test
确定库中函数的功能、接口
编写库源码hello.c
#include  <stdio.h>
void  hello(void) {
   printf(“hello  world\n”);
   return;
}
编译生成目标文件

$ gcc -c hello.c -Wall

创建静态库 hello

$ ar -rsv libhello.a hello.o

ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,
里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的member)。
原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。
同时:这个也是很好的可以学习的Makefile文件,阅读源码应该慢慢的养成一种习惯。

ar 参数:
c 禁止在创建库时产生的正常消息
r 如果指定的文件已经存在于库中,则替换它
s 无论 ar 命令是否修改了库内容都强制重新生成库符号表
v 将建立新库的详细的逐个文件的描述写至标准输出
q 将指定的文件添加到库的末尾
t 将库的目录写至标准输出

注意:
1 静态库名字要以lib开头,后缀名为.a
2 没有main函数的.c 文件不能生成可执行文件。

查看库中符号信息
     $nm  libhello.a
     
      hello.o:
      0000000 T  hello
              U  puts
 nm:显示指定文件中的符号信息
   -a 显示所有符号

链接静态库

编写应用程序test.c
#include  <stdio.h>
 void  hello(void);    
 int  main() {
    hello();
    return  0;
 }
编译test.c 并链接静态库libhello.a
$  gcc  -o  test  test.c  -L.  -lhello
$ ./test
hello  world

小结

库-library
静态库

5.4动态库的使用

课程目标

共享库(了解)
共享库创建(熟练)
链接共享库(熟练)
加载共享库(熟练)

命令
ldd 可执行文件名 查看使用动态库名称( ldd:list dynamic dependencies, 列出动态库依赖关系;dll:dynamic link library,动态链接库)
nm 文件名.o 查看目标文件符号表
创建使用步骤
1.编写库文件代码 vi xxx.c ;vi xxxx.c; vi test.c
2.生成位置无关代码的目标文件 gcc -c -fPIC xxx.c xxxx.c
3.生成动态库 gcc -shared -o libcommon.so.1 xxx.o xxxx.o
4.为共享库文件创建链接文件 ln -s libcommon.so.1 libcommon.so
5.编译test.c 并链接共享库 gcc -o test test.c -L. -lcommon
6.将当前路径添加到共享库的加载路径 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
7…/test

共享库的特点

编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
程序不包含库中代码,尺寸小
多个程序可共享同一个库
程序运行时需要加载库
库升级方便,无需重新编译程序
使用更加广泛

共享库的创建

确定库中函数的功能、接口
编写库源码

编写库源码 hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}
编写库源码 bye.c
#include <stdio.h>
void bye(void) {
printf(“bye\n”);
return;
}

编译生成目标文件

$ gcc -c -fPIC hello.c bye.c -Wall

创建共享库 common

$ gcc -shared -o libcommon.so.1 hello.o bye.o

动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号

为共享库文件创建链接文件

$ ln -s libcommon.so.1 libcommon.so

符号链接文件命名规则
lib<库名>.so

链接共享库

编写应用程序test.c

#include  <stdio.h>
#include “common.h”  
int  main() {
   hello();
   bye();
   return  0;
}

编译test.c 并链接共享库libcommon.so

$  gcc  -o  test  test.c  -L.  -lcommon

加载共享库

加载共享库

执行程序
$ ./test
./test: error while loading shared libraries: libcommon.so
出现报错:cannot open shared object file : No such file or directory

添加共享库的加载路径
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
$ ./test
hello world
bye!

如何找到共享库

为了让系统能找到要加载的共享库,有下边几种方法 :

  • 把库拷贝到/usr/lib和/lib目录下

  • 在LD_LIBRARY_PATH环境变量中添加库所在路径

  • export LD_LIBRARY_PATH= (可以填写当前路径或库的绝对路径,只在当前窗口内有效,新开窗口失效)

  • 添加在~/.bashrc 文件里面,使用source ~/.bashrc 生效。
    在这里插入图片描述

  • 添加/etc/ld.so.conf.d/*.conf文件(需要root用户权限),执行ldconfig刷新
    在这里插入图片描述

查看可执行文件使用的动态库:

ldd 命令 : ldd 你的可执行文件
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现这个功能,需要以下几个步骤: 1. 初始化SPIFFS文件系统,读取四个GIF文件到内存中,可以使用SPIFFS库实现。 2. 初始化LVGL库,创建一个显示屏和四个Image对象用于显示GIF图像。 3. 初始化GPIO口,设置1,45,46,48这四个IO口为输入模式,通过GPIO读取输入状态。 4. 在主循环中不断轮询GPIO状态,如果检测到IO口有输入信号,就根据输入信号对应的GIF文件,将GIF图像数据传输给相应的Image对象,通过调用LVGL库的函数,显示GIF图像在屏幕上。 以下是伪代码实现: ```c #include "SPIFFS.h" #include "lvgl.h" #define GIF1_PATH "/gif1.gif" #define GIF2_PATH "/gif2.gif" #define GIF3_PATH "/gif3.gif" #define GIF4_PATH "/gif4.gif" #define GPIO1 1 #define GPIO45 45 #define GPIO46 46 #define GPIO48 48 void setup() { // 初始化SPIFFS文件系统 SPIFFS.begin(); // 读取四个GIF文件到内存中 lv_img_dsc_t *gif1 = lv_img_dsc_load(SPIFFS.open(GIF1_PATH, FILE_READ)); lv_img_dsc_t *gif2 = lv_img_dsc_load(SPIFFS.open(GIF2_PATH, FILE_READ)); lv_img_dsc_t *gif3 = lv_img_dsc_load(SPIFFS.open(GIF3_PATH, FILE_READ)); lv_img_dsc_t *gif4 = lv_img_dsc_load(SPIFFS.open(GIF4_PATH, FILE_READ)); // 初始化LVGL库 lv_init(); // 创建一个显示屏 lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); lv_disp_drv_register(&disp_drv); // 创建四个Image对象用于显示GIF图像 lv_obj_t *img1 = lv_img_create(lv_scr_act(), NULL); lv_obj_t *img2 = lv_img_create(lv_scr_act(), NULL); lv_obj_t *img3 = lv_img_create(lv_scr_act(), NULL); lv_obj_t *img4 = lv_img_create(lv_scr_act(), NULL); // 设置四个Image对象的位置和大小 lv_obj_set_pos(img1, 0, 0); lv_obj_set_size(img1, LV_HOR_RES / 2, LV_VER_RES / 2); lv_obj_set_pos(img2, LV_HOR_RES / 2, 0); lv_obj_set_size(img2, LV_HOR_RES / 2, LV_VER_RES / 2); lv_obj_set_pos(img3, 0, LV_VER_RES / 2); lv_obj_set_size(img3, LV_HOR_RES / 2, LV_VER_RES / 2); lv_obj_set_pos(img4, LV_HOR_RES / 2, LV_VER_RES / 2); lv_obj_set_size(img4, LV_HOR_RES / 2, LV_VER_RES / 2); // 初始化GPIO口 pinMode(GPIO1, INPUT); pinMode(GPIO45, INPUT); pinMode(GPIO46, INPUT); pinMode(GPIO48, INPUT); } void loop() { // 轮询GPIO状态 if (digitalRead(GPIO1) == HIGH) { // 读取GIF1文件数据并显示在img1上 lv_img_set_src(lv_obj_get_child(lv_scr_act(), 0), gif1); } if (digitalRead(GPIO45) == HIGH) { // 读取GIF2文件数据并显示在img2上 lv_img_set_src(lv_obj_get_child(lv_scr_act(), 1), gif2); } if (digitalRead(GPIO46) == HIGH) { // 读取GIF3文件数据并显示在img3上 lv_img_set_src(lv_obj_get_child(lv_scr_act(), 2), gif3); } if (digitalRead(GPIO48) == HIGH) { // 读取GIF4文件数据并显示在img4上 lv_img_set_src(lv_obj_get_child(lv_scr_act(), 3), gif4); } // 延时等待下一次轮询 delay(10); } ``` 上述代码中,lv_img_dsc_load()函数用于从文件中读取GIF图像数据,lv_img_create()函数用于创建Image对象,lv_obj_set_pos()和lv_obj_set_size()函数用于设置Image对象的位置和大小,lv_img_set_src()函数用于设置Image对象的图像数据源。在主循环中,通过digitalRead()函数读取GPIO口的状态,如果检测到有输入信号,就将相应的GIF文件数据传输给Image对象,并通过lv_img_set_src()函数将GIF图像显示在屏幕上。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值