标准IO/文件IO

  • Linux环境软件开发
  • 应用层开发
  • I/O操作 input/output
  • 进程(任务/task)创建与回收(基于操作系统)
  • 多线程编程技术(进程里有多个线程)
  • 进程间的通信技术

文件IO

文件的概念和 类型

概念

一组相关数据的有序集合。

文件类型

  • 常规文件 r
  • 目录文件 d
  • 字符设备文件 c(读取单位一个字节)
  • 块设备文件 b(磁盘扇区512字节读取)
  • 管道文件 p
  • 套接字文件 s
  • 符号链接文件 l

系统调用和库函数(Linux将底层IO屏蔽了,Linux内核实现硬件软件的隔离)。
在这里插入图片描述

如何理解标准IO-流

	标准IO 由ANSI C标准定义,主流操作系统上都实现C库,标准IO通过缓冲机制减少系统调用,实现更高的效率。通过内存进行缓存,减少了系统调用的次数。

流(FILE)的含义

FILE

			标准IO用一个结构体类型来存放打开的文件的相关信息。
			标准IO的所有操作都是围绕FILE来进行。

(stream)
FILE又称为流(stream)
文本流或二进制流

	文本流和二进制流
	Windows
			二进制流:换行符  ‘\n'
			文本流:换行符   ’\r'   '\n'
	linux
			换行符      '\n'

