C 语言字符大小写互转:tolower / toupper 详解与实战
🚀个人主页:BabyZZの秘密日记
📖收入专栏:C语言
🌍文章目入
- 一、函数原型
- 二、实现原理(glibc 2.39 源码节选)
- 三、常见陷阱与最佳实践
- 四、完整示例:大小写不敏感查找子串
- 五、性能扩展:批量转换的 SIMD 思路
- 六、小结
在文本处理、协议解析、命令行解析等场景中,“大小写不敏感”是十分常见的需求。C 标准库 <ctype.h>
提供了两个最常用的工具函数:
int tolower(int c);
—— 大写 → 小写int toupper(int c);
—— 小写 → 大写
本文将从 函数原型、实现原理、可移植陷阱、完整示例、性能扩展 五个方面带你一次吃透它们。
一、函数原型
#include <ctype.h>int tolower(int c); /* 若 c 是大写字母('A'~'Z'),返回对应小写字母;否则返回原值 */
int toupper(int c); /* 若 c 是小写字母('a'~'z'),返回对应大写字母;否则返回原值 */
注意:参数与返回值类型都是
int
,但实际只使用低 8 位;传入EOF(-1)
是合法的,函数会原样返回。
二、实现原理(glibc 2.39 源码节选)
int tolower(int c) {if (isupper(c))return c | 0x20; /* 0x20 = 32,把第 5 位(bit5)置 1 即可 */return c;
}int toupper(int c) {if (islower(c))return c & ~0x20; /* 把第 5 位清 0 */return c;
}
-
为什么用第 5 位?
ASCII 表中,'A'=0x41
,'a'=0x61
,差值正好是0x20
,因此可以用位运算实现极快的转换。 -
与 locale 的关系
在默认的"C"
或"POSIX"
locale 下,仅对 26 个英文字母生效;若切换到其它 locale(如 UTF-8),tolower('İ')
也可能返回'i'
(视实现而定)。因此 不要假设 ASCII 以外字符的行为。
三、常见陷阱与最佳实践
-
参数必须先用
unsigned char
强转
char
可能为有符号类型,值 >127 会被当作负数,导致未定义行为(UB)。char buf[] = "Straße"; // 'ß' = 0xDF for (size_t i = 0; buf[i]; ++i)buf[i] = (char)tolower((unsigned char)buf[i]);
-
不能链式连续调用
toupper(tolower(c))
看起来可以“统一成大写”,但如果c
不是字母,会两次经过函数调用,浪费 CPU 缓存行。更好的做法是先判断:c = islower(c) ? toupper(c) : c;
-
宏 vs 函数
<ctype.h>
还提供了宏_tolower(c)
/_toupper(c)
,它们 不做范围检查,只在已知c
为大/小写时调用,速度更快。使用前要自行保证条件。
四、完整示例:大小写不敏感查找子串
#include <stdio.h>
#include <ctype.h>
#include <string.h>/* 忽略大小写的 strstr */
const char *strcasestr(const char *haystack, const char *needle) {if (!*needle) return haystack;for (; *haystack; ++haystack) {const char *h = haystack;const char *n = needle;while (*h && *n && tolower((unsigned char)*h) == tolower((unsigned char)*n)) {++h;++n;}if (*n == '\0') return haystack;}return NULL;
}int main(void) {const char *text = "Hello, C Language!";const char *key = "c lAn";const char *pos = strcasestr(text, key);if (pos)printf("Found at offset %ld: %s\n", pos - text, pos);elseputs("Not found");return 0;
}
编译运行:
$ gcc demo.c -o demo && ./demo
Found at offset 7: C Language!
五、性能扩展:批量转换的 SIMD 思路
若字符串非常长(>1 MB),可借助 SSE2/AVX2 指令一次处理 16/32 字节:
#include <immintrin.h>void tolower_avx2(char *s, size_t n) {const __m256i delta = _mm256_set1_epi8('a' - 'A');const __m256i upper = _mm256_set1_epi8('Z');const __m256i lower = _mm256_set1_epi8('A' - 1);size_t i = 0;for (; i + 31 < n; i += 32) {__m256i v = _mm256_loadu_si256((__m256i*)(s + i));__m256i gt = _mm256_cmpgt_epi8(v, lower);__m256i le = _mm256_cmpgt_epi8(upper, v);__m256i mask = _mm256_and_si256(gt, le);v = _mm256_add_epi8(v, _mm256_and_si256(mask, delta));_mm256_storeu_si256((__m256i*)(s + i), v);}/* 尾部不足 32 字节回退到单字节 */for (; i < n; ++i)s[i] = (char)tolower((unsigned char)s[i]);
}
实测在 -O3
下可带来 3~5 倍 的吞吐量提升,但代码复杂、需要 CPU 支持,请按场景权衡。
六、小结
函数 | 作用 | 关键点 |
---|---|---|
tolower | 大写 → 小写 | 先强转 unsigned char ;与 locale 相关 |
toupper | 小写 → 大写 | 同上;可配合 _toupper 宏提速 |
牢记 先做范围检查再转换,就能安全、高效地应对绝大多数文本处理需求。