Linux标准I0编程

Linux标准I0编程

Linux系统调用和用户程序编程接口

系统调用

操作系统提供的一组特殊接口---系统调用.通过这组接口,用户程序可以使用操作系统内核提供的各种功能.

用户程序向操作系统提出请求的接口就是系统调用.

用户程序编程接口

实际开发中程序并不会直接使用系统调用接口 而是使用用户程序编程接口(API)

为什么不使用系统调用接口,原因如下

  1. 系统调用接口功能非常简单,无法法満足程序的需求.
  2. 不同操作系统的系统调用接口不兼容,程序移植时工作量大.

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. 按字符(字节)输入/输出
  2. 字符输入/输出函数一次仅读写一个字符。字符输入/输出函数语法要点

表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. 按行输入/输出
    行输入/输出函数一次操作一行。行输入/输出函数语法要点如表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;
}
  1. 以指定大小为单位读写文件

在文件流被打开之后,可对文件流按指定大小为单位进行读写操作。

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;
}
相关推荐
Kina_C5 小时前
Linux iptables 防火墙原理与实操——从四表五链到 NAT 配置
linux·运维·服务器·iptables
wdfk_prog5 小时前
嵌入式面试真题第 15 题:不可恢复异常后的通用崩溃快照、调用栈保存与离线分析架构
linux·开发语言·面试·架构
jsons111 小时前
autofs挂载
linux·服务器·网络
liwulin050611 小时前
【ollama】自定义结构化输出
linux·前端·数据库·ollama
gs8014011 小时前
【实战】记一次 Linux 服务器入侵排查:grok-agent 后门木马深度剖析与彻底清理
linux·运维·服务器
时空无限12 小时前
vllm 大模型启动缓存相关环境变量 export
linux·缓存·vllm
Echo_cy_12 小时前
基于ZYNQ-7000的Ethernet驱动配置
linux·驱动开发·嵌入式硬件·fpga开发·ethernet·zynq
ShineWinsu12 小时前
对于Linux:模版方法类的解析以及socket、TcpSocket的封装
linux·c++·面试·socket·模板方法模式·封装·tcpsocket
做个文艺程序员13 小时前
Linux第27篇:在Linux服务器部署本地大模型:Ollama+开源LLM实战
linux·服务器·开源·大模型·ollama
无足鸟ICT13 小时前
【RHCA+】查看变量
linux·运维·服务器