欢迎光临
我们一直在努力

大学生版保姆式学《C和指针》——C标准库:常用函数全解与避坑

前言

       上一章我们完整掌握了文件操作与标准IO,学会了数据的持久化存储。这一章我们把C语言标准库中最常用、工程开发中最依赖的工具函数集中梳理一遍——包括字符串处理、内存操作、数学计算、错误提示与时间相关函数。        很多同学写代码时总在“重复造轮子”:手动写字符串长度计算、自己实现内存拷贝、处理错误时只打印“出错了”却不知道具体原因,本质都是对标准库函数不熟悉。标准库是C语言自带的“官方工具包”,经过严格测试、效率高、跨平台性好,熟练使用能大幅减少代码量、降低bug率,也是从“新手代码”到“工程代码”的关键一步。        本章只聚焦日常开发真正高频使用的核心函数,不讲偏门用法;每个函数都会讲清“功能+用法+安全坑点+工程规范写法”,搭配可直接编译运行的示例,确保你学完就能用。


目录

  • 一、本章学习目标
  • 二、核心知识点
    • 2.1 字符串处理函数:文本操作的核心(string.h)
      • 2.1.1 基础函数:strlen/strcpy/strncpy
      • 2.1.2 拼接与比较:strcat/strncat/strcmp/strncmp
      • 2.1.3 查找与分割:strstr/strchr/strtok
    • 2.2 内存操作函数:通用数据处理(string.h)
      • 2.2.1 内存初始化:memset
      • 2.2.2 内存拷贝:memcpy/memmove
      • 2.2.3 内存比较:memcmp
    • 2.3 数学函数:数值计算(math.h)
    • 2.4 错误处理函数:让程序更健壮(stdio.h/errno.h)
    • 2.5 时间函数:时间戳与格式化(time.h)
  • 三、Dev-C++保姆式实操
    • 3.1 实操1:安全字符串处理全流程
    • 3.2 实操2:内存函数处理结构体/数组
    • 3.3 实操3:错误处理与数学函数结合
    • 3.4 实操4:获取并格式化系统时间
    • 3.5 保姆式排错指南
  • 四、踩坑记录
  • 五、课后小练习
  • 六、小结

一、本章学习目标

  • 熟练使用字符串核心函数,区分“不安全函数”与“安全函数”,避免内存越界、乱码等问题;
  • 掌握内存操作函数,能处理结构体、数组、二进制数据等非字符串类型的内存初始化/拷贝/比较;
  • 会用常用数学函数,清楚数据类型要求与编译注意事项;
  • 掌握错误处理函数,能在文件操作、内存申请失败时输出清晰的错误原因;
  • 了解时间函数基础用法,能获取系统时间、实现简单计时功能;
  • 养成“优先用标准库,不手写重复逻辑”的工程化编程习惯。

  • 二、核心知识点

    2.1 字符串处理函数:文本操作的核心(#include <string.h>)

    C语言没有原生的“字符串类型”,所有字符串本质是以\\0(空字符,ASCII码0)结尾的字符数组。字符串函数的核心逻辑都是围绕“识别\\0、操作字符数组”展开,是日常开发中使用频率最高的函数群。

    2.1.1 基础函数:strlen/strcpy/strncpy
    1. strlen:获取字符串有效长度

    size_t strlen(const char *s);

    • 功能:计算字符串s的有效长度(只统计\\0之前的字符,不包含\\0本身);
    • 返回值:size_t类型(无符号整数),这是新手最容易踩坑的点;
    • 示例:char str[] = "hello";
      printf("strlen: %zu\\n", strlen(str)); // 输出5(有效字符数)
      printf("sizeof: %zu\\n", sizeof(str)); // 输出6(包含\\0)
    • 坑点:不要用strlen的返回值做减法比较。比如strlen(a) – strlen(b),如果a比b短,无符号数相减会得到一个极大的正数,而非负数:char a[] = "hi";
      char b[] = "hello";
      if (strlen(a) – strlen(b) < 0) { // 错误:结果是无符号数,永远为正
      printf("a更短\\n");
      }
      // 正确写法:先转成有符号数
      if ((int)strlen(a) – (int)strlen(b) < 0) {
      printf("a更短\\n");
      }
    2. strcpy:字符串拷贝(不安全)

    char *strcpy(char *dest, const char *src);

    • 功能:把src指向的字符串(包含\\0)完整拷贝到dest指向的内存;
    • 风险:完全不检查dest的内存空间大小,如果src长度超过dest,会直接导致内存越界,破坏其他变量/数据,甚至让程序崩溃;
    • 示例(错误):char dest[3]; // 只有3字节空间
      char src[] = "hello"; // 6字节(含\\0)
      strcpy(dest, src); // 内存越界,程序大概率崩溃
    3. strncpy:有限长度字符串拷贝(工程推荐)

    char *strncpy(char *dest, const char *src, size_t n);

    • 功能:最多拷贝n个字符从src到dest,避免无脑越界;
    • 关键规则:
    • 如果src长度 < n:拷贝完src后,会补\\0直到凑够n个字符;
    • 如果src长度 >= n:只拷贝前n个字符,不会自动补\\0;
    • 工程规范写法(必记):char dest[32];
      char src[] = "这是一个很长的字符串,可能超过dest长度";
      // 1. 最多拷贝sizeof(dest)-1个字符(留1个位置给\\0)
      strncpy(dest, src, sizeof(dest)–1);
      // 2. 强制补\\0,避免src长度>=n时没有结束符导致乱码
      dest[sizeof(dest)–1] = '\\0';
    2.1.2 拼接与比较:strcat/strncat/strcmp/strncmp
    1. strcat/strncat:字符串拼接

    char *strcat(char *dest, const char *src); // 不安全
    char *strncat(char *dest, const char *src, size_t n); // 安全

    • 功能:把src拼接到dest的末尾(覆盖dest原有的\\0);
    • strcat风险:不检查dest剩余空间,容易越界;
    • strncat优势:只拼接前n个字符,且自动在末尾补\\0(无需手动补);
    • 示例:char dest[32] = "姓名:";
      char src[] = "张三 年龄:20";
      strncat(dest, src, sizeof(dest)–strlen(dest)–1); // 计算剩余空间
      printf("%s\\n", dest); // 输出:姓名:张三 年龄:20
    2. strcmp/strncmp:字符串比较

    int strcmp(const char *a, const char *b); // 比较整个字符串
    int strncmp(const char *a, const char *b, size_t n); // 只比较前n个字符

    • 核心规则:按ASCII码逐字符比较,直到遇到不同字符或\\0;
    • 返回值:
      • 0:两段字符串(前n个字符)相等;
      • < 0:a的第一个不同字符ASCII码 < b;
      • 0:a的第一个不同字符ASCII码 > b;

    • 新手致命坑:绝对不要用==比较字符串!==比较的是字符数组的地址,而非内容:char a[] = "hello";
      char b[] = "hello";
      if (a == b) { // 错误:a和b是不同数组,地址不同
      printf("相等\\n");
      }
      if (strcmp(a, b) == 0) { // 正确:比较字符串内容
      printf("相等\\n");
      }
    2.1.3 查找与分割:strstr/strchr/strtok
    1. strstr:子串查找

    char *strstr(const char *haystack, const char *needle);

    • 功能:在haystack中查找needle第一次出现的位置(区分大小写);
    • 返回值:找到则返回子串起始地址,没找到返回NULL;
    • 示例:char str[] = "hello world, hello C";
      char *pos = strstr(str, "world");
      if (pos != NULL) {
      printf("找到子串:%s\\n", pos); // 输出:world, hello C
      }
    2. strchr:字符查找

    char *strchr(const char *s, int c);

    • 功能:在s中查找字符c第一次出现的位置;
    • 示例:查找字符串中的第一个逗号:char str[] = "张三,20,90.5";
      char *comma = strchr(str, ',');
      if (comma != NULL) {
      *comma = '\\0'; // 截断字符串
      printf("姓名:%s\\n", str); // 输出:姓名:张三
      }
    3. strtok:字符串分割(按分隔符拆分)

    char *strtok(char *str, const char *delim);

    • 核心规则:
    • 第一次调用:str传入要分割的字符串,函数会找到第一个分隔符,替换为\\0,返回第一个子串地址;
    • 后续调用:str传入NULL,函数会从上次分割的位置继续找下一个子串;
    • 分割完成(无分隔符):返回NULL;
    • 示例(拆分CSV格式字符串):char str[] = "张三,20,90.5"; // 必须是可修改的字符数组,不能是字符串常量
      char *delim = ",";
      // 第一次分割
      char *name = strtok(str, delim);
      // 后续分割
      char *age = strtok(NULL, delim);
      char *score = strtok(NULL, delim);

      printf("姓名:%s,年龄:%s,分数:%s\\n", name, age, score);

    2.2 内存操作函数:通用数据处理(#include <string.h>)

    字符串函数只认\\0,无法处理“不含\\0的二进制数据”(如结构体、int数组、图片),此时必须用内存操作函数——它们以“字节”为单位操作数据,不关心内容是什么。

    2.2.1 内存初始化:memset

    void *memset(void *s, int c, size_t n);

    • 功能:把s指向的内存起始位置的n个字节,逐字节设置为c(c会被转为unsigned char);
    • 最常用场景:结构体/数组清零(工程开发中几乎必用);
    • 示例:// 1. 数组清零
      int arr[10];
      memset(arr, 0, sizeof(arr)); // 把arr的10*4=40字节全部设为0

      // 2. 结构体清零
      typedef struct {
      char name[20];
      int id;
      double score;
      } Student;
      Student stu;
      memset(&stu, 0, sizeof(stu)); // 所有成员初始化为0

    • 坑点:不要用memset给非字符类型数组设“非0值”!比如想把int数组设为1:int arr[2];
      memset(arr, 1, sizeof(arr)); // 错误:每个字节设为1,而非每个int设为1
      // arr[0]实际值:0x01010101 = 16843009,而非1
    2.2.2 内存拷贝:memcpy/memmove
    1. memcpy:基础内存拷贝

    void *memcpy(void *dest, const void *src, size_t n);

    • 功能:从src拷贝n个字节到dest;
    • 限制:不能处理内存重叠的情况(如dest和src指向的内存有交集),否则拷贝结果会错乱;
    • 示例(拷贝结构体数组):Student stus1[3] = {{"张三", 1, 90}, {"李四", 2, 88}};
      Student stus2[3];
      // 拷贝2个Student结构体(每个占20+4+8=32字节)
      memcpy(stus2, stus1, 2 * sizeof(Student));
    2. memmove:安全内存拷贝(支持重叠)

    void *memmove(void *dest, const void *src, size_t n);

    • 核心优势:内部做了内存重叠判断,即使dest和src重叠,也能正确拷贝;
    • 工程建议:不确定内存是否重叠时,一律用memmove,性能损失可以忽略,安全性大幅提升;
    • 示例(内存重叠拷贝):int arr[] = {1,2,3,4,5};
      // 把arr[1]开始的3个元素(2,3,4)拷贝到arr[0]位置,内存重叠
      memmove(arr, arr+1, 3*sizeof(int));
      // 结果:arr = {2,3,4,4,5}(如果用memcpy会出错)
    2.2.3 内存比较:memcmp

    int memcmp(const void *a, const void *b, size_t n);

    • 功能:逐字节比较a和b的前n个字节;
    • 返回值:和strcmp规则一致(0=相等,<0=a<b,>0=a>b);
    • 适用场景:比较结构体、二进制数据是否完全一致;
    • 示例:Student stu1 = {"张三", 1, 90};
      Student stu2 = {"张三", 1, 90};
      if (memcmp(&stu1, &stu2, sizeof(Student)) == 0) {
      printf("两个结构体内容一致\\n");
      }

    2.3 数学函数:数值计算(#include <math.h>)

    数学函数默认以double类型为输入/输出,使用时注意类型匹配,避免精度丢失。

    常用数学函数表
    函数名功能示例
    sqrt(x) 计算平方根(x≥0) sqrt(16.0) → 4.0
    fabs(x) 浮点数绝对值 fabs(-3.14) → 3.14
    abs(x) 整数绝对值(头文件stdlib.h) abs(-5) → 5
    pow(x, y) x的y次方 pow(2, 3) → 8.0
    ceil(x) 向上取整 ceil(3.2) → 4.0
    floor(x) 向下取整 floor(3.9) → 3.0
    round(x) 四舍五入 round(3.5) → 4.0
    sin(x)/cos(x) 正弦/余弦(x为弧度) sin(M_PI/2) → 1.0
    注意事项
  • 编译链接:部分编译器(如GCC)使用数学函数时,需要在编译命令后加-lm(链接数学库):gcc test.c -o test -lm
  • 精度问题:不要用sqrt等函数判断整数是否为完全平方数,浮点数精度误差会导致错误:// 错误示例:10000000000000001的平方根是1000000000.000000005,会被floor成1000000000
    int num = 10000000000000001;
    double s = sqrt(num);
    if (s == floor(s)) {
    printf("是完全平方数\\n"); // 错误判断
    }
  • 类型匹配:传入int类型时会自动转为double,但接收返回值时要注意:int a = 16;
    int res = sqrt(a); // 错误:sqrt返回4.0,转为int是4(看似正确,但不规范)
    double res = sqrt(a); // 正确
  • 2.4 错误处理函数:让程序更健壮(#include <stdio.h>/#include <errno.h>)

    开发中经常遇到“文件打不开”“内存申请失败”“系统调用出错”等情况,只打印“出错了”毫无意义——需要知道具体错在哪里,这就是错误处理函数的核心作用。

    1. errno:错误码全局变量
    • errno是<errno.h>中定义的全局变量,系统调用/库函数失败时,会把对应的错误码存入errno;
    • 每个错误码对应一个具体的错误原因(如文件不存在、权限不足、内存不足);
    • 关键规则:只有函数明确返回“失败”(如fopen返回NULL、malloc返回NULL)时,才检查errno——成功时errno的值是未定义的(可能保留上次的错误码)。
    2. perror:打印错误原因(最常用)

    void perror(const char *msg);

    • 功能:先打印自定义提示msg,再自动拼接冒号、空格,最后打印errno对应的系统错误描述;
    • 示例(文件打开失败处理):FILE *fp = fopen("nonexist.txt", "r");
      if (fp == NULL) {
      perror("fopen failed"); // 输出:fopen failed: No such file or directory
      return 1;
      }
    3. strerror:获取错误描述字符串

    char *strerror(int errnum);

    • 功能:根据错误码errnum,返回对应的错误描述字符串;
    • 示例:#include <errno.h>
      #include <string.h>

      FILE *fp = fopen("nonexist.txt", "r");
      if (fp == NULL) {
      printf("打开文件失败:%s\\n", strerror(errno));
      return 1;
      }

    2.5 时间函数:时间戳与格式化(#include <time.h>)

    时间函数用于获取系统时间、记录日志时间戳、实现程序计时等,是工程开发中常用的辅助功能。

    1. 核心类型与函数
    类型/函数功能
    time_t 时间戳类型(本质是long,存储从1970-01-01 00:00:00到现在的秒数)
    struct tm 分解时间结构体,包含年、月、日、时、分、秒等字段
    time(&t) 获取当前系统时间戳,存入t;传入NULL直接返回时间戳
    localtime(&t) 把时间戳t转为本地时间的struct tm结构体
    difftime(t2, t1) 计算t2 – t1的秒数(返回double)
    2. struct tm结构体字段

    struct tm {
    int tm_sec; // 秒(0~59)
    int tm_min; // 分(0~59)
    int tm_hour; // 时(0~23)
    int tm_mday; // 日(1~31)
    int tm_mon; // 月(0~11,0=1月)
    int tm_year; // 年(从1900开始算,如2024=124)
    int tm_wday; // 星期(0~6,0=周日)
    int tm_yday; // 年内天数(0~365)
    int tm_isdst; // 夏令时标记
    };

    3. 基础用法示例

    // 1. 获取当前时间戳
    time_t now_ts = time(NULL);
    printf("当前时间戳:%ld\\n", now_ts);

    // 2. 转为本地时间
    struct tm *now_tm = localtime(&now_ts);
    // 注意:tm_year要+1900,tm_mon要+1
    printf("当前时间:%04d-%02d-%02d %02d:%02d:%02d\\n",
    now_tm->tm_year + 1900,
    now_tm->tm_mon + 1,
    now_tm->tm_mday,
    now_tm->tm_hour,
    now_tm->tm_min,
    now_tm->tm_sec);

    // 3. 程序计时(计算函数执行耗时)
    time_t start = time(NULL);
    // 模拟耗时操作
    for (int i = 0; i < 100000000; i++);
    time_t end = time(NULL);
    printf("耗时:%.0f 秒\\n", difftime(end, start));

    坑点
    • localtime返回的是静态内存区的地址,多次调用会覆盖之前的结果:time_t t1 = time(NULL) – 86400; // 昨天
      time_t t2 = time(NULL); // 今天
      struct tm *tm1 = localtime(&t1);
      struct tm *tm2 = localtime(&t2); // 覆盖tm1的内容
      // 此时tm1和tm2都指向今天的时间
    • 解决方法:拷贝struct tm的内容到自定义变量:struct tm tm1, tm2;
      time_t t1 = time(NULL) – 86400;
      time_t t2 = time(NULL);
      tm1 = *localtime(&t1); // 拷贝内容
      tm2 = *localtime(&t2); // 拷贝内容

    三、Dev-C++保姆式实操

    3.1 实操1:安全字符串处理全流程

    实现字符串的安全拷贝、拼接、查找、分割,Dev-C++可直接编译运行:

    #include <stdio.h>
    #include <string.h>

    int main() {
    // 1. 安全字符串拷贝
    char dest[64];
    char src[] = "C语言标准库字符串处理示例,这是一段较长的文本";
    // 安全拷贝:留1字节给\\0
    strncpy(dest, src, sizeof(dest)–1);
    dest[sizeof(dest)–1] = '\\0';
    printf("1. 安全拷贝结果:%s\\n", dest);

    // 2. 字符串拼接
    char append[] = " | 拼接的内容";
    strncat(dest, append, sizeof(dest)–strlen(dest)–1);
    printf("2. 拼接结果:%s\\n", dest);

    // 3. 子串查找
    char *sub = strstr(dest, "标准库");
    if (sub != NULL) {
    printf("3. 找到子串:%s\\n", sub);
    }

    // 4. 字符串分割
    char csv_str[] = "张三,男,20,90.5";
    char *token = strtok(csv_str, ",");
    int i = 1;
    printf("4. 分割CSV结果:\\n");
    while (token != NULL) {
    printf(" 字段%d:%s\\n", i++, token);
    token = strtok(NULL, ",");
    }

    getchar();
    return 0;
    }

    运行结果

    1. 安全拷贝结果:C语言标准库字符串处理示例,这是一段较长的文本
    2. 拼接结果:C语言标准库字符串处理示例,这是一段较长的文本 | 拼接的内容
    3. 找到子串:标准库字符串处理示例,这是一段较长的文本 | 拼接的内容
    4. 分割CSV结果:
    字段1:张三
    字段2:男
    字段3:20
    字段4:90.5

    3.2 实操2:内存函数处理结构体/数组

    用memset/memcpy/memmove处理结构体和数组,Dev-C++可直接编译运行:

    #include <stdio.h>
    #include <string.h>

    // 定义学生结构体
    typedef struct {
    char name[20];
    int id;
    double score;
    } Student;

    int main() {
    // 1. 结构体清零与赋值
    Student stu;
    memset(&stu, 0, sizeof(stu)); // 清零
    strncpy(stu.name, "李四", sizeof(stu.name)–1);
    stu.id = 202402;
    stu.score = 88.5;
    printf("1. 初始化后的结构体:\\n");
    printf(" 姓名:%s,学号:%d,分数:%.1lf\\n", stu.name, stu.id, stu.score);

    // 2. 结构体拷贝
    Student stu_copy;
    memcpy(&stu_copy, &stu, sizeof(Student));
    printf("2. 拷贝后的结构体:\\n");
    printf(" 姓名:%s,学号:%d,分数:%.1lf\\n", stu_copy.name, stu_copy.id, stu_copy.score);

    // 3. 数组内存重叠拷贝
    int arr[] = {1,2,3,4,5,6};
    printf("3. 原数组:");
    for (int i = 0; i < 6; i++) {
    printf("%d ", arr[i]);
    }
    // 把arr[2]开始的3个元素(3,4,5)拷贝到arr[0]位置(内存重叠)
    memmove(arr, arr+2, 3*sizeof(int));
    printf("\\n 内存重叠拷贝后:");
    for (int i = 0; i < 6; i++) {
    printf("%d ", arr[i]);
    }

    getchar();
    return 0;
    }

    运行结果

    1. 初始化后的结构体:
    姓名:李四,学号:202402,分数:88.5
    2. 拷贝后的结构体:
    姓名:李四,学号:202402,分数:88.5
    3. 原数组:1 2 3 4 5 6
    内存重叠拷贝后:3 4 5 4 5 6

    3.3 实操3:错误处理与数学函数结合

    实现“计算圆面积”,并处理输入错误、数学函数调用错误,Dev-C++可直接编译运行:

    #include <stdio.h>
    #include <math.h>
    #include <errno.h>
    #include <string.h>

    #define PI 3.1415926

    int main() {
    double r;
    printf("请输入圆的半径:");
    // 检查输入是否有效
    if (scanf("%lf", &r) != 1) {
    perror("scanf failed");
    return 1;
    }

    // 检查半径是否非负
    if (r < 0) {
    errno = EINVAL; // 手动设置无效参数错误码
    printf("计算面积失败:%s\\n", strerror(errno));
    return 1;
    }

    // 计算面积
    double area = PI * pow(r, 2);
    printf("半径为%.2lf的圆面积:%.2lf\\n", r, area);

    getchar();
    getchar();
    return 0;
    }

    运行结果1(正常输入)

    请输入圆的半径:2
    半径为2.00的圆面积:12.57

    运行结果2(输入非数字)

    请输入圆的半径:abc
    scanf failed: Invalid argument

    运行结果3(输入负数)

    请输入圆的半径:-3
    计算面积失败:Invalid argument

    3.4 实操4:获取并格式化系统时间

    获取当前时间、计算程序耗时,Dev-C++可直接编译运行:

    #include <stdio.h>
    #include <time.h>
    #include <unistd.h> // 用于sleep函数(Dev-C++需勾选“兼容POSIX”)

    int main() {
    // 1. 获取当前时间并格式化
    time_t now_ts = time(NULL);
    struct tm now_tm = *localtime(&now_ts); // 拷贝内容,避免被覆盖
    printf("1. 当前系统时间:\\n");
    printf(" 时间戳:%ld\\n", now_ts);
    printf(" 格式化:%04d-%02d-%02d %02d:%02d:%02d\\n",
    now_tm.tm_year + 1900,
    now_tm.tm_mon + 1,
    now_tm.tm_mday,
    now_tm.tm_hour,
    now_tm.tm_min,
    now_tm.tm_sec);

    // 2. 程序计时(模拟耗时操作)
    time_t start = time(NULL);
    printf("\\n2. 开始执行耗时操作…\\n");
    sleep(2); // 休眠2秒
    time_t end = time(NULL);
    printf(" 耗时:%.0f 秒\\n", difftime(end, start));

    getchar();
    return 0;
    }

    运行结果

    1. 当前系统时间:
    时间戳:1741567890
    格式化:2025-03-09 15:31:30

    2. 开始执行耗时操作…
    耗时:2 秒

    3.5 保姆式排错指南

    报错/异常现象核心原因解决方法
    字符串打印出现乱码 strncpy拷贝后未手动补\\0,或字符串无结束符 拷贝后强制设置dest[sizeof(dest)-1] = ‘\\0’
    用==比较字符串永远不相等 ==比较的是地址,而非内容 改用strcmp函数比较字符串内容
    memset给int数组设1后,数值不是1 memset逐字节赋值,int占4字节,每个字节设1导致数值错误 非字符数组清零用memset,设非0值手动循环赋值
    数学函数编译报错“undefined reference” 未链接数学库 Dev-C++中:项目→项目属性→参数→链接器,添加“-lm”
    localtime多次调用后结果相同 localtime返回静态区地址,被后续调用覆盖 拷贝struct tm的内容到自定义变量
    perror打印的错误信息不对 函数成功时检查了errno,读取到旧的错误码 只在函数明确失败后(返回NULL/-1)检查errno
    strtok分割字符串崩溃 传入了字符串常量(不可修改),strtok会修改原字符串 确保传入可修改的字符数组,而非const char*

    四、踩坑记录

  • 字符串比较用==:初学阶段写登录功能,用==比较输入的密码和正确密码,无论输入什么都提示错误,排查了半天才知道要用水strcmp;
  • strcpy不检查空间:写文件解析程序时,用strcpy拷贝未知长度的字符串到固定大小的数组,程序偶尔崩溃,换成strncpy+手动补\\0后彻底解决;
  • memset设非0值:想把int数组全部设为1,用memset后打印数组发现数值全是16843009,查了文档才明白memset是逐字节赋值;
  • localtime返回值被覆盖:打印“昨天”和“今天”的时间,结果两个都是今天,后来才知道要拷贝结构体内容;
  • 数学函数精度误差:用sqrt判断完全平方数,遇到大数时出错,放弃浮点数方案,改用整数乘法判断;
  • strtok传入字符串常量:用strtok分割"a,b,c",程序直接崩溃,才知道strtok会修改原字符串,必须用字符数组;
  • perror提前检查errno:函数成功后打印errno,结果显示“没有这样的文件或目录”,误以为函数失败,后来记住“失败才查errno”。

  • 五、课后小练习

  • 写一个函数,输入一个字符串,返回它的逆序字符串(要求用strlen+memmove实现,不能手动循环);
  • 用memset和memcpy实现“结构体数组的批量初始化与拷贝”;
  • 写一个程序,接收用户输入的两个数字,计算它们的平方根、绝对值、乘积的次方,并处理输入错误;
  • 实现一个简单的日志函数,每次打印日志时,自动拼接当前系统时间(格式:[2024-01-01 12:00:00] 日志内容);
  • 用strstr和strncpy实现“截取字符串中两个指定子串之间的内容”(如从“abc123def”中截取“abc”和“def”之间的“123”)。

  • 六、小结

           本章我们把C语言标准库中最核心、最实用的函数全部梳理完毕:字符串函数解决文本处理问题,内存函数解决通用数据的初始化/拷贝/比较,数学函数提供数值计算能力,错误处理函数让程序更健壮,时间函数让程序能和系统时间交互。        标准库的设计思想是“通用、高效、安全”,熟练使用这些函数,能让你摆脱“重复造轮子”的低效状态,写出更简洁、更稳定的代码。需要注意的是,每个函数都有其适用场景和坑点——比如strcpy的越界风险、memset的字节赋值规则、localtime的静态内存问题,这些都是工程开发中容易出bug的地方,一定要牢记规范用法。        下一章我们将进入内存管理与指针进阶总结,把前面学过的指针、数组、动态内存、结构体、函数指针串联成完整的内存模型,帮你建立真正的C语言底层思维,彻底搞懂“内存到底是怎么回事”。

    赞(0)
    未经允许不得转载:171主机测评 » 大学生版保姆式学《C和指针》——C标准库:常用函数全解与避坑
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址