目录
库函数IO接口
fopen
file *fopen(cosnt char *path, const char *mode);
path:要打开的文件路径名称; mode:“r”,“r+”,“w”,“w+”,“a”,“a+”,“b”
r:只读; r+:可读可写;
w:只写,文件不存在则创建新文件; w+:可读可写,不存在则创建新文件;
a:追加只写(总是将数据写入文件末尾(其他则是覆盖写),不存在则创建新文件);a+:读和追加写,打开文件后从起始读,但是写总是在末尾,不存在则创建新文件;
b:对文件数据进行二进制操作
返回值:失败返回NULL;成功返回一个文件流指针FILE*
fwrite
size_t *fwrite(cosnt void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:要写入文件的数据的空间地址;
size:块大小;nmemb:块个数;两个相乘为总体要操作数据大小。
stream:操作句柄–fopen返回的文件流指针;
返回值:实际写入文件的块个数
fread
size_t *fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:一块缓冲区内存空间的地址,用于存放读取的数据
size:块大小;nmemb:块个数;
stream:操作句柄–fopen返回的文件流指针
返回值:成功返回读取到完整块个数;出错返回0;读取到文件末尾返回0,读取的数据不足一块也返回0;
建议:块大小为1,块个数为操作数据长度,否则若读取一块数据50字节,但是实际只读取了40字节的时候也会返回0,通过返回值无法确定是否已经读取到的数据
需要通过其他判断是哪个出错,feof判断读取到文件末尾,ferror判断读取数据是否出错。
fseek
int fseek(FILE *stream, long offset, int whence);
–跳转文件读写位置
stream:操作句柄-打开文件返回的文件流指针
offset:相对起点开始偏移量(偏移量可以是负数–正数向后,负数向前)
whence:相对起点–SEEK_SET-起始位置;SEEK_CUR-当前位置;SEEK_END-末尾位置
返回值:成功返回0;失败返回-1
fclose
int fclose(FILE *fp);
–关闭文件,释放资源
实例
#include <stdio.h>
#include <string.h>
int main()
{
//fopen(lujingming, dakaifangshi)
FILE *fp = fopen("./test.txt", "w+");
if(fp == NULL)
{
perror("fopen error");
return -1;
}
char *ptr = "nihaoya!\n";
//fwrite(dizhi, daxiao, geshu, jubing)
int ret = fwrite(ptr, strlen(ptr), 1, fp);
if(ret == 0)
{
perror("fwrite error");
return -1;
}
printf("ret:%d\n", ret);
char buf[1024] = {0};
ret = fread(buf, 1, 1023, fp);
if(ret == 0)
{
printf("not read data");
}
printf("buf:%s----%d\n", buf, ret);
//fseek(jubing, pianyiliang, xiangduiqidian)
fseek(fp, 0, SEEK_SET);
fclose(fp);
return 0;
}
系统调用IO接口
open
int open(const char *pathname, int flags, mode_t mode);
pathname:文件路径名
flags:标志位
必选其一:O_RDONLY,O_WRONLY,O_RDWR
可选项:
O_CREAT:如果文件不存在则创建
O_EXCL:跟O_CREAT一起使用,如果文件已经存在报错返回
O_TRUNC:打开文件时截断文件长度为0–清空内容
O_APPEND:将写入设置为追加写
例:w+:O_RDWR | O_CREAT | O_TRUNC ----- 可读可写,文件不存在时创建文件,文件存在时清空内容
mode:文件的权限设置,0777;–如果使用了O_CREAT就一定要指定
返回值:成功返回非负整数–操作句柄–文件描述符;失败返回-1
write
size_t write(int fd, const void *buf, size_t count);
fd:open返回的操作句柄,用于明确操作的是哪个打开的文件
buf:要写入文件的数据
count:要写入的数据长度
返回值:成功返回实际写入文件的数据长度;失败返回-1;
read
size_t read(int fd, const void *buf, size_t count);
fd:open返回的操作句柄,用于明确操作的是哪个打开的文件
buf:存放读取到的数据的缓冲区
count:要读取的数据长度
返回值:成功返回实际读取到的数据长度;失败返回-1;
lseek
off_t lseek(int fd, off_t offset, int whence);
fd:open返回的操作句柄
offset:偏移量
whence:相对起始偏移位置-SEEK_SET / SEEK_CUR / SEEK_END
返回值:跳转后的位置,相对于文件起始位置的偏移量,失败返回-1
close
int close(int fd);
实例
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
int main()
{
//jiang dang qian jincheng wenjianquanxianyanma shezhiwei 0
umask(0);
//open(name, flag, mode)
int fd = open("test.txt", O_RDWR|O_CREAT|O_APPEND, 0777);
if(fd < 0)
{
perror("open error");
return -1;
}
char *ptr = "hello\n";
//write(jubing, data, length)
int ret = write(fd, ptr, strlen(ptr));
if(ret < 0)
{
perror("write error");
return -1;
}
lseek(fd, 0, SEEK_SET);
char buf[1024] = {0};
ret = read(fd, buf, 1023);
if(ret < 0)
{
perror("read error");
return -1;
}
printf("%d-%s\n", ret, buf);
close(fd);
return 0;
}
探讨IO操作句柄
库函数的IO接口和系统调用的IO接口–都是用于完成IO的;
文件描述符:系统调用IO接口的操作句柄–非负整数
文件描述符本质:内核中进程打开的文件描述信息表中的下标。
也就是fd,当前进程pcb有一个指针指向文件结构体file_struct,该结构体内有一个数组保存每个文件的描述(地址),每个文件在数组内的下标即为文件描述符fd。当需要进行修改哪个文件时,通过查找fd进而去修改对应的文件。
重定向实现
重定向原理:通过改变文件描述符这个下标位置所保存的文件描述信息,进而改变这个描述符所操作的文件,实现改变数据的流向。
文件描述符分配规则:最小未使用
举例:0:标准输入文件;1:标准输出文件;2:标准错误文件
当close(1)时,则清空数组下标1里面的结构体,即断开文件描述符1与标准输出文件之间的联系。
这时文件描述符1为空,此时打开fopen一个文件,如test.txt,则该文件分配的文件描述符为1;
此时进行printf,则打印出来的信息出现在test.txt文件中,而并非标准输出中。
printf本质上是write(1 …),将信息写入文件描述符1中。
dup2
int dup2(int oldfd, int newfd);
让newfd从oldfd中拷贝文件描述信息地址,把newfd重定向到oldfd对应的文件上
在自己实现的minishell中加入重定向功能:>> / >
功能:将原本要写入标准输出的数据,写入到指定文件中
echo “nihao” >> a.txt
解释:>>:追加重定向,保持文件原内容不变,将新数据追加到文件末尾;>:清空重定向,清空文件原有内容,将新数据写入文件中。
ls -a > test.txt
1.捕捉键盘输入
1.1解析重定向(判断有没有>符号)
1.2将重定向符号之前的信息按照以前的命令处理方式进行处理
1.3取出重定向的新的文件名称
2.解析输入(得到命令名称+参数)
3.创建子进程
3.1在程序替换之前进行标准输出重定向到指定的文件,open,dup2
4.在子进程中进行程序替换
5.父进程进行进程等待
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include<sys/wait.h>
#include<fcntl.h>
int main(int argc, char *argv[])
{
while(1)
{
printf("[user@host path]$ ");
fflush(stdout);
char buf[1024] = {0};
fgets(buf, 1023, stdin);
buf[strlen(buf) - 1] = '\0';
char *str = buf;
int reflag = 0;
char *refile = NULL;
while(*str != '\0')
{
if(*str == '>')
{
reflag++;
*str = '\0';
str++;
if(*str == '>')
{
reflag++;
*str = '\0';
str++;
}
while(*str != '\0' && *str == ' ') str++;
refile = str;
while(*str != '\0' && *str != ' ') str++;
*str = '\0';
}
str++;
}
int myargc = 0;
char *ptr = buf, *myargv[32] = {NULL};
while(*ptr != '\0')
{
if(*ptr != ' ')
{
myargv[myargc] = ptr;
myargc++;
while(*ptr != '\0' && *ptr != ' ')
ptr++;
*ptr = '\0';
}
ptr++;
}
myargv[myargc] = NULL;
for(int i = 0; i < myargc; i++)
{
printf("[%s]\n", myargv[i]);
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork error");
continue;
}
else if(pid == 0)
{
if(reflag == 1)
{
int fd;
fd = open(refile, O_RDWR|O_CREAT|O_TRUNC,0664);
dup2(fd, 1);
}
else if(reflag == 2)
{
int fd;
fd = open(refile, O_RDWR|O_CREAT|O_APPEND,0664);
dup2(fd, 1);
}
execvp(myargv[0], myargv);
perror("execvp error");
exit(-1);
}
wait(NULL);
}
return 0;
}
文件描述符与文件流指针
文件流指针与文件描述符之间的关系
库函数:fopen,fwrite,fread,fseek,fclose,printf,fprintf,fgets…
系统调用接口:open,write,read,lseek,close
文件描述符:int,系统调用IO接口的操作句柄
文件流指针:FILE*,库函数IO接口的操作句柄
库函数与系统调用接口的关系:库函数封装了系统调用接口
通过库函数进行IO操作,最终是会归纳到使用系统调用接口完成IO操作。
库函数句柄:文件流指针
系统调用接口句柄:文件描述符
文件流指针是一个结构体,其中内部有一个成员变量就是文件描述符。当通过库函数操作文件流指针,最终会演变为通过系统调用操作描述符。
库函数IO与系统调用IO的区别:
缓冲区:通常所说的换行刷新缓冲区的该缓冲区,是文件流指针所有的,对于文件描述符或者系统调用来说,是没有的。
动态库与静态库的生成与使用
生成可执行程序的链接方式:
动态链接:链接动态库,只是在生成的程序中记录库中的函数信息表,并没有将具体代码实现写入到程序中。因此运行动态链接生成的程序的时候需要依赖动态库的存在,好处是内存中共享一份。
静态链接:链接静态库,在生成的程序中直接将库中所需函数的实现写入到可执行程序中,生成的程序比较大,但是没有依赖性。
库的生成:库中的函数不能有main函数
1.将源码经过编译汇编之后,解释成为二进制指令
gcc -c -fPIC testlib.c -o testlib.o
其中:-FPIC是产生位置无关代码
2.将编译完成后的二进制指令组织打包成为库文件
动态库:gcc --shared testlib.o … -o libtestlib.so
其中:–share表示生成的是库文件,不包含main函数。最后是生成库的名称,lib是前缀,.so是后缀,中间为库的名字。
静态库:ar -cr libtestlib.a testlib.o …
库的使用:
生成可执行程序时链接使用:使用gcc的 -l 选项指定要链接的库名称:gcc main.c -o main -ltestlib
其中:testlib为库文件名称
但是链接器在链接库文件生成可执行程序的时候,会到指定的一些路径下去找库文件,找到了就链接,找不到就会报错
生成可执行程序时链接使用:
1.库文件放在指定路径下:/usr/lib64
2.设置环境变量:export LIBRARY_PATH=$LIBRARY_PATH:./
其中:./为库所在路径
3.使用gcc的-L选项指定库路径:gcc main.c -o main -L./ -ltestlib
运行可执行程序时加载使用:仅针对动态链接生成的程序。
要生成一个可执行程序时用到了库函数,使用动态库好还是用静态库好?
动态库的优点:共享,代码冗余小,便于模块代码替换一些模块化,便于功能替换的一些接口使用动态库。
静态库的优点:依赖性低,功能改动性小,并且只有当前程序使用的时候使用静态库