C 语言(八)|字符串与内存函数:实现、比较与边界安全
详细介绍C语言字符操作函数、字符串处理函数、内存操作函数的用法与模拟实现。
版权声明:本文为作者原创,版权归作者所有。如需转载或引用,请联系作者获得授权,并在使用时注明出处。
一、字符操作函数
字符操作函数是处理单个字符的工具,分为字符分类函数和字符转换函数两大类,所有函数都需要包含头文件<ctype.h>。
1.1 字符分类函数
这类函数的核心作用是:判断传入的字符是否符合指定类型,符合则返回非0整数,不符合则返回0。
| 函数名 | 符合条件返回真(非0) |
|---|---|
| iscntrl | 任何控制字符 |
| isspace | 空白字符(空格、换行\n、回车\r、制表符\t等) |
| isdigit | 十进制数字字符 '0'~'9' |
| isxdigit | 十六进制数字字符(0-9、a-f、A-F) |
| islower | 小写字母 a~z |
| isupper | 大写字母 A~Z |
| isalpha | 字母(a~z、A~Z) |
| isalnum | 字母或数字 |
| ispunct | 标点符号(非字母、数字的可打印图形字符) |
| isgraph | 任何图形字符 |
| isprint | 任何可打印字符(含图形字符和空白字符) |
实例:判断字符是否为小写字母
#include <stdio.h>
#include <ctype.h>
int main() {
char str[] = "Test String.\n";
int i = 0;
while (str[i]) {
char c = str[i];
if (islower(c)) {
printf("%c是小写字母\n", c);
}
i++;
}
return 0;
}
1.2 字符转换函数
C语言标准库提供了两个专门的字符转换函数:
int tolower(int c):将大写字母转换为小写字母,非字母字符不做修改int toupper(int c):将小写字母转换为大写字母,非字母字符不做修改
实例:字符串小写转大写
#include <stdio.h>
#include <ctype.h>
int main() {
char str[] = "Test String. 123!";
int i = 0;
while (str[i]) {
if (islower(str[i])) {
str[i] = toupper(str[i]);
}
i++;
}
printf("转换结果:%s\n", str);
return 0;
}
运行结果:转换结果:TEST STRING. 123!
二、字符串函数
所有字符串函数都需要包含头文件<string.h>。
2.1 strlen - 字符串长度统计
函数原型:size_t strlen(const char *str);
核心功能:统计str指向的字符串中,\0之前的字符个数(不包含\0本身)
关键坑点:
- 字符串必须以
\0结尾,否则strlen会继续向后遍历内存 - 返回值类型是
size_t(无符号整数),两个strlen结果相减永远不会得到负数
错误示例:
#include <stdio.h>
#include <string.h>
int main() {
const char *str1 = "abcdef";
const char *str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0) {
printf("str2更长\n");
} else {
printf("str1更长\n");
}
return 0;
}
正确写法:if (strlen(str2) > strlen(str1))
strlen的三种模拟实现:
方式1:计数器法(最直观)
#include <stdio.h>
#include <assert.h>
int my_strlen(const char *str) {
assert(str);
int count = 0;
while (*str) {
count++;
str++;
}
return count;
}
方式2:递归实现(不创建临时变量)
#include <stdio.h>
#include <assert.h>
int my_strlen(const char *str) {
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
方式3:指针-指针法(最简洁)
#include <stdio.h>
#include <assert.h>
int my_strlen(const char *str) {
assert(str);
const char *start = str;
while (*str) {
str++;
}
return str - start;
}
2.2 strcpy - 字符串拷贝
函数原型:char strcpy(char dest, const char *src);
核心功能:将src指向的字符串拷贝到dest指向的空间
注意事项:
- 源字符串必须以
\0结束 - 目标空间必须足够大
- 目标空间必须可修改
模拟实现:
#include <stdio.h>
#include <assert.h>
char *my_strcpy(char *dest, const char *src) {
assert(dest && src);
char *ret = dest;
while (*dest++ = *src++) {
;
}
return ret;
}
int main() {
char dest[20];
my_strcpy(dest, "Hello");
printf("%s\n", dest);
return 0;
}
2.3 strcat - 字符串追加
函数原型:char strcat(char dest, const char *src);
核心功能:将src指向的字符串追加到dest指向的字符串末尾
注意事项:
- 源字符串必须以
\0结束 - 目标空间必须足够大,能容纳源字符串内容
- 目标字符串的
\0会被覆盖,追加后自动添加新的\0
模拟实现:
#include <stdio.h>
#include <assert.h>
char *my_strcat(char *dest, const char *src) {
assert(dest && src);
char *ret = dest;
while (*dest) {
dest++;
}
while (*dest++ = *src++) {
;
}
return ret;
}
int main() {
char str[50] = "Hello";
my_strcat(str, " World!");
printf("%s\n", str);
return 0;
}
2.4 strcmp - 字符串比较
函数原型:int strcmp(const char str1, const char str2);
比较规则:比较两个字符串中对应位置上字符的ASCII码值
返回值:
- 第一个字符串 > 第二个字符串:返回正数
- 第一个字符串 < 第二个字符串:返回负数
- 两个字符串相等:返回0
模拟实现:
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char *str1, const char *str2) {
assert(str1 && str2);
while (*str1 && *str2 && *str1 == *str2) {
str1++;
str2++;
}
return *str1 - *str2;
}
int main() {
printf("%d\n", my_strcmp("abc", "abd"));
printf("%d\n", my_strcmp("abc", "abc"));
return 0;
}
2.5 strncpy - 指定长度拷贝
函数原型:char strncpy(char dest, const char *src, size_t n);
核心功能:将src指向的字符串的前n个字符拷贝到dest
注意事项:如果src的长度小于n,则拷贝完src后会在后面补\0直到拷贝了n个字符
2.6 strncat - 指定长度追加
函数原型:char strncat(char dest, const char *src, size_t n);
核心功能:将src指向的字符串的前n个字符追加到dest末尾
2.7 strncmp - 指定长度比较
函数原型:int strncmp(const char str1, const char str2, size_t n);
核心功能:比较两个字符串的前n个字符
模拟实现:
#include <stdio.h>
#include <assert.h>
int my_strncmp(const char *str1, const char *str2, size_t num) {
assert(str1 && str2);
while (num > 0) {
if (*str1 != *str2) {
return (unsigned char)*str1 - (unsigned char)*str2;
}
if (*str1 == '\0') {
return 0;
}
str1++;
str2++;
num--;
}
return 0;
}
2.8 strstr - 字符串查找
函数原型:char strstr(const char str1, const char *str2);
核心功能:在str1主串中查找str2子串第一次出现的位置
返回值:找到返回子串起始地址,找不到返回NULL
模拟实现(暴力匹配算法):
#include <stdio.h>
#include <assert.h>
char *my_strstr(const char *haystack, const char *needle) {
assert(haystack && needle);
const char *cp = haystack;
if (*needle == '\0') {
return (char *)haystack;
}
while (*cp) {
const char *s1 = cp;
const char *s2 = needle;
while (*s1 != 0 && *s2 != 0 && *s1 == *s2) {
s1++;
s2++;
}
if (*s2 == '\0') {
return (char *)cp;
}
cp++;
}
return NULL;
}
2.9 strtok - 字符串分割
函数原型:char strtok(char str, const char *delim);
核心功能:根据delim指定的分隔符集合,把str字符串拆分成多个子串
关键特性:
- 首次调用传入待分割字符串,后续调用第一个参数传NULL
- 会直接修改原字符串,将分隔符替换为
\0 - 多个连续分隔符视为一个
用法示例:
#include <stdio.h>
#include <string.h>
int main() {
char arr[] = "wyh@luminous.com";
char arr2[20] = {0};
const char *delim = "@.";
strcpy(arr2, arr);
char *res = NULL;
for (res = strtok(arr2, delim); res != NULL; res = strtok(NULL, delim)) {
printf("%s\n", res);
}
return 0;
}
2.10 错误信息处理函数
2.10.1 strerror - 错误码转字符串
函数原型:char *strerror(int errnum);
核心功能:将错误码转换为对应的错误信息字符串
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
FILE *pf = fopen("data.txt", "r");
if (pf == NULL) {
printf("打开失败:%s(错误码:%d)\n", strerror(errno), errno);
return 1;
}
return 0;
}
2.10.2 perror - 直接打印错误信息
函数原型:void perror(const char *s);
核心功能:自动打印当前errno对应的错误信息
#include <stdio.h>
#include <errno.h>
int main() {
FILE *pf = fopen("data.txt", "r");
if (pf == NULL) {
perror("打开文件失败");
}
return 0;
}
三、内存操作函数
3.1 memcpy - 内存拷贝
函数原型:void memcpy(void dest, const void *src, size_t n);
核心功能:从src的位置开始向后拷贝n个字节的数据到dest
与strcpy的区别:memcpy可以拷贝任意类型的数据,不限于字符串
模拟实现:
#include <stdio.h>
#include <assert.h>
void *my_memcpy(void *dest, const void *src, size_t n) {
assert(dest && src);
void *ret = dest;
while (n--) {
*(char *)dest = *(char *)src;
dest = (char *)dest + 1;
src = (char *)src + 1;
}
return ret;
}
int main() {
int arr1[] = {1, 2, 3, 4, 5};
int arr2[5];
my_memcpy(arr2, arr1, sizeof(arr1));
for (int i = 0; i < 5; i++) {
printf("%d ", arr2[i]);
}
return 0;
}
3.2 memmove - 内存移动
函数原型:void memmove(void dest, const void *src, size_t n);
核心功能:与memcpy相同,但可以安全处理源内存和目标内存重叠的情况
内存重叠处理:
- 当dest < src:从前向后拷贝
- 当dest > src且dest < src + n:从后向前拷贝
模拟实现:
#include <stdio.h>
#include <assert.h>
void *my_memmove(void *destination, const void *source, size_t num) {
assert(destination && source);
char *dest = (char *)destination;
const char *src = (const char *)source;
if (dest > src && dest < src + num) {
// 内存重叠,从后向前拷贝
for (size_t i = num; i > 0; i--) {
dest[i - 1] = src[i - 1];
}
} else {
// 无重叠,从前向后拷贝
for (size_t i = 0; i < num; i++) {
dest[i] = src[i];
}
}
return destination;
}
// 测试代码
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
my_memmove(arr + 2, arr, 20);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
3.3 memset - 内存设置
函数原型:void memset(void dest, int c, size_t n);
核心功能:将dest指向的内存的前n个字节设置为值c
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello World";
memset(str, 'x', 5);
printf("%s\n", str);
return 0;
}
运行结果:xxxxx World
3.4 memcmp - 内存比较
函数原型:int memcmp(const void ptr1, const void ptr2, size_t n);
核心功能:比较ptr1和ptr2指向的内存的前n个字节
返回值:与strcmp类似
#include <stdio.h>
#include <string.h>
int main() {
int arr1[] = {1, 2, 3, 4, 5};
int arr2[] = {1, 2, 3, 4, 6};
printf("%d\n", memcmp(arr1, arr2, 4 * sizeof(int)));
return 0;
}
四、常见面试题
题目1:不使用strlen计算字符串长度
#include <stdio.h>
int my_strlen(const char *str) {
const char *end = str;
while (*end) {
end++;
}
return end - str;
}
int main() {
char str[] = "hello";
printf("%d\n", my_strlen(str));
return 0;
}
题目2:模拟实现strstr
#include <stdio.h>
char *my_strstr(const char *haystack, const char *needle) {
if (*needle == '\0') return (char *)haystack;
while (*haystack) {
const char *h = haystack;
const char *n = needle;
while (*h && *n && (*h == *n)) {
h++;
n++;
}
if (*n == '\0') {
return (char *)haystack;
}
haystack++;
}
return NULL;
}
int main() {
char str[] = "Hello World";
char *result = my_strstr(str, "World");
printf("%s\n", result ? result : "未找到");
return 0;
}
五、总结
本篇我们学习了C语言字符串与内存函数的核心知识:
- 字符分类函数:islower、isupper、isdigit等判断字符类型
- 字符转换函数:tolower、toupper进行大小写转换
- 字符串函数:strlen、strcpy、strcat、strcmp及其变体
- 内存函数:memcpy、memmove、memset、memcmp操作任意内存
熟练掌握这些函数的底层原理和模拟实现,能帮助我们更好地理解C语言的内存管理机制,在笔试面试中游刃有余!