c语言进阶 字符函数和字符串函数

字符函数和字符串函数

  • 字符函数和字符串函数
    • 1. strlen
      • strlen 函数详解
      • 模拟实现
        • 1.计数器方式
        • 2.不能创建临时变量计数器(递归)
        • 3.指针-指针的方式
    • 2. strcpy
      • strcpy 函数详解
      • 模拟实现
    • 3. strcat
      • strcat 函数详解
      • 模拟实现
    • 4. strcmp
      • strcmp 函数详解
      • 模拟实现
    • 5. strncpy
      • strncpy 函数详解
      • 模拟实现
    • 6.strncat
      • strncat 函数详解
      • 模拟实现
    • 7.strncmp
      • strncmp 函数详解
      • 模拟实现
    • 8. strstr
      • strstr 函数详解
      • 模拟实现
    • 9. strtok
      • strtok 函数详解
      • 模拟实现
    • 10.strerror
      • strerror 函数详解
      • 模拟实现
    • 11.字符分类函数
    • 12.memcpy
      • memcpy 函数详解
      • 模拟实现
    • 13.memmove
      • memmove 功能详解
    • 14.memcmp
      • memcmp 功能详解
    • 15. memset
      • memset 功能详解
      • 模拟实现

字符函数和字符串函数

1. strlen

strlen 函数详解

功能
计算字符串长度,返回 \0 前的字符个数(不包含 \0)。

关键特性

  • \0 为结束标志
    字符串必须包含 \0,否则会越界访问内存。

    char arr[] = {'a', 'b', 'c'};  // 无 `\0`
    printf("%zu\n", strlen(arr));   // 输出随机值
    
  • 参数合法性
    指针需指向以 \0 结尾的有效字符串,传入 NULL 会触发段错误。

    char* ptr = NULL;
    strlen(ptr);  // 段错误
    
  • 返回值为 size_t
    无符号类型,与负数比较可能导致逻辑错误。

    if (strlen("abc") - 5 < 0) {  // 永远为假
        printf("Less than 0\n");   // 不会执行
    }
    

模拟实现

1.计数器方式
size_t strlen(const char* str)
{
	assert(str);
	int num = 0;
	while (*str != '\0')
	{
		num++;
		str++;
	}
	return num;
}
2.不能创建临时变量计数器(递归)
size_t strlen(const char * str)
{
 if(*str == '\0')
 return 0;
 else
 return 1+my_strlen(str+1);
}
3.指针-指针的方式
size_t strlen(char *s)
{
       char *p = s;
       while(*p != ‘\0)
              p++;
       return p-s;
}

2. strcpy

strcpy 函数详解

功能
复制源字符串到目标空间,包含 \0

关键特性

  • 源串需包含 \0
    若源串无\0,strcpy会持续复制直至遇到内存中的\0,导致缓冲区溢出。

    char src[] = {'a', 'b', 'c'};  // 无 `\0`
    char dest[10];
    strcpy(dest, src);  // 越界复制
    
  • 目标空间需足够大
    目标数组长度需至少为源串长度 + 1(含\0)。若空间不足,会覆盖相邻内存。

    char dest[3];
    strcpy(dest, "abcdef");  // 缓冲区溢出
    
  • 目标空间必须可写
    目标指针不能指向常量字符串或只读内存。
    错误示例:

    char* dest = "hello";  // 只读内存
    strcpy(dest, "world");  // 段错误
    

模拟实现

char* strcpy(char* destination, const char* source)
{
	assert(destination&&source);

	char* tmp = destination;

	while (*tmp++ = *source++)
	{
		;
	}
	*tmp = '\0';

	return destination;
}

3. strcat

strcat 函数详解

功能
源字符串追加到目标字符串末尾。

关键特性

  • 源串和目标串需包含 \0
    源串必须以\0结尾,否则会导致越界复制。
    示例

    char src[] = {'a', 'b', 'c'};  // 无 `\0`
    char dest[10] = "hello";
    strcat(dest, src);  // 越界复制
    
  • 目标空间需足够大
    目标数组需容纳原内容 + 源串 + \0
    计算示例:

    char dest[10] = "abc";
    strcat(dest, "defgh");  // 需 4 + 5 + 1 = 10 字节
    

模拟实现

char* strcat(char* destination, const char* source)
{
	assert(destination&&source);
	char* tmp= destination;
	while (*tmp!='\0')
	{
		tmp++;
	}
	strcpy(tmp, source);
	return destination;
}

4. strcmp

strcmp 函数详解

功能
字符比较两字符串,返回比较结果。

返回值规则

  • > 0:首个不匹配字符中,s1 的 ASCII 值大于 s2
  • = 0:两字符串完全相同。 (长度和内容均一致)
  • < 0:首个不匹配字符中,s1 的 ASCII 值小于 s2

比较逻辑:

  1. 按字节比较:从首字符开始逐字节比较,直到字符不等或遇\0。
  2. 大小写敏感:‘A’(65) < ‘a’(97)。
  3. 长度影响结果:若短串是长串的前缀,则短串 < 长串。

示例

strcmp("abc", "abd");    // 返回负值('c' < 'd')
strcmp("abc", "abc");    // 返回0
strcmp("abc", "ab");     // 返回正值('\0' < 'c')
strcmp("123", "12");     // 返回正值('3' > '\0')
strcmp("A", "a");        // 返回负值(65 < 97)

模拟实现

