Linux标准I0编程
Linux系统调用和用户程序编程接口
系统调用
操作系统提供的一组特殊接口---系统调用.通过这组接口,用户程序可以使用操作系统内核提供的各种功能.
用户程序向操作系统提出请求的接口就是系统调用.
用户程序编程接口
实际开发中程序并不会直接使用系统调用接口 而是使用用户程序编程接口(API)
为什么不使用系统调用接口,原因如下
- 系统调用接口功能非常简单,无法法満足程序的需求.
- 不同操作系统的系统调用接口不兼容,程序移植时工作量大.
Linux标准I/0概述
标准IO的由来
标准I/0指的是ANSI C中定义的用于I/0操作的一系列函数.
只要操作系统中安装了C库,标准I/0函数就可以调用.如查程序中使用的是标准I/0函数,那么源代码不需修改就可以在其他操作系统下编译运行,具有更好的可移值性.
除此之外,使用标准I/0可以减少系统调用的次数,提高系统效率.标准I/O函数在执行时也会用到系统调用.在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态.如果频繁地执行系统调用会增加系统的开销.为了避免这种情况,标准I/0使用时在用户空间创建缓冲区,读写时先操作缓冲区,在合适的时机通过系统调用访问实际的文件,从而减少了使用系统调用的次数.
流的含义
标准I/0的核心对象就是流.当用标准I/0打开一个文件时,就会创建一个FILE结构体描述该文件(或者理解为创建一个FILE结构和实际打开的誩件关联起来).我们把这个FILE结构体形象地称为流.标准I/O函数都基于流进行各种操作.
标准I/0中流的缓冲类型有以下3种
(1) 全缓冲. 在这种情况下,当填满I/0缓冲区后地进行实际I/0操作.对于存在磁盘上普通文件,用标准I/O打开时默认是全缓冲的.当缓冲区已满或执行flush操作时才会进行磁盘操作.
(2) 行缓冲.在这个种情况下,当在输入和输出中遇到换行符时执行I/0操作.标准输入流和标准输出流就是使用行缓冲的典型例子.
(3)无缓冲,不对I/O操作进行缓冲,即对流的读写时会立刻操作实际的文件.标准出错流是不带缓冲的,这就使行出错信息可以立刻显示在终端上,而不管输出内容是包含换行符.
标准I/O编程
1.3.1流的打开
用使用标准I/O打开文件的函数有fopen()、fdopen()和freopen()。它们可以以不同的模式打开文件,都返回一个指向 FILE的指针,该指针指向对应的I/O流。此后,对文件的读写都通过这个FILE指针来进行。其中,fopenO可以指定打开文件的路径和模式,fdopen()可以指定打开的文件描述符和模式,而freopen(除可指定打开的文件和模式外,还可指定特定的I/O流。
fopen()函数语法要点如表1.1所示。
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | FILE * fopen ( const char * path, const char * mode) ; |
| 函数参数 | path:包含要打开的文件路径及文件名, mode:文件打开方式 |
| 函数返回值 | 成功:指向FILE的指针 ,失败:NULL |
表1.2mode取值说明
| 值 | 操作说明 |
|---|---|
| r或rb | 打开只读文件,该文件必须存在 |
| r+成r+b | 打开可读写的文件,该文件必须存在 |
| w成wb | 打开只写文件,若文件存在则文件长度为0,即会擦写文件以前的内容:若文件不存在则建立该文件 |
| w+或w+b | 打开可读写文件,若文件存在则文件长度为0,即会擦写文件以前的内容:若文件不存在则建立该文件 |
| a或ab | 以附加的方式打开只写文件。若文件不存在,则会建立该文件:如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保 |
| a+或a+b | 以附加方式打开可读写的文件。若文件不存在,则会建立该文件;如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留 |
在每个选项中加入b字符用来告诉函数库打开的文件为二进制文件,而非纯文本文件。不过在Linux系统中会忽略该符号。
当用户程序运行时,系统自动打开了3个流:标准输入流stdin、标准输出流stdout 和标准错误流stderr。stdin用来从标准输入设备(默认是键盘中读取输入内容:stdout 用来向标准输出设备(默认是当前终端)输出内容:stderr用来向标准错误设备(默认是当前终端)输出错误信息。流的打开与关闭
流的关闭
关闭流的函数为fclose(),该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。
fclose()函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include<stdio.h> |
| 函数原型 | int fclose(FILE·stream): |
| 函数参数 | stream:已打开的流指针 |
| 函数返回值 | 成功:0 ,失败:EOF |
程序结束时会自动关闭所有打开的流。
错误处理
标准I/0函数执行时如果出现错误,会把错误码何存在全局变量errno中,程序员可以通过相应的函数打印错误信息.
perr()语法要点如表 1.4 所示。
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | void perror(const char*s) |
| 函数参数 | s:在标准错误流上输出的信息 |
| 函数返回值 | 无 |
c
#include <stdio.h>
int main(){
FILE*fp;//定义流指针
if((fp=fopen("1.txt"))==NULL){//NULL是系统定义的宏,其值为0
perror("fail to fopen):输出错误信息
return-1:
}
fclose(fp);
return 0;
}
如果文件1.txt不存在,程序执行时会打印如下信息。
fail to fopen:No such file or directory
错误处理相关函数strerror0语法要点如表1.5所示。
表1.5strerror()函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <string.h>,#include <errno.h> |
| 函数原型 | char *strerror(int errnum) |
| 函数参数 | 错误码 |
| 函数返回值 | 错误码对应的错误信息 |
c
include <stdio.h>
int main(){
FILE *p
if((fp = fopen("1.txt","r"))==NULL){
pintt(fail to fopen:sinstrerror(errno))
return-1:
}
felose(fp);
return 0:
}
如果文件1.txt不存在,程序执行时会打印如下信息。
fail to fopen:No such file or directory
流的读写
- 按字符(字节)输入/输出
- 字符输入/输出函数一次仅读写一个字符。字符输入/输出函数语法要点
表1.6字符输入函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | int getc(FILE·stream);int fgetc(FILE·stream);int getchar (void); |
| 函数参数 | stream:要输入的文件流 |
| 函数返回值 | 成功:读取的字符 失败:EOF |
getc()和fgetc()从指定的流中读取一个字符(节),getchar0()从stdin中读取一个字符(节)。
表1.7字符输出函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | int putc(int c,FILE* stream);int fpute(int c,FILE stream);int putchar(int c); |
| 函数返回值 | 成功:输出的字符 失败:EOF |
putc()和fputc()向指定的流输出一个字符(节),putchar()函数向stdout输出一个字符(节).
c
include <stdio.b>
int main(){
int c;
while(1){
c = fgetc(stdin);//从键盘读取一个字符
if((c>='0') && (c<='9')){
fputc(c,stdout);//若输入的是数字输出
}
if(c == '\n'){
break; //若遇到换行符,跳出循环
}
}
return O;
}
- 按行输入/输出
行输入/输出函数一次操作一行。行输入/输出函数语法要点如表1.8和表1.9所示。
表1.8行输入函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | char*gets(char *s) |
| 函数参数 | stream:对应的流 |
| 函数返回值 | 成功:s ,失败或到达文件末尾:NULL |
gets函数容易造成缓冲区溢出,不推荐大家使用。
fgets从指定的流中读取一个字符串,当遇到n时,会读取n 或读取
size-1个字符后返回。注意:fgets不能保证每次都能读出一行。
表1.9行输出函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | int puts (const char *s),int fputs (const char * s,FILE * stream) |
| 函数参数 | s:存放输出字符串的缓冲区首地址 ,stream:对应的流 |
| 函数返回值 | 成功:s,失败:NULL |
下面以fgets()为例计算一个文本文件的行数。
c
/*fgets.c*/
#include<stdio.h>
#include<string.h>
int main(int argc,char *argv[]){
int line=0;
char buf[128];
FILE *fp
if(argc<2){
printf("Usage: %s<file>\n",argv[0]);
return -1;
}
if((fp=fopen(argv[1],"r"))==NULL){
perror("fail to fopen")
return -1;
}
while(fgets(buf,128,fp) != NUll){
if(buf[strlen(buf)-1]=='\n'){
line++;
}
}
printf("The line of %s is %d\n",argv[1],line);
return 0;
}
- 以指定大小为单位读写文件
在文件流被打开之后,可对文件流按指定大小为单位进行读写操作。
fread(函数语法要点如表1.10所示。
表1.10 fread()函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | size_t fread (void·ptr, size_t size, size_tnmemb, FILE* stream); |
| 函数参数 | ptr:存放读入记录的缓冲区 ,size:读取的每个记录的大小,nmemb:读取的记录数,strea m:要读取的文件流 |
| 函数返回值 | 成功:返何实际读取到的nmemb数目,失败: EOF |
|
fwrite()函数语法要点如表1.11所)
表1.11 fwrite()函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | size_t fwrite (const voidptr, size_t size, size t nmemb, FILE strea m); |
| 函数参数 | ptr:存放写入记录的缓冲区,size:写入的每个记录的大小,nmemb:写入的记录数,stream:要写入的文件流 |
| 函数返回值 | 成功:返回实际写入的nmemb数目,失败:EOF |
流的定位
每个打开的流内部都有一个当前读写位置。流在打开时,当前读写位置
为0,表示文件的开始位置。每读写一次后,当前读写位置自动增加实际读
写的大小。在读写流之间可先对流进行定位,即移动到指定的位置再操作。
流的定位相关函数语法要点如表1.12和表1.13所示。
表1.12fseek 函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | int fseek(FTr, stream,1onofSet,inthene), |
| 函数参数 | stream:要定位的文件流 offset:相对于基准值的偏移量 whence:基准值 |
| 函数参数 | SEEK SET代表文件起始位置 SEEK _END代表文件结束位置 SEEK CUR代表文件当前读写位置 |
| 函数返回值 | 成功:0 ,失败:EOF |
表1.13ftell()函数语
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | long ftell ( FILE *stream); |
| 函数参数 | strea m:要定位的文件流 |
| 函数返回值 | 成功:返回当前读写位置,失败:EOF |
下面的例子为获取一个文件的大小。
c
/*ftell.c*/
#include <stdio.h>
int main (int argc, char *argv[]){
FILE *fp;
if(argc <2)
{
printf("Usage:%s <file>\n",argv[0]);
return-1:
}
if((fp = fopen(argv[1],"r"))==NULL){
perror("fail to fopen")
return -1;
}
fseek(fp,0,SEEK_END);
printf("The size of %s is %ld\n",argv[1],ftell(fp));
return 0;
}
运行该程序,结果如下。 $./a.out test.txt
The size of test.txt is 305
格式化输入/输出
格式化输入/输出函数可以指定输入/输出的具体格式,包括读者已经非常熟悉的printf(、scanf(等函数。以下简要介绍它们的语法要点,如表1.14 和表1.15所示。
表1.14格式化输入函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | |
| 函数原型 | int scanf (const char *format,...); |
| 函数原型 | int fscanf (FILE *fp,const char *format,...); |
| 函数原型 | int sscanf (char *buf, const char *format,...); |
| 函数传入值 | format:输入的格式 fp:作为输入的流 ,buf:作为输入的缓冲区 |
| 函数返回值 | 成功:输出字符数( sprintf返回存入数组中的字符数),失败:EOF |
表1.15格式化输出函数语法要点
| 描述 | c |
|---|---|
| 所需头文件 | #include <stdio.h> |
| 函数原型 | int printf (const char *format,...); |
| 函数原型 | int sprintf (char *buf, const char *format,...); |
| 函数原型 | int fprintf (FILE*fp, const char *format,...); |
| 函数参值 | format:输出的格式,fp:接收输出的流buf:接收输出的缓冲区 |
| 函数返值 | 成功:输出字符数(sprintf返回存入数组中的字符数) ,失败:EOF |
fprintf和sprintf在应用开发中经常会用到,建议读者查看其帮助信息以掌握其用法。
实验内容
文件的复制
1.实验目的
通过对文件进行复制,掌握流的基本操作。
2.实验内容
在程序中分别打开源文件和目标文件。循环从源文件中读取内容并写入目标文件。
3.实验步骤
(1)设计流程。
检查参数→打开源文件→打开目标文件→循环读写文件→关闭文件(2)编写代码。
c
/*mycopy.c*/
#include <stdio.h>
#include serrno.h>
#define N 64
int main (int argc, char *argv[]){
int n;
char buf[N];
FILE *fps,*fpd;
if(argc<3)
{
printf(" Usage:%s <sr_file> <dst_file>\n",argv[0]);
return -1;
}
if((fps = fopen(argv[1],"r"))==NULL)
{
fprintf(stderr,"fail to fopen %s:%s\n",argv[1],strerror(errno));
return -1;
}
if((fpd = fopne(avgv[2],"w"))==NULL){
fprintf(stderr,"fail to fopen %s\n",argv[2],strerror(errno));
fcolse(fps);
}
while((n = fred(buf,1,N,fps))>=0){
fwrite(buf,1,N,fpd);
}
fclose(fps);
fclose(fpd);
return 0;
}
循环记录系统时间
1.实验目的
· 获取系统时间。
在程序中延时。
·流的格式化输出。
2.实验内容
程序中每隔一秒读取一次系统时间并写入文件。
3.实验步骤
(1)设计流程。
打开文件→获取系统时间→写入文件→延时1s→返回第二步(获取系统
时间)
(2)编写代码。
c
/*mycopy.c*/
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#define N 64
int main Cint argc, char *argv0) {
int n;
char buf[N];
FILE *fps;
if(argc<2){
printf(" Usage:%s <file>\n",argv[0]
return -1;
}
if((fp=fopen(argv[1],"w"))==NULL){
perror("fail to fopen");
return -1;
}
while(1){
time(&t);//获取系统时间
fprintf(fp,"%s\n",ctime(&t));//将秒数转换成本地时间并写入指定的流
sleep(1);//延时1s
}
fclose(fp)
return 0;
}