第1章:C标准库(一)——核心函数详解

说实话,很多初学者觉得标准库就是些现成的函数,用就完了。但我在项目里踩过不少坑之后才发现——不懂标准库的底层逻辑,写出来的代码就像纸糊的房子。今天咱们就好好聊聊 stdio.hstdlib.h 里那些你天天用、但未必真懂的函数。

C标准库核心函数体系 <stdio.h> 输入输出 printf / scanf fopen / fclose fread / fwrite fgets / fputs <stdlib.h> 通用工具 malloc / free rand / srand qsort / bsearch atoi / atof 掌握这些,面试基础题稳了

1.1 核心函数——别只会用 printf

我面试过不少人,问 printf 怎么用,都能答上来。但一问 fopen 的打开模式有哪些、fread 的返回值代表什么,就卡壳了。说白了,面试官想看的不是你背了多少函数,而是你理解了多少

1.1.1 格式化输入输出:printf / scanf

这两个函数是 C 语言的「门面」。我个人习惯是:能用 printf 调试的,绝不上调试器——当然,这是玩笑话,但足以说明它们有多常用。

#include <stdio.h>

int main() {
    int a = 42;
    float b = 3.14159;
    char str[] = "Hello, C!";
    
    // 格式说明符要一一对应
    printf("整数:%d,浮点数:%.2f,字符串:%s\n", a, b, str);
    
    // scanf 要注意 & 符号
    int input;
    printf("请输入一个整数:");
    scanf("%d", &input);  // 别忘了 &
    printf("你输入了:%d\n", input);
    
    return 0;
}
⚠️ 我曾经踩过的坑:scanf 读字符串时没限制长度,结果用户输入超长,直接缓冲区溢出。后来我养成了习惯——一律用 %Ns 指定最大长度,比如 scanf("%19s", buf),给终止符留个位置。

1.1.2 文件操作:fopen / fclose / fread / fwrite

文件操作是嵌入式开发的家常便饭。我记得有一次做数据采集项目,文件没关导致句柄泄漏,程序跑了三天就崩了。从那以后,我写文件操作必配 fclose,而且用 if 检查返回值。

打开模式 含义 文件不存在时
"r" 只读 返回 NULL
"w" 只写,清空原内容 创建新文件
"a" 追加写 创建新文件
"rb" / "wb" 二进制读写 同上
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }
    
    // 写入数据
    fprintf(fp, "行号:%d,数值:%.2f\n", 1, 99.99);
    
    // 别忘了关!
    fclose(fp);
    
    // 二进制读写示例
    int arr[5] = {10, 20, 30, 40, 50};
    FILE *bfp = fopen("binary.dat", "wb");
    fwrite(arr, sizeof(int), 5, bfp);
    fclose(bfp);
    
    return 0;
}
💡 我的小技巧:fread 时,返回值是实际读取的元素个数,不是字节数。比如 fread(buf, sizeof(int), 10, fp) 返回 0~10,表示读到了几个 int。如果返回值小于 10,要么到文件尾了,要么出错了——用 feof(fp)ferror(fp) 区分一下。

1.2 核心函数——内存与算法的基础设施

这个头文件里的函数,说白了就是 C 语言的「瑞士军刀」。内存管理、随机数、排序查找,全在这了。我建议你qsort 的用法刻在脑子里——面试手撕代码时,考官很可能不让你用现成的排序函数,但如果你能随口说出 qsort 的回调函数怎么写,印象分直接拉满。

1.2.1 动态内存分配:malloc / free

动态内存是 C 语言的精髓,也是 bug 的重灾区。我见过最经典的错误是:malloc 之后忘了 free,或者 free 了还在用。嗯,这里要注意——每次 malloc 都要配对 free,就像开门关门一样自然。

#include <stdlib.h>
#include <stdio.h>

