标准IO
标准IO
-
输入输出设备
-
字符输入设备:键盘
-
图像输入设备:鼠标,扫描仪
-
图像输出设备:显示器,打印机
-
输入输出设备:USB,u盘
-
-
标准IO:表示程序与操作系统或其他程序之间进行数据交换的方式。
- 标准输入(stdin)是程序从外部获取数据的接口,通常指键盘输入
- 标准输出(stdout)是程序向外部输出数据的接口,通常指屏幕输出
-
标准I/O-流:FILE被称为流(stream)
-
文本流:以逐行或逐字符的方式处理文本数据
-
二进制流:以字节为单位处理数据
-
注意:文本流可以直接用于处理普通的文本文件,例如.txt文件。而二进制流适用于处理任意类型的文件。由于字符编码的原因,文本流在跨平台和多语言处理时可能存在一些问题。而二进制流不受字符编码的限制,可以更好地进行数据传输和处理。
-
-
流的缓冲类型
-
无缓冲流:无缓冲流将数据直接从输入设备读取或直接将数据写入输出设备,没有中间缓冲区。每个读写操作都直接与底层设备进行交互,读写效率较低。如:
System.in
和System.out
在某些环境下就是无缓冲的。 -
行缓冲流:行缓冲流会在遇到换行符时进行数据的读取或写入,否则将数据存储在内部缓冲区中。当达到一行结束时,或者遇到缓冲区已满的情况,才进行实际的I/O操作。如:使用
BufferedReader
和BufferedWriter
包装的字符流对象 -
缓冲流(全缓冲流):缓冲流缓存一定量的数据,减少了实际I/O操作的次数,提高了读写的效率。在从输入设备读取数据或向输出设备写入数据时,缓冲流会先将数据存储在内部缓冲区中,当缓冲区满时才进行实际的读写操作。如:
BufferedInputStream
和BufferedOutputStream
用于处理二进制数据,以及BufferedReader
和BufferedWriter
用于处理字符数据
-
#include <stdio.h> //标准IO的头文件
int main(int argc,char*argv[]){
printf("Hello world1\n");//遇到换行符,输出
printf("Hello world2");//程序结束后,会清空缓存,输出
}
-
标准I/O-stdin,stdout,stderr
-
stdin(Standard Input)0:标准输入流是程序从用户获取输入数据的通道。通过stdin,程序可以读取用户在终端键盘上输入的数据。
-
stdout(Standard Output)1:标准输出流是程序向用户输出结果的通道。通过stdout,程序可以将文本、数据等输出到终端显示器上。
-
stderr(Standard Error)2:标准错误流是程序在发生错误或异常时输出错误信息的通道。与stdout类似,stderr也将输出内容发送到终端显示器上。
-
文件的打开和关闭
-
打开文件:
FILE * fopen(const char * filename, const char * mode)
-
该函数用于打开一个文件,并返回一个指向
FILE
结构体的指针,表示文件流 -
filename
:要打开的文件的路径字符串。可以是相对路径或绝对路径。 -
mode
:打开文件的模式字符串,指定打开方式。常用的模式包括: -
"r"
:以只读方式打开文件。如果文件不存在,打开失败。 -
"w"
:以写入方式打开文件。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件。 -
"a"
:以追加方式打开文件。如果文件存在,则在文件末尾追加内容;如果文件不存在,则创建新文件。
-
mode模式 | 描述 | 同 |
---|---|---|
" r" | 以只读方式打开文件。如果文件不存在,打开失败 | “rb” |
“ r+” | 以读写方式打开文件,其他同“r” | “r+b” |
" w" | 以写入方式打开文件。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件 | “wb” |
" w+" | 以读写方式打开文件,其他同“w” | “w+b” |
" a" | 以追加方式打开文件。如果文件存在,则在文件末尾追加内容;如果文件不存在,则创建新文件 | “ab” |
" a+" | 以读写方式打开文件,其他同“a” | “a+b” |
-
关闭文件:
int fclose(FILE * stream)
- 该函数用于关闭指定的文件流。它接受一个
FILE
结构体指针作为参数,表示要关闭的文件流 - 如果关闭成功,则返回 0 ;否则返回
EOF
(-1),并设置errno - 流关闭时自动刷新缓冲中的数据并释放缓冲区
- 当一个程序正常终止时,所有打开的流都会被关闭
- 该函数用于关闭指定的文件流。它接受一个
-
处理错误信息
perror(const char* str)
perror
函数将上一个发生的系统错误转换为人类可读的错误描述,并将其输出到标准错误流(stderr
)。- 可以传递一个字符串参数
str
,用于在错误描述之前添加额外的说明信息。 - 如果发生了错误,它会打印类似于 “str: 错误描述” 的内容,其中 “str” 是传递的字符串参数,“错误描述” 是根据错误代码生成的具体错误信息。
char* strerror(int errnum)
strerror
函数将给定的错误号errnum
转换为对应的错误描述字符串。- 它返回一个指针,指向一个包含错误描述的静态字符数组。
errnum
通常是errno
全局变量的值,用于表示最近发生的错误。
#include <stdio.h> //标准IO的头文件
#include <errno.h> //perror,streeeor
#include <string.h>
int main(int argc,char*argv[]){
FILE *fp;
fp = fopen("1.txt","r");//以只读方式打开当前文件夹下的1.txt文件
if(fp==NULL){
perror("Open file failed:");//若文件不存在,输出“Open file failed:No such file or directory”
printf("Open file failed:%s\n",strerror(errno));//strerror 函数将 errno 的值转换为相应的错误描述字符串,并将其打印出来
}else{
printf("Open file success\n");
fpret = fclose(fp);//关闭文件
if(fpret == 0){
printf("File close sucess\n");
}else{
perror("fclose error:");
}
}
return 0;
}
标准IO的读写
- 流支持不同的读写方式:
- 读写一个字符:
fgetc()/fputc()
一次读/写一个字符 - 读写一行:
fgets()/fputs
一次读/写一行 - 读写若干个对象:
fread()/fwrite()
每次读/写若干个对象,而每个对象具有相同的长度
- 读写单个字符
-
int fgetc(FILE* stream)
-
fgetc
,从指定的文件流stream
中读取下一个字符,并将其作为无符号字符转换为整数类型返回。 -
如果读取成功,则返回读取的字符的整数表示。
-
如果到达文件末尾或发生错误,则返回
EOF
。 -
等同于
getc
-
getchar
等同于fgetc(stdin)
,标准输入(键盘)
-
int rec;
rec = fgetc(fp); //取出文件的一个字符
printf("Get char=%c\n",rec);
rec = getchar(); //阻塞,等待键盘输入
printf("Get STD input=%c\n",rec);
-
int fputc(int c, FILE* stream)
-
fputc
,将整数c
对应的字符写入到指定的文件流stream
中。 -
如果写入成功,则返回写入的字符的整数表示。
-
如果发生错误,则返回
EOF
。 -
等同于
putc
-
putchar(c)
等同于fputc(c,stdout)
-
int rec,wrc = 'w';
rec = fputc(wrc,fp);//文件需有写入格式打开才可写入,否则报错
if (rec==-1){ //出错处理
perror("fputc:");
fclose(fp);
return 0;
}
fputchar(wrc);
- 按行输入
-
char *fgets(char *str, int num, FILE *stream)
fgets
,从给定的文件流中读取一行文本,并将其存储在指定的字符数组str
中。- 它会读取最多
num-1
个字符或者遇到换行符为止,然后在末尾添加null终止符(‘\0’)。 - 如果成功读取一行,则返回指向
str
的指针;否则返回NULL - 注意:gets函数已被淘汰,因为会导致缓存区溢出
-
int fputs(const char *str, FILE *stream)
fputs
,将给定的字符串str
写入到指定的文件流中。- 它会将字符串中的所有字符都写入文件,直到遇到null字符为止。
- 如果成功写入,则返回非负值;否则返回EOF。
ret = fgets(buff,5,stdin);//键盘输入
ret = fgets(buff,5,pf);//文件读取
if(ret==NULL){
perror("fgets:");
fclose(fp);
return 0;
}
printf("buff=%s\n",buff);
puts("hello world1");//将缓冲区总督字符串输出到stdout,并追加'\n'
fputs("hello world2",stdout);//不最加'\n'
fputs("hello world2",fp);//写入到文件(文件以可写方式打开)
- 二进制读写
size_t fread(void *ptr, size_t size, size_t count, FILE *stream)
fread
,从给定的文件流中读取二进制数据,并将其存储到指定的内存块ptr
中。- 读取
count
个数据项,每个数据项的大小为size
字节,并将读取的数据存储在ptr
中。 - 如果成功读取了全部或部分数据,则返回实际读取的数据项个数,否则返回一个小于
count
的值
char *buff;
buff = (char*)malloc(100);//申请内存
ret = fread(buff,10,1,fp);
printf("buff=%s\n",buff);
free(buff);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
fwrite
,将二进制数据从给定的内存块ptr
写入到指定的文件流中。- 将
count
个数据项,每个数据项的大小为size
字节,从ptr
中写入到文件流中。 - 如果成功写入了全部或部分数据,则返回实际写入的数据项个数,否则返回一个小于
count
的值。
struct student{
char name[10];
int age;
char sex[8];
}stu;
strcpy(stu.name,"zhangsan");
stu.age = 28;
strcpy(stu.sex,"male");
ret = fwrite(&stu,sizeof(stu),1,fp);
流刷新定位、格式化输入输出
- 流的刷新:
fflush
函数
int fflush(FILE *fp);
- 刷新输出缓冲区,以确保将缓冲区中的数据立即写入到目标设备,如文件或终端。
- 成功时返回0,出错放回EOF
- Linux下只能刷新输出缓冲区,输入缓冲区丢弃
- 流的定位:
ftell、fseek和rewind函数
-
进行文件流的定位操作。
-
ftell函数:
long ftell(FILE *stream);
-
ftell函数用于获取当前文件指针的位置。
-
参数stream是一个指向FILE对象的指针,该对象表示要进行定位操作的文件流。ftell函数返回一个long类型的值,表示当前文件指针相对于文件起始位置的偏移量(以字节为单位)。如果调用失败,它将返回-1。
-
注意:以 a 模式打开文件,fseek无效
-
-
fseek函数:
int fseek(FILE *stream, long offset, int origin);
-
fseek函数用于将文件指针移动到指定位置。
-
参数stream是一个指向FILE对象的指针,offset是一个长整型的值,表示要移动的偏移量,可正负。origin指定了基准点,可以取:SEEK_SET,SEEK_CUR,SEEK_END
-
SEEK_SET:从文件起始位置开始计算偏移量。
-
SEEK_CUR:从当前文件指针位置开始计算偏移量。
-
SEEK_END:从文件末尾位置开始计算偏移量。 fseek函数返回0表示成功,返回非零值表示失败。
-
-
rewind函数:
void rewind(FILE *stream);
-
rewind函数用于将文件指针重置到文件起始位置。
-
参数stream是一个指向FILE对象的指针。该函数不返回任何值。
-
这些定位函数在文件操作中非常有用。通过ftell函数可以获取当前文件指针的位置,fseek函数可以将文件指针移动到指定位置,而rewind函数则可以将文件指针重置到起始位置。这样就可以根据需要随时定位文件流的读写位置。
-
rewind(fp)相当于fseek(fp,0,SEEK_SET)
-
-
注意:这三个函数只适用2G以下的文件
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("Failed to open the file.");
return 1;
}
long position = ftell(file); //ftell函数获取当前文件指针的位置
printf("Current position: %ld\n", position);
fseek(file, 10L, SEEK_SET); //fseek函数将文件指针移动到从文件起始位置偏移10个字节的位置
rewind(file); //rewind函数将文件指针重置到文件起始位置
fclose(file);
return 0;
}
- 格式化输出
printf
函数:int printf(const char *fmt,...);
- printf函数将格式化的字符串输出到控制台
fprintf
函数:int fprintf(FILE *stream,const char *fmt,...);
- fprintf函数将格式化的字符串写入到文件中
sprintf
函数:int sprintf(char *s,const char *fmt,..);
- sprintf函数将中格式化的字符串写入到字符数组str
- 成功返回输出的字符个数,出错时返回EOF
#include<stdio.h>
int main(){
int num=42;
char str[100];
sprintf(str,"The number is :%d",num);
printf("%s\n",str);
FILE *fp = fopen("output.txt","w");
if (fp == NULL){
printf("Failed to open the file.");
return 1;
}
fprintf(file,"The number is:%d\n",num);
fclose(fp);
return 0;
}
- 格式化输入
fscanf
函数:int fscanf(FILE *stream,const char *format,...);
- fscanf函数从文件中读取格式化的内容
sscanf
函数:int sscanf(const char *str,const char *format,...);
- sscanf函数从格式化的字符串中读取内容
#include <stdio.h>
int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("Failed to open the file.");
return 1;
}
int num;
fscanf(fp, "%d", &num);
printf("The number is: %d\n", num);
fclose(file);
char str[] = "The number is: 42";
sscanf(str, "The number is: %d", &num);
printf("The number is: %d\n", num);
return 0;
}
文件I/O
-
文件 I/O(Input/Output)是指在计算机系统中对文件进行读取和写入的操作。它允许程序从外部存储器(如硬盘或网络)读取数据,并将数据写入到文件中。
- 文件 I/O 的实现方式因编程语言而异。在 Python 中,可以使用内置的 open() 函数打开文件,然后使用文件对象的方法读取或写入数据。
- 不提供缓冲机制
-
POSIX(Portable Operating System Interface for Unix)是一个面向操作系统接口的标准。定义了许多操作系统功能和服务的接口,包括进程管理、线程、文件系统、I/O、网络、信号处理、时间和日期等。它还提供了用于编译和链接的宏和常量,以及用于命令行和脚本编程的工具。
- 提高程序的可移植性、促进代码重用、促进互操作性、
标准I/O | 文件I/O(低级IO) | |
---|---|---|
打开 | fopen,freopen,fdopen | open |
关闭 | fclose | close |
读 | getc,fgetc,getchar; fgets,gets; gread | read |
写 | putc,fputs,putchar; fputs,puts; fwrite | write |
- open
include<fcntl.h>
int open(const char*pathname,int flags);//打开文件
int open(const chat*pathname,int flags,made_t mode);//创建文件,mode指定新文件的权限
//真正建文件时的权限会受到umask值影响,实际权限时mode-umaks
-
成功时返回文件描述符,出错时返回EOF
-
可以打开设备文件,但不能创建设备文件
-
umask:用来设定文件或目录的初始权限
- 文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限
- close
#include<unistd.h>
int close(int fd);//用于关闭一个打开的文件
- 成功时返回0,出错时返回EOF
- 程序结束时自动关闭所有打开的文件
- 文件关闭后,文件描述符不再代表文件
- read、write
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t count);//从文件读取数据
ssize_t write(int fd,void *buf,size_t count);//向文件写入内容
- 成功时返回实际读取的字节数;出错时返回EOF
- 读到文件末尾时返回0
- buf是接收数据的缓冲区
- count不应超过buf大小
- lseek
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
-
成功时返回当前的文件读写位置,出错时返回EOF
-
参数offset和参数whence同fseek完全一样
标准I/O与文件I/O
-
相同点:
- 数据交互:都是用于数据的输入和输出,实现程序与外部环境的数据交互。
- 数据流:两者都是通过数据流的方式进行读取和写入操作。
-
不同点:
- 数据来源和去向:标准I/O主要用于从标准输入设备(例如键盘)读取数据或将数据输出到标准输出设备(例如终端),而文件I/O主要用于读取和写入文件中的数据。
- I/O对象:标准I/O使用标准输入流(
stdin
)、标准输出流(stdout
)和标准错误流(stderr
)作为I/O对象,通过这些对象进行输入和输出。文件I/O使用文件对象作为I/O对象,通过打开和操作文件对象来进行读写操作。 - 编程接口:标准I/O通常以函数调用的形式提供,例如在C语言中使用
scanf()
、printf()
等函数。文件I/O则常通过文件对象的方法和属性进行操作,例如在Python中使用read()
、write()
等方法。 - 数据存储:标准I/O的数据通常是临时性的,不会被永久保存,而文件I/O的数据通常会被写入到文件中,进行持久化存储。
- 使用环境:标准I/O是在终端或控制台等交互式环境中使用较多,而文件I/O则适用于需要读写文件的情况,比如处理大量数据、配置文件操作等。
文件属性和目录操作
- 访问目录
- opendir :函数用于打开一个目录文件
#include<dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd);//使用文件描述符,要配合opendir使用
- DIR是用来描述一个打开的目录文件的结构体类型
- 成功时返回目录流指针,出错时返回NULL
- chmod / fchmod:修改文件访问权限
#include<sys/stat.h>
int chmod(const char*path,mode_t mode);
int fchmod(int fd, mode_t mode);
- 成功时返回0;出错时返回EOF
- root和文件所有者修改文件的访问权限
- chmod(“test.txt”, 0666);
- stat / lstat / fstat:获取文件属性
#include<sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd,struct stat *buf);
-
成功时返回0;出错时返回EOF
-
如果path是符号链接,stat获取的是目标文件的属性,而lstat获取的是链接文件的属性
-
stat
结构体中包含了多个字段来描述文件的各种属性信息,如:-
st_dev
:文件所在设备的ID。 -
st_ino
:文件的inode节点号。 -
st_mode
:文件的类型和访问权限。 -
st_nlink
:文件的硬链接数目。 -
st_uid
:文件所有者的用户标识符。 -
st_gid
:文件所有者的组标识符。 -
st_size
:文件大小(字节数)。 -
st_blksize
:文件系统的I/O缓冲区大小。 -
st_blocks
:分配给文件的块数。
-
静态库和动态库
-
库的概念
-
库是一个二进制文件,包含的代码可被程序调用
-
标准C库,数学库,线程库
-
库有源码,可下载编译,也可以直接安装二进制文件
-
/lib、/usr/lib
-
Linux下包含静态库和动态库
-
-
静态库
-
编译(链接)时静态库中相关代码复制到可执行文件中;程序中已包含代码,运行时不再需要静态库;程序运行时无需加载库,运行速度更快
-
占用更多磁盘和内存空间;静态库升级后,程序需要重新编译链接
-
-
静态库的创建
- 确定库中函数的功能、接口
- 编写库源码hello.c
#include<stdio.h> void hello(void){ printf("hello world\n"); return; }
- 编译生成
.o
目标文件:$ gcc -c hello.c -Wall
- ar命令创建静态库hello:
$ ar -rsv libhello.a hello.o
- 静态库文件名: 前缀(
lib
)、后缀(.a
(Unix/Linux 平台).lib
(Windows 平台))
ar参数 | 描述 | ar参数 | 描述 |
---|---|---|---|
c | 创建一个新的静态库文件,如果该文件已存在,则先删除旧的静态库文件再创建 | v | 将建立新库的逐个文件的详细描述写至标准输出 |
r | 将指定的文件添加到静态库中,如果文件已存在于库中,则替换原有的文件 | q | 快速追加文件到静态库中,不对库文件进行索引,适用于对已存在的库进行追加操作 |
s | 创建一个包含符号表的静态库,用于保存对象文件中的全局符号信息 | t | 列出静态库中包含的文件名列表 |
-
链接静态库
- 编写运用程序test.c
#include<stdio.h> void hello(void); int main(){ hello(); return; }
- 编写test.c并链接静态库libhello.a
$ gcc -o test test.c -L. -lhello
$ gcc -o 目标文件 源码 -L路径 -l库名称
- 动态库
- 程序不包含库中代码,尺寸小
- 多个程序可共享同一个库
- 程序运行时需要加载库
- 库升级方便,无需重新编译程序
- 使用更加广泛
-
创建动态库
- 生成位置无关代码的目标文件:
gcc -c -fPIC xxx.c xxxx.c ...
- 生成动态库:
gcc -shared -o libxxx.so xxx.o xxx.o ...
- 动态库文件名:前缀(
lib
),后缀(.so
(Unix/Linux 平台),.dll
(Windows 平台))
- 生成位置无关代码的目标文件:
-
链接动态库
- 编写运用程序test.c
#include<stdio.h> #include "common.h" int main(){ bye(); return; }
- 编写test.c并链接静态库libcommon.so
-
$ gcc -o test test.c -L. -lcommon
-
$ gcc -o 目标文件 源码 -L路径 -l库名称
-
共享库:让系统能找到要加载的共享库
- 把库拷贝到/usr/lib和/lib目录下
- 把LD_LIBRARY_PATH环境变量中添加库所在路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库目录
- 添加再~/.bashrc 文件里面,使用 source ~/.bashrc生效
- 添加/etc/ld.so.conf.d/*.conf文件,执行Idconfig刷新
-
查看可执行文件使用的动态库
-
Ldd命令:
ldd 可执行文件
xx.o xxx.o …` -
动态库文件名:前缀(
lib
),后缀(.so
(Unix/Linux 平台),.dll
(Windows 平台))
-
-
链接动态库
- 编写运用程序test.c
#include<stdio.h> #include "common.h" int main(){ bye(); return; }
- 编写test.c并链接静态库libcommon.so
-
$ gcc -o test test.c -L. -lcommon
-
$ gcc -o 目标文件 源码 -L路径 -l库名称
-
共享库:让系统能找到要加载的共享库
- 把库拷贝到/usr/lib和/lib目录下
- 把LD_LIBRARY_PATH环境变量中添加库所在路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库目录
- 添加再~/.bashrc 文件里面,使用 source ~/.bashrc生效
- 添加/etc/ld.so.conf.d/*.conf文件,执行Idconfig刷新
-
查看可执行文件使用的动态库
- Ldd命令:
ldd 可执行文件
- Ldd命令: