《C 语言字符串操作从入门到实战(上篇):字符分类、转换及strlen/strcpy等函数详解》

目录

一. 字符分类函数

二. 字符转换函数

三. strlen的使用和模拟实现

3.1 strlen函数理解与使用示例

3.2 ⚠️ 重要注意事项

3.3 strlen返回值陷阱

3.4 strlen的模拟实现

四. strcpy的使用和模拟实现

4.1 strcpy函数理解与使用示例

4.2 ⚠️ 重要注意事项

4.3 strcpy的模拟实现

五. strcat的使用和模拟实现

5.1 strcat的理解和使用示例

5.2 ⚠️ 重要注意事项

5.3  strcat的模拟实现

六. strcmp的使用和模拟实现

6.1 strcmp的理解和使用示例

6.2  ⚠️ 重要注意事项

6.3  strcat的模拟实现


一. 字符分类函数

字符分类函数是C/C++标准库中用于判断字符属性的函数

这些函数的使用都需要包含头文件ctype.h

这些函数的使用方法极其类似 现在我就以isupper函数 来举例使用

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <ctype.h>
int main() {
    char c;
    scanf("%c", &c);
    if (isupper(c)) 
    {
        printf("%c是大写字母\n", c);
    }
    else
    {
        printf("%c是小写子母\n", c);
    }
    return 0;
}

运行结果如下

可以看到 我们使用了isupper函数 下面是isupper函数的介绍 其他函数类似

下面让我们进行一个小练习

练习:

 写一个代码,将字符串中的小写字符全部转为大写,其它字符不变。

#include <stdio.h>
#include <ctype.h>
int main()
{
	int i = 0;
	char arr[] = "hello WORLD";
	while (arr[i])
	{
		if (islower(arr[i]))
			arr[i] -= 32;//小写字母比大写字母的Ascll码值大32
		printf("%c", arr[i]);
		i++;
	}
	return 0;
}

运行结果如下

二. 字符转换函数

字符转换函数是C/C++标准库中用于字符处理的函数   也需要包含头文件ctype.h

这些函数主要用于字符的大小写转换。

主要字符转换函数:

  1. tolower(int c) - 将字符转换为小写

    • 如果c是大写字母(A-Z),返回对应小写字母(a-z)
    • 其他字符保持不变
  2. toupper(int c) - 将字符转换为大写

    • 如果c是小写字母(a-z),返回对应大写字母(A-Z)
    • 其他字符保持不变

上面练习中我们将小写转大写,是利用了小写字母和大写字母的Ascll码值差32的性质,现在有了转换函数,我们就可以直接使用toupper函数实现小写转大写了。代码修改如下

练习:

 写一个代码,将字符串中的小写字符全部转为大写,其它字符不变。

#include<stdio.h>
#include<ctype.h>
int main()
{
	int i = 0;
	char arr[] = "hello WORLD";
	while (arr[i])
	{
		if (islower(arr[i]))
			arr[i]=toupper(arr[i]);
		printf("%c", arr[i]);
		i++;
	}
	return 0;
}

三. strlen的使用和模拟实现

3.1 strlen函数理解与使用示例

strlen 是 C 标准库中用于计算字符串长度的函数,定义在 <string.h> 头文件中。

函数原型

size_t strlen(const char *str);

功能说明

  • 计算以空字符 '\0' 结尾的字符串的长度

  • 返回字符串中 '\0' 之前的字符个数(不包括 '\0' 本身)

  • 返回值类型: size_t - 无符号整数类型,通常用于表示大小和计数

strlen函数使用示例

#include <stdio.h>
#include <string.h>
int main() {
    const char* str = "Hello, World!";
    size_t length = strlen(str);
    printf("The string \"%s\" has %zu characters.\n", str, length);
    return 0;
}

3.2 ⚠️ 重要注意事项

1. 字符串必须正确终止

  • strlen 依赖 '\0' 判断字符串结束,未正确终止会导致 缓冲区溢出 或 未定义行为
    char s[3] = {'a', 'b', 'c'};  // 错误!缺少 '\0'
    printf("%zu\n", strlen(s));    // 未定义行为
    

2. 不能传递 NULL

  • strlen(NULL) 会崩溃,应先检查指针:
    if (str != NULL) {
        size_t len = strlen(str);
    }
    

3. size_t 是无符号类型

  • 不要用 int 接收返回值,否则可能截断:
    size_t len = strlen("hello");  // 正确
    int bad_len = strlen("hello"); // 可能截断(如果字符串很长)
    