流的缓冲类型

	全缓冲
			当流的缓冲区无数据或无空间时才执行实际I/0操作。
	行缓冲
			当在输入和输出中遇到换行符(’\n')时,进行I/O操作。
			当流和一个终端关联时,典型的行缓冲。
	无缓冲
		 	数据直接写入文件,流不进行缓冲。
	标准I/O预定义3个流,程序运行时自动打开
	标准输入流         0              STDIN_FILENO        stdin  行缓冲
	标准输出流         1             STDOUT_FILENO     stdout  行缓冲
	标准错误流          2             STDERR_FILENO    stderr  无缓冲	
#include<stdio.h>
#define N 100
int main(){
	char a='a';
	if(argc!=2){
		printf("usage:%s Number",argv[0]);
		return 0;
	}
	for(i=0;i<atoi(argv[1]);i++){
		printf("%c",a);
	}
	while(1);
}
gcc -o stdout stdout.c
./stdout 1025
屏幕上并不会打印出,在缓冲区中,输入1025后,屏幕上有输出,说明输出缓冲区大小为1024

标准IO------打开流

FILE *fopen(const char *path,const char *mode);
成功时返回流指针;出错返回NULL

fopen-mode参数

mode描述
“r"或“rb”以只读的方式打开文件,文件必须存在
"r+“或“r+b”以读写方式打开文件,文件必须存在,会覆盖
“w ”或“wb"以只写方式打开文件,若文件存在则文件长度清0。若文件不存在则创建一个新的文件
“a"或”ab”以只写的方式打开文件,若文件不存在创建;向文件写入的数据被追加到文件末尾
”“w+“或”w+b”以读写方式打开文件,其他同“w"一样,清空再写进去
“a"或”a+b"以读写方式打开文件,其他与“a"相同
#include<stdio.h>
int main(int argc,char *argv[])
{
	FILE *fp;
	fp=fopen("1.txt","r");
	printf("fp=%x\n",(int)fp);
}

标准IO–fopen–新建文件权限
fopen()创建的文件访问权限是0666(rw-rw-rw)
linux 系统中umask的设定会影响文件的访问权限,其规则为(0666&~umask)
root用户是022,普通用户是002
用户可以通过umask函数或命令来修改相关设定。
如果希望umask不影响文件访问权限,该如何设定。
标准IO–处理错误信息
extern int errno;
void perror(const char *s);
char *strerror(int errno);
errno 存放错误号,由系统生成;
perror先存放字符串s,再输出错误号对应的错误信息;
strerror根据错误号返回对应的错误信息;
标准IO—关闭流
int fclose(FILE *stream);
fclose()调用成功返回0,失败返回EOF,并设置errno;
流关闭时自动刷新缓冲中的数据并释放缓冲区‘;
当一个程序正常执行时,所有打开的流都会被关闭;
流一旦关闭后就不能执行任何操作;

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main(int argc,char *argv[])
{
	FILE *fp;
	int ret;
	fp=fopen("1.txt","r");
	perror("open");
	printf("%s\n",strerror(errno));
	printf("fp=%x\n",(int)fp);
	ret=fclose(fp);
	perror("close");
	printf("ret=%d\n",ret);
}
文件1.txt 存在才不会出错。

程序中能够打开的文件或流的个数有限制,如何测试?
循环打开流,成功则计数器累加,直到出错为止。
1021+stdin+stdout+stderr=1024

#include <stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
int main(int argc,char *argv[])
{
	FILE *fp;
	int ret;
	char fn[16];
	for(i=0;i<atoi(argv[1]);i++){
		sprintf("fn,%d.txt",i);
		fp=fopen(fn,"w");
		printf("i=%d:%s\n",i,strerror(errno));
		printf("fp=%x\n",(int)fp);
}

按字符输入,按字符输出

	流支持不同的读写方式:
			读写一个字符:fgetc()/fputc() 一次读写一个字符
			读写一行:fgets()和fputs() 一次读写一行
			读写若干个对象:fread()/fwrite()每次读写若干个对象,而每个对象具有相同的长度
	标准I0-按字符输入
			#include<stdio.h>
			int fgetc(FILE *stream);
			int getc(FILE *stream);
			int getchar(void);
			成功时返回读取的字符;若到文件末尾或出错时返回EOF;
			getchar ()等同于fgetc(stdin);
			getc和fgetc区别是一个是宏一个是函数。
		
	按标准字符输出一个字符:
			#include<stdio.h>
			int fputc(int c,FILE *stream);
			int putc(int c,FILE *stream);
			int putchar (int c);
			成功时,返回写入的字符,出错时,返回EOF
			putchar()等同与fputc(c,stdout);
int ch;
ch=fgetc(stdin);
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);
#include<stdio.h>
int main(int argc,char *argv[])
{
	char ch;
	FILE *fp;
	fp=fopen("1.txt","a+");
	if(fp!=NULL){
		ch=(int)fputc('x',fp);
		if(ch=EOF){
			perror("fputc");
			fclose(fp);
			return 0;
		}
		printf("write char %c\n",ch);
		}else{
			perror("open");
		}
}

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

#include<stdio.h>
#include<stdlib.h>
int main(int argc,char *argv[])
{
	if(argc!=3){
		printf("usage :%s srcfile destfile\n",argv[0]);
		return 0;
	}
	FILE *fp1,fp2;
	fp1=fopen(argv[1],"r");
	fi(fp1==NULL){
		perror("fopen");
		return 0;
	}
	fp2=fopen(argv[2],"w");
	if(fp2==NULL){
		perror("fopen");
		return 0;
	}
	char ch;
	while((ch=fgetc(fp1))!=EOF){
		fputc(fp2);
	}
	fclose(fp1);
	fclose(fp2);
}

按行输入,按行输出

标准IO–按行输入
#include<stdio.h>
char *gets(char *s);
char fgets(char *s,int size,FILE *stream);
成功时返回s,到文件末尾或出错时返回NULL
gets不推荐使用,容易造成缓冲区溢出。
遇到’\n’或已输入size-1个字符时返回,总是包含’\0’

标准IO–按行输出
#include<stdio.h>
int puts(const char *s);
int fputs(const char *s,FILE *stream);
成功时返回输出字符的个数,出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加’\n’
fputs将缓冲区s中的字符串输出到stream,不追加’\n’

fgets示例

#define N 6
char buf[N];
fgets(buf,N,stdin);
printf("%s",buf);
//若键盘的输入分别是:abcd<回车>,abcdef<回车>,buf中的内容是什么?
puts("hello world");

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

按指定对象输入,按指定对象输出

标准IO–按对象读写
#include<stdio.h>
size_t fread(void ptr,size_t size,size_t n,FILEfp);
size_t fwrite(void *ptr,size_t size,size_t n,FILE *fp);
成功时返回读写的对象个数,出错时返回EOF
即可以读写文本文件,也可以读写数据文件,效率高。

//fread.c文件
#include<stdio.h>
#include<string.h>
struct student {
	char name[16];
	int no;
	char sex[8];
}
int main()
{
	struct student stu;
	FILE *fp;
	fp=fopen("1.txt","r");
	if(fp){
		ret=fread(&stu,sizeof(stu),1,fp);
		if(ret>0){
			printf("name:%s,no:%d,sex:%s\n",stu.name,stu.no,stu.sex);
		}
}

//fwrite.c文件
#include<stdio.h>
#include<string.h>
struct student{
	char name[16];
	int no;
	char sex[8];
}
int main()
{
	struct student stu;
	int ret;
	FILE *fp;
	strcpy(stu.name,"zhangshan");
	stu.no=1;
	strcpy(str.sex,"male");
	fp=fopen("1.txt","a");
	if(fp){
		ret=fwrite(&stu,sizeof(stu),1,fp);
		if(ret>0){
			printf("write successful\n");
			}
	}

标准IO–格式化输入输出
#include<stdio.h>

  • int fprintf(FILE *stream,const char *format,…);
  • int sprintf(char *str,const char *format…);
  • int printf(const char *format,…);
    成功时返回输出的字符的个数,失败时返回EOF
int year,month,date;
FILE *fp;
char buf[64];
int year=2021;month=4;date=30;
fp=fopen("test.txt","a+");
fprintf(fp,%d-%d-%d\n",year,month,date);
sprintf(buf,%d-%d-%d\n",year,month,date);
  • int fscanf(FILE *stream,const char *format,…);
  • int sscanf(const char *str,const char *format,…);
#include<stdio.h>
int main()
{
	int year,month,date;
	sscanf("2021-4-30","%04d-%02d-%02d\n",&year,&month,&date);
	printf("%d,%d,%d\n",year,month,date);
}

每隔1秒向文件test.txt中写入当前系统时间,格式为:
1,2021-5-12 11:34:3
2,2021-5-12 11:34:4
该程序无限循环,直到按Ctrl+c中断程序
每次执行时,系统时间追加到文件末尾,序号递增。


流的刷新,流的定位

标准IO–刷新流
#include<stdio.h>
int fflush(FILE *fp)
成功返回0,出错时返回EOF
将流缓冲区的数据写入到实际的文件
Linux下只能刷新输出缓冲区,输入缓冲区丢弃

#include<stdio.h>
int main()
{
	int i=0;
	for(i=0;i<100;i++){
		printf("a");
		fflush(stdout);
	}
	whille(1);
}

定位流–ftell/fseek/rewind
#include<stdio.h>
long ftell(FILE *stream);
long fseek(FILE *stream,long offset,int whence);
long rewind(FILE *stream);
ftell()成功时返回流的当前读写位置,出错时返回EOF
fseek()定位一个流,成功时返回0,出错时返回EOF
whence参数:SEEK_SET/SEEK_CUR/SEEK_END

#include<stdio.h>
int main()
{
	char buf[16];
	FILE *fp;
	int st;
	fp=fopen("1.txt","a+");
	if(fp){
		st=ftell(fp);
		printf("st=%d\n",st);
		fputs("abcdf",fp);
		st=ftell(fp);
		printf("st=%d\n",st);
		//fseek(fp,0,SEEK_SET);
		rewind(fp);
		fget(buf,5,fp);
		printf("buf:%s\n",buf);
}		

标准IO–判断流是否出错或结束
#include<stdio.h>
int ferror(FILE *stream)
int feof(FILE *stream)
ferror()返回1表示流出错,否则返回0
feof()返回1表示文件已经到末尾,否则返回0

文件IO

什么是文件I/O
posix(可移植操作系统接口)定义的一组函数不提供缓冲机制,每次读写操作都引起系统调用
核心:文件描述符
访问各种文件
Linux下,标准IO基于文件IO实现。
在这里插入图片描述
文件IO–open
在这里插入图片描述
O_CREAT|O_TRUNC w
O_CREAT|O_APPEND_O_RDWR a+
O_RDONLY|O_EXCL r
O_RDWR|O_OEXCL r+
O_CREAT|O_TRUNC|O_WRONLY w
O_CREAT|O_TRUNC|O_RDWR w+
O_CREAT|O_APPEND|O_WRONLY a
O_CREAT|O_APPEND|O_RDWR a+
文件IO–close
close函数用来关闭一个打开的文件
#include<unistd.h>
int close(int fd);
成功时返回0;出错时返回EOF
程序结束时自动关闭所有打开的文件
文件关闭后,文件描述符不再代表文件。

文件IO–read
read函数用来从文件读取数据:
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t count);
成功时返回实际读取的数据,出错时返回EOF
读到文件末尾时返回0
buf是接受数据的缓冲区
count不应超过buf大小

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
	int fd;
	int n=0;
	char buf[8];
	fd=open("1.txt",O_RDONLY|O_EXCL,0666);
	if(fd){
		while(read(fd,buf,1)>0){
			n++;
		}
		printf("n=%d\n",n);
}

文件I/O–write
write函数用来向文件写入数据:
#include<unistd.h>
ssize_t write(int fd,void *buf,size_t count);
成功时返回实际写入的字节数,出错时返回EOF
buf是发送数据的缓冲区
count不应超过buf大小

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
	int fd;
	char buf[16];
	fd=open("1.txt",O_RDWR|O_APPEND,666);
	if(fd){
		fgets(buf,10,stdin);
		write(fd,buf,10);
		}
}

文件IO–lseek
lseek函数用来定位文件:
#include<unistd.h>
off_t lseek(int fd,off_t offset,intt whence);
成功时返回当前的文件读写位置,出错时返回EOF
参数offset和参数whence同fseek完全一样

读取目录/修改文件访问权限/获取文件属性

访问目录–opendir
#include<dirnet.h>
DIR *opendir(const char *name);
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针,出错返回NULL

readdir 函数用来读取目录流中的内容:
#include<dirnet.h>
strcut dirent *readdir(DIR *dirp);
struct dirnet是用来描述目录流中一个目录项的结构体类型
包含成员char d_name[256]
成功时返回目录流dirp中下一个目录项
出错或到末尾时返回NULL

访问目录–closedir
closedir函数用来关闭一个目录文件:
#include<dirent.h>
int close(DIR *dirp);
成功时返回0,出错时返回EOF;

#include<stdio.h>
#includee<dirnet.h>
int main()
{
	DIR *dir;
	struct dirent *dent;
	dir=open(".");
	if(dir){
		while((dent=readdir(dir))!=NULL){
			printf("%s\t",dent->d_name);
		}
	}
}

修改文件的访问权限–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);

获取文件属性—stat/lstat/fstat
stat/lstat/fstat函数用来获取文件属性:
#include<sys/stat.h>
int stat(const char path,struct statbuf);
int lstat(const char path,struct statbuf);
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 ;最后修改时间

库的概念/静态库/静态库的创建/链接静态库

库是一个二进制文件,包含的代码可被程序调用标准C库,数学库,线程库…
库有源码,可下载后编译;也可以直接安装二进制包/lib /usr/lib

静态库的特点

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

静态库创建

  • 确定库中函数的功能,接口
  • 编写库源码hello.c
#include<stdio.h>
void hello(void){
	printf("hello world\n");
	return ;
}
  • 编译生成目标文件
    $ gcc -c hello.c -Wall
  • 创建静态库hello
    $ ar crs libhello.a hello.o
  • 查看库中符号信息
    $ nm libhello.a
    hello.o:
    0000000 T hello
    U puts

链接静态库

  • 编译应用程序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

共享库/共享库创建/链接共享库/加载共享库

动态库特点:

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

共享库创建

  • 确定库中函数的功能,接口

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

  • 编译生成目标文件
    $ gcc -c -fPIC hello.c bye.c -Wall
    -fPLC:位置无关代码

  • 创建共享库common
    $ gcc -shared -o libcommon.so.1 hello.o bye.o

  • 为共享库文件创建链接文件
    $ 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

如何找到共享库

为了让系统能找到要加载的共享库,有三种方法:
把库拷贝到/usr/lib和/lib目录下
在LD_LIBRARY_PATH环境变量中添加所在路径
添加/etc/ld.so.conf.d/*.conf文件,执行ldconfig刷新。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值