Skip to content

数组 ​

  • 声明数组必须要去指定数组长度
c
#include <stdio.h>

int main() {
    // 主动声明长度
    int arr1[3] = {1, 3, 5};

    // 长度由内容确定(编译器自动计算长度)
    int arr2[] = {2, 4, 6, 8};

    printf("arr1[0] = %d \n", arr1[0]); // 1
    printf("arr2[3] = %d \n", arr2[3]); // 8
}

遍历数组 ​

同其他编程语言一样, 但是在 c 语言中, 无法直接获取数组的长度, 比如 js 的 arr.length 属性, 需要手动声明需要遍历多少次, 所以我推荐, 在使用数组的时候, 先声明数组的长度

c
#include <stdio.h>

int main() {
    int arr[] = {2, 4, 6, 8};
    for (int i = 0; i < 4; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
}
c
#include <stdio.h>

int main() {
    // 注意: 必须加 const, 否则 len 是变长数组(VLA), 不能带初始化器
    const int len = 4;
    int arr[len] = {2, 4, 6, 8};
    for (int i = 0; i < len; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
}

推荐先声明数组长度, 这样需要遍历多少次一目了然, 也不容易写错

二维数组 ​

数组的元素也可以是数组, 这就是二维数组, 可以理解为"表格"

c
#include <stdio.h>

int main() {
    // 3 行 4 列的二维数组
    int matrix[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };

    // 遍历二维数组(双重循环)
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%2d ", matrix[i][j]);
        }
        printf("\n");
    }
}

字符串 ​

字符串的本质 ​

字符串的本质就是 char 数组, 编译器会在最后自动增加一个 '\0' 元素作为结束标志

c
#include <stdio.h>

int main() {
    // 直接声明字符串(编译器自动加上 '\0')
    char str1[] = "hello";
    printf("str1 %s\n", str1);

    // 使用数组声明字符串, 注意最后一个元素 '\0' 不能少
    char str2[] = {'h', 'e', 'l', 'l', 'o', '\0'};
    printf("str2 %s\n", str2);

    // 如果少了 '\0', printf 会一直往后读到内存中的 '\0' 为止
    char str3[] = {'h', 'i'};
    printf("str3 %s\n", str3);
}

为什么需要 '\0'?

因为字符串没有"长度"属性, 程序只能靠 '\0' 来判断字符串在哪里结束, 所以所有字符串处理函数都是遇到 '\0' 才停下来的

获取字符串长度 ​

注意需要导入头文件 <string.h>

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

int main() {
    char str[] = "hello";
    int len    = strlen(str);
    printf("length is %d \n", len); // 5
}

sizeof 和 strlen 的区别

  • strlen: 字符串的长度(不包含 '\0')
  • sizeof: 整个数组占用的字节数(包含 '\0', 也叫数组容量)
c
#include <stdio.h>
#include <string.h>

int main() {
    char str[64] = "hello";

    printf("strlen = %zu \n", strlen(str)); // 5    (内容长度, 不含 '\0')
    printf("sizeof = %zu \n", sizeof(str)); // 64   (整个数组占用的空间)
}

字符串常用函数 ​

函数作用
strcpy复制字符串(覆盖)
strcat拼接字符串
strcmp比较字符串(相等返回 0)
strstr查找子串, 返回指针
c
#include <stdio.h>
#include <string.h>

int main() {
    char str[64] = "hello";

    // strcat: 拼接(在末尾追加)
    strcat(str, " world");
    printf("%s\n", str); // hello world

    // strcpy: 复制(会覆盖目标数组原来的内容)
    char copy[64];
    strcpy(copy, str);
    printf("%s\n", copy); // hello world

    // strcmp: 比较, 相等返回 0, 常用于判断两个字符串是否相同
    if (strcmp(str, copy) == 0) {
        printf("两个字符串相等\n"); // 两个字符串相等
    }

    // strstr: 查找子串, 找到返回子串位置的指针, 没找到返回 NULL
    char *found = strstr(str, "world");
    if (found != NULL) {
        printf("找到了: %s\n", found); // 找到了: world
    }
}

正则表达式 ​

正则表达式(Regular Expression)是用来匹配字符串的规则

比如要校验用户输入的手机号: 需要判断是否是 11 位、是否全是数字、是否以 1 开头, 用 if 一个个判断非常麻烦, 而正则表达式用一行规则就能搞定

C 语言标准本身没有正则, 但是 POSIX 标准提供了 <regex.h> 头文件, Linux/macOS 自带

基本语法 ​

POSIX 扩展正则(配合 REG_EXTENDED 标志)常用语法:

语法作用示例
普通字符匹配字符本身a 匹配 "a"
.匹配任意单个字符a.c 匹配 "abc"
[...]字符集合[0-9] 匹配数字
[^...]取反字符集合[^0-9] 匹配非数字
*前一个字符出现 0 次或多次ab*c 匹配 "ac"/"abbc"
+前一个字符出现 1 次或多次ab+c 匹配 "abc"/"abbc"
?前一个字符出现 0 次或 1 次ab?c 匹配 "ac"/"abc"
{n,m}前一个字符出现 n 到 m 次a{2,3} 匹配 "aa"/"aaa"
^匹配行首^hello 以 hello 开头
$匹配行尾world$ 以 world 结尾
(...)分组(可以提取内容)(ab)+ 匹配 "ab"/"abab"
|或cat|dog 匹配 cat 或 dog

注意: POSIX 正则和 JavaScript/Python 的正则不太一样

  • 不支持 \d \w \s 这种简写, 数字要写 [0-9] 或 [[:digit:]]
  • 默认是"基础正则"(BRE), + ? | () 这些语法需要加 REG_EXTENDED 标志才能用

匹配流程 ​

使用 POSIX 正则分三步: regcomp 编译 → regexec 匹配 → regfree 释放

函数作用
regcomp编译正则表达式
regexec执行匹配, 返回 0 表示匹配成功
regfree释放编译后的正则(必须调用)
regerror获取错误信息
c
#include <stdio.h>
#include <regex.h>

int main() {
    regex_t regex;

    // 1. 编译正则表达式
    // REG_EXTENDED: 使用扩展正则(支持 + ? | 分组等)
    // 返回 0 表示编译成功
    int ret = regcomp(&regex, "^h.*o$", REG_EXTENDED);
    if (ret != 0) {
        printf("正则编译失败\n");
        return 1;
    }

    // 2. 执行匹配, regexec 返回 0 表示匹配成功
    ret = regexec(&regex, "hello", 0, NULL, 0);
    if (ret == 0) {
        printf("匹配成功\n"); // 匹配成功
    } else {
        printf("匹配失败\n");
    }

    // 3. 释放资源(不调用会造成内存泄漏)
    regfree(&regex);
}

提取匹配的内容 ​

regexec 的第四个参数是 regmatch_t 数组, 用来保存匹配到的位置

字段作用
rm_so匹配内容开始的位置(下标)
rm_eo匹配内容结束的位置(下标)
c
#include <stdio.h>
#include <regex.h>
#include <string.h>

int main() {
    regex_t regex;
    regmatch_t match[1]; // 只需要第 0 组(整个匹配)

    regcomp(&regex, "[0-9]+", REG_EXTENDED);

    const char *text = "订单号: 10086, 金额: 99";
    int ret = regexec(&regex, text, 1, match, 0);

    if (ret == 0) {
        // 根据匹配到的位置, 从原字符串中截取内容
        int start = match[0].rm_so;
        int end   = match[0].rm_eo;

        char result[32] = {0};
        strncpy(result, text + start, end - start);
        printf("匹配到的内容: %s\n", result); // 匹配到的内容: 10086
    } else {
        printf("没有匹配到\n");
    }

    regfree(&regex);
}

