文章目录
-
- 引言
- stdio.h
-
- 常量和指针
- 打开文件
- FILE结构体
- 读写和定位文件
- 其他操作文件和路径
- get和put
- printf
- stdlib.h
-
- 内存分配
- 系统交互
- system函数
- 字符串函数
- 数学函数和算法
- string.h
- ctype.h
- math.h
- 时间与货币
-
- time.h
- locale.h
- 单功能库
-
- assert.h
- stdarg.h
- stddef.h
- setjmp.h
- 常量库
引言
C语言标准库是定义在15个头文件中的内置函数和常量,为C语言提供了最基本的功能
即标准输入输出,涵盖文件读写的全过程。
所谓标准输入输出,直观点是键盘和显示屏:键盘将标准输入传输到终端,终端将内容打印在显示屏上。关于基于字符串的人机交互,stdio.h
主要提供两组函数,一组是get
和put
,另一组是print
和scan
。
从fopen
打开文件,到fread, fwrite
读写文件,再到fflush
最后,强制写入缓存fclose
关闭文件;如果需要准确定位文件的字节位置,可以通过fseek
或fgetpos
。
即是standard library
,其他类别的标准库主要分为内存、系统、字符串转值和数学算法四个部分。
:提供查询、比较、复制字符串等功能
:判断数据类型
:提供最基本的数学运算
:C标准库为不同国家提供本地化头函数,具体体现在时间和货币上。C标准库本地化描述了不同地区的时间和货币表达,并分别包装在time.h
和locale.h
中。
有很多库十分简单,故统一归类到单一功能库下,包括
- stdarg.h:支持可变参数
- assert.h:用于报错
- stddef.h:指针相减
- setjmp.h:跳转
一些标准库只定义了一些宏,单并没有声明函数,因此将这些库统一归类为常量库。
- 系统错误码errno.h
- 信号signal.h
- 浮点限制信息float.h
- 整形限制信息limits.h
stdio.h
std
是标准的,io
即输入输出,合起来stdio
是标准输入输出。
常量和指针
#define |
功能 | 默认操作系统 |
---|---|---|
EOF (-1) |
文件结束符 | |
BUFSIZ 1024 |
setbuf 函数缓冲区字节数 |
__BUFSIZ__ |
FOPEN_MAX 20 |
系统可同时打开的文件数量 | __FOPEN_MAX__ |
FILENAME_MAX 1024 |
最大长度的文件名 | __FILENAME_MAX__ |
L_tmpnam FILENAME_MAX |
tmpnam 创建的临时文件名的最大长度 |
__L_tmpnam__ |
TMP_MAX 26 |
tmpnam 最多可以生成独立文件名称 |
stdio.h
定义了三个FILE
指针类型,
#define stdin (_REENT->_stdin) #define stdout (_REENT->_stdout) #define stderr (_REENT->_stderr)
其中,_REENT
在reent.h
由_impure_ptr
定义,而_impure_ptr
则为_reent
的指针。
在_reent
定义了三个FILE
型的指针__FIE *_stdin, *_stdout, *_stderr;
,分别代表标准输入、标准输出和标准错误。
打开文件
fopen
和freopen
均为C语言标准库stdio.h
中的函数,分别用于打开和重新打开某个stream,二者均返回一个FILE
指针。
FILE *fopen(const char *filename, const char *mode)
FILE *freopen(const char *filename, const char *mode, FILE *stream)
其中filename
为文件路径,mode
为文件打开模式,freopen
中输入的的stream
为现存的流,freopen
将新打开的文件注入stream
中,同时关闭旧文件。
其中mode
的取值如下,第一列为常规模式,第二列为二进制模式,在二进制模式下,读取的是二进制文件,其他与常规模式相同。
mode | 模式 | 说明 | |
---|---|---|---|
“r” | “rb” | 只读 | 打开的文件必须存在。 |
“w” | “wb” | 写入 | 创建空文件,若文件已存在,会删除原有内容。 |
“a” | “ab” | 追加 | 向文件末尾追加数据,若文件不存在,则创建文件。 |
“r+” | “rb+” | 更新 | 打开一个文件进行读写,该文件必须存在。 |
“w+” | “wb+” | 读写 | 创建一个用于读写的空文件。 |
“a+” | “ab+” | 打开一个用于读取和追加的文件 |
fopen
和freopen
的返回值为FILE
指针,刚好可以通过stdio.h
中的close
进行关闭。
stdio.h
中定义了三个单参函数用以调控文件的写入,其输入均为FILE *stream
,若成功则返回1,失败则返回0。
返回类型 | 函数 | 功能 |
---|---|---|
int |
fclose |
关闭stream,刷新缓冲区 |
int |
fflush |
刷新stream的输出缓冲区 |
int |
ferror |
测试stream的错误标识符 |
如果在没有完成写入的情况下调用close
,可能会致使数据丢失,故stdio.h
中提供了fflush
函数,用于强制将缓冲区的内容写入文件。而fclose
则综合了二者的功能,先flush,再close。
FILE结构体
FILE
是C语言标准库stdio.h
中定义的一个结构体,用于数据缓存,一般写为
//stdio.h
typedef struct _iobuf
{
char* _ptr; //文件输入的下一个位置
int _cnt; //当前缓冲区的相对位置
char* _base; //文件初始位置
int _flag; //文件标志
int _file; //文件有效性
int _charbuf; //缓冲区是否可读取
int _bufsiz; //缓冲区字节数
char* _tmpfname; //临时文件名
} FILE;
其中,_bufsiz
为缓冲区字节数,一般由宏来定义
#define BUFSIZ 1024
临时文件名_tmpfname
可通过函数tmpnam
进行设置,或者通过tmpfile
创建二进制文件。
为了理解这些字段的含义,可以写一个小例子
//test.c
#include<stdio.h>
//这个函数用来打印FILE内部的字段
void printFILE(FILE *fp){
printf("fp->_ptr=%s\n",fp->_ptr);
printf("fp->_cnt=%d\n",fp->_cnt);
printf("fp->_base=%s\n",fp->_base);
printf("fp->_flag=%d\n",fp->_flag);
printf("fp->_file=%d\n",fp->_file);
printf("fp->_charbuf=%d\n",fp->_charbuf);
printf("fp->_bufsiz=%d\n",fp->_bufsiz);
printf("fp->_tmpfname=%s\n",fp->_tmpfname);
printf("-------------------------------------");
printf("-------------------------------------\n");
}
int main(){
FILE* fp = fopen("test.c","r"); //打开test.c文件,即本文件
printFILE(fp);
char buf[20];
fread(buf,1,5,fp); //读取fp中的数据
printFILE(fp);
fclose(fp); //关闭fp
printFILE(fp);
return 0;
}
得到其输出如下,为了便于阅读,用//
和/**/
对输出进行注释
>gcc test.c
>a.exe
fp->_ptr=(null) //打开文件后,由于未作操作,故该指针为空
fp->_cnt=0 //当前缓冲区相对位置为0
fp->_base=(null) //文件初始位置也是一个空指针
fp->_flag=1
fp->_file=3
fp->_charbuf=0 //缓冲区文件
fp->_bufsiz=0 //缓冲区是空的
fp->_tmpfname=(null)
--------------------------------------------------------------------------
// 下面的输出是在执行fread之后,
// 由于读取了5个字节,所以指针跳过了#incl
fp->_ptr=ude<stdio.h>
void printFILE(FILE *fp){
/* 这一段将test.c中的内容原封不动地打印了出来 因为太长,所以省略不写了 */
printFILE(fp);
return 0;
}
LE(fp);
return 0;
}
fp->_cnt=665 #当前缓冲区的相对位置是65
fp->_base=#include<stdio.h> #指向文件初始位置的指针
void printFILE(FILE *fp){
/* 这一段将test.c中的内容原封不动地打印了出来 因为太长,所以省略不写了 */
printFILE(fp);
return 0;
}
LE(fp);
return 0;
}
fp->_flag=9
fp->_file=3
fp->_charbuf=0
fp->_bufsiz=4096 //缓冲区尺寸为4096
fp->_tmpfname=(null) //并没有临时名字
--------------------------------------------------------------------------
// 关闭文件后,一切又变得未知,但缓冲区尺寸并未变化
fp->_ptr=(null)
fp->_cnt=0
fp->_base=(null)
fp->_flag=0
fp->_file=3
fp->_charbuf=0
fp->_bufsiz=4096
fp->_tmpfname=(null)
--------------------------------------------------------------------------
在上面的输出中,_flag
值在打开文件后为1,读取文件后变为9,关闭文件后变为0.
_file
则一直为3。
在stdio.h
中定义了一系列常量用于描述文件流的状态
#define __SLBF 0x0001 /* 行缓冲 */
#define __SNBF 0x0002 /* 无缓冲 */
#define __SRD 0x0004 /* 可读 */
#define __SWR 0x0008 /* 可写 */
#define __SRW 0x0010 /* 可读写 */
#define __SEOF 0x0020 /* 发现 EOF */
#define __SERR 0x0040 /* 发现 error */
#define __SMBF 0x0080 /* _buf来自内存(malloc) */
#define __SAPP 0x0100 /* fdopen()ed in append mode */
#define __SSTR 0x0200 /* 是一个sprintf/snprintf字符串 */
#define __SOPT 0x0400 /* 进行fseek()优化 */
#define __SNPT 0x0800 /* 不进行fseek()优化 */
#define __SOFF 0x1000 /* set iff _offset is in fact correct */
#define __SMOD 0x2000 /* true => fgetln modified _p text */
#define __SALC 0x4000 /* 动态分配字符串内存*/
#define __SIGN 0x8000 /* 在_fwalk是忽略本文件*/
_flag
的值为1,表示启用行缓冲;为9,则是__SLBF | __SWR
,说明可写。
文件读写和定位
在stdio.h
中定义了读、写还有查找的函数,其中fread
用于将文件中的数据写入内存;fwrite
将内存中的数据写入文件;fseek
则操作文件指针,使之偏移,从而可以更加灵活地读取。
- 读:
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)
- 移:
int fseek(FILE *stream, long int offset, int whence)
其中,ptr
为指向某个内存块的指针;stream
为文件的数据流;nmemb
为元素个数,size
为每个元素的字节数;whence
为添加偏移的位置,offset
为相对于whence
的偏移量。
在stdio.h
中为whence
定义了宏,其含义如下
#define SEEK_SET 0 |
从0开始 |
#define SEEK_CUR 1 |
从当前位置开始 |
#define SEEK_END 2 |
从EOF开始 |
读写相对来说比较直观,接下来主要测试一下fseek
,说明均在注释中。
//test.c
#include<stdio.h>
void printChar(char *buffer, int len){
for(int i = 1; i < len; i++)
printf("%c",buffer[i-1]);
printf("\n-------------------------\n");
}
int main(){
FILE* fp = fopen("test.c","r"); //打开test.c文件,即本文件
char buf[20];
fread(buf,1,20,fp); //读取fp中的数据
printf("printf the first 20 bytes:");
printChar(buf,20);
fseek(fp, 24, SEEK_CUR); //从当前位置开始偏移
fread(buf,1,20,fp);
printf("fseek 24 from now:");
printChar(buf,20);
fseek(fp, 24, SEEK_SET); //从0开始偏移
fread(buf,1,20,fp);
printf("fseek 24 from 0:");
printChar(buf,20);
fseek(fp, -20, SEEK_END); //从结束位置向前偏移
fread(buf,1,20,fp);
printf("fseek 20 from END:");
printChar(buf,20);
fclose(fp); //关闭fp
return 0;
}
输出结果为
>gcc test.c
E:\Documents\00\1217>a.exe
printf the first 20 bytes://test.c
#include<s
-------------------------
fseek 24 from now:har *buffer, int le
-------------------------
fseek 24 from 0:.h>
void printChar(
-------------------------
fseek 20 from END:
return 0;
}
r(
-------------------------
除了fseek
可以对文件指针进行移动之外,fsetpos
可以直接对文件指针进行定位。相应地,fgetpos
可以获取文件指针的位置,二者声明为:
int fgetpos(FILE *stream, fpos_t *pos)
int fsetpos(FILE *stream, const fpos_t *pos)
二者的返回值均为设置之后的位置,关于输入参数,FILE *stream
是大家非常熟悉的文件流,而fpos_t
是一个结构体,代表相对位置,通常定义为
typedef struct
{
unsigned long _off;
}fpos_t;
接下来做一个简单的测试
//pos.c
#include <string.h>
#include <stdio.h>
int main(void)
{
fpos_t pos;
FILE* fp = fopen("pos.c", "r"); //打开pos.c文件,即本文件
fgetpos(fp, &pos); //获取指针位置
printf("file pointer : %ld\n", pos);
fseek(fp,10,0); // 移动指针位置
fgetpos(fp, &pos); // 获取指针位置并存入&pos所指向的对象
printf("file pointer : %ld\n", pos);
fclose(fp);
return 0;
}
结果如下,可见getpos
和fseek
的单位是一致的,fseek
移动了10个字节,则fgetpos
也获取了位置10.
>gcc pos.c
>a.exe
file pointer : 0
file pointer : 10
结合fgetpos
和fsetpos
,可完成类似fseek
的操作
//setpos.c
#include <string.h>
#include <stdio.h>
int main(void)
{
fpos_t pos;
FILE* fp = fopen("pos.c", "r"); //打开pos.c文件,即本文件
printf("file pointer : %ld\n", fgetpos(fp, &pos));
pos += 10;
fsetpos(fp, &pos); //设置指针位置
printf("file pointer : %ld\n", fgetpos(fp, &pos));
fclose(fp);
return 0;
}
测试结果为
>gcc setpos.c
>a.exe
file pointer : 0
file pointer : 10
文件和路径的其他操作
输入为FILE *stream
的单参函数,EOF为文件结束标识符。
返回类型 | 功能 | |
---|---|---|
void |
clearerr |
清除stream的文件结束和错误标识符 |
int |
feof |
返回stream的文件结束标识符,若未设置,则返回0 |
long int |
ftell |
返回stream的文件位置,如果发生错误,则返回-1L,全局变量errno被设置为一个正值。 |
void |
rewind |
设置文件位置为stream的开头 |
int |
fgetc getc |
从stream获取下一个字符,并把位置标识符往前移动 |
在打开文件后还没有做其他操作的时候,可以通过ssetvbuf
来设置缓冲格式,其声明为
int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
其中stream
为文件流;buffer
为分配给用户的缓冲;size
为缓冲的字节数;mode
为缓冲模式,有三种
宏 | 值 | 类别 | 说明 |
---|---|---|---|
_IOFBF |
0 | 全缓冲 | 输出时,数据在缓冲填满时被一次性写入输入时,缓冲在请求输入且缓冲为空时被填充。 |
_IOLBF |
1 | 行缓冲 | 输出时,在换行处或缓冲填满时写入数据输入时,缓冲至下一个换行符 |
_IONBF |
2 | 无缓冲 | 不使用缓冲。I/O操作即时写入,忽略buffer和size |
setbuf
是setvbuf
的一个特例,其中mode
为_IONBF
,size
为BUFSIZ
。
stdio.h
还提供了两个操作文件的函数,分别是删除文件remove
和重命名文件rename
,其声明分别为:
int remove(const char *filename)
int rename(const char *old_filename, const char *new_filename)
get和put
get
和put
有互为反函数的感觉,例如getchar()
从标准输入stdin
获取一个字符,而putchar(int c)
将字符c
送到标准输出。
例如
#include <stdio.h>
int main ()
{
char ch;
printf("getchar: ");
ch = getchar();
printf("putchar: ");
putchar(ch);
return 0;
}
测试结果为
>gcc gets.c
>a.exe
getchar: a
putchar: a
为了便于对比阅读,下面令
#define FS FILE *stream
#define IC int char
#define CS char *str
#define CCS const char *str
则与get
和put
有关的函数如下表所示
get | fget | put | fput |
---|---|---|---|
getchar() |
putchar(IC) |
||
getc(FS) |
fgetc(FS) |
putc(IC, FS) |
fputc(IC, FS) |
gets(CS) |
fgets(CS, int n, FS) |
puts(CCS) |
fputs(CCS, FS) |
ungetc(IC, FS) |
其中,除fgets
和gets
的返回值为字符指针,其余均为整型,getchar()
也会将输入的字符转为整型。
所有的get
和put
函数均用于字符的输入输出,后缀c
表示从文件输入或输出到文件,没有c
则表示基于标准输入输出流。前缀f
表示是否由宏来实现,例如getc
通常由宏来实现且经过高度优化,故常作为首选,但目前来说与fgetc
在速度相差无几。
ungetc
把一个无符号字符推入到指定的流stream中,以便接下来被读取。
接下来做一些简单的示例
//fget.c
#include <stdio.h>
int main ()
{
FILE *fp;
fp = fopen("file.txt", "w+");
//将字符逐一写入文件
for(int