一、标准I/O函数:
1.fopen/fclose
#include
FILE *fopen(const char *path, const char *mode);
返回值:成功返回文件指针,错误返回NULL ,并设置errno,FILE *指针称为不透明指针(Opaque Pointer)或句柄(handle) 。
int fclose(FILE *fp);
返回值:成功返回0,错误返回EOF ,并设置errno
2.stdin/stdout/stderr
因为在程序启动时(在main函数还没开始执行之前)会自动把终端设备打开三次,分别赋给三个FILE
*指针stdin、stdout和stderr,这三个文件指针是libc中定义的全局变量,在stdio.h中声明,printf向stdout写,而
scanf从stdin读,后面我们会看到,用户程序也可以直接使用这三个文件指针。
if ((fp = fopen("/hom/download/file1","r")) == NULL)
{
fputs("ERROR open file /home/download/file1\n",stderr);
exit(1);
}
3.errno与perror函数
很多系统函数在错误返回时将错误原因记录在libc定义的全局变量errno中,打印errno时,打印出来的只是一个整数值,所以用perror或
strerror函数將errno解释成字符串再打印。一个系统函数错误返回后应马上检查errno,在检查errno之前不能再调用其他系统函数。
#include
#include
#include
int main(void)
{
FILE *fp = fopen("abcd","r");
if(fp ==NULL)
{
//perror("Open file abcd");
printf("errno: %d\n",errno);
exit(1);
}
return 0;
}
结果:
errno: 2
如果不注销//perror("Open file abcd");此行的结果为:
Open file abcd: No such file or directory
errno: 29
strerror函数可以根据错误号返回错误原因字符串。
#include
char *strerror (int errnum);
有些函数的错误码并不保存在errno中,而是通过返回值返回,就不能调用perror打印错误原因了,这时strerror就派上用场了fputs(strerror(n),stderr);
4.以字节为单位的I/O函数:fgetc/fputc/getchar/putchar
int fgetc(FILE *stream);
int getchar(void); //相当于调用fgetc(stdin)
返回值:成功返回读到的字节,出错或读到文件末尾时返回EOF
int fputc (int c,FILE *stream);
int putchar(int c);//相当于调用fputc(c,stdout)
返回值:成功返回写入的字节,出错返回EOF
例子:
#include
#include
int main(void)
{
FILE *fp; int ch;
if ( (fp = fopen("file2", "w+")) == NULL)
{
perror("Open file file2\n");
exit(1);
}
while ( (ch = getchar()) != EOF)
fputc(ch, fp);
rewind(fp); //将文件指针重新指向一个流的开头
while ( (ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
return 0;
}
编写一个简单的文件拷贝程序:
#include
int main(int argc,char *argv[])
{
FILE *input,*output;
int letter;
char *srcFile=NULL;
char *szTargetFile=NULL;
srcFile=argv[1];
szTargetFile=argv[2];
if((input = fopen(srcFile,"r")) == NULL)
{
printf("can't open %s \n",argv[1]);
}
else if((output = fopen(szTargetFile,"w")) == NULL)
{
printf("can't open %s \n",argv[2]);
}
else
{
while((letter = fgetc(input)) != EOF)
{
fputc(letter,output);
}
fclose(input);
fclose(output);
}
return 0;
}
5.操作读写位置的函数:fseek/ftell/rewind
int fseek(FILE *stream, long offset. int whence); //whence:SEEK_SET(从开头移动offset个字节),SEEK_CUR,SEEK_END
返回值:成功返回0,出错返回-1并设置errno
int ftell(FILE *stream);
返回值:成功返回当前读写位置,出错返回-1并设置errno
void rewind(FILE *stream);
6.以字符串为单位的I/O函数
- #include
- char *gets(char *s); //慎用,最好不用
- fgets从指定的文件中读一行字符到调用者提供的缓冲区中.
- gets从标准输入读入一行字符到调用者提供的缓冲区
- 返回值:成功时s指向哪返回的指针就指向哪,出错或读到文件末尾时返回NULL
-
- int fputs(const char *s, FILE *stream);
- int puts(const char *s);
- fputs将缓冲区的字符串写入文件stream,但并不写入结尾的'\0'.
- puts将字符串s写到标准输出(不包含结尾的'\0'),然后自动写一个到标准输出。
- 返回值:成功返回一个非负整数,出错返回EOF
7.以记录为单位的I/O函数
fread和fwrite用于读写记录,这里的记录是指一串固定长度的字节,比如一个int,一个结构体或者一个定长数组。
- #include
- size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- //返回值:读或写的记录数,成功时返回的记录数等于nmemb,出错或读到文件末尾时返回的记录数小于nmemb,也可
- //能返回0
练习:
- #include
- #include
-
- struct record
- {
- char name[10];
- int age;
- };
-
- int main(void)
- {
- struct record array[2] = {{"ken",24},{"knuth",28}};
- FILE *fp = fopen("file1","w");
- if (fp == NULL)
- {
- perror("open file file1");
- exit(1);
- }
- fwrite(array,sizeof(struct record),2,fp);
- fclose(fp);
- return 0;
- }
- #include
- #include
-
- struct record
- {
- char name[10];
- int age;
- };
-
- int main(void)
- {
- struct record array[2];
- FILE *fp = fopen("file1","r");
- if (fp == NULL)
- {
- perror("open file file1");
- exit(1);
- }
- fread(array,sizeof(struct record),2,fp);
- printf("Name1:%s\tAge1:%d\n",array[0].name,array[0].age);
- printf("Name2:%s\tAge2:%d\n",array[1].name,array[1].age);
- fclose(fp);
- return 0;
- }
结果:
[root@localhost fread]# od -tx1 -tc -Ax file1
000000 6b 65 6e 00 00 00 00 00 00 00 00 00 18 00 00 00
k e n \0 \0 \0 \0 \0 \0 \0 \0 \0 030 \0 \0 \0
000010 6b 6e 75 74 68 00 00 00 00 00 00 00 1c 00 00 00
k n u t h \0 \0 \0 \0 \0 \0 \0 034 \0 \0 \0
000020
[root@localhost fread]# ./fread
Name1:ken Age1:24
Name2:knuth Age2:28
8.格式化io函数
printf函数
- #include
- int printf(const char *format, ...);//格式化打印到标准输出
- int fprintf(FILE *stream, const char *format, ...);//打印到指定的文件stream中
- int sprintf(char *str, const char *format, ...);
- //并不打印到文件,而是打印到用户提供的缓冲区str中并在末尾加'\0'
- int snprintf(char *str, size_t size, const char *format, ...);//指定了缓冲区的长度size
-
- #include
- int vprintf(const char *format, va_list ap);
- int vfprintf(FILE *stream, const char *format, va_list ap);
- int vsprintf(char *str, const char *format, va_list ap);
- int vsnprintf(char *str, size_t size, const char *format, va_list ap);
- 返回值:成功返回格式化输出的字节数(不包括字符串的结尾'\0'),出错返回一个负值。
scanf函数
- #include
- int scanf(const char *format, ...);//从标准输入读字符
- int fscanf(FILE *stream, const char *format, ...);//从指定的文件stream中读字符
- int sscanf(const char *str, const char *format, ...);//从指定的字符串str中读字符
-
- #include
- int vscanf(const char *format, va_list ap);
- int vfscanf(FILE *stream, const char *format, va_list ap);
- int vsscanf(const char *str, const char *format, va_list ap);
- 返回值:返回成功匹配和负值的参数个数,成功匹配的参数可能少于所提供的负值参数,返回0表示一个都不匹配,出错或者读到文件或字符串末尾时返回EOF并设置errno
例子:
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define MAXLINE 80
-
- void err_sys(const char *fmt, ...)
- {
- int err = errno;
- char buf[MAXLINE+1];
- va_list ap; va_start(ap, fmt);
- vsnprintf(buf, MAXLINE, fmt, ap);
- snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s", strerror(err));
- strcat(buf, "\n");
- fputs(buf, stderr);
- va_end(ap);
- exit(1);
- }
-
- int main(int argc, char *argv[])
- {
- FILE *fp;
- if (argc != 2)
- {
- fputs("Usage: ./a.out pathname\n", stderr);
- exit(1);
- }
- fp = fopen(argv[1], "r");
- if (fp == NULL)
- err_sys("Line %d - Open file %s", __LINE__, argv[1]);
- printf("Open %s OK\n", argv[1]);
- fclose(fp);
- return 0;
- }
9.c标准库的I/O缓冲区
c标准库的I/O缓冲区有三种类型:
全缓冲:如果缓冲区写满了就写回内核,常规文件通常是全缓冲的。
行缓冲:如果用户程序写的数据中有换行符,就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。
无缓冲:用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。
二、Unbuffered I/O
1.open/close
- #include
- #include
- #include
-
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, made_t mode);
- 返回值:成功返回新分配的文件描述符,错误返回-1 ,并设置errno
- flags:
- O_RDONLY只读打开;O_WRONLY只些打开;O_RDWR可读可写打开,以上三个常数中必须指定一个,且仅允许指定一个,以下选项可以同时指定0个或多个。
- O_APPEND追加;
- O_CREAT若此文件不存在则创建它,此时必须提供参数mode
- O_EXCL如果同时指定了O_CREAT,并且文件已存在,则出错返回
- O_TRUNC如果文件存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)为0字节
- O_NONBLOCK对于设备文件,以此方式打开可以做非阻塞I/O
-
- mode:指定文件权限,可以用八进制数表示,如0644表示-rw-r--r--,也可以用S_IRUSR,S_IWUSR等宏定义按位或表示
-
- #include
- int close(int fd);//fd是要关闭的文件描述符
- 返回值:成功返回0,出错返回-1并设置errno
2.read/write函数
- #include
- ssize_t read(int fd, void *buf, size_t count);//从打开的设备或文件中读取数据
- 返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0
- ssize_t write(int fd, const void *buf, size_t count);//向打开的设备或文件中写数据
- 返回值:成功返回写入的字节数,出错返回-1并设置errno
3.lseek函数:
- #include
- #include
-
- off_t lseek(int fd, off_t offset, int whence);
- 参数offset和whence的含义和fseek函数完全相同
- 若lseek成功执行,则返回新的偏移量,因此可以用以下方法确定一个打开文件的当前偏移量:
- off_t currpos;
- currpos = lseek(fd, 0, SEEK_CUR);
- 注意:fseek成功时返回0失败时返回-1,要返回当前偏移量需要调用ftell,而lseek成功时返回当前偏移量失败时返回-1
4.fcntl函数:
可以用fcntl函数改变一个已打开的文件的属性,可以重新设置读、些、追加、非阻塞等标志(这些标志称为File Status Flag),而不必重新open文件。
- #include
- #include
-
- int fcntl(int fd, int cmd);
- int fcntl(int fd, int cmd, long arg);
- int fcntl(int fd, int cmd, struct flock *lock);
例子:
- #include
- #include
- #include
- #include
- #include
-
- #define MSG_TRY "try again\n"
- int main(void)
- {
- char buf[10];
- int n;
- int flags;
- flags = fcntl(STDIN_FILENO, F_GETFL);
- flags |= O_NONBLOCK;
- if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1)
- {
- perror("fcntl");
- exit(1);
- }
- tryagain:
- n = read(STDIN_FILENO, buf, 10);
- if (n < 0)
- {
- if (errno == EAGAIN)
- {
- sleep(1);
- write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
- goto tryagain;
- }
- perror("read stdin");
- exit(1);
- }
- write(STDOUT_FILENO, buf, n);
- return 0;
- }
5.ioctl函数:
ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的,称为Out-of-band
数据。也就是说,read/write读写的数据是in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数
据。
- #include
- int ioctl(int d, int request, ...);
- d是某个设备的文件描述符,request是ioctl的命令,可变参数取决于request,通常是一个指向变量或结构体的指针。若出错则返回-1,若成功则返回其它值,返回值也是取决于request
例:
- #include
- #include
- #include
- #include
- int main(void)
- {
- struct winsize size;
- if (isatty(STDOUT_FILENO) == 0)
- exit(1);
- if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)<0)
- {
- perror("ioctl TIOCGWINSZ error");
- exit(1);
- }
- printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
- return 0;
- }
6.mmap函数:
mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来做而不需要read/write函数。
- includ
- void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
- 如果mmap成功则返回映射首地址,如果出错则返回参数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射,munmap成功返回0,出错返回-1.
- addr:内存地址
- len:是需要映射的那一部分文件的长度
- off:是从文件的什么位置开始映射,必须是页大小的整数倍(32位体系结构上通常是4k)
- filedes:代表文件的描述符
- port参数有四种取值:
- 1.PROT_EXEC:表示映射的这一段可执行,例如映射共享库
- 2.PROT_READ:表示映射的这一段可读
- 3.PROT_WRITE:表示映射的这一段可写
- 4.PROT_NONE:表示映射的这一段不可访问
- flag:常用有两种参数
- 1. MAP_SHARED:多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
- 2. MAP_PRIVATE:多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程也不会看到这种变化,也不会真的写到文件中去。