分组捕获 ​

用 (...) 分组之后, regmatch_t 数组的每一项就对应一个分组(第 0 项是整个匹配)

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

int main() {
    regex_t regex;
    regmatch_t match[3]; // 第 0 组是整个匹配, 第 1/2 组是括号里的内容

    // 两个分组: (4位数字)-(2位数字)
    regcomp(&regex, "([0-9]{4})-([0-9]{2})", REG_EXTENDED);

    const char *text = "日期: 2026-06";
    int ret = regexec(&regex, text, 3, match, 0);

    if (ret == 0) {
        for (int i = 0; i < 3; i++) {
            char buf[32] = {0};
            strncpy(buf, text + match[i].rm_so, match[i].rm_eo - match[i].rm_so);
            printf("第%d组: %s\n", i, buf);
        }
        // 第0组: 2026-06
        // 第1组: 2026
        // 第2组: 06
    }

    regfree(&regex);
}

匹配所有结果 ​

使用循环, 每次把游标移动到上次匹配结束的位置, 继续找下一个

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

int main() {
    regex_t regex;
    regmatch_t match[1];

    regcomp(&regex, "[0-9]+", REG_EXTENDED);

    const char *text   = "今年是2026年, 有12个月, 365天";
    const char *cursor = text; // 游标: 从哪个位置开始匹配

    while (regexec(&regex, cursor, 1, match, 0) == 0) {
        char buf[32] = {0};
        strncpy(buf, cursor + match[0].rm_so, match[0].rm_eo - match[0].rm_so);
        printf("数字: %s\n", buf); // 数字: 2026 / 数字: 12 / 数字: 365

        // 移动游标到本次匹配结束的位置, 继续找下一个
        cursor += match[0].rm_eo;
    }

    regfree(&regex);
}

注意点

  • regfree 一定要调用, 否则会内存泄漏
  • Windows 的 MSVC 没有 <regex.h>, 需要使用第三方库(如 PCRE)
  • POSIX 正则不支持 \d \w 简写, 数字用 [0-9] 或 [[:digit:]]

获取用户输入 ​

c
#include <stdio.h>

int main() {
  // 获取用户输入(数字)
  int num;
  scanf("%d", &num);
  printf("your input is: %d \n", num);

  // 获取用户输入(浮点数)
  float fnum;
  scanf("%f", &fnum);
  printf("your input is: %f \n", fnum);
}

注意: scanf 读取字符串时, 遇到空格会停下来, 所以只能读取"单词"

c
#include <stdio.h>

int main() {
  char name[32];
  scanf("%s", name);       // 输入: tom jack
  printf("name is: %s \n", name); // 只会输出: tom
}

猜数字游戏 ​

结合前面学过的内容, 写一个完整的程序

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

// 可以先不管这些是什么, 只需要知道
// 这个方法生成指定范围内的随机数字
int range(int min, int max) {
  srand(time(NULL));
  return rand() % (max - min + 1) + min;
}

int main() {
  printf("===== 猜数字游戏 =====\n");

  // 数字范围
  int min = 1;
  int max = 9;

  // 谜底数字
  int secret_num = range(min, max);

  // 输入次数控制
  int guess_changes = 3;
  printf("请输入一个数字(%d-%d), 你有%d次机会\n", min, max, guess_changes);

  for (int i = 0; i < guess_changes; i++) {
    // 获取用户输入(数字)
    int input_num;
    scanf("%d", &input_num);

    if (input_num > secret_num) {
      printf("你猜的数字太大了\n");
    } else if (input_num < secret_num) {
      printf("你猜的数字太小了\n");
    } else {
      printf("恭喜你猜对了, 你赢了\n");
      break;
    }

    if (i == guess_changes - 1) {
      printf("很遗憾, 你的机会用完了\n");
    }
  }
}

Released under the MIT License.