int main() {
    int *p = (int*)malloc(10 * sizeof(int));
    if (p == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }
    
    // 使用内存
    for (int i = 0; i < 10; i++) {
        p[i] = i * i;
    }
    
    // 释放内存
    free(p);
    p = NULL;  // 好习惯:释放后置空,防止野指针
    
    return 0;
}
⚠️ 我曾经踩过的坑:realloc 时直接 p = realloc(p, new_size)。如果 realloc 失败返回 NULL,原来的 p 就丢了!正确做法是用临时变量:int *tmp = realloc(p, new_size); if (tmp) p = tmp;

1.2.2 随机数生成:rand / srand

很多人以为 rand() 每次运行结果都不一样。其实不然——不调用 srand 的话,每次运行生成的序列是固定的。为什么?因为 rand 是伪随机数生成器,种子不变,序列就不变。

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

int main() {
    // 用当前时间做种子
    srand((unsigned int)time(NULL));
    
    // 生成 0~99 的随机数
    int r = rand() % 100;
    printf("随机数:%d\n", r);
    
    // 生成 50~100 的随机数
    int r2 = rand() % 51 + 50;
    printf("范围随机数:%d\n", r2);
    
    return 0;
}
💡 注意:rand() % N 得到的随机数分布并不均匀,尤其是当 RAND_MAX 不能被 N 整除时。对精度要求高的场景,建议用 arc4random 或自己实现更好的生成器。

1.2.3 排序与查找:qsort / bsearch

qsort 是 C 标准库提供的快速排序实现。你想想看,面试官让你手写快排,你写出来了,然后补一句「其实标准库的 qsort 也能用,但需要自己写比较函数」——这叫什么?这叫既懂原理,又懂工程

#include <stdlib.h>
#include <stdio.h>

// 比较函数:升序
int cmp_int(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main() {
    int arr[] = {34, 7, 23, 32, 5, 62};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    qsort(arr, n, sizeof(int), cmp_int);
    
    printf("排序结果:");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 二分查找(要求数组已排序)
    int key = 23;
    int *found = (int*)bsearch(&key, arr, n, sizeof(int), cmp_int);
    if (found) {
        printf("找到了:%d\n", *found);
    } else {
        printf("没找到\n");
    }
    
    return 0;
}
🔑 核心要点:
  • qsort 的比较函数返回 负数、0、正数,分别表示 a<b、a==b、a>b
  • bsearch 要求数组必须已排序,否则结果未定义
  • 比较函数的参数是 const void*,需要先转型再解引用

1.3 字符串转换:atoi / atof / strtol

从命令行参数或配置文件里读到的都是字符串,怎么转成数字?atoiatof 是最直接的。但我个人建议:能用 strtol 就别用 atoi。为什么?因为 atoi 遇到非法输入返回 0,你分不清是「输入了 0」还是「输入了 abc」。

#include <stdlib.h>
#include <stdio.h>

int main() {
    char *str1 = "123";
    char *str2 = "  456abc";  // 前面有空格,后面有字母
    char *str3 = "abc";
    
    int a = atoi(str1);  // 123
    int b = atoi(str2);  // 456(自动跳过前导空格,遇到字母停止)
    int c = atoi(str3);  // 0(非法输入)
    
    printf("a=%d, b=%d, c=%d\n", a, b, c);
    
    // 用 strtol 更安全
    char *endptr;
    long d = strtol("  789xyz", &endptr, 10);
    printf("d=%ld, 剩余部分:%s\n", d, endptr);
    
    return 0;
}
💡 我的习惯:做数据解析时,一律用 strtolstrtod,配合 endptr 检查是否真的转换成功了。这样代码更健壮,排查问题也方便。

1.4 本章小结

这一章咱们聊了 stdio.hstdlib.h 里最常用的函数。你可能会觉得内容有点多,但别急——这些函数你每天写代码都会碰到。我的建议是:把上面的代码示例自己敲一遍,改改参数,看看输出有什么变化。亲手试过的东西,印象才深。

记住一句话:标准库是 C 语言的基石,理解它,你才能写出真正可靠的代码


公众号:蓝海资料掘金营,微信 deep3321