C语言IO系列函数


本文是为理解Perl中的一些高级函数(主要为文件IO上),因为它们来自于C语言,有强烈的C背景,总结自互联网,感谢各位网友。
一、open
open是一个非标准的低级文件I/O函数,返回的是文件的低级句柄,原型:
int open(char* path, int access, ...);
open是一个可变参数的函数实现,后面的可变参数通常表示unsigned mode,mode参数是否存在要看access的值,path是文件的路径。
相关的头文件:#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
access和mode的可取值通常在FCNTL.h里面定义,access的可取值如下:
#define O_RDONLY 1
#define O_WRONLY 2
#define O_RDWR 4
access还可以是以下flag及它们之间的组合而得到的性质:
#define O_CREAT 0x0100 /* create and open file */
#define O_TRUNC 0x0200 /* open with truncation */
#define O_EXCL 0x0400 /* exclusive open */
#define O_APPEND 0x0800 /* to end of file */
#define O_CHANGED 0x1000 /* user may read these bits, but */
#define O_DEVICE 0x2000 /* only RTL/io functions may touch. */
#define O_TEXT 0x4000 /* CR-LF translation */
#define O_BINARY 0x8000 /* no translation */
使用O_CREAT的时候,必须指定mode参数,mode的可取值在sys/stat.h里面定义,也可以是它们的组合,如下:
#define S_IREAD 0x0100 /* owner may read */
#define S_IWRITE 0x0080 /* owner may write */
open(s[i],0x0100,0x0080);
意思是以O_CREAT和可写的方式打开s[i]中指出的文件,如果文件不存在,就创建它,返回这个文件的低级句柄。
定义函数:
int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
函数说明:
参数 pathname 指向欲打开的文件路径字符串.下列是参数flags所能使用的旗标:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合.
O_CREAT 若欲打开的文件不存在则自动建立该文件.
O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机.
O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
O_NDELAY 同O_NONBLOCK.
O_SYNC 以同步的方式打开文件.
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。注:此为Linux2. 2 以后特有的旗标,以避免一些系统安全问题.
参数mode则有下列数种组合, 只有在建立新文件时才会生效, 此外真正建文件时的权限会受到umask 值所影响, 因此该文件权限应该为 (mode-umaks).
S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.
S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.
S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.
S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.
S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.
S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.
S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.
S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.
S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.
S_IROTH 00004 权限, 代表其他用户具有可读的权限
S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.
S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.
返回值:若所有欲核查的权限都通过了检查则返回0值,表示成功,只要有一个权限被禁止则返回-1.
错误代码:
EEXIST 参数pathname 所指的文件已存在, 却使用了O_CREAT 和O_EXCL 旗标.
EACCESS 参数pathname 所指的文件不符合所要求测试的权限.
EROFS 欲测试写入权限的文件存在于只读文件系统内.
EFAULT 参数pathname 指针超出可存取内存空间.
EINVAL 参数mode 不正确.
ENAMETOOLONG 参数 pathname 太长.
ENOTDIR 参数pathname 不是目录.
ENOMEM 核心内存不足.
ELOOP 参数pathname 有过多符号连接问题.
EIO I/O 存取错误.
附加说明:使用access()作用户认证方面的判断要特别小心,例如在access()后再作open()空文件可能会造成系统安全上的问题.
函数原型:int open(const char *path, int access,int mode);
作用:以各种方式打开文件
返回值:返回打开的文件句柄,-1打开失败
输入参数说明:path 要打开的文件路径和名称,access 访问模式,宏含义如下:O_RDONLY 1 只读;O_WRONLY 2 只写;O_RDWR 4 读写
还可选择以下模式与以上3种基本模式相与:
O_CREAT 0x0100 创建一个文件并打开
O_TRUNC 0x0200 打开一个存在的文件并将文件长度设置为0,其他属性保持
O_EXCL 0x0400 未使用
O_APPEND 0x0800 追加打开文件
O_TEXT 0x4000 打开文本文件翻译CR-LF控制字符
O_BINARY 0x8000 打开二进制字符,不作CR-LF翻译
mode 该参数仅在access=O_CREAT方式下使用,其取值如下:
S_IFMT 0xF000 文件类型掩码
S_IFDIR 0x4000 目录
S_IFIFO 0x1000 FIFO 专用
S_IFCHR 0x2000 字符专用
S_IFBLK 0x3000 块专用
S_IFREG 0x8000 只为0x0000
S_IREAD 0x0100 可读
S_IWRITE 0x0080 可写
S_IEXEC 0x0040 可执行
二、fopen
fopen函数是在当前目录下打开一个文件,其调用的一般形式为:
文件指针名=fopen(文件名,使用文件方式);
"文件指针名"必须是被声明为FILE 类型的指针变量;
"文件名"是被打开文件的文件名;
"使用文件方式"是指文件的类型和操作要求;
"文件名"是字符串常量或字符数组。
fphzk=fopen("c:\\freeoa.txt","rb");
其意义是打开C驱动器磁盘的根目录下的文件freeoa.txt,这是一个二进制文件,只允许按二进制方式进行读操作。两个反斜线"\\ "中的第一个表示转义字符,第二个表示根目录。使用文件的方式共有12种,下面给出了它们的符号和意义:
"r" = "rt"
打开一个文本文件,文件必须存在,只允许读
"r+" = "rt+"
打开一个文本文件,文件必须存在,允许读写
"rb"
打开一个二进制文件,文件必须存在,只允许读
"rb+"
打开一个二进制文件,文件必须存在,允许读写
"w" = "wt"
新建一个文本文件,已存在的文件将被删除,只允许写
"w+" = "wt+"
新建一个文本文件,已存在的文件将被删除,允许读写
"wb"
新建一个二进制文件,已存在的文件将被删除,只允许写
"wb+"
新建一个二进制文件,已存在的文件将被删除,允许读写
"a" = "at"
打开或新建一个文本文件,只允许在文件末尾追写
"a+" = "at+"
打开或新建一个文本文件,可以读,但只允许在文件末尾追写
"ab"
打开或新建一个二进制文件,只允许在文件末尾追写
"ab+"
打开或新建一个二进制文件,可以读,但只允许在文件末尾追写
对于文件使用方式有以下几点说明:
1) 文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:
r(read): 只读
w(write): 只写
a(append): 追加
t(text): 文本文件,可省略不写
b(binary): 二进制文件
+: 读和写
2) 凡用"r"打开一个文件时,该文件必须已经存在,且只能从该文件读出。
3) 用"w"打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。
4) 若要向一个已存在的文件追加新的信息,只能用"a"方式打开文件。如果指定文件不存在则尝试创建该文件。
5) 在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:
if((fp=fopen("c:\\freeoa.txt","rb"))==NULL){
printf("\nerror on open c:\\freeoa.txt file!");
getch();
exit(1);
}
这段程序的意义是,如果返回的指针为空,表示不能打开C盘根目录下的freeoa.txt文件,则给出提示信息"error on open c:\freeoa.txt file!",下一行getch()的功能是从键盘输入一个字符,但不在屏幕上显示。在这里,该行的作用是等待,只有当用户从键盘敲任一键时,程序才继续执行,因此用户可利用这个等待时间阅读出错提示。敲键后执行exit(1)退出程序。
7) 把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。
8) 标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开的,可直接使用。
fopen()函数:
1.作用:在C语言中fopen()函数用于打开指定路径的文件,获取指向该文件的指针。
2.函数原型:
FILE * fopen(const char * path,const char * mode);
-- path: 文件路径,如:"F:\FreeOA\test.txt"
-- mode: 文件打开方式,例如:
"r" 以只读方式打开文件,该文件必须存在。
"w" 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
"w+" 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
"a" 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
"a+" 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。(原来的EOF符不保留)
"wb" 只写打开或新建一个二进制文件,只允许写数据。
"wb+" 读写打开或建立一个二进制文件,允许读和写。
"ab" 追加打开一个二进制文件,并在文件末尾写数据。
"ab+"读写打开一个二进制文件,允许读,或在文件末追加数据。
-- 返回值: 文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在errno中。
fwrite()函数:
1.作用:在C语言中fwrite()函数常用语将一块内存区域中的数据写入到本地文本。
2.函数原型:
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
-- buffer:指向数据块的指针
-- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4)
-- count:数据个数
-- stream:文件指针
注意:返回值随着调用格式的不同而不同:
(1)调用格式:fwrite(buf,sizeof(buf),1,fp);
成功写入返回值为1(即count)
(2)调用格式:fwrite(buf,1,sizeof(buf),fp);
成功写入则返回实际写入的数据个数(单位为Byte)
3.注意事项:写完数据后要调用fclose()关闭流,不关闭流的情况下,每次读或写数据后,文件指针都会指向下一个待写或者读数据位置的指针。
fread()函数:
1. 作用:从一个文件流中读取数据
2. 函数原型如下:
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
-- buffer:指向数据块的指针
-- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4)
-- count:数据个数
-- stream:文件指针
注意:返回值随着调用格式的不同而不同:
(1)调用格式:fread(buf,sizeof(buf),1,fp);
读取成功时:当读取的数据量正好是sizeof(buf)个Byte时,返回值为1(即count),否则返回值为0(读取数据量小于sizeof(buf))。
(2)调用格式:fread(buf,1,sizeof(buf),fp);
读取成功返回值为实际读回的数据个数(单位为Byte)
三、Linux 系统应用编程之标准I/O
标准I/O的由来:标准I/O指的是ANSI C 中定义的用于I/O操作的一系列函数。只要操作系统安装了C库,标准I/O函数就可以调用。换句话说,如果程序中使用的是标准I/O函数,那么源代码不需要任何修改就可以在其他操作系统下编译运行,具有更好的可移植性。
除此之外,使用标准I/O可以减少系统调用的次数,提高系统效率。标准I/O函数在执行时也会用到系统调用。在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁的执行系统调用会增加系统的开销。为避免这种情况,标准I/O在使用时为用户控件创建缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际的文件,从而减少了使用系统调用的次数。
流的含义
标准I/O的核心对象就是流。当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件或者理解为创建一个FILE结构体和实际打开的文件关联起来。我们把这个FILE结构体形象的称为流,我们在stdio.h里可以看到这个FILE结构体。
typedef struct {
short level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
short bsize; /* Buffer size */
unsigned char *buffer; /* Data transfer buffer */
unsigned char *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
这个结构体:
1).对 fd 进行了封装;
2).对缓存进行了封装 unsigned char *buffer; 这而指向了buffer 的地址,实际这块buffer是cache,我们要将其与用户控件的buffer分开。
标准I/O函数都是基于流的各种操作,标准I/O中的流的缓冲类型有下面三种:
1)、全缓冲
在这种情况下,实际的I/O操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准I/O库提供全缓冲。缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用malloc函数分配得到的。术语flush描述了标准I/O缓冲的写操作。缓冲区可以由标准I/O函数自动flush(例如缓冲区满的时候);或者我们对流调用fflush函数。
2)、行缓冲
在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的I/O操作。这允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的流--例如标注输入(stdin)和标准输出(stdout)--是行缓冲的。
3)、无缓冲
标准I/O库不缓存字符,需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。标准I/O函数时库函数,是对系统调用的封装,所以我们的标准I/O函数其实都是基于文件I/O函数的,是对文件I/O函数的封装,下面具体介绍·标准I/O最常用的函数:
1、流的打开与关闭
使用标准I/O打开文件的函数有fopen()、fdopen()、freopen()。他们可以以不同的模式打开文件,都返回一个指向FILE的指针,该指针指向对应的I/O流。此后,对文件的读写都是通过这个FILE指针来进行。
fopen函数描述如下:
所需头文件 #include <stdio.h>
函数原型 FILE *fopen(const char *path, const char *mode);
函数参数
path: 包含要打开的文件路径及文件名
mode:文件打开方式
函数返回值
成功:指向FILE的指针
失败:NULL
mode用于指定打开文件的方式。
关闭流的函数为fclose(),该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。
fclose()函数描述如下:
所需头文件 #include <stdio.h>
函数原型 int fclose(FILE *stram);
函数参数
stream:已打开的流指针
函数返回值
成功:0
失败:EOF
2、流的读写
1)、按字符字节输入/输出
字符输入/输出函数一次仅读写一个字符。
字符输入函数原型如下:
所需头文件 #include <stdio.h>
函数原型
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar (void);
函数参数
stream:要输入的文件流
函数返回值
成功:读取的字符
失败:EOF
函数getchar等价于get(stdin)。前两个函数的区别在于getc可被实现为宏,而fgetc则不能实现为宏。这意味着:
(1).getc 的参数不应当是具有副作用的表达式;
(2).因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传给另一个参数;
(3).调用fgetc所需时间很可能长于调用getc,因为调用函数通常所需的时间长于调用宏。
这三个函数在返回下一个字符时,会将其unsigned char 类型转换为int类型。说明为什么不带符号的理由是,如果是最高位为1也不会使返回值为负。要求整数返回值的理由是,这样就可以返回所有可能的字符值再加上一个已出错或已达到文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一个负值,其值经常是-1。这就意味着不能将这三个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值与常量EOF相比较。
注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。
#include <stdio.h>
int ferror (FILE *fp);
int feof (FILE *fp);
两个函数返回值;若条件为真则返回非0值真,否则返回0假;在大多数实现中,为每个流在FILE对象中维持了两个标志:
出错标志。
文件结束标志。
字符输出-函数原型如下:
所需头文件 #include <stdio.h>
函数原型
int putc (int c ,FILE *stream);
int fputc (int c, FILE *stream);
int putchar(int c);
函数返回值
成功:输出的字符c
失败:EOF
putc()和fputc()向指定的流输出一个字符节,putchar()向stdout输出一个字符节。
2)、按行输入、输出
行输入/输出函数一次操作一行。
行输入函数原型如下:
所需头文件 #include <stdio.h>
函数原型
char *gets(char *s);
char *fgets(char *s,int size,FILE *stream);
函数参数
s:存放输入字符串的缓冲区首地址;
size:输入的字符串长度
stream:对应的流
函数返回值
成功:s
失败或到达文件末尾:NULL
这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。
gets函数容易造成缓冲区溢出,不推荐使用;
fgets从指定的流中读取一个字符串,当遇到 \n 或读取了 size - 1个字符串后返回。注意,fgets不能保证每次都能读出一行。 如若该行包括最后一个换行符的字符数超过size -1 ,则fgets只返回一个不完整的行,但是,缓冲区总是以null字符结尾。对fgets的下一次调用会继续执行。
行输出函数原型如下:
所需头文件 #include <stdio.h>
函数原型
int puts(const char *s);
int fgets(const char *s,FILE *stream);
函数参数
s:存放输入字符串的缓冲区首地址;
stream:对应的流
函数返回值
成功:非负值
失败或到达文件末尾:NULL
函数fputs将一个以null符终止的字符串写到指定的流,尾端的终止符null不写出。注意,这并不一定是每次输出一行,因为它并不要求在null符之前一定是换行符。通常,在null符之前是一个换行符,但并不要求总是如此。
下面举个例子:模拟文件的复制过程:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define maxsize 5
int main(int argc, char *argv[]) {
FILE *fp1 ,*fp2;
char buffer[maxsize];
char *p,*q;
if(argc < 3){
printf("Usage:%s <srcfile> <desfile>\n",argv[0]);
return -1;
}
if((fp1 = fopen(argv[1],"r")) == NULL){
perror("fopen argv[1] fails");
return -1;
}
if((fp2 = fopen(argv[2],"w+")) == NULL){
perror("fopen argv[2] fails");
return -1;
}
while((p = fgets(buffer,maxsize,fp1)) != NULL){
fputs(buffer,fp2);
}
if(p == NULL) {
if(ferror(fp1))
perror("fgets failed");
if(feof(fp1))
printf("cp over!\n");
}
fclose(fp1);
fclose(fp2);
return 0;
}
执行结果如下:
$ ls -l
total 16
-rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
-rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
-rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
$ ./cp time.c 1.c
cp over!
$ ls -l
total 20
-rw-rw-r-- 1 fs fs 437 Jan 5 21:09 1.c
-rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
-rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
-rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
我们可以看到,这里将time.c拷贝给1.c ,1.c和time.c大小一样,都是437个字节。
3)、以指定大小为单位读写文件
3、流的定位
4、格式化输入输出
这里举个相关应用例子:循环记录系统时间
实验内容:程序每秒一次读取依次系统时间并写入文件
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#define N 64
int main(int argc, char *argv[]){
int n;
char buf[N];
FILE *fp;
time_t t;
if(argc < 2){
printf("Usage : %s <file >\n",argv[0]);
return -1;
}
if((fp = fopen(argv[1],"a+")) == NULL){
perror("open fails");
return -1;
}
while(1) {
time(&t);
fprintf(fp,"%s",ctime(&t));
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}
执行结果如下:
$ ls -l
total 12
-rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
-rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
$ ./time 1.txt
^C
$ ls -l
total 16
-rw-rw-r-- 1 fs fs 175 Jan 5 21:14 1.txt
-rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
-rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
$ cat 1.txt
Tue Jan 5 21:14:11 2016
Tue Jan 5 21:14:12 2016
Tue Jan 5 21:14:13 2016
Tue Jan 5 21:14:14 2016
Tue Jan 5 21:14:15 2016
Tue Jan 5 21:14:16 2016
Tue Jan 5 21:14:17 2016
四、Linux 系统应用编程之标准I/O系列函数说明
打开文件-open函数
头文件包含:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname:为文件的路径,可以带路径描述。
flags:文件打开方式。一会仔细写
mode:当flags满足一定条件就会使用mode位。
mode使用4个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用0666
返回值:
返回值为文件描述符,如果失败则返回-1,文件描述符不可能是负数
open函数flags详解
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
以读写或者只写方式写文件时。是在文件的前面进行写操作,覆盖原有内容
O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为0.如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾
O_CREAT:如果改文件不存在,就创建一个新的文件,并用第三个参数为其设置权限。如果文件按已经存在的话,则删除原来内容。
O_EXCL:如果使用O_CREAT时文件存在,则返回错误消息。这一参数可测试文件是否存在。此时open是原子操作,防止多个进程同时创建同一个文件
如果我CREAT要创建的是一个已经存在的名字的文件,则给我报错,不要去创建。这个效果就要靠O_EXCL标志和O_CREAT标志来结合使用。当这连个标志一起的时候,则没有文件时创建文件,有这个文件时会报错提醒我们。
O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用open()的那个进程的控制终端
O_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
O_SYNC:使每次write都等到物理I/O操作完成。
O_RSYNC:read 等待所有写入同一区域的写操作完成后再进行
在open()函数中,falgs参数可以通过组合构成,但前3个标准常量O_RDONLY,O_WRONLY,和O_RDWR不能互相组合。
O_NONBLOCK
(1)阻塞与非阻塞。如果一个函数是阻塞式的,则我们调用这个函数时当前进程有可能被卡住阻塞住,实质是这个函数内部要完成的事情条件不具备,当前没法做,要等待条件成熟,函数被阻塞住了就不能立刻返回;如果一个函数是非阻塞式的那么我们调用这个函数后一定会立即返回,但是函数有没有完成任务不一定。
(2)阻塞和非阻塞是两种不同的设计思路,并没有好坏。总的来说,阻塞式的结果有保障但是时间没保障;非阻塞式的时间有保障但是结果没保障。
(3)操作系统提供的API和由API封装而成的库函数,有很多本身就是被设计为阻塞式或者非阻塞式的,所以我们应用程度调用这些函数的时候心里得非常清楚。
(4)我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志。
O_SYNC
(1)write阻塞等待底层完成写入才返回到应用层。
(2)无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不好等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志。
关闭文件-close函数
头文件包含:
#include <unistd.h>
函数原型:
int close(int fd)
参数:
fd文件描述符
返回值:
0成功,-1出错
读文件-read函数
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
参数:
fd:将要读取数据的文件描述词。
buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。
count:表示调用一次read操作,应该读多少数量的字符。
返回值:
ssize_t其实就是Linux中typedef之后的int而已
返回所读取的字节数;0读到EOF;-1出错。
以下几种情况会导致读取到的字节数小于 count :
A. 读取普通文件时,读到文件末尾还不够 count 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
B. 从终端设备terminal device读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。
D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 count 。
E. 从面向记录record-oriented的设备读取时,某些面向记录的设备如磁带每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。 读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
写文件-write函数
头文件:
#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
返回值:
写入文件的字节数成功;-1出错
参数:
fd:文件描述符
buf:缓冲区
count:一次写的字符数
终止一个进程用exit函数,而return只能在main函数中退出
记得包含相应头文件,文件读写的一些细节:
errno和perror
(1)errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了。
(2)errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。
(3)errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字譬如-37,不适应于人看。
(4)linux系统提供了一个函数perror意思print error,perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后print打印出来。
read和write的count
(1)count和返回值的关系。count参数表示我们想要写或者读的字节数,返回值表示实际完成的要写或者读的字节数。实现的有可能等于想要读写的,也有可能小于说明没完成任务
(2)count再和阻塞非阻塞结合起来,就会更加复杂。如果一个函数是阻塞式的,则我们要读取30个,结果暂时只有20个时就会被阻塞住,等待剩余的10个可以读。
(3)有时候我们写正式程序时,我们要读取或者写入的是一个很庞大的文件譬如文件有2MB,我们不可能把count设置为210241024,而应该去把count设置为一个合适的数字譬如2048、4096,然后通过多次读取来实现全部读完。
lseek函数
功能描述:用于在指定的文件描述符中将将文件指针定位到相应位置。
头文件:
#include <unistd.h>
#include <sys/types.h>
函数原型:
off_t lseek(int fd, off_t offset,int whence);
参数:
fd:文件描述符
offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负向前移,向后移
whence:就是参照物
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为指针的位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件大小加上偏移量的大小
返回值:
成功:返回当前位移
失败:返回-1
用lseek计算文件长度
(1)linux中并没有一个函数可以直接返回一个文件的长度。但是我们做项目时经常会需要知道一个文件的长度,怎么办?自己利用lseek来写一个函数得到文件长度即可。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int cal_len(const char *pathname){
int fd = -1; // fd 就是file descriptor,文件描述符
int ret = -1;
// 第一步:打开文件
fd = open(pathname, O_RDONLY);
if (-1 == fd) // 有时候也写成: (fd < 0)
{
//printf("\n");
perror("文件打开错误");
// return -1;
return -1;
}
// 此时文件指针指向文件开头
// 我们用lseek将文件指针移动到末尾,然后返回值就是文件指针距离文件开头的偏移量,也就是文件的长度了
ret = lseek(fd, 0, SEEK_END);
return ret;
}
int main(int argc, char *argv[]){
int fd = -1; // fd 就是file descriptor,文件描述符
int ret = -1;
if (argc != 2){
printf("usage: %s filename\n", argv[0]);
_exit(-1);
}
printf("文件长度是:%d字节\n", cal_len(argv[1]));
return 0;
}
用lseek构建空洞文件
(1)空洞文件就是这个文件中有一段是空的。
(2)普通文件中间是不能有空的,因为我们write时文件指针是依次从前到后去移动的,不可能绕过前面直接到后面。
(3)我们打开一个文件后,用lseek往后跳过一段,再write写入一段,就会构成一个空洞文件。
(4)空洞文件方法对多线程共同操作文件是及其有用的。有时候我们创建一个很大的文件,如果从头开始依次构建时间很长。
有一种思路就是将文件分为多段,然后多线程来操作每个线程负责其中一段的写入。
多次打开同一文件与O_APPEND
重复打开同一文件读取
分别读说明:我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。文件指针是包含在动态文件的文件管理表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件管理表。
重复打开同一文件写入
有时候我们希望接续写而不是分别写?办法就是在open时加O_APPEND标志即可
复制文件描述符dup和dup2函数
头文件:
#include <unistd.h>
函数原型:
int dup(int oldfd);
int dup2(int oldfd, int newfd);
当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd,而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
fcntl系统调用-fcntl函数
功能描述:根据文件描述词来操作文件的特性。
函数原型:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
参数:
第一个参数是fd表示要操作哪个文件,第二个参数是cmd表示要进行哪个命令操作。
变参是用来传递参数的,要配合cmd来使用。
有以下操作命令可供使用
1、F_DUPFD:复制文件描述词。
2、FD_CLOEXEC :设置close-on-exec标志。
如果FD_CLOEXEC位是0,执行execve的过程中,文件保持打开。反之则关闭。
3、F_GETFD:读取文件描述词标志。
4、F_SETFD:设置文件描述词标志。
5、F_GETFL:读取文件状态标志。
6、F_SETFL:设置文件状态标志。
其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响,能更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。
7、F_GETLK, F_SETLK 和 F_SETLKW :获取,释放或测试记录锁,使用到的参数是以下结构体指针:
F_SETLK:在指定的字节范围获取锁F_RDLCK, F_WRLCK或释放锁F_UNLCK。如果和另一个进程的锁操作发生冲突,返回 -1并将errno设置为EACCES或EAGAIN。
F_SETLKW:行为如同F_SETLK,除了不能获取锁时会睡眠等待外。如果在等待的过程中接收到信号,会即时返回并将errno置为EINTR。
F_GETLK:获取文件锁信息。
F_UNLCK:释放文件锁。
为了设置读锁,文件必须以读的方式打开。为了设置写锁,文件必须以写的方式打开。为了设置读写锁,文件必须以读写的方式打开。
stat 函数讲解
头文件:
#include <sys/stat.h>
定义函数:
int stat ( const char file_name , struct stat buf );
函数说明:
通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
返回值:
执行成功则返回0,失败返回-1,错误代码存于errno
fstat函数
int fstat(int fd, struct stat *buf);
传参是文件描述符
lstat函数
int lstat(const char restrict pathname, struct stat restrict buf);
lstat函数和stat和fstat的区别是lstat是直接查看链接文件的属性。而stat和fstat是看的连接文件指向的文件的属性
access函数检查权限设置
(1)文本权限管控其实蛮复杂,一般很难很容易的确定对一个文件是否具有某种权限。设计优秀的软件应该是:在操作某个文件之前先判断当前是否有权限做这个操作,如果有再做如果没有则提供错误信息给用户。
(2)access函数可以测试得到当前执行程序的那个用户在当前那个环境下对目标文件是否具有某种操作权限。
umask与文件权限掩码
(1)文件掩码是linux系统中维护的一个全局设置,umask的作用是用来设定我们系统中新创建的文件的默认权限的。
(2)umask命令就是用umask API实现的
目录(dir)操作函数系例
opendir
头文件:
#include <sys/types.h>
#include <dirent.h>
函数原型:
DIR opendir(const char name);
描述:
opendir函数打开一个与给定的目录名name相对应的目录流,并返回一个指向该目录流的指针。打开后,该目录流指向了目录中的第一个目录项。
返回值:
opendir函数,打开成功,返回指向目录流的指针;打开失败,则返回NULL,并设置相应的错误代码errno。
readdir
头文件:
#include <sys/types.h>
#include <dirent.h>
函数原型:
struct dirent readdir(DIR dir);
描述:
readdir函数返回一个指向dirent结构体的指针,该结构体代表了由dir指向的目录流中的下一个目录项;如果读到end-of-file或者出现了错误,那么返回NULL。
readdir函数返回的值会被后续调用的针对同一目录流的readdir函数返回值所覆盖。
返回值:
readdir函数,成功时返回一个指向dirent结构体的指针;失败时或读到end-of-file时,返回NULL,并且设置相应的错误代码errno。
closedir
头文件:
#include <sys/types.h>
#include <dirent.h>
函数原型:
int closedir(DIR *dir);
描述:
closedir函数关闭与指针dir相联系的目录流。关闭后,目录流描述符dir不再可用。
返回值:
closedir函数,成功时返回0;失败是返回-1,并设置相应的错误代码errno。
系统时间API和库函数
用的时候直接去man手册查即可
time
(1)time能得到一个当前时间距离标准起点时间1970-01-01 00:00:00 +0000(UTC)过去了多少秒
ctime库函数
(1)ctime可以从time_t出发得到一个容易观察的字符串格式的当前时间。ctime中的参数也是调用time这个API返回回来的时间得到的数字,ctime也是不能离开time来使用的
(2)ctime好处是很简单好用,可以直接得到当前时间的字符串格式,直接打印来看。坏处是ctime的打印时间格式是固定的,没法按照我们的想法去变
(3)实验结果可以看出ctime函数得到的时间是考虑了计算机中的本地时间的计算机中的时区设置
gmtime和localtime
(1)gmtime获取的时间中:年份是以1970为基准的差值,月份是0表示1月,小时数是以UTC时间的0时区为标准的小时数北京是东8区,因此北京时间比这个时间大8
(2)猜测localtime和gmtime的唯一区别就是localtime以当前计算机中设置的时区为小时的时间基准,其余一样。实践证明我们的猜测是正确的。
mktime
(1)从OS中读取时间时用不到mktime的,这个mktime是用来向操作系统设置时间时用的。
asctime
(1)asctime得到一个固定格式的字符串格式的当前时间,效果上和ctime一样的。区别是ctime从time_t出发,而asctime从struct tm出发。
strftime
(1)asctime和ctime得到的时间字符串都是固定格式的,没法用户自定义格式
(2)如果需要用户自定义时间的格式,则需要用strftime。
gettimeofday和settimeofday
(1)前面讲到的基于time函数的那个系列都是以秒为单位来获取时间的,没有比秒更精确的时间。
(2)有时候我们程序希望得到非常精确的时间譬如以us为单位,这时候就只能通过gettimeofday来实现了。
生成随机数
rand是一个库函数并不是一个API
(1)单纯使用rand重复调用n次,就会得到一个0-RAND_MAX之间的伪随机数,如果需要调整范围,可以得到随机数序列后再进行计算。
(2)单纯使用rand来得到伪随机数序列有缺陷,每次执行程序得到的伪随机序列是同一个序列,没法得到其他序列。
(3)原因是因为rand内部的算法其实是通过一个种子seed,其实就是一个原始参数,int类型,rand内部默认是使用1作为seed的,种子一定的算法也是一定的,那么每次得到的伪随机序列肯定是同一个。
(4)所以要想每次执行这个程序获取的伪随机序列不同,则每次都要给不同的种子。用srand函数来设置种子。
一、open
open是一个非标准的低级文件I/O函数,返回的是文件的低级句柄,原型:
int open(char* path, int access, ...);
open是一个可变参数的函数实现,后面的可变参数通常表示unsigned mode,mode参数是否存在要看access的值,path是文件的路径。
相关的头文件:#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
access和mode的可取值通常在FCNTL.h里面定义,access的可取值如下:
#define O_RDONLY 1
#define O_WRONLY 2
#define O_RDWR 4
access还可以是以下flag及它们之间的组合而得到的性质:
#define O_CREAT 0x0100 /* create and open file */
#define O_TRUNC 0x0200 /* open with truncation */
#define O_EXCL 0x0400 /* exclusive open */
#define O_APPEND 0x0800 /* to end of file */
#define O_CHANGED 0x1000 /* user may read these bits, but */
#define O_DEVICE 0x2000 /* only RTL/io functions may touch. */
#define O_TEXT 0x4000 /* CR-LF translation */
#define O_BINARY 0x8000 /* no translation */
使用O_CREAT的时候,必须指定mode参数,mode的可取值在sys/stat.h里面定义,也可以是它们的组合,如下:
#define S_IREAD 0x0100 /* owner may read */
#define S_IWRITE 0x0080 /* owner may write */
open(s[i],0x0100,0x0080);
意思是以O_CREAT和可写的方式打开s[i]中指出的文件,如果文件不存在,就创建它,返回这个文件的低级句柄。
定义函数:
int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
函数说明:
参数 pathname 指向欲打开的文件路径字符串.下列是参数flags所能使用的旗标:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合.
O_CREAT 若欲打开的文件不存在则自动建立该文件.
O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机.
O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
O_NDELAY 同O_NONBLOCK.
O_SYNC 以同步的方式打开文件.
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。注:此为Linux2. 2 以后特有的旗标,以避免一些系统安全问题.
参数mode则有下列数种组合, 只有在建立新文件时才会生效, 此外真正建文件时的权限会受到umask 值所影响, 因此该文件权限应该为 (mode-umaks).
S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.
S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.
S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.
S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.
S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.
S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.
S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.
S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.
S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.
S_IROTH 00004 权限, 代表其他用户具有可读的权限
S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.
S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.
返回值:若所有欲核查的权限都通过了检查则返回0值,表示成功,只要有一个权限被禁止则返回-1.
错误代码:
EEXIST 参数pathname 所指的文件已存在, 却使用了O_CREAT 和O_EXCL 旗标.
EACCESS 参数pathname 所指的文件不符合所要求测试的权限.
EROFS 欲测试写入权限的文件存在于只读文件系统内.
EFAULT 参数pathname 指针超出可存取内存空间.
EINVAL 参数mode 不正确.
ENAMETOOLONG 参数 pathname 太长.
ENOTDIR 参数pathname 不是目录.
ENOMEM 核心内存不足.
ELOOP 参数pathname 有过多符号连接问题.
EIO I/O 存取错误.
附加说明:使用access()作用户认证方面的判断要特别小心,例如在access()后再作open()空文件可能会造成系统安全上的问题.
函数原型:int open(const char *path, int access,int mode);
作用:以各种方式打开文件
返回值:返回打开的文件句柄,-1打开失败
输入参数说明:path 要打开的文件路径和名称,access 访问模式,宏含义如下:O_RDONLY 1 只读;O_WRONLY 2 只写;O_RDWR 4 读写
还可选择以下模式与以上3种基本模式相与:
O_CREAT 0x0100 创建一个文件并打开
O_TRUNC 0x0200 打开一个存在的文件并将文件长度设置为0,其他属性保持
O_EXCL 0x0400 未使用
O_APPEND 0x0800 追加打开文件
O_TEXT 0x4000 打开文本文件翻译CR-LF控制字符
O_BINARY 0x8000 打开二进制字符,不作CR-LF翻译
mode 该参数仅在access=O_CREAT方式下使用,其取值如下:
S_IFMT 0xF000 文件类型掩码
S_IFDIR 0x4000 目录
S_IFIFO 0x1000 FIFO 专用
S_IFCHR 0x2000 字符专用
S_IFBLK 0x3000 块专用
S_IFREG 0x8000 只为0x0000
S_IREAD 0x0100 可读
S_IWRITE 0x0080 可写
S_IEXEC 0x0040 可执行
二、fopen
fopen函数是在当前目录下打开一个文件,其调用的一般形式为:
文件指针名=fopen(文件名,使用文件方式);
"文件指针名"必须是被声明为FILE 类型的指针变量;
"文件名"是被打开文件的文件名;
"使用文件方式"是指文件的类型和操作要求;
"文件名"是字符串常量或字符数组。
fphzk=fopen("c:\\freeoa.txt","rb");
其意义是打开C驱动器磁盘的根目录下的文件freeoa.txt,这是一个二进制文件,只允许按二进制方式进行读操作。两个反斜线"\\ "中的第一个表示转义字符,第二个表示根目录。使用文件的方式共有12种,下面给出了它们的符号和意义:
"r" = "rt"
打开一个文本文件,文件必须存在,只允许读
"r+" = "rt+"
打开一个文本文件,文件必须存在,允许读写
"rb"
打开一个二进制文件,文件必须存在,只允许读
"rb+"
打开一个二进制文件,文件必须存在,允许读写
"w" = "wt"
新建一个文本文件,已存在的文件将被删除,只允许写
"w+" = "wt+"
新建一个文本文件,已存在的文件将被删除,允许读写
"wb"
新建一个二进制文件,已存在的文件将被删除,只允许写
"wb+"
新建一个二进制文件,已存在的文件将被删除,允许读写
"a" = "at"
打开或新建一个文本文件,只允许在文件末尾追写
"a+" = "at+"
打开或新建一个文本文件,可以读,但只允许在文件末尾追写
"ab"
打开或新建一个二进制文件,只允许在文件末尾追写
"ab+"
打开或新建一个二进制文件,可以读,但只允许在文件末尾追写
对于文件使用方式有以下几点说明:
1) 文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:
r(read): 只读
w(write): 只写
a(append): 追加
t(text): 文本文件,可省略不写
b(binary): 二进制文件
+: 读和写
2) 凡用"r"打开一个文件时,该文件必须已经存在,且只能从该文件读出。
3) 用"w"打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。
4) 若要向一个已存在的文件追加新的信息,只能用"a"方式打开文件。如果指定文件不存在则尝试创建该文件。
5) 在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:
if((fp=fopen("c:\\freeoa.txt","rb"))==NULL){
printf("\nerror on open c:\\freeoa.txt file!");
getch();
exit(1);
}
这段程序的意义是,如果返回的指针为空,表示不能打开C盘根目录下的freeoa.txt文件,则给出提示信息"error on open c:\freeoa.txt file!",下一行getch()的功能是从键盘输入一个字符,但不在屏幕上显示。在这里,该行的作用是等待,只有当用户从键盘敲任一键时,程序才继续执行,因此用户可利用这个等待时间阅读出错提示。敲键后执行exit(1)退出程序。
7) 把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。
8) 标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开的,可直接使用。
fopen()函数:
1.作用:在C语言中fopen()函数用于打开指定路径的文件,获取指向该文件的指针。
2.函数原型:
FILE * fopen(const char * path,const char * mode);
-- path: 文件路径,如:"F:\FreeOA\test.txt"
-- mode: 文件打开方式,例如:
"r" 以只读方式打开文件,该文件必须存在。
"w" 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
"w+" 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
"a" 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
"a+" 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。(原来的EOF符不保留)
"wb" 只写打开或新建一个二进制文件,只允许写数据。
"wb+" 读写打开或建立一个二进制文件,允许读和写。
"ab" 追加打开一个二进制文件,并在文件末尾写数据。
"ab+"读写打开一个二进制文件,允许读,或在文件末追加数据。
-- 返回值: 文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在errno中。
fwrite()函数:
1.作用:在C语言中fwrite()函数常用语将一块内存区域中的数据写入到本地文本。
2.函数原型:
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
-- buffer:指向数据块的指针
-- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4)
-- count:数据个数
-- stream:文件指针
注意:返回值随着调用格式的不同而不同:
(1)调用格式:fwrite(buf,sizeof(buf),1,fp);
成功写入返回值为1(即count)
(2)调用格式:fwrite(buf,1,sizeof(buf),fp);
成功写入则返回实际写入的数据个数(单位为Byte)
3.注意事项:写完数据后要调用fclose()关闭流,不关闭流的情况下,每次读或写数据后,文件指针都会指向下一个待写或者读数据位置的指针。
fread()函数:
1. 作用:从一个文件流中读取数据
2. 函数原型如下:
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
-- buffer:指向数据块的指针
-- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4)
-- count:数据个数
-- stream:文件指针
注意:返回值随着调用格式的不同而不同:
(1)调用格式:fread(buf,sizeof(buf),1,fp);
读取成功时:当读取的数据量正好是sizeof(buf)个Byte时,返回值为1(即count),否则返回值为0(读取数据量小于sizeof(buf))。
(2)调用格式:fread(buf,1,sizeof(buf),fp);
读取成功返回值为实际读回的数据个数(单位为Byte)
三、Linux 系统应用编程之标准I/O
标准I/O的由来:标准I/O指的是ANSI C 中定义的用于I/O操作的一系列函数。只要操作系统安装了C库,标准I/O函数就可以调用。换句话说,如果程序中使用的是标准I/O函数,那么源代码不需要任何修改就可以在其他操作系统下编译运行,具有更好的可移植性。
除此之外,使用标准I/O可以减少系统调用的次数,提高系统效率。标准I/O函数在执行时也会用到系统调用。在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁的执行系统调用会增加系统的开销。为避免这种情况,标准I/O在使用时为用户控件创建缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际的文件,从而减少了使用系统调用的次数。
流的含义
标准I/O的核心对象就是流。当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件或者理解为创建一个FILE结构体和实际打开的文件关联起来。我们把这个FILE结构体形象的称为流,我们在stdio.h里可以看到这个FILE结构体。
typedef struct {
short level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
short bsize; /* Buffer size */
unsigned char *buffer; /* Data transfer buffer */
unsigned char *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
这个结构体:
1).对 fd 进行了封装;
2).对缓存进行了封装 unsigned char *buffer; 这而指向了buffer 的地址,实际这块buffer是cache,我们要将其与用户控件的buffer分开。
标准I/O函数都是基于流的各种操作,标准I/O中的流的缓冲类型有下面三种:
1)、全缓冲
在这种情况下,实际的I/O操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准I/O库提供全缓冲。缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用malloc函数分配得到的。术语flush描述了标准I/O缓冲的写操作。缓冲区可以由标准I/O函数自动flush(例如缓冲区满的时候);或者我们对流调用fflush函数。
2)、行缓冲
在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的I/O操作。这允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的流--例如标注输入(stdin)和标准输出(stdout)--是行缓冲的。
3)、无缓冲
标准I/O库不缓存字符,需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。标准I/O函数时库函数,是对系统调用的封装,所以我们的标准I/O函数其实都是基于文件I/O函数的,是对文件I/O函数的封装,下面具体介绍·标准I/O最常用的函数:
1、流的打开与关闭
使用标准I/O打开文件的函数有fopen()、fdopen()、freopen()。他们可以以不同的模式打开文件,都返回一个指向FILE的指针,该指针指向对应的I/O流。此后,对文件的读写都是通过这个FILE指针来进行。
fopen函数描述如下:
所需头文件 #include <stdio.h>
函数原型 FILE *fopen(const char *path, const char *mode);
函数参数
path: 包含要打开的文件路径及文件名
mode:文件打开方式
函数返回值
成功:指向FILE的指针
失败:NULL
mode用于指定打开文件的方式。
关闭流的函数为fclose(),该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。
fclose()函数描述如下:
所需头文件 #include <stdio.h>
函数原型 int fclose(FILE *stram);
函数参数
stream:已打开的流指针
函数返回值
成功:0
失败:EOF
2、流的读写
1)、按字符字节输入/输出
字符输入/输出函数一次仅读写一个字符。
字符输入函数原型如下:
所需头文件 #include <stdio.h>
函数原型
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar (void);
函数参数
stream:要输入的文件流
函数返回值
成功:读取的字符
失败:EOF
函数getchar等价于get(stdin)。前两个函数的区别在于getc可被实现为宏,而fgetc则不能实现为宏。这意味着:
(1).getc 的参数不应当是具有副作用的表达式;
(2).因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传给另一个参数;
(3).调用fgetc所需时间很可能长于调用getc,因为调用函数通常所需的时间长于调用宏。
这三个函数在返回下一个字符时,会将其unsigned char 类型转换为int类型。说明为什么不带符号的理由是,如果是最高位为1也不会使返回值为负。要求整数返回值的理由是,这样就可以返回所有可能的字符值再加上一个已出错或已达到文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一个负值,其值经常是-1。这就意味着不能将这三个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值与常量EOF相比较。
注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。
#include <stdio.h>
int ferror (FILE *fp);
int feof (FILE *fp);
两个函数返回值;若条件为真则返回非0值真,否则返回0假;在大多数实现中,为每个流在FILE对象中维持了两个标志:
出错标志。
文件结束标志。
字符输出-函数原型如下:
所需头文件 #include <stdio.h>
函数原型
int putc (int c ,FILE *stream);
int fputc (int c, FILE *stream);
int putchar(int c);
函数返回值
成功:输出的字符c
失败:EOF
putc()和fputc()向指定的流输出一个字符节,putchar()向stdout输出一个字符节。
2)、按行输入、输出
行输入/输出函数一次操作一行。
行输入函数原型如下:
所需头文件 #include <stdio.h>
函数原型
char *gets(char *s);
char *fgets(char *s,int size,FILE *stream);
函数参数
s:存放输入字符串的缓冲区首地址;
size:输入的字符串长度
stream:对应的流
函数返回值
成功:s
失败或到达文件末尾:NULL
这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。
gets函数容易造成缓冲区溢出,不推荐使用;
fgets从指定的流中读取一个字符串,当遇到 \n 或读取了 size - 1个字符串后返回。注意,fgets不能保证每次都能读出一行。 如若该行包括最后一个换行符的字符数超过size -1 ,则fgets只返回一个不完整的行,但是,缓冲区总是以null字符结尾。对fgets的下一次调用会继续执行。
行输出函数原型如下:
所需头文件 #include <stdio.h>
函数原型
int puts(const char *s);
int fgets(const char *s,FILE *stream);
函数参数
s:存放输入字符串的缓冲区首地址;
stream:对应的流
函数返回值
成功:非负值
失败或到达文件末尾:NULL
函数fputs将一个以null符终止的字符串写到指定的流,尾端的终止符null不写出。注意,这并不一定是每次输出一行,因为它并不要求在null符之前一定是换行符。通常,在null符之前是一个换行符,但并不要求总是如此。
下面举个例子:模拟文件的复制过程:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define maxsize 5
int main(int argc, char *argv[]) {
FILE *fp1 ,*fp2;
char buffer[maxsize];
char *p,*q;
if(argc < 3){
printf("Usage:%s <srcfile> <desfile>\n",argv[0]);
return -1;
}
if((fp1 = fopen(argv[1],"r")) == NULL){
perror("fopen argv[1] fails");
return -1;
}
if((fp2 = fopen(argv[2],"w+")) == NULL){
perror("fopen argv[2] fails");
return -1;
}
while((p = fgets(buffer,maxsize,fp1)) != NULL){
fputs(buffer,fp2);
}
if(p == NULL) {
if(ferror(fp1))
perror("fgets failed");
if(feof(fp1))
printf("cp over!\n");
}
fclose(fp1);
fclose(fp2);
return 0;
}
执行结果如下:
$ ls -l
total 16
-rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
-rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
-rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
$ ./cp time.c 1.c
cp over!
$ ls -l
total 20
-rw-rw-r-- 1 fs fs 437 Jan 5 21:09 1.c
-rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
-rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
-rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
我们可以看到,这里将time.c拷贝给1.c ,1.c和time.c大小一样,都是437个字节。
3)、以指定大小为单位读写文件
3、流的定位
4、格式化输入输出
这里举个相关应用例子:循环记录系统时间
实验内容:程序每秒一次读取依次系统时间并写入文件
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#define N 64
int main(int argc, char *argv[]){
int n;
char buf[N];
FILE *fp;
time_t t;
if(argc < 2){
printf("Usage : %s <file >\n",argv[0]);
return -1;
}
if((fp = fopen(argv[1],"a+")) == NULL){
perror("open fails");
return -1;
}
while(1) {
time(&t);
fprintf(fp,"%s",ctime(&t));
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}
执行结果如下:
$ ls -l
total 12
-rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
-rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
$ ./time 1.txt
^C
$ ls -l
total 16
-rw-rw-r-- 1 fs fs 175 Jan 5 21:14 1.txt
-rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
-rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
$ cat 1.txt
Tue Jan 5 21:14:11 2016
Tue Jan 5 21:14:12 2016
Tue Jan 5 21:14:13 2016
Tue Jan 5 21:14:14 2016
Tue Jan 5 21:14:15 2016
Tue Jan 5 21:14:16 2016
Tue Jan 5 21:14:17 2016
四、Linux 系统应用编程之标准I/O系列函数说明
打开文件-open函数
头文件包含:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname:为文件的路径,可以带路径描述。
flags:文件打开方式。一会仔细写
mode:当flags满足一定条件就会使用mode位。
mode使用4个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用0666
返回值:
返回值为文件描述符,如果失败则返回-1,文件描述符不可能是负数
open函数flags详解
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
以读写或者只写方式写文件时。是在文件的前面进行写操作,覆盖原有内容
O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为0.如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾
O_CREAT:如果改文件不存在,就创建一个新的文件,并用第三个参数为其设置权限。如果文件按已经存在的话,则删除原来内容。
O_EXCL:如果使用O_CREAT时文件存在,则返回错误消息。这一参数可测试文件是否存在。此时open是原子操作,防止多个进程同时创建同一个文件
如果我CREAT要创建的是一个已经存在的名字的文件,则给我报错,不要去创建。这个效果就要靠O_EXCL标志和O_CREAT标志来结合使用。当这连个标志一起的时候,则没有文件时创建文件,有这个文件时会报错提醒我们。
O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用open()的那个进程的控制终端
O_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
O_SYNC:使每次write都等到物理I/O操作完成。
O_RSYNC:read 等待所有写入同一区域的写操作完成后再进行
在open()函数中,falgs参数可以通过组合构成,但前3个标准常量O_RDONLY,O_WRONLY,和O_RDWR不能互相组合。
O_NONBLOCK
(1)阻塞与非阻塞。如果一个函数是阻塞式的,则我们调用这个函数时当前进程有可能被卡住阻塞住,实质是这个函数内部要完成的事情条件不具备,当前没法做,要等待条件成熟,函数被阻塞住了就不能立刻返回;如果一个函数是非阻塞式的那么我们调用这个函数后一定会立即返回,但是函数有没有完成任务不一定。
(2)阻塞和非阻塞是两种不同的设计思路,并没有好坏。总的来说,阻塞式的结果有保障但是时间没保障;非阻塞式的时间有保障但是结果没保障。
(3)操作系统提供的API和由API封装而成的库函数,有很多本身就是被设计为阻塞式或者非阻塞式的,所以我们应用程度调用这些函数的时候心里得非常清楚。
(4)我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志。
O_SYNC
(1)write阻塞等待底层完成写入才返回到应用层。
(2)无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不好等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志。
关闭文件-close函数
头文件包含:
#include <unistd.h>
函数原型:
int close(int fd)
参数:
fd文件描述符
返回值:
0成功,-1出错
读文件-read函数
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
参数:
fd:将要读取数据的文件描述词。
buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。
count:表示调用一次read操作,应该读多少数量的字符。
返回值:
ssize_t其实就是Linux中typedef之后的int而已
返回所读取的字节数;0读到EOF;-1出错。
以下几种情况会导致读取到的字节数小于 count :
A. 读取普通文件时,读到文件末尾还不够 count 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
B. 从终端设备terminal device读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。
D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 count 。
E. 从面向记录record-oriented的设备读取时,某些面向记录的设备如磁带每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。 读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
写文件-write函数
头文件:
#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
返回值:
写入文件的字节数成功;-1出错
参数:
fd:文件描述符
buf:缓冲区
count:一次写的字符数
终止一个进程用exit函数,而return只能在main函数中退出
记得包含相应头文件,文件读写的一些细节:
errno和perror
(1)errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了。
(2)errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。
(3)errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字譬如-37,不适应于人看。
(4)linux系统提供了一个函数perror意思print error,perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后print打印出来。
read和write的count
(1)count和返回值的关系。count参数表示我们想要写或者读的字节数,返回值表示实际完成的要写或者读的字节数。实现的有可能等于想要读写的,也有可能小于说明没完成任务
(2)count再和阻塞非阻塞结合起来,就会更加复杂。如果一个函数是阻塞式的,则我们要读取30个,结果暂时只有20个时就会被阻塞住,等待剩余的10个可以读。
(3)有时候我们写正式程序时,我们要读取或者写入的是一个很庞大的文件譬如文件有2MB,我们不可能把count设置为210241024,而应该去把count设置为一个合适的数字譬如2048、4096,然后通过多次读取来实现全部读完。
lseek函数
功能描述:用于在指定的文件描述符中将将文件指针定位到相应位置。
头文件:
#include <unistd.h>
#include <sys/types.h>
函数原型:
off_t lseek(int fd, off_t offset,int whence);
参数:
fd:文件描述符
offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负向前移,向后移
whence:就是参照物
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为指针的位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件大小加上偏移量的大小
返回值:
成功:返回当前位移
失败:返回-1
用lseek计算文件长度
(1)linux中并没有一个函数可以直接返回一个文件的长度。但是我们做项目时经常会需要知道一个文件的长度,怎么办?自己利用lseek来写一个函数得到文件长度即可。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int cal_len(const char *pathname){
int fd = -1; // fd 就是file descriptor,文件描述符
int ret = -1;
// 第一步:打开文件
fd = open(pathname, O_RDONLY);
if (-1 == fd) // 有时候也写成: (fd < 0)
{
//printf("\n");
perror("文件打开错误");
// return -1;
return -1;
}
// 此时文件指针指向文件开头
// 我们用lseek将文件指针移动到末尾,然后返回值就是文件指针距离文件开头的偏移量,也就是文件的长度了
ret = lseek(fd, 0, SEEK_END);
return ret;
}
int main(int argc, char *argv[]){
int fd = -1; // fd 就是file descriptor,文件描述符
int ret = -1;
if (argc != 2){
printf("usage: %s filename\n", argv[0]);
_exit(-1);
}
printf("文件长度是:%d字节\n", cal_len(argv[1]));
return 0;
}
用lseek构建空洞文件
(1)空洞文件就是这个文件中有一段是空的。
(2)普通文件中间是不能有空的,因为我们write时文件指针是依次从前到后去移动的,不可能绕过前面直接到后面。
(3)我们打开一个文件后,用lseek往后跳过一段,再write写入一段,就会构成一个空洞文件。
(4)空洞文件方法对多线程共同操作文件是及其有用的。有时候我们创建一个很大的文件,如果从头开始依次构建时间很长。
有一种思路就是将文件分为多段,然后多线程来操作每个线程负责其中一段的写入。
多次打开同一文件与O_APPEND
重复打开同一文件读取
分别读说明:我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。文件指针是包含在动态文件的文件管理表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件管理表。
重复打开同一文件写入
有时候我们希望接续写而不是分别写?办法就是在open时加O_APPEND标志即可
复制文件描述符dup和dup2函数
头文件:
#include <unistd.h>
函数原型:
int dup(int oldfd);
int dup2(int oldfd, int newfd);
当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd,而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
fcntl系统调用-fcntl函数
功能描述:根据文件描述词来操作文件的特性。
函数原型:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
参数:
第一个参数是fd表示要操作哪个文件,第二个参数是cmd表示要进行哪个命令操作。
变参是用来传递参数的,要配合cmd来使用。
有以下操作命令可供使用
1、F_DUPFD:复制文件描述词。
2、FD_CLOEXEC :设置close-on-exec标志。
如果FD_CLOEXEC位是0,执行execve的过程中,文件保持打开。反之则关闭。
3、F_GETFD:读取文件描述词标志。
4、F_SETFD:设置文件描述词标志。
5、F_GETFL:读取文件状态标志。
6、F_SETFL:设置文件状态标志。
其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响,能更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。
7、F_GETLK, F_SETLK 和 F_SETLKW :获取,释放或测试记录锁,使用到的参数是以下结构体指针:
F_SETLK:在指定的字节范围获取锁F_RDLCK, F_WRLCK或释放锁F_UNLCK。如果和另一个进程的锁操作发生冲突,返回 -1并将errno设置为EACCES或EAGAIN。
F_SETLKW:行为如同F_SETLK,除了不能获取锁时会睡眠等待外。如果在等待的过程中接收到信号,会即时返回并将errno置为EINTR。
F_GETLK:获取文件锁信息。
F_UNLCK:释放文件锁。
为了设置读锁,文件必须以读的方式打开。为了设置写锁,文件必须以写的方式打开。为了设置读写锁,文件必须以读写的方式打开。
stat 函数讲解
头文件:
#include <sys/stat.h>
定义函数:
int stat ( const char file_name , struct stat buf );
函数说明:
通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
返回值:
执行成功则返回0,失败返回-1,错误代码存于errno
fstat函数
int fstat(int fd, struct stat *buf);
传参是文件描述符
lstat函数
int lstat(const char restrict pathname, struct stat restrict buf);
lstat函数和stat和fstat的区别是lstat是直接查看链接文件的属性。而stat和fstat是看的连接文件指向的文件的属性
access函数检查权限设置
(1)文本权限管控其实蛮复杂,一般很难很容易的确定对一个文件是否具有某种权限。设计优秀的软件应该是:在操作某个文件之前先判断当前是否有权限做这个操作,如果有再做如果没有则提供错误信息给用户。
(2)access函数可以测试得到当前执行程序的那个用户在当前那个环境下对目标文件是否具有某种操作权限。
umask与文件权限掩码
(1)文件掩码是linux系统中维护的一个全局设置,umask的作用是用来设定我们系统中新创建的文件的默认权限的。
(2)umask命令就是用umask API实现的
目录(dir)操作函数系例
opendir
头文件:
#include <sys/types.h>
#include <dirent.h>
函数原型:
DIR opendir(const char name);
描述:
opendir函数打开一个与给定的目录名name相对应的目录流,并返回一个指向该目录流的指针。打开后,该目录流指向了目录中的第一个目录项。
返回值:
opendir函数,打开成功,返回指向目录流的指针;打开失败,则返回NULL,并设置相应的错误代码errno。
readdir
头文件:
#include <sys/types.h>
#include <dirent.h>
函数原型:
struct dirent readdir(DIR dir);
描述:
readdir函数返回一个指向dirent结构体的指针,该结构体代表了由dir指向的目录流中的下一个目录项;如果读到end-of-file或者出现了错误,那么返回NULL。
readdir函数返回的值会被后续调用的针对同一目录流的readdir函数返回值所覆盖。
返回值:
readdir函数,成功时返回一个指向dirent结构体的指针;失败时或读到end-of-file时,返回NULL,并且设置相应的错误代码errno。
closedir
头文件:
#include <sys/types.h>
#include <dirent.h>
函数原型:
int closedir(DIR *dir);
描述:
closedir函数关闭与指针dir相联系的目录流。关闭后,目录流描述符dir不再可用。
返回值:
closedir函数,成功时返回0;失败是返回-1,并设置相应的错误代码errno。
系统时间API和库函数
用的时候直接去man手册查即可
time
(1)time能得到一个当前时间距离标准起点时间1970-01-01 00:00:00 +0000(UTC)过去了多少秒
ctime库函数
(1)ctime可以从time_t出发得到一个容易观察的字符串格式的当前时间。ctime中的参数也是调用time这个API返回回来的时间得到的数字,ctime也是不能离开time来使用的
(2)ctime好处是很简单好用,可以直接得到当前时间的字符串格式,直接打印来看。坏处是ctime的打印时间格式是固定的,没法按照我们的想法去变
(3)实验结果可以看出ctime函数得到的时间是考虑了计算机中的本地时间的计算机中的时区设置
gmtime和localtime
(1)gmtime获取的时间中:年份是以1970为基准的差值,月份是0表示1月,小时数是以UTC时间的0时区为标准的小时数北京是东8区,因此北京时间比这个时间大8
(2)猜测localtime和gmtime的唯一区别就是localtime以当前计算机中设置的时区为小时的时间基准,其余一样。实践证明我们的猜测是正确的。
mktime
(1)从OS中读取时间时用不到mktime的,这个mktime是用来向操作系统设置时间时用的。
asctime
(1)asctime得到一个固定格式的字符串格式的当前时间,效果上和ctime一样的。区别是ctime从time_t出发,而asctime从struct tm出发。
strftime
(1)asctime和ctime得到的时间字符串都是固定格式的,没法用户自定义格式
(2)如果需要用户自定义时间的格式,则需要用strftime。
gettimeofday和settimeofday
(1)前面讲到的基于time函数的那个系列都是以秒为单位来获取时间的,没有比秒更精确的时间。
(2)有时候我们程序希望得到非常精确的时间譬如以us为单位,这时候就只能通过gettimeofday来实现了。
生成随机数
rand是一个库函数并不是一个API
(1)单纯使用rand重复调用n次,就会得到一个0-RAND_MAX之间的伪随机数,如果需要调整范围,可以得到随机数序列后再进行计算。
(2)单纯使用rand来得到伪随机数序列有缺陷,每次执行程序得到的伪随机序列是同一个序列,没法得到其他序列。
(3)原因是因为rand内部的算法其实是通过一个种子seed,其实就是一个原始参数,int类型,rand内部默认是使用1作为seed的,种子一定的算法也是一定的,那么每次得到的伪随机序列肯定是同一个。
(4)所以要想每次执行这个程序获取的伪随机序列不同,则每次都要给不同的种子。用srand函数来设置种子。