简介:《C语言标准函数库详解》面向C语言初学者,以及需要日常查阅库函数的开发者,重点说明各头文件的定位、函数原型与典型用法,帮助读者解决库函数记不牢、翻英文手册效率低的问题。整包仅包含1个PDF文件,约967KB;内容按章节展开:第一章逐一梳理assert.h、ctype.h、errno.h、limits.h、math.h、stdio.h、stdlib.h、time.h等头文件的用途与相关宏定义;后续章节按IO函数分类,讲解clearerr、feof、fopen/fclose、fprintf、fread等函数的功能与调用要点。目录层级清晰,读者可按头文件或函数名快速检索。已有12394人学习下载,适合课堂同步学习,也适合项目开发时随手查阅。读者能得到一份结构完整的速查手册,借助参数与返回值说明理解调用细节,减少反复搜索的时间,也便于系统梳理标准库知识体系。
1. 先别急着背函数:C语言标准函数库里到底装着什么
很多人第一次翻 C 语言标准函数库的资料,看到目录从<assert.h>一路排到<time.h>,第一反应是“这得背到什么时候”。换个场景就懂了:标准函数库像一个已经配好格子的工具箱,锤子、螺丝刀、卷尺各有各的位置,你不需要知道锤子怎么炼钢,只要知道哪一格拿、什么时候用。这份《C语言标准函数库详解.pdf》干的就是把每个格子打开给你看:这个头文件管什么、里面的函数怎么调、参数填什么、返回什么、出错看哪里。它不讲 C 语言语法,也不讲算法,专门解决“想写点东西,却不知道现成的工具在哪儿”这个卡点。零基础读得动,前提只是你能写一个带main函数的空程序,会编译、会运行。
2. 为什么新手要先学标准库:不学它,写代码就像每次自己造轮子
不少人学完变量、循环、数组就卡住了,不知道下一步写什么。想读个文件,不会;想把两段文字接起来,得自己写循环;想算个平方根,还得手推公式。这种卡顿不是语法没学扎实,是工具没上手。C 语言本身特别小,关键字几十个,连屏幕输出都不在语法里——printf不是关键字,它是一个库函数。也就是说,学 C 这件事一半在语法,一半在库。语法决定你能表达什么,库决定你少写多少行。
新手常见的心态是“我得先把语法学到很熟,再去碰库”。这条路走起来很别扭,因为你练语法总得有输入输出,而输入输出本身就得靠库。更顺的顺序是语法和库一起走:学一个语法点,顺手找一个库函数把它用出去。这份资料按头文件把函数分好类,正好适合当“随手查”的工具书,不用从头读到尾。
2.1 标准库替你干掉的四类重复劳动
在没有库的世界里,下面这些事都得自己从零写。学了库,它们变成一两行调用:
- 屏幕和文件的读写。自己写要处理缓冲区、编码、错误状态;用
printf、fgets、fopen就几行。 - 字符串和内存块的处理。比较两段文字、复制一段内容、在内存里填同一个值,这些循环写一百遍也不长记性,
strcmp、memcpy、memset一个调用解决。 - 字符分类和数字转换。判断一个字符是不是数字、把
"123"变成整数 123,自己写要处理边界和符号位,isdigit、atoi直接给结果。 - 时间、随机数和程序退出。取当前时间、生成随机数、异常终止进程,都有现成的口子。
提示:标准库的“标准”指的是接口统一。同一个函数在不同编译器上的实现可能不同,但调用方式、参数顺序、返回值含义是一致的,所以你在一台机器上学会,换一台照样用。
这四类之外还有错误处理、可变参数、非局部跳转这些偏进阶的内容,资料里也都收了,但新手阶段先不用管,知道目录里有就行。
2.2 include 那一行到底在干什么:头文件和函数的关系
很多人写了半年#include <stdio.h>,也说不出这行在干嘛。打个比方:头文件是餐厅的菜单,库文件是后厨。菜单上写着“宫保鸡丁,微辣,28 元”,告诉你这道菜存在、叫什么、什么规格;真正的菜在后厨做,你点单之后才端出来。头文件里放的是函数声明——函数名、参数类型、返回类型;函数的实体编译好放在库里,最后一步链接时才拼进你的程序。
明白了这层,两类报错就好区分了。忘记#include时,编译器没见过这个函数名,会警告“隐式声明”,按老规矩猜它返回int;函数名拼错或者库没链上,编译能过,链接阶段才报“找不到符号”。新手第一个分水岭就是能看懂这两种错分别出在哪一步。
2.3 第一个能跑的例子:三行代码用上两个库函数
先跑通一个最小的,跑通了心里就有底。新建一个文件,随便叫什么名字:
#include <stdio.h> /* 提供 printf */ #include <string.h> /* 提供 strlen */ int main(void) { const char *name = "C standard library"; /* strlen 数的是字符个数,不包含结尾的 '\0' */ printf("字符串是:%s\n", name); printf("长度是:%lu\n", (unsigned long)strlen(name)); return 0; }编译并运行,命令行里敲:
cc demo.c -o demo ./demo预期输出是两行,第一行原样打印那句话,第二行是 18。
逐项说一下这里发生了什么。#include <stdio.h>把printf的声明拿进来,编译器才知道它接受一个格式串加若干参数;#include <string.h>同理给strlen用。strlen的返回值类型是size_t,一种无符号整数类型,不同平台上可能是 32 位也可能是 64 位,所以打印时用%lu并显式转成unsigned long,避免格式符和实际类型对不上。"%s\n"里的%s是占位符,表示这里要填一串字符,\n是换行。
第一次跑的时候建议故意改坏一次:把#include <string.h>那行删掉再编译,看看警告长什么样;再把strlen拼成strln编译一次,看看报错出现在哪一步。亲眼见过这两个错,下次遇到就不会慌。
3. 从哪五个头文件下手:新手最该先啃的 stdio.h、string.h、ctype.h、math.h、stdlib.h
C 标准库的头文件有十几个,全铺开学会让人失去耐心。按使用频率排,新手阶段真正天天打交道的只有五个:<stdio.h>管输入输出,<string.h>管字符串和内存块,<ctype.h>管单个字符的判断和转换,<math.h>管数学运算,<stdlib.h>管类型转换、内存分配和程序退出。剩下的<assert.h>、<errno.h>、<time.h>等,用到再查,不影响你写出能跑的程序。
这一章每个头文件配一个小例子,都是十来行,照着敲一遍比读十页说明管用。
3.1 输入输出:stdio.h 是全库最常用的入口
<stdio.h>里的 “stdio” 是 standard input/output 的缩写,标准输入输出。屏幕打印、键盘读取、文件读写全在这一块。新手最先用的是printf和scanf,但真正让程序有用的往往是文件操作:fopen打开、fgets按行读、fclose关闭。
#include <stdio.h> int main(void) { FILE *fp = fopen("note.txt", "r"); /* "r" 表示只读 */ if (fp == NULL) { /* 打开失败会返回空指针 */ perror("打开文件失败"); /* 打印系统给的错误原因 */ return 1; } char line[128]; /* fgets 最多读 127 个字符,留一个位置给 '\0' */ while (fgets(line, sizeof line, fp) != NULL) { printf("读到:%s", line); } fclose(fp); /* 用完必须关,否则文件句柄会一直占着 */ return 0; }fopen的第二个参数是模式,"r"只读、"w"覆盖写、"a"追加写,这三个够用很久。fgets的三个参数分别是目标数组、最多读多少字节、从哪个流读;返回NULL表示读到文件末尾或者出错。perror值得记住,它会把系统给的错误描述打出来,比你自己猜“是不是路径写错了”高效得多。
注意:
fgets会把行尾的换行符一起读进数组,所以打印时不用再加\n。这一点和scanf("%s")的行为不一样,混着用容易多出空行。
3.2 字符串:string.h 是新手翻车最多的地方
<string.h>里的函数都在跟内存打交道,而 C 的字符串本质是“一串字符 + 结尾的'\0'”。这个结尾符是理解所有字符串函数的关键:strlen数到'\0'就停,strcpy连'\0'一起搬,strcmp逐字符比到不同为止。忘记留这个位置,就是后面避坑章节要讲的头号事故。
#include <stdio.h> #include <string.h> int main(void) { char a[32] = "hello"; char b[32] = "world"; char c[64]; /* 容量要够装下 a + b + '\0' */ strcpy(c, a); /* 把 a 复制进 c */ strcat(c, " "); /* 在 c 末尾接一个空格 */ strcat(c, b); /* 再接上 b */ printf("%s\n", c); /* hello world */ printf("长度 %lu\n", (unsigned long)strlen(c)); /* 11 */ if (strcmp(a, b) != 0) { printf("两段文字不相等\n"); /* 返回 0 才表示相等 */ } return 0; }strcpy和strcat都不检查目标数组够不够大,这是它们的天然缺陷,所以目标数组一定要按最坏情况开。strcmp的返回值不是“相等为真”,而是相等返回 0、前者大返回正数、后者大返回负数,新手写成if (strcmp(a, b))意思正好反了。稳妥的写法是用带长度限制的版本strncpy、strncat,或者干脆自己先算好长度。
3.3 字符判断与转换:ctype.h 的一排小开关
<ctype.h>里的函数都只处理一个字符,返回“是”或“不是”,用起来像一排开关:isdigit判断数字、isalpha判断字母、isspace判断空白、isupper判断大写,tolower和toupper负责大小写转换。写输入校验、统计文本时特别顺手。
#include <stdio.h> #include <ctype.h> int main(void) { const char *text = "C 语言 2024 版"; int digits = 0, letters = 0, spaces = 0; for (int i = 0; text[i] != '\0'; i++) { unsigned char ch = (unsigned char)text[i]; if (isdigit(ch)) digits++; else if (isalpha(ch)) letters++; else if (isspace(ch)) spaces++; } printf("数字 %d 个,字母 %d 个,空白 %d 个\n", digits, letters, spaces); return 0; }这里有个细节值得记住:传进去之前先转成unsigned char。这些函数内部大多用查表实现,如果直接传负值(某些编码下汉字字节会是负数),下标就越界了,行为不可预料。养成这个习惯,能省掉一类很隐蔽的崩溃。
3.4 数学与随机数:math.h 和 rand 的配合
<math.h>负责sqrt开方、pow求幂、fabs取绝对值、ceil向上取整、floor向下取整这些。随机数在<stdlib.h>里,rand产生随机整数,srand设置种子。
#include <stdio.h> #include <math.h> #include <stdlib.h> #include <time.h> int main(void) { printf("根号 2 = %.4f\n", sqrt(2.0)); printf("2 的 10 次方 = %.0f\n", pow(2.0, 10.0)); srand((unsigned)time(NULL)); /* 用当前时间做种子,避免每次结果一样 */ for (int i = 0; i < 3; i++) { printf("随机数:%d\n", rand() % 100); /* 取 0 到 99 */ } return 0; }pow和sqrt的参数和返回值都是浮点类型,传整数进去会被自动转换,但结果仍然是浮点,打印时用%f系列。rand()单独用的问题在于种子固定,每次运行得到同一串数,配合srand才像真的随机。% 100是取余,用来把范围压到 0 到 99。
注意:
<math.h>里的函数在不少环境里需要额外链接数学库,编译命令末尾要加-lm。忘了加的表现是编译通过、链接报错,后面避坑章节会细说。
3.5 内存、转换与退出:stdlib.h 的三件套
<stdlib.h>内容很杂,新手先掌握三组:字符串转数字的atoi、atof,动态分配内存的malloc和free,以及提前结束程序的exit。
#include <stdio.h> #include <stdlib.h> int main(void) { const char *s = "42"; int n = atoi(s); /* 字符串转整数,失败时返回 0 */ printf("n + 1 = %d\n", n + 1); int *arr = malloc(10 * sizeof(int)); /* 申请 10 个 int 的空间 */ if (arr == NULL) { /* 申请失败返回空指针 */ fprintf(stderr, "内存不足\n"); return 1; } for (int i = 0; i < 10; i++) arr[i] = i * i; printf("arr[9] = %d\n", arr[9]); free(arr); /* 用完还回去 */ arr = NULL; /* 置空,防止后面误用 */ return 0; }atoi遇到非数字字符就停下,不做错误提示,需要严格校验时用strtol,它能告诉你转换停在了哪个位置。malloc的参数是字节数,习惯写法是个数 * sizeof(类型),别手算。free之后把指针置NULL是个便宜又有效的保险,后面再误用会立刻崩在你眼前,而不是在别处莫名其妙地出问题。
4. 第一个完整例子:用标准库写一个统计行数、单词数、字符数的小程序
零散调用练过之后,需要一个把所有东西串起来的例子。这里做一个最朴素的文本统计:从输入里读内容,最后输出行数、单词数、字符数。功能不花哨,但它同时用到了<stdio.h>的按行读取、<ctype.h>的字符判断,还顺带练了循环和状态标记。
4.1 先把任务拆成三步
写程序之前先想清楚要维护哪几个变量。第一,三个计数器:行数、单词数、字符数。第二,一个状态标记,记录“当前光标是不是在单词内部”,因为单词数是按“从空白进入非空白”的次数来算的,不是按字符数算。第三,一个循环,每次读一行,逐字符扫过去。
拆完这三步,代码结构就出来了:初始化变量、循环读、循环内判断、最后打印。新手容易一上来就写for,写到一半发现不知道循环该到哪儿停,问题就出在没先想清楚数据从哪来。
4.2 完整代码
#include <stdio.h> #include <ctype.h> int main(void) { char line[256]; long chars = 0, words = 0, lines = 0; int in_word = 0; /* 0 表示不在单词里,1 表示在 */ /* fgets 返回 NULL 表示读完或出错,循环结束 */ while (fgets(line, sizeof line, stdin) != NULL) { lines++; for (int i = 0; line[i] != '\0'; i++) { chars++; if (isspace((unsigned char)line[i])) { in_word = 0; /* 遇到空白,退出单词 */ } else if (!in_word) { in_word = 1; /* 从空白进入非空白,算一个新单词 */ words++; } } } printf("行数 %ld,单词数 %ld,字符数 %ld\n", lines, words, chars); return 0; }编译:
cc wc.c -o wc运行时把内容喂进去,手动输入的话按 Ctrl+D(或对应平台的结束符)表示输入结束:
./wc < note.txt4.3 每一步在调哪个库函数
fgets(line, sizeof line, stdin)里的stdin是标准输入流,不用自己定义,<stdio.h>里已经声明好了。第二个参数用sizeof line而不是写死 256,好处是以后改数组大小时这行不用跟着改。函数返回NULL就退出循环,这个判断同时覆盖了“读完了”和“出错了”两种情况。
isspace判断空格、制表符、换行符等,判断放在chars++之后,所以字符数里包含了行尾的换行符,跟常见统计工具的口径略有差别,自己用无所谓,对不上数的时候别怀疑代码写错了。
in_word这个标记是整个程序的核心。如果只数非空白字符的数量,得到的是字母数不是单词数;只有在“上一次是空白、这一次不是”的瞬间才words++,才能数对。这类“用变量记住上一刻状态”的写法在文本处理里非常常见,值得单独记一笔。
4.4 换成从文件读,改动只有两行
从键盘读和从文件读,差别只在流的来源。把stdin换成一个FILE *就行:
FILE *fp = fopen("note.txt", "r"); if (fp == NULL) { perror("打开失败"); return 1; } while (fgets(line, sizeof line, fp) != NULL) { /* 循环体不变 */ } fclose(fp);这么改的意义在于,你会意识到“输入”是一个抽象的东西,键盘、文件、管道在 C 里都是流,函数接口不因为来源不同而变。这份资料里<stdio.h>那章的freopen、setvbuf、tmpfile都是围绕这个思路展开的,等你能自己写出上面这段,再去翻就不会觉得陌生。
写完这个例子,可以自己加两个小功能练手:统计最长的一行有多少字符,或者把单词数改成不区分大小写。改的过程就是查资料的过程,比从头读到尾效率高得多。
5. 新手最容易踩的五个坑:数组越界、格式化输入、链接失败、内存泄漏和隐式声明
这一章列的是我在带人时见得最多的五类问题。共同点是:报错信息和真正的原因往往不在一个地方,不知道套路就会在原地耗很久。每条按“看到什么现象、为什么、怎么改”说清楚。
5.1 打印字符串打出乱码,或者程序直接崩
现象:printf("%s", buf)输出一堆看不懂的字符,有时候打印完立刻崩溃。
原因:char buf[5] = "hello";这种写法放不下。"hello"是 5 个字符加 1 个结尾的'\0',一共 6 字节,数组只给了 5 个位置,结尾符被挤掉了。字符串函数一路往后找'\0',找到哪儿算哪儿,越界读到别人家的内存。
解决:数组长度按“字符数 + 1”开,拿不准就开大一点,比如char buf[64]。用strncpy时也要记得手动补结尾符,它不保证补。
5.2 scanf 读字符串出问题,或者读数字之后下一次读输入是空的
现象一:输入很长的内容,程序崩溃。现象二:scanf("%d", &n)之后紧跟着fgets,什么都没读到就返回了。
原因:%s不限制长度,来多少写多少;%d只把数字拿走,你按的那个回车还留在输入缓冲区里,下一次读输入先读到这个换行符,看起来就像“什么都没读”。
解决:读字符串用宽度限制,写成%99s(数组开 100);读完整行统一用fgets,它连换行一起拿走,行为更好预测。数字和整行混着读时,中间用一次getchar()把残留的换行吃掉。
5.3 用了 math.h 却链接失败
现象:编译没报错,最后一步冒出undefined reference to 'sqrt'之类的提示。
原因:数学函数的实现放在单独的库文件里,头文件只给了声明,编译器知道有这么个函数,链接器却没找到实体。
解决:编译命令末尾加-lm,注意放在源文件后面,顺序反了在部分环境里仍然会失败。这类错误的位置很典型——出现在链接阶段而不是编译阶段,看到“undefined reference”就先想“是不是少链了库”。
5.4 malloc 之后忘了 free,或者 free 完接着用
现象:程序跑得越久占用内存越大;或者崩溃位置飘忽不定,改一行无关代码崩溃点就变了。
原因:申请的内存没有归还;或者已经free的指针又被访问了一次,那块内存可能已经分给别人了。
解决:养成“谁申请谁释放”的习惯,申请和释放写在同一个函数里,中间不要跨太多层。free之后立刻把指针置NULL。写代码时先把free那行敲出来,再回头填中间的逻辑,这个顺序能救很多次。
5.5 编译器警告隐式声明,程序行为诡异
现象:warning: implicit declaration of function 'strlen',或者返回值明显不对,指针被截断成奇怪的值。
原因:忘记#include对应的头文件。老标准允许不声明就调用,默认按返回int处理,在 64 位环境里指针被截断,问题就来了。
解决:看到“implicit declaration”先去检查 include,函数名对不上头文件是最常见的原因。把编译选项调严一点,让警告当成错误处理(常见写法是加-Wall -Werror),逼自己在warning阶段就解决,别拖到运行时。
6. 下一步学什么:用四周时间把标准库从“认识”练到“顺手”
入门阶段最怕漫无目的地翻目录。给一个能落地的顺序,每周专注一块,每块都配一个小程序,四周下来手感就出来了。
| 周次 | 重点头文件 | 练手目标 |
|---|---|---|
| 第一周 | <stdio.h> | 能读写文件、按行处理文本,会用perror看错误 |
| 第二周 | <string.h>、<ctype.h> | 能自己做大小写转换、单词切分、简单的字符串比较 |
| 第三周 | <stdlib.h>、<math.h> | 会用malloc管理一块动态数据,会用strtol做安全转换 |
| 第四周 | <time.h>加综合 | 把前几周的片段拼成一个小工具,比如带时间戳的记事本 |
第一周结束后,你应该能独立写出“打开文件、逐行读、统计、输出”的完整流程;第二周的重点是彻底接受'\0'这个设定,所有字符串操作先在纸上把长度算一遍;第三周开始接触指针和堆内存,慢一点没关系,把malloc和free的配对写成本能;第四周把time()和localtime()用起来,给输出加个时间,程序会立刻显得“像个东西”。
查资料的方式也值得说一下。不要从第一页顺序读,按“我要做什么”去目录里找对应的头文件,找到函数看三样:参数类型、返回值含义、失败时返回什么。这三样看懂了就能用,剩下的细节等真出问题再回来补。这份资料按头文件分章、按函数逐个列条目,正好适合这种查法,需要的时候拿去对照着翻就行。
我刚开始写字符串处理的时候,觉得“多开几个字节至于吗”,结果一个统计程序在长文本上跑一次崩一次,排查了大半天才定位到数组少留了一个位置。从那以后我每次写和char数组相关的代码,都先把长度算一遍、先把'\0'的位置标出来,再动手写逻辑。这个笨习惯救过我不止一次。希望帮到你。
本文还有配套的精品资源,点击获取