input/output 输入输出,针对于文件的输入输出。
linux中有7种文件类型,b(块设备block)c(字符设备)d(目录文件)-(普通文件)l (链接文件)s (套接字文件)p(管道)
标准IO
概念
定义:在c库中的一组专门用于输入输出的函数
特点:通过缓冲机制减少系统调用次数,提高效率。
围绕“流”进行操作,流用FILE *描述
默认打开3个流,stdin(标准输入),stdout(标准输出),stderr(标准错误)
只能操作普通文件
缓存区:
全缓存:和文件相关
行缓存:和终端相关
不缓存:没有缓存区,标准错误
刷新缓存条件 | |
全缓存 |
|
行缓存 |
|
练习:计算标准输出的缓存区大小。
#include<stdio.h>
int main (int argc, const char *argv[])
{
printf("zise:");//开辟缓冲区
printf("%d\n",stdout->_IO_buf_end-stdout->_IO_buf_base);//标准输出的缓冲区的结束地址-起始地址
return 0;
}
函数接口
打开文件-读写文件-关闭文件-定位操作
1、打开文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:打开文件
参数:path:打开文件的路径
mode:打开方式
r 只读,流被定义在文件开头
r+ 可读可写,流被定义在文件开头
w 只写,文件不存在创建文件,存在则清空文件,流被定义在文件开头
w+ 可读可写,文件不存在创建,存在清空,流被定位到文件开头
a 追加,文件不存在创建,存在追加,流被定义在文件末尾
a+ 可读可写,文件不存在创建,存在追加;第一次读流被定义在文件开头,写始终在文件末尾
返回值:
成功:打开文件的文件流
失败:NULL,并且设置errno
重定向打开文件
FILE * freopen(const char *pathname, const char *mode, FILE* fp)
功能:将指定的文件流重定向到打开的文件中
参数:path:文件路径
mode:打开文件的方式(同fopen)
fp:文件流指针
返回值:成功:返回文件流指针
失败:NULL
练习:将标准输出重定向到当前路径下的out.c文件中,在将标准输出重定向到终端文件中。
#include<stdio.h>
int main(int argc, const char *argv[])
{
printf("hello\n");//输出在终端
freopen("out.c","w+",stdout);//重定向到out.c文件中
printf("world\n");//输出在out.c文件中
freopen("/dev/tty","r+",stdout);//重定向到终端文件中
printf("123\n");//输出在终端
return 0;
}
2、读写文件
每次一个字符的读写
int fgetc(FILE *stream)
功能:从文件中读一个字符
参数:stream:文件流
返回值:
成功:
读到字符的ASCII码
失败或读到文件末尾:EOF//EOF=-1,以下都是
int fputc(int c, FILE * stream)
功能:向文件中写入一个字符
参数:c:要写的字符
stream:文件流
返回值:
成功:写的字符的ASCII
失败:EOF
练习:编程实现cat功能。cat 文件名//a.out 文件名
#include<stdio.h>
int main(int argc, const char *argv[])
{
FILE *fd=fopen(argv[1],"r");//引用函数打开文件
if(NULL==fd)//容错判断
{
perror("fopen err");//打印错误信息
return -1;
}
int ascii;//定义int类型接受一下ASCII码
while((ascii=fgetc(fd))!=-1)//循环打印
fputc(ascii,stdout);
return 0;
}
补充:
int feof(FILE * stream);
功能:判断文件有没有到结尾
返回:到达文件末尾,返回非零值
int ferror(FILE * stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值
每次一串字符的读写
char * fgets(char *s, int size, FILE * stream);
功能:从文件中每次读取一行字符串
参数:s:存放字符串的地址
size:一次读取的字符个数
stream:文件流
返回值:成功:s的地址
失败或读到文件末尾:NULL
特性:每次实际读取的最大字符个数为size-1个,会在末尾自动添加\0
遇到\n结束一次读
int fputs(const char *s, FILE * stream);
功能:向文件中写字符串
参数:s:要写的内容
stream:文件流
返回值:成功:非负整数
失败:EOF
练习:编程实现wc -l命令功能。计算文件行数
#include<stdio.h>
#include<string.h>
int main(int argc, const char *argv[])
{
FILE *fd=fopen(argv[1],"r");//引用函数打开文件
if(NULL==fd)//容错判断
{
perror("fopen err");//打印错误信息
return -1;
}
char buf[32];//用于存放字符串
int num=0;//记录行数
while(fgets(buf,32,fd)!=NULL)//读文件一直到结尾
if(buf[strlen(buf)-1]=='\n')//根据特点(遇到\n结束一次读)判断条件
num++;
printf("%d %s\n",num,argv[1]);
return 0;
}
二进制数据读写
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件流读取多个元素
参数: ptr :用来存放读取元素
size :元素大小 sizeof(数据类型)
nmemb :读取对象的个数
stream :要读取的文件流
返回值:成功:读取对象的个数
读到文件尾: 0
失败: -1
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:按对象写
参数:同上
返回值:成功:写的元素个数
失败 :-1
fread和fwrite函数注意:
1)两个函数的返回值为:读或写的对象数
2)对于二进制数据我们更愿意一次读或写整个结构。
3、关闭文件
int fclose(FILE* stream);
功能:关闭文件
参数:stream:文件流
练习:编程实现“head -n 文件名”的功能
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc, char const *argv[])
{
if(argc!=3)
{
printf("Usage:%s <-n> <file>\n",argv[0]);
return -1;
}
FILE *f=fopen(argv[2],"r");
if(NULL==f)
{
perror("fopen err");
return -1;
}
int n= atoi(&argv[1][1]);//要打印几行
printf("%d\n",n);
int i=0;
char sh[32]={};
while(n!=i&&fgets(sh,32,f)!=NULL)
{
if(sh[strlen(sh)-1]=='\n')
i++;
printf("%s",sh);
fflush(NULL);
}
fclose(f);
return 0;
}
练习:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<time.h>
int main(int argc, const char *argv[])
{
FILE *fd=fopen("test.txt","a+");//引用函数打开文件
if(NULL==fd)//容错判断
{
perror("fopen err");//打印错误信息
return -1;
}
int num=0;
char buf[32];
while(fgets(buf,32,fd)!=NULL)//判断文件要从第几行开始
if(buf[strlen(buf)-1]=='\n')
num++;
time_t t;
struct tm *ti;
while(1)//循环录入
{
t=time(NULL);
ti=localtime(&t);
fprintf(fd,"%d, %d-%d-%d %d:%d:%d\n",++num,ti->tm_year+1900,ti->tm_mon+1,ti->tm_mday,ti->tm_hour,ti->tm_min,ti->tm_sec);
fflush(NULL);//刷新缓存区,发数据刷新到文件中
sleep(1);
}
return 0;
}
4、文件定位操作
void rewind(FILE *stream);
功能:将文件位置指针定位到起始位置
int fseek(FILE *stream, long offset, int whence);
功能:文件的定位操作
参数:stream:文件流
offset:偏移量:正数表示向后文件尾部偏移,负数表示向文件开头偏移
whence:相对位置:
SEEK_SET:相对于文件开头
SEEK_CUR:相对于文件当前位置
SEEK_END:相对于文件末尾
返回值:成功:0
失败:-1
注:当打开文件的方式为a或a+时,fseek不起作用
long ftell(FILE *stream);
功能:获取当前的文件位置
参数:要检测的文件流
返回值:成功:当前的文件位置,出错:-1
fseek(fp, 0, SEEK_SET); ==> rewind(fp);
文件IO
概念
定义:在posix(可移植操作系统接口)中定义的一组用于输入输出的函数
特点:没有缓冲机制,每次操作都会引起系统调用
围绕“文件描述符”进行操作,文件描述符是非负整数,顺序分配
默认打开三个文件描述符:0(标准输入)、1(标准输出)、2(标准错误)
可以操作任意类型文件,除目录文件
函数接口
打开文件-读写文件-关闭文件-定位操作
1、打开文件
int open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
flags:打开文件的方式
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:创建
O_TRUNC:清空
O_APPEND:追加
返回值:成功:文件描述符
失败:-1
注意:
当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限
int open(const char *pathname, int flags, mode_t mode);
创建出来的文件权限为指定权限值&(~umask) //umask为文件权限掩码0002
与标准IO打开方式对应关系
标准IO | 文件IO |
r | O_RDONLY |
r+ | O_RDWR |
w | O_WRONLY|O_CREAT|O_TRUNC,0666(需要指定文件权限) |
w+ | O_RDWR|O_CREAT|O_TRUNC,0666(同上) |
a | O_WRONLY|O_CREAT|O_APPEND,0666(同上) |
a+ | O_RDWR|O_CREAT|O_APPEND,0666(同上) |
2、读写文件
ssize_t read(int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数:fd 文件描述符
buf 存放位置
count 期望的个数
返回值:成功:实际读到的个数
返回-1:表示出错,并设置errno号
返回0:表示读到文件结尾
ssize_t write(int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd 文件描述符
buf 要写的内容
count 期望值
返回值:成功:实际写入数据的个数
失败 : -1
3、关闭文件
int close(int fd);
功能:关闭文件
参数:fd:文件描述符
练习:编程实现cp命令功能。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main(int argc, char const *argv[])
{
int fd=open(argv[1],O_RDONLY);
if(fd<0)
{
perror("open fd err");
return -1;
}
int fb=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fb<0)
{
perror("open fb err");
return -1;
}
char buf[32];
ssize_t s;
while((s=read(fd,buf,32))!=0)
write(fb,buf,s);
close(fd);
close(fb);
return 0;
}
4、定位操作
off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
offset偏移量
正数:向文件结尾位置移动
负数:向文件开始位置
whence 相对位置
SEEK_SET 开始位置
SEEK_CUR 当前位置
SEEK_END 结尾位置
返回值:成功:文件的当前位置
失败:-1
标准IO和文件IO对比
标准IO | 文件IO | |
定义 | c库中定义的一组输入输出函数 | posix中定义的一组输入输出函数 |
特点 | 有缓冲机制 围绕流进行操作 默认打开三个流:stdin/stdout/stderr 只能操作普通文件 | 无缓冲机制 围绕文件描述符操作 默认打开三个文件描述符:0/1/2 除目录外可以操作任意类文件 |
函数 | 打开文件:fopen/freopen 读写文件:fgetc/fputc、fgets/fputs、fread/fwrite 关闭文件:fclose 文件定位:fseek、ftell、rewind | 打开文件:open 读写文件:read/write 关闭文件:close 文件定位:lseek |
文件属性获取
int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:path:文件路径名
buf:保存文件属性信息的结构体
返回值:成功:0
失败:-1
struct stat {
ino_t st_ino; /* inode号 */
mode_t st_mode; /* 文件类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 用户ID */
gid_t st_gid; /* 组ID */
off_t st_size; /* 大小 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态改变时间 */
};
练习: 编程实现“ls -l 文件名”命令的功能
hq@Ubuntu:~/22121/IO/day2$ ls -l 1.c
-rw-rw-r-- 1 hq hq 527 2月 8 11:47 1.c
文件类型和权限 硬链接数 用户名 组名 大小 最后修改时间 文件名
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
int main(int argc, char const *argv[])
{
struct stat s;
if (stat(argv[1], &s) != 0)//容错判断
{
perror("stat err");
return -1;
}
switch (s.st_mode & S_IFMT)//判断文件类型
{
case S_IFSOCK:putchar('s');break;
case S_IFLNK:putchar('l');break;
case S_IFREG:putchar('-');break;
case S_IFBLK:putchar('b');break;
case S_IFDIR:putchar('d');break;
case S_IFCHR:putchar('c');break;
case S_IFIFO:putchar('p');break;
}
char mod[4]="rwx-";//判断文件权限
for(int i=0;i<9;i++)
printf("%c",(s.st_mode&0400>>i)?mod[i%3]:mod[3]);
printf(" %d ", s.st_nlink);//硬链接数
struct passwd *p = getpwuid(s.st_uid);//用户名
printf("%s ", p->pw_name);
struct group *q = getgrgid(s.st_gid);//组名
printf("%s ", q->gr_name);
printf("%ld ", s.st_size);//大小
time_t t = s.st_mtime;//最后修改时间
struct tm *ti = localtime(&t);
printf("%d月 %d %d:%d ", ti->tm_mon + 1, ti->tm_mday, ti->tm_hour, ti->tm_min);
printf("%s\n", argv[1]);//文件名
return 0;
}
目录操作
打开目录-读目录-关闭目录
1、打开目录
DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
失败:NULL
2、读目录
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息
失败或读到目录结尾:NULL
注意:
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
ino_t d_ino; /* 索引节点号*/
off_t d_off; /*在目录文件中的偏移*/
unsigned short d_reclen; /* 文件名长度*/
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名 */
};
3、关闭目录
int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流
练习:实现ls命令功能。不显示隐藏文件
hq@Ubuntu:~/22121/IO/day2$ ls -a
. 11.c a.out liu.c open.c stat.c text.c
.. 1.c head.c lseek.c out.c .stat.c.swp
hq@Ubuntu:~/22121/IO/day2$ ls
11.c 1.c a.out head.c liu.c lseek.c open.c out.c stat.c text.c
ls打开的是当前目录,而且里面没有以“.”开头的隐藏文件
#include<stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
DIR *dir=opendir(".");//打开当前目录
if(NULL==dir)//容错判断
{
perror("opendir err");
return -1;
}
struct dirent *d;
while((d=readdir(dir))!=NULL)//读当前目录
if(d->d_name[0]!='.')//排除以“.”开头的文件
printf("%s\t",d->d_name);
putchar(10);
closedir(dir);//关闭目录
return 0;
}
库
补充:
·源文件:xx.c 包含main函数的.c、包含子函数的.c
头文件:xx.h 函数声明、头文件、结构体/共用体/枚举定义、typedef、define
库文件:如下
库的定义
当使用别人的函数时除了包含头文件以外还要有库
库:就是把一些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用;本质上来说库是一种可执行代码的二进制形式
由于windows和linux的本质不同,因此二者库的二进制是不兼容的
库的分类
静态库和动态库,本质区别是代码被载入时刻不同。
1) 静态库在程序编译时会被连接到目标代码中。
优点:程序运行时将不再需要该静态库;运行时无需加载库,运行速度更快
缺点:静态库中的代码复制到了程序中,因此体积较大;
静态库升级后,程序需要重新编译链接
2) 动态库是在程序运行时才被载入代码中。
优点:程序在执行时加载动态库,代码体积小;
程序升级更简单;
不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差
静态库的制作
1-将源文件编译生成目标文件
gcc -c add.c -o add.o
2-创建静态库用ar命令,它将很多.o转换成.a
ar crs libmyadd.a add.o
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
3-测试使用静态库:
gcc main.c -L. -lmyadd // -L指定库的路径 -l指定库名
执行./a.out
动态库的制作
1-我们用gcc来创建共享库
gcc -fPIC -c hello.c -o hello.o
-fPIC 创建与地址无关的编译程序
gcc -shared -o libmyhello.so hello.o
2-测试动态库使用
gcc main.c -lmyhello
可以正常编译通过,但是运行时报错./a.out: error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件
解决方法(有三种):
(1)把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(终端关闭,环境变量就没在了)
(3) 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/hq/teach/22092/day3/dynamic
补充:
库默认的查找路径:/lib 或 /usr/lib
头文件默认的查找路径:/usr/include
#include <stdio.h> :从系统路径下查找
#include “head.h” :从当前路径下查找
gcc编译选项:
-L路径:指定库的路径
-I路径:(i大写)指定头文件的路径
-l库名:(L小写)指定库文件
当头文件不在当前路径下时,两种解决方法:
1. gcc main.c -I头文件路径
2. 将头文件存放在系统路径下/usr/include