4. strlen vs sizeof

函数作用示例
strlen计算字符串长度(不包括 '\0'strlen("abc") → 3
sizeof计算变量/数组的字节数(包括 '\0'sizeof("abc") → 4

3.3 strlen返回值陷阱

首先让我们观察下面一段代码

#include <stdio.h>
#include <string.h>
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "abc";
	if (strlen(str2) - strlen(str1) > 0)
	{
		printf("str2>str1\n");
	}
	else
	{
		printf("srt2<str1\n");
	}
	return 0;
}

不出意外 应该会打印srt2<str1 但是真是如此吗 让我们运行试试

结果却恰恰相反 为什么呢? 让我们分析一下

关键问题分析

strlen返回值类型: 返回size_t(无符号整型)

无符号数减法永远不会为负,当str2str1短时,strlen(str2)-strlen(str1)会产生一个巨大的正数(回绕)因此运算结果远大于0 执行了错误的分支语句

具体回绕细节 有兴趣的可以自行了解

正确的写法应该是

if (strlen(str2) > strlen(str1) )
	{
		printf("str2>str1\n");
	}
	else
	{
		printf("srt2<str1\n");
	}

这样就避免了无符号整型运算导致回绕的问题

3.4 strlen的模拟实现

方法一:  计数器

#include<stdio.h>
#include<assert.h>
size_t mystrlen(const char* str)
{
	int count = 0;
  assert(str!=NULL);
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	char arr[] = "abcdefg";
	size_t ret = mystrlen(arr);
	printf("%d", ret);
	return 0;
}

方法二:  指针-指针 

#include<stdio.h>
#include<assert.h>
size_t mystrlen(char* str)
{
	assert(str != NULL);
	char* p = str;
	while (*p)//注:\0的ASCLL码值是0
	{
		p++;
	}
	return p - str;
	
}
int main()
{
	char arr[] = "abcdefg";
	size_t ret = mystrlen(arr);
	printf("%d", ret);
	return 0;
}

方法三:  递归(不创建临时变量)

#include<stdio.h>
#include<assert.h>
size_t mystrlen(const char* str)
{
	assert(str != NULL);
	if (*str == '\0')
	{
		return 0;
	}
	else
	{
		return 1 + mystrlen(str + 1);
	}
 
}
int main()
{
	char arr[] = "abcdef";
	size_t ret = mystrlen(arr);
	printf("%d", ret);
	return 0;
}

四. strcpy的使用和模拟实现

4.1 strcpy函数理解与使用示例

strcpy 是 C 标准库中用于字符串复制的函数,定义在 <string.h> 头文件中。

函数原型

char *strcpy(char *dest, const char *src);
  • 功能说明

  • 将 src 指向的字符串(包括终止的空字符 '\0')复制到 dest 指向的缓冲区
  • 返回 dest 指针

使用示例

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = { 0 };
	char arr2[] = "hello";
	char*p=strcpy(arr1, arr2);
	printf("%s\n", p);
	printf("%s\n", arr1);
	//两种打印方式都传入的是字符串的首地址 并无区别
	return 0;
}

4.2 ⚠️ 重要注意事项

  1. 缓冲区溢出风险

    • 必须确保目标缓冲区足够大(包括存储 '\0' 的空间)
    • 错误示例:
      char dest[5];
      strcpy(dest, "Hello World"); // 缓冲区溢出
      
  2. 目标缓冲区必须可写

    • 不能拷贝到只读内存区域
    • 错误示例:
      char *dest = "Read-only"; // 字符串字面量通常存储在只读段
      strcpy(dest, "new");      // 运行时错误
      
  3. 源字符串必须合法

    • 必须以 '\0' 结尾
    • 不能是 NULL 指针
    • 错误示例:
      char src[3] = {'a','b','c'}; // 无终止符
      char dest[10];
      strcpy(dest, src);           // 未定义行为
      
  4. 不会自动截断

    • 如果源串比目标缓冲区长,必定导致溢出
    • 错误示例:
      char dest[5];
      strcpy(dest, "This is too long"); // 必然溢出

4.3 strcpy的模拟实现

#include<stdio.h>   // 标准输入输出头文件
#include<assert.h>  // 断言头文件,用于检查运行时条件

// 自定义字符串拷贝函数
// 参数:arr1 - 目标字符串指针,arr2 - 源字符串指针
// 返回:目标字符串的起始地址
char* mystrcpy(char* arr1, const char* arr2)
{
    // 使用断言确保两个指针都不为NULL
    // 如果任一指针为NULL,程序会终止并报错
    assert(arr1 && arr2);
    
    // 保存目标字符串的起始地址,因为arr1会在拷贝过程中被修改
    char* p = arr1;
    
    // 拷贝过程:逐字符拷贝,包括结尾的'\0'
    // 这是一个经典的C语言字符串拷贝写法
    // 1. *arr2++ 先解引用arr2,然后arr2指针后移
    // 2. 将arr2的字符赋值给arr1的位置,然后arr1指针后移
    // 3. 整个表达式的值是赋值的字符,当遇到'\0'时循环结束
    while ((*arr1++ = *arr2++))
    {
        ; // 空语句,所有操作都在while条件中完成
    }
    
    // 返回目标字符串的起始地址
    return p;
}

int main()
{
    char arr1[20];          // 声明目标字符数组,分配20字节空间
    char arr2[] = "abcdef"; // 初始化源字符串,包含"abcdef\0"(自动添加'\0')
    
    // 调用自定义拷贝函数
    char* ret = mystrcpy(arr1, arr2);
    
    // 打印拷贝结果
    printf("%s", ret);      // 输出:abcdef
    
    return 0;
}

五. strcat的使用和模拟实现

5.1 strcat的理解和使用示例

strcat 是 C 标准库中用于字符串拼接的函数,定义在 <string.h> 头文件中。

函数原型

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

功能说明

  • 将 src 字符串追加到 dest 字符串的末尾(覆盖 dest 的终止空字符)

  • 保证结果字符串以 '\0' 结尾

  • 返回 dest 指针(便于链式调用)

  • 要求 dest 必须有足够的空间容纳拼接后的结果

使用示例

#include <stdio.h>
#include <string.h>
int main() {
    char dest[20] = "Hello";
    const char* src = " World!";
   char*p=strcat(dest, src);
    printf("拼接结果: %s\n",p); // 输出: Hello World!
    printf("拼接结果: %s\n",dest); // 输出: Hello World!
    return 0;
}

5.2 ⚠️ 重要注意事项

1. 目标缓冲区必须足够大

严重性:高
问题:目标缓冲区必须能容纳源字符串和目标字符串的内容(包括终止符’\0’),否则会导致缓冲区溢出

// 危险示例
char dest[10] = "hello";
strcat(dest, " world!");  // 缓冲区溢出

// 安全做法
char dest[20] = "hello";  // 确保足够空间
strcat(dest, " world!");

2. 目标字符串必须以’\0’结尾

严重性:高
问题strcat 从目标字符串的’\0’处开始追加,如果目标字符串未正确终止,会导致未定义行为

// 错误示例
char dest[10] = {'h', 'e', 'l', 'l', 'o'};  // 没有'\0'
strcat(dest, " world");  // 未定义行为

// 正确做法
char dest[10] = {'h', 'e', 'l', 'l', 'o', '\0'};
strcat(dest, " world");

3. 源字符串必须以’\0’结尾

严重性:高
问题strcat 会一直读取源字符串直到遇到’\0’,如果源字符串未正确终止,会导致未定义行为

// 错误示例
char src[] = {'w', 'o', 'r', 'l', 'd'};  // 没有'\0'
char dest[10] = "hello";
strcat(dest, src);  // 未定义行为

4. 不能处理重叠的内存区域

严重性:中
问题:如果源字符串和目标字符串内存重叠,行为未定义

// 错误示例
char str[20] = "hello";
strcat(str, str + 2);  // 未定义行为

5.3  strcat的模拟实现

#include<stdio.h>
#include<assert.h>

// 自定义字符串连接函数
// 参数:arr1 - 目标字符串(必须有足够空间),arr2 - 要追加的源字符串
// 返回值:返回连接后的字符串首地址
char* mystrcat(char* arr1, const char* arr2)
{
    // 使用断言确保两个指针都不是NULL
    // 如果任一指针为NULL,程序会在此处终止并报错
	assert(arr1 && arr2);
	
    // 保存目标字符串的起始地址,用于最后返回
	char* p = arr1;
	
    // 第一个while循环:找到arr1的字符串结束符'\0'的位置
    // 通过递增指针arr1,直到遇到'\0'(值为0)为止
	while (*arr1)
	{
		arr1++;
	}
	
    // 第二个while循环:将arr2的内容追加到arr1末尾
    // 这里使用了赋值表达式的值作为循环条件:
    // 1. 先执行 *arr1 = *arr2 进行字符复制
    // 2. 然后判断赋值表达式的值(即复制的字符)
    // 3. 如果复制的字符不是'\0'(值为0),则继续循环
    // 4. 每次循环后指针arr1和arr2都自增
    // 注意:这个循环会连带arr2的结束符'\0'一起复制过去
	while ((*arr1++ = *arr2++))
	{
		; // 空语句,所有操作都在循环条件中完成
	}
	
    // 返回最初保存的目标字符串首地址
	return p;
}

int main()
{
    // 目标字符串数组,必须足够大以容纳连接后的结果
    // 初始化内容为"hello"(自动包含'\0')
	char arr1[20] = "hello";
    
    // 源字符串数组,内容为"world"(自动包含'\0')
	char arr2[] = "world";
    
    // 调用自定义的字符串连接函数
	char* ret = mystrcat(arr1, arr2);
    
    // 打印连接后的字符串
    // 输出结果为"helloworld"
	printf("%s", ret);
    
	return 0;
}

六. strcmp的使用和模拟实现

6.1 strcmp的理解和使用示例

strcmp 是 C 标准库中用于比较两个字符串的函数,定义在 <string.h> 头文件中。

函数原型

int strcmp(const char *str1, const char *str2);

功能说明

  • 按字典顺序比较两个以空字符 '\0' 结尾的字符串

  • 比较是基于字符的 ASCII 值进行的

  • 返回值表示比较结果:

    • 返回 0:两个字符串相等

    • 返回 负值str1 小于 str2(按字典顺序)

    • 返回 正值str1 大于 str2(按字典顺序)

使用示例

#include <stdio.h>
#include <string.h>
int main() {
    const char* str1 = "apple";
    const char* str2 = "banana";
    int result = strcmp(str1, str2);
    if (result < 0) {
        printf("\"%s\" < \"%s\"\n", str1, str2);
    }
    else if (result > 0) {
        printf("\"%s\" >\"%s\"\n", str1, str2);
    }
    else {
        printf("The strings are equal\n");
    }
    return 0;
}

6.2  ⚠️ 重要注意事项

 关键注意事项

1. 字符串必须正确终止

  • 严重性: 高

  • 问题: 未终止的字符串会导致未定义行为

char s1[3] = {'a','b','c'}; // 无终止符
char s2[] = "abc";
int result = strcmp(s1, s2); // 危险!

2. NULL 指针检查

  • 严重性: 高

  • 问题: 传递 NULL 指针会导致程序崩溃

char *s1 = NULL;
char *s2 = "hello";
int result = strcmp(s1, s2); // 崩溃!

3. 区分大小写

  • 注意strcmp 是大小写敏感的

strcmp("hello", "HELLO"); // 返回非零值

6.3  strcat的模拟实现

#include<stdio.h>   // 包含标准输入输出函数库
#include<string.h>  // 包含字符串操作函数库

int main()
{
    // 定义并初始化两个字符数组(字符串)
    char arr1[] = "abcdef";  // arr1包含字符串"abcdef"和自动添加的'\0'
    char arr2[] = "abc";    // arr2包含字符串"abc"和自动添加的'\0'

    // 使用strcmp比较两个字符串
    // strcmp函数会逐个字符比较,直到遇到不同字符或'\0'
    // 返回值:
    //   >0 表示arr1大于arr2
    //   <0 表示arr1小于arr2
    //   =0 表示arr1等于arr2
    int ret = strcmp(arr1, arr2);

    // 根据比较结果输出不同信息
    if (ret > 0)
        printf("arr1 > arr2\n");  // 输出arr1大于arr2
    else if (ret < 0)
        printf("arr1 < arr2\n");  // 输出arr1小于arr2
    else
        printf("arr1 == arr2\n"); // 输出arr1等于arr2

    return 0;  // 程序正常结束
}

以上就是字符函数和字符串函数上篇的全部内容 希望能够为您提供帮助

往期回顾

《C 语言 sizeof 与 strlen 深度对比:原理、差异与实战陷阱》

《C 语言 sizeof 与 strlen 深度对比:原理、差异与实战陷阱》

《VS2022 调试实战手册:高效排查代码问题的必备攻略》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿方猛敲c嘎嘎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值