int strcmp(const char * str1, const char * str2)
{
	assert(str1&&str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return (*str1 - *str2);
}

5. strncpy

strncpy 函数详解

功能
复制源串的前 n 个字符到目标空间。

特殊规则

  • 源串长度 < n:补 \0n 字节。
    实例:
    char dest[5];
    strncpy(dest, "abc", 4);  // "abc\0\0"
    
  • 源串长度 ≥ n:不自动补 \0,需手动处理。
    易错示例:
char dest[3];
strncpy(dest, "abcdef", 3);  // dest内容:"abc"(无`\0`)
printf("%s\n", dest);         // 可能输出乱码(继续读取后续内存)

模拟实现

char * strncpy(char * destination, const char * source, size_t num)
{
	assert(destination&&source);
	char* tmp = destination;

	while (*source&&num>0)
	{
		*tmp++ = *source++;
		num--;
	}
	while (num>0)
	{
		*tmp++ = '\0';
		num--;
	}

	return destination;
}

6.strncat

strncat 函数详解

功能
追加源串的前 n 个字符到目标串末尾。

特殊规则

  • 追加后自动补\0:
    无论n是否大于源串长度,strncat都会在追加后添加\0。`

    char dest[10] = "abc";
    strncat(dest, "def", 5);  // "abcdef\0"
    
  • 源串长度< n

  • 仅追加至\0,不补多余字符。
    示例:

    char dest[10] = "abc";
    strncat(dest, "de", 5);   // 追加"de",结果:"abcde\0"
    
  • 目标空间需足够大
    需容纳原内容 + 源串前n字节(或全部) + \0

模拟实现

char * strncat(char * destination, const char * source, size_t num)
{
	assert(destination&&source);
	char* tmp = destination;
	while (*tmp != '\0')
	{
		tmp++;
	}
	while (*source&&num>0)
	{
		*tmp++ = *source++;
		num--;
	}
	*tmp = '\0';
	return destination;
}

7.strncmp

strncmp 函数详解

功能
比较两字符串的前 n 个字符。

返回值规则:
strcmp,但仅比较至n字节或\0

示例

strncmp("abcdef", "abcxyz", 3);  // 返回0(前3字节相同)
strncmp("abc", "abcd", 4);        // 返回负值(`\0` < 'd')
strncmp("123", "12a", 2);         // 返回0(仅比较前2字节)

strcmp的区别:
strncmp最多比较n字节,而strcmp会比较至\0
示例对比:

strcmp("abc", "abcdef");  // 返回0(`abc`与`abc`相等)
strncmp("abc", "abcdef", 6);  // 返回负值(`abc\0\0\0` < `abcdef`)

模拟实现

int strncmp(const char * str1, const char * str2, size_t num)
{
	assert(str1&&str2);
	while ((*str1 == *str2)&&num>0)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
		num--;
	}
	if (num == 0)
		return 0;
	return (*str1 - *str2);
}

8. strstr

strstr 函数详解

功能
查找子串在主串中的首次出现位置。

返回值

  • 找到:返回子串首字符地址。
  • 未找到:返回 NULL

匹配规则:

  • 完全匹配:子串必须连续且完整出现。
  • 空串处理:strstr(s, "")始终返回s(空串是任何串的子串)。

示例

char* p = strstr("abcdef", "cde");  // p指向"cdef"
char* q = strstr("abc", "acd");     // q为NULL(未找到)

模拟实现

char* strstr(const char *str1, const char * str2)
{
	assert(str1&&str2);
	if (*str2 == '\0') {
		return (char*)str1; 
	}
	const char* ps1 = str1;
	
	while (*ps1 != '\0')
	{
		const char* begin = ps1;
		const char* ps2 = str2;

		while (*ps1 != '\0' && *ps2 != '\0' && *ps1 == *ps2)
		{
			ps1++;
			ps2++;
		}
		if (*ps2 == '\0')
			return (char*)begin;

		ps1 = begin+1;

	}
	if (*ps1 == '\0')
		return NULL;
}

9. strtok

strtok 函数详解

功能
按分隔符分割字符串。

特性

  • 破坏性操作:替换分隔符为 \0。 并保存后续位置。
char str[] = "hello,world";
char* token = strtok(str, ",");  // str变为"hello\0world",返回"hello"
  • 状态保存
    首次调用需传入原串,后续传NULL继续分割。
    示例:

    char str[] = "a,b,c";
    char* t1 = strtok(str, ",");    // 返回"a"
    char* t2 = strtok(NULL, ",");   // 返回"b"
    char* t3 = strtok(NULL, ",");   // 返回"c"
    char* t4 = strtok(NULL, ",");   // 返回NULL(已无分隔符)
    
  • 线程不安全:使用静态变量保存状态。
    内部使用静态变量保存位置,多线程环境下会导致冲突。

模拟实现

模拟实现原理:

  1. 首次调用时,找到首个非分隔符字符,将其后的分隔符替换为\0并记录位置;
  2. 后续调用时,从记录位置继续查找,重复上述操作;
  3. 无剩余分隔符时返回NULL。
char * strtok(char* str, const char * sep) {
    static char* last = NULL; // 静态变量保存上次处理的位置
    
    // 首次调用或显式传入NULL继续处理
    if (str != NULL) {
        last = str;
    } else if (last == NULL) {
        return NULL; // 没有更多标记
    }
    
    // 跳过前导分隔符
    while (*last != '\0' && strchr(sep, *last) != NULL) {
        last++;
    }
    
    if (*last == '\0') {
        last = NULL; // 没有更多标记
        return NULL;
    }
    
    // 找到当前标记的结束位置
    char* token = last;
    while (*last != '\0' && strchr(sep, *last) == NULL) {
        last++;
    }
    
    if (*last != '\0') {
        *last = '\0'; // 替换分隔符为字符串结束符
        last++;       // 移动到下一个位置
    } else {
        last = NULL;  // 处理完毕,下次调用返回NULL
    }
    
    return token;
}

10.strerror

strerror 函数详解

功能
将错误码(errno)转换为错误信息字符串。
关键细节:

  • 错误码存储在errno
    系统调用或库函数出错时会设置errno,需包含
    示例
FILE* fp = fopen("nonexistent.txt", "r");
if (!fp) {
    printf("Error: %s\n", strerror(errno));  // 输出:"No such file or directory"
}
  • 返回静态字符串:
    每次调用返回同一缓冲区,结果可能被后续调用覆盖。
    示例
char* err1 = strerror(1);  // "Operation not permitted"
char* err2 = strerror(2);  // "No such file or directory"
printf("%s\n", err1);      // 可能输出"No such file or directory"(被覆盖)

模拟实现

char * strerror(int errnum) {
    // 错误信息数组(实际实现可能更大)
    static const char* const error_messages[] = {
        [0] = "No error",
        [1] = "Operation not permitted",
        [2] = "No such file or directory",
        [3] = "No such process",
        [4] = "Interrupted system call",
        [5] = "Input/output error",
        [6] = "Device not configured",
        [7] = "Argument list too long",
        [8] = "Exec format error",
        [9] = "Bad file descriptor",
        [10] = "No child processes",
        // 更多错误码...
    };
    
    // 静态缓冲区用于返回错误信息
    static char unknown_error[32];
    
    // 检查错误码是否在预定义范围内
    size_t max_errors = sizeof(error_messages) / sizeof(error_messages[0]);
    if (errnum >= 0 && (size_t)errnum < max_errors && error_messages[errnum] != NULL) {
        return (char*)error_messages[errnum];
    }
    
    // 处理未知错误码
    snprintf(unknown_error, sizeof(unknown_error), "Unknown error %d", errnum);
    return unknown_error;
}

11.字符分类函数

函数 判断条件(返回真的情况)
iscntrl 控制字符(如\t, \n, ASCII 0-31, 127)
isspace 空白字符(, \t, \n, \r, \f, \v)
isdigit 十进制数字(0-9)
isxdigit 十六进制数字(0-9, a-f, A-F)
islower 小写字母(a-z)
isupper 大写字母(A-Z)
isalpha 字母(a-z, A-Z)
isalnum 字母或数字(a-z, A-Z, 0-9)
ispunct 标点符号(非字母、数字、空白的可打印字符)
isgraph 图形字符(非空白的可打印字符)
isprint 可打印字符(包括空白)

12.memcpy

memcpy 函数详解

功能
内存复制 (按字节),不处理重叠区域。
原型:

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

关键特性:

  • 按字节复制:
    不关心内容是否为字符串,遇\0不停止。
    示例:
int src[5] = {1, 2, 3, 4, 5};
int dest[5];
memcpy(dest, src, 20);  // 复制5个int(20字节)
  • 不处理重叠区域:
    若源/目标区域重叠,结果未定义(可能数据覆盖)。
    错误示例:
int arr[10] = {1, 2, 3, 4, 5};
memcpy(arr + 2, arr, 12);  // 重叠区域,结果不可预测

模拟实现

void * memcpy(void* destination, const void* source, size_t num)
{
	assert(destination != NULL || num == 0);
	assert(source != NULL || num == 0);

	if (destination == source) {
		return destination;
	}
	void *orig_dest = destination;
	while (num--) {
		*(char *)destination = *(char *)source;
		destination = (char *)destination + 1;
		source = (char *)source + 1;
	}
	return orig_dest;
}

13.memmove

memmove 功能详解

功能:

  • 处理重叠内存区域的复制。

特性详解:

  • 安全处理重叠内存区域
    memmove 能够安全处理源内存(src)和目标内存(dest)重叠的情况。通过检查目标地址是否位于源地址的后半段,自动选择从后向前或从前向后复制,避免数据覆盖。

示例代码

int arr[10] = {1, 2, 3, 4, 5};
memmove(arr + 2, arr, 20);  // 复制前5个int(20字节)到位置2
// 结果:arr = {1, 2, 1, 2, 3, 4, 5, 0, 0, 0}

模拟实现

void * memmove(void * destination, const void * source, size_t num)
{
	assert(destination != NULL || num == 0);
	assert(source != NULL || num == 0);
	void *orig_dest = destination;
	if (destination < source) 
	{	
		memcpy(destination, source, num);
	}
	else
	{
		// ��ȫ�������
		char *dst = (char *)destination + num - 1;
		const char *src = (const char *)source + num - 1;
		while (num--) {
			*(char *)dst = *(char *)src;
			dst = (char *)dst - 1;
			src = (char *)src - 1;
		}
	}
	
	return orig_dest;
}

14.memcmp

memcmp 功能详解

**功能:**按字节比较内存区域。

按字节比较内存区域
memcmp 逐字节比较两块内存区域,返回值的符号由首个不匹配字节的差值决定:

  • >0ptr1 的字节值大于 ptr2
  • =0:所有字节相等。
  • <0ptr1 的字节值小于 ptr2

示例代码

char s1[] = {1, 2, 3};
char s2[] = {1, 2, 4};
memcmp(s1, s2, 2);  // 返回0(前2字节相同)
memcmp(s1, s2, 3);  // 返回负值(3 < 4)

与 strcmp 的区别

  • memcmp 严格比较指定字节数,不关心 \0
  • strcmp 遇到 \0 停止比较。

对比示例

char a[] = "abc\0def";
char b[] = "abc\0xyz";
memcmp(a, b, 4);  // 返回0(前4字节均为 `abc\0`)
strcmp(a, b);     // 返回0(因 `\0` 终止比较)

模拟实现逻辑

int memcmp(const void * ptr1, const void * ptr2, size_t num)
{
	assert(ptr1 && ptr2);
	const char *p1 = (const char *)ptr1;
	const char *p2 = (const char *)ptr2;

	while (num-- > 0) 
	{
		if (*p1 != *p2) 
			return (char)*p1 - (char)*p2;
		p1++;
		p2++;
	}
	return 0;  
}

15. memset

memset 功能详解

功能:将内存块的前n个字节设置为指定的字符值(按字节填充)。
函数原型

void* memset(void* ptr, int value, size_t n);
  • ptr:指向要填充的内存块的起始地址(可修改);
  • value:要设置的字符值(虽然类型是int,但实际只使用低 8 位,即unsigned char范围);
  • n:要设置的字节数;
  • 返回值:指向ptr的指针(方便链式操作)。

关键特性

  • 按字节操作:
    无论内存块存储的是何种类型(int、float等),memset都会逐个字节设置为value的低 8 位。
    示例(正确用法):
char str[10];
memset(str, 'a', 5);  // 前5字节设为'a',结果:"aaaaa????"(?为未初始化值)
  • 对非字符类型的影响:
    若用于int、long等多字节类型,可能导致非预期结果(因每个字节都被设置为相同值)。
    易错示例:
int arr[5];
memset(arr, 1, 20);  // 错误:每个字节设为1,每个int为0x01010101(十进制16843009)
// 期望:arr全为1 → 实际:arr元素为16843009
  • n不可超过内存块大小:
    n大于ptr指向的内存块长度,会导致缓冲区溢出(覆盖相邻内存)。

典型用法

  • 初始化字符数组(清空或填充特定字符):
char buf[100];
memset(buf, 0, 100);  // 清空缓冲区(所有字节设为0)
  • 初始化结构体(快速清零):
typedef struct {
    int a;
    char b[20];
} Stu;
Stu s;
memset(&s, 0, sizeof(Stu));  // 结构体所有成员清零

模拟实现

void * memset(void * ptr, int value, size_t num)
{
	assert(ptr);
	void *orig_ptr = ptr;
	while (num--)
	{
		*(char*)ptr = (char)value;
		ptr = (char*)ptr + 1;
	}
	return orig_ptr;
}

你可能感兴趣的:(c语言,c语言)