linux高级编程——IO
标准IO:stdio.h
IO也就是输入input和输出output;
I: 键盘是标准输入设备,默认输入就是指键盘 /dev/input;
O: 显示器是标准输出设备,默认输出就是指显示器;
stdio是属于C语言标准库,那么它存在哪呢?
stdio.h存放的路径是:/usr/include/stdio.h路径下;
按照常理来说标准输入的实现是放在stdio.c当中,但是在系统当中这个实现文件并不是以.c的形式提供的,而是将stdio.c的文件编译了一下(链接),产生libc.so文件但是该文件当中是没有main函数的,因为该函数的实现是要被调用的所以该文件当中不会有main函数。该文件的后缀名在编译之后变成了.so这个表示这是一个动态库文件。因为系统认为标准C库当中的printf()和scanf()之类的函数被调用的频次比较高,所以该动态库放在linux操作系统当中放着,当要使用到标准输入和输出的相关函数的时候需要向操作系统发生申请。不把它放在a.out当中,可以节省开销,减少代码的冗余。
了解了标准IO那么我们应该怎么样去操作文件呢?
要想了解文件的读写操作就先要去了解流的概念:
流: FILE*
数据从文件当中流入和流出所体现出来的字节流叫做流。
流的分类:
二进制流: 2001 \n
二进制数据的流
文本流:
ASCII码数据的流 \n \t
FILE 结构定义的对象 FILE * 称之为流对象,也叫文件流指针。
文件操作的步骤:
1、打开文件 FILE;
2、io操作,读写操作;
3、关闭文件;
文件读写的相关函数:
**FILE *fopen(const char pathname, const char mode);
fopen()函数打开名为路径名指向的字符串的文件,并将流与之关联
形参:pathname为文件名,mode为打开文件的方式是读还是写;文件打开的方式又可以分为:
w:如果文件已经存在且文件当中有内容则在该模式下再打开文件是会先清空文件,如果文件不存在则会先创建文件然后再打开,具有读写的权限;
w+:在该模式下打开文件后清空而且带创建功能;
r:该模式下表示再打开文件是只具有读的权限;
r+:在该模式下打开文件后不清空文件的内容,可以读取旧的内容也可以写进新的内容;
a和a+:均有写的权限但是如果文件中有内容会接着旧的内容继续写,如果文件不存在则会创建文件然后打开。
*int fgetc(FILE stream);
fgetc()从流中读取下一个字符,并将其作为无符号char强制转换为int,或在文件末尾返回EOF,或错误。
返回值:在文件末尾或出现错误时,将读取的字符作为无符号char强制转换为int或EOF。
应用举例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1.txt", "r");
if (NULL == fp)
{
printf("fopen error\n");
}
while(1)
{
int ret = fgetc(fp);
if (EOF == ret)
{
break;
}
printf("%c\n", ret);
}
fclose(fp);
return 0;
}
上述程序打开了1.txt文件并读取里面的内容然后打印。因为fgetc一次只能读取一个字符所以采用了循环读取的方式,fgetc()适合读取小文件在读取大文件时效率比较低。
*int fputc(int c, FILE stream);
fputc()函数是能将转换为无符号char的字符c写入流。
返回值:在出错时将写为无符号char的字符转换为int或EOF。
应用举例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("1.txt", "w");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
int ret = fputc('h', fp);
if (-1 == ret)
{
printf("fputs error\n");
return 1;
}
fputc('e', fp);
fputc('l', fp);
fputc('l', fp);
fputc('o', fp);
fclose(fp);
return 0;
}
上述程序是把hello 写入了打开的1.txt文件当中。
**char *fgets(char s, int size, FILE stream);
fgets()从流中最多读取一个小于大小的字符,并将其存储到s指向的缓冲区中在EOF或换行符后停止。如果读取换行符,则将其存储到缓冲区中。终止符’\0’,存储在缓冲区中最后一个字符之后。
形参当中的s为读取缓冲区该缓冲区是本地内存当中的一段空间在读取大文件时通常缓冲区的大小设置在1k到4k之间,读取的过程是将文件的内容先读取到缓冲区然后再将缓冲区的内容读取到指定文件当中。
返回值:读取成功时返回s,出错时返回NULL,或者在没有读取字符的情况下发生文件结束时返回NULL。
应用举例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("/etc/passwd", "r");
if (NULL == fp || NULL == fp2)
{
printf("fopen error\n");
return 1;
}
char buf[1024] = { 0 };
while(1)
{
char * s = fgets(buf, 1024, fp);
if (NULL == s)
{
break;
}
printf("%s\n", s);
}
fclose(fp);
return 0;
}
上述程序把/etc/passwd文件的内容读取后并打印,char buf[1024] = { 0 };这就是把文件中的内容读取到缓冲区这一个过程是通过fgets(buf, 1024, fp);实现的,然后接收并打印返回值。
**int fputs(const char s, FILE stream);
参数:
const char *s表示要写入的内容;
FILE *stream表示要写入的目标流文件;
fputs()将字符串s写入流,而不将’\0’写入。
返回值:成功写入时返回非负数,错误时返回EOF。
应用举例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1.txt", "w");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
char data[] = "hello world!";
int ret = fputs(data, fp);
if (EOF == ret)
{
printf("fputs error\n");
return 1;
}
fclose(fp);
return 0;
}
上述程序通过fputs(data, fp);将data字符数组中的内容将hello world!字符串写进了1.txt文件当中。
通过fgets()和fputs()函数实现拷贝的功能:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("/etc/passwd", "r");
FILE* fp2 = fopen("2.txt", "w");
if (NULL == fp || NULL == fp2)
{
printf("fopen error\n");
return 1;
}
char buf[1024] = { 0 };
while(1)
{
char * s = fgets(buf, 1024, fp);
if (NULL == s)
{
break;
}
int ret = fputs(s, fp2);
if(EOF == ret)
{
printf("fputs error\n");
return 1;
}
}
fclose(fp);
fclose(fp2);
return 0;
}
上述程序把/etc/passwd文件当中的内容读取到缓冲区然后将缓冲区的内容写入2.txt文件当中,从而实现了拷贝的效果但是在拷贝照片文件时会发生照片的大小被缩小的问题,原因是:fgets()在读取时遇到\n就停止了在照片的二进制文件当中会存在\n所以在读取时不能将文件中的内容完整地读取而导致读取内容所占的字节数缩小。
**size_t fread(void ptr, size_t size, size_t nmemb, FILE stream);
函数fread()从流指向的流中读取nmemb个数据项(nmenb是可以指定的),每个数据项的长度为字节,并将其存储在由ptr给出的位置。
返回值:返回读取或写入的项目数。此数字等于传输的字节数仅当大小为1时才使用。如果发生错误,或者到达文件末尾,则返回值是一个零。
参数:
void *ptr表示读取到的数据存储的地方;
size_t size:表示读取每个项目是所占的字节数;
size_t nmemb:读取个数;
FILE *stream:表示从哪里读数据;
应用举例:
#include <stdio.h>
#include <string.h>
typedef struct
{
char name[50];
int age;
char phone[15];
}PER;
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1.txt", "r");
if (NULL == fp)
{
printf("fopen error\n");
}
PER per;
bzero(&per, sizeof(PER));//在读取前将per变量清0;
size_t ret = fread(&per, sizeof(PER), 1, fp);
printf("name : %s age : %d phone : %s\n", per.name, per.age, per.phone);//如果能正常打印就说明读取成功;
if (ret != 1)
{
printf("fread error\n");
}
fclose(fp);
return 0;
}
size_t fwrite(const void ptr, size_t size, size_t nmemb, FILEstream);
函数fwrite()将nmemb个数据项(每个大小为字节长)写入流指向的流中,并获取它们从ptr给出的位置。
返回值同fread;
简单的应用举例:
#include <stdio.h>
#include <string.h>
typedef struct
{
char name[50];
int age;
char phone[15];
}PER;
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1.txt", "w");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
PER per;
strcpy(per.name, "xuan");
per.age = 18;
strcpy(per.phone, "11223344556677");
size_t ret = fwrite(&per, sizeof(PER), 1, fp);
if (ret != 1)
{
printf("fwrite error\n");
return 1;
}
fclose(fp);
return 0;
}
上述程序通过fwrite(&per, sizeof(PER), 1, fp);就能写入一个per大小的结构体变量per的内容到1.txt间当中。
*int fseek(FILE stream, long offset, int whence);
功能:将stream流文件中的文件指针从whence位置开始偏移offset字节的长度。可以用于文件流指针的定位。
参数:
stream 要移动文件指针的目标文件流对象。
注意:不支持设备文件,一般用于普通文件。
offset 要在文件内偏移的距离,单位字节。如果值为整数,则向文件末尾偏移,如果值为负数,则向文件开头偏移
whence偏移的起始位置,由系统定义的三个宏开始。
SEEK_SET表示从文件的开头位置开始偏移;
SEEK_CUR表示从文件的当前位置开始偏移;
SEEK_END表示从文件的末尾位置开始偏移;
返回值:
成功: 返回 0,失败: 返回-1;
如果从文件的指定位置向后偏移过程中已经超过了文件的当前末尾位置,则会自动以’\0’来填充文件内容,从而形成一种被称为"空洞文件" 的特殊文件。如果在超过文件当前的末尾位置写入数据时就会增大文件的大小。
应用示例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("1fopen.c", "r");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
int ret = fseek(fp, 9, SEEK_SET);
if (-1 == ret)
{
printf("fseek error\n");
return 1;
}
char buf[512] = { 0 };
fgets(buf, sizeof(buf), fp);
printf("%s\n", buf);
fclose(fp);
return 0;
}
上述程序打开1open.c文件,文件打开时流指针默认位置在文件开头,通过fseek(fp, 9, SEEK_SET);使文件流指针从文件开头位置往后偏移九个字节大小,然后从该位置开始读取数据并打印。程序运行结果如图所示:
*long ftell(FILE stream);
功能:获取当前文件流指针的具体位置,一般以文件开头到当前指针的字节数为返回值。
参数:stream 要返回指针距离的文件流对象;
返回值:
成功 获取到的距离长度,单位是字节
失败 -1;
该函数可以配合fseek函数用于求文件的大小。用法如下:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1fopen.c", "r");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
int ret = fseek(fp, 0, SEEK_END);
if (-1 == ret)
{
printf("fseek error\n");
return 1;
}
long size = ftell(fp);
printf("size = %ld\n", size);
fclose(fp);
return 0;
}
上述程序先利用fseek将文件流指针定位到文件的末尾(偏移量为0,参考位置为文件末尾),然后利用ftell的返回值来计算文件的大小,运行结果如下:
通过对比计算出的文件大小和文件的实际大小是一致的输出结果正确。
rewind(fp);
rewind(fp);函数的功能是将文件流指针复位,也就是将文件流指针恢复到文件开头的位置。
其实文件流指针可以使用fssek来替换,只需要将偏移量offset设置为0参考点设置为SEEK_SET就能将文件流指针复位了,只不过rewind(fp);函数的形参个数比较少使用起来稍微方便一点。
用法案例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE * fp = fopen("1fopen.c", "r");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
int ret = fseek(fp, 0, SEEK_END);
if (-1 == ret)
{
printf("fseek error\n");
return 1;
}
long size = ftell(fp);
printf("size = %ld\n", size);
rewind(fp);
char buf[512] = { 0 };
if(fgets(buf, sizeof(buf), fp))
{
printf("%s\n", buf);
}
else
{
printf("end of file\n");
}
fclose(fp);
return 0;
}
上述程序通过fseek和ftell计算文件大小后使用rewind(fp);将文件流指针复位再通过fgets(buf, sizeof(buf), fp)读取文件的内容,输出结果为:
缓冲区的分类:
行缓冲,大小为1k,与终端的操作有关,主要用于人机交互stdout。
行缓冲缓冲区的刷新条件:
1.遇到\n刷新;
2.缓存区满刷新
3.程序正常结束时刷新
4.fflush强制刷新(即使没满足上述三个条件也把缓冲区内容刷新到相应的位置),fflush(stdout);
行缓冲缓冲区的刷新条件验证:
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("hello");
while(1)
sleep(1);
return 0;
}
通过运行结果可以看到程序并没有输出hello,而是a.out在执行时把hello放进了缓冲区while(1) sleep(1);阻塞程序不让程序结束,说明在不满足缓冲区刷新条件时并不会将缓冲区的内容刷新。
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int i = 0;
for(i = 0; i < 1025; ++i)
{
fputc('a', stdout);
}
while(1)
sleep(1);
return 0;
}
通过上述程序可以看到,当把行缓冲区填满时即使程序没有结束也会把缓冲区的内容刷新出来。
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("hello");
fflush(stdout);
while(1)
sleep(1);
return 0;
}
通过上述测试代码可以看到即使没满足缓冲区的刷新条件通过fflush强制刷新也能够把缓冲区的内容刷新出来。
全缓冲,全缓冲区的空间大小为4k,主要用于文件的读写。
对普通文件进行标准IO操作时,建立的缓存一般为全缓存
全缓冲的刷新条件为:
1.缓存区满刷新;
2.程序结束刷新;
3.fflush强制刷新,fflush(fp);
全缓冲刷新条件的验证:
1.缓存区满刷新;
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1.txt", "w");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
int i = 0;
for (i = 0; i < 4097; ++i)
{
fputc('a', fp);
}
while(1)
sleep(1);
return 0;
}
a.out执行后程序没有结束,通过查看1.txt文件属性可以知道已经把缓冲区的内容全部写入了文件,即缓冲区满会刷新缓冲区。
2.程序结束刷新;
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("1.txt", "w");
if (NULL == fp)
{
printf("fopen error\n");
return 1;
}
char buf[] = "hello";
fputs(buf, fp);
fflush(fp);
while(1)
sleep(1);
return 0;
}
这里是阻塞等待;
使用fflush强制刷新即使没有满足缓冲区的刷新条件也会把缓冲区的内容刷新到指定文件当中。
无缓冲,缓冲区大小0k 主要用于出错处理信息的输出 stderr,不对数据缓存直接刷新。
为什么程序的报错要无缓冲呢?
假设程序报错时先把错误信息放进缓冲区,如果此时达不到缓冲区刷新的条件就不把错误信息刷新出来直到满足刷新条件才会刷新错误信息,此时已经错过了解决错误的时间点就很难去寻找是哪里报的错,不利于问题的及时解决,综上要设置无缓冲是为了在程序出错时能够将错误信息直接打印。
文件IO
文件io是操作系统为了方便用户使用系统功能而对外提供的一组系统函数。又称之为系统调用。其中有个文件IO一般都是对设备文件操作,当然也可以对普通文件进行操作。文件io是一个基于Linux内核的没有缓存的IO机制。
文件IO的特性:
1 没有缓存区
2 操作对象不在是流,而是文件描述符;
3文件描述符是很小的非负的整数它的范围在:0-1023之间总共1024个;内核每打开一个文件就会获得一个文件描述符
每个程序在启动的时候操作系统默认为其打开三个描述符,这三个描述符与流对象匹配:
0 ==>STDIN_FILENO === stdin
1 ==>STDOUT_FILENO == stdout
2 >STDERR_FILENO == stderr
stdin,stdout,stderr,=>FILE*
文件IO的相关函数:
函数接口
open(“1.c”,O_WRONLY|O_CREAT,0666 );
*int open(const char pathname, int flags,int mode);
功能:
打开一个文件获得一个文件描述符。
参数:
pathname为文件名;
flags:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
O_CREAT, 创建文件,在含有该标志位时必须要规定文件权限,否则mode可以不写。
O_TRUNC打开文件时清空文件内容。
返回值:成功返回文件描述符,失败返回-1。
*ssize_t write(int fd, const void buf, size_t count);
功能:
通过文件描述符向文件中写一串数据;
参数:
fd:文件描述符
buf:要写入文件的字符串的首地址
count:要写入字符的个数
返回值:
成功写入返回实际写入的个数,失败返回-1;
应用举例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = open("1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (-1 == fd)
{
fprintf(stderr, "open error\n");
return 1;
}
printf("fd is %d\n", fd);
char buf[512] = "hello";
int ret = write(fd, buf, strlen(buf));
if (-1 == ret)
{
fprintf(stderr, "write error\n");
return 1;
}
close(fd);
return 0;
}
上述程序打开1.txt文件打开时如果文件不存在就创建同名文件,如果存在且含有内容就清空文件的内容,对该文件具有读写执行的权限,但是在运行a.out之后查看1.txt的权限是0664原因是对于新建的文件或者目录权限会与umask(0002)相减这样做的目的是为了让文件或者目录拥有一个合理的权限。定义要写入的数据buf然后将buf中的数据写入到1.txt文件当中。
*ssize_t read(int fd, void buf, size_t count);
功能:
通过文件描述符读取文件中的数据
参数:
fd:文件描述符
buf:存放数据空间的首地址
count:要读到数据的个数,读取的长度可以比有效的数据个数长;
返回值:
成功读取就返回实际读到数据的个数,失败返回-1或者读到文件结尾返回0;
应用举例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = open("1.txt", O_RDONLY);
if (-1 == fd)
{
fprintf(stderr, "open error\n");
return 0;
}
printf("fd is 3\n");
char buf[512] = { 0 };
while (1)
{
int ret = read(fd, buf, sizeof(buf));
if (ret <= 0)
{
break;
}
printf("%s\n", buf);
}
close(fd);
return 0;
}
读取刚刚向1.txt文件中的数据,读取时要循环读取只读一次可能会导致数据读取不完整,同时加上读取结束判断完成读取时跳出循环。
off_t lseek(int fd, off_t offset, int whence);
功能:
定位光标的位置
参数:
fd:文件描述符
offset:偏移量
正:向后偏移
负:向前偏移
零:不偏移
whence:参考点,参考点共分为三种:
SEEK_SET
SEEK_CUR
SEEK_END
返回值:
偏移成功返回偏移量,失败返回-1;
应用举例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = open("1.txt",O_RDWR);
if(-1 == fd)
{
fprintf(stderr,"open error\n");
return 1;
}
off_t off = lseek(fd,5,SEEK_SET);
printf("off %ld\n",off);
char buf[]="hello";
write(fd,buf,strlen(buf));
printf("fd is %d\n",fd);
close(fd);
return 0;
}
上述程序让光标偏移5之后再向1.txt文件当中写入内容(hello),运行程序查看1.txt可以看到在第一个hello后面写入了另一个hello。