C语言——深入解析字符串函数与其模拟实现

目录

一、strlen的使用和模拟实现

[1.1 strlen的使用](#1.1 strlen的使用)

[1.2 strlen模拟实现](#1.2 strlen模拟实现)

[二、strcpy 的使用和模拟实现](#二、strcpy 的使用和模拟实现)

[2.1 strcpy的使用](#2.1 strcpy的使用)

三、strcat函数的使用和模拟实现

[3.1 strcat模拟实现](#3.1 strcat模拟实现)

四、strcmp的使用和模拟实现

[4.1 strcmp使用:](#4.1 strcmp使用:)

[4.2 strcmp模拟实现:](#4.2 strcmp模拟实现:)

[五 、strstr的使用和模拟实现](#五 、strstr的使用和模拟实现)

[5.1 strstr使用](#5.1 strstr使用)

[5.2 strstr模拟](#5.2 strstr模拟)

六、strtok的使用和模拟实现

七、strerror函数


在日常编程中,我们常常会遇到这样的问题:如何计算字符串的长度?如何追加一个已经初始化的字符串内容?如何将字符串的大小?这些看似简单的操作,实际上却涉及到一系列强大的字符串函数。本文将带您深入了解这些函数的使用方法和背后的原理。

本文主要通过字符串的实现以及通过自定义函数来模拟实现字符串功能来帮助大家更好的理解以下笔者举出的字符串函数

  • strlen ------ 计算字符串的长度
  • strcpy------将源字符串复制到目标字符串。
  • strcat------将源字符串追加到目标字符串的末尾。
  • strcmp------ 比较两个字符串的大小。
  • strstr------查找一个字符串在另一个字符串中的首次出现位置。
  • strtok------将字符串分割成一系列标记(tokens),通常用于解析字符串。
  • strerror------ 返回与错误码对应的错误信息字符串。

一、strlen的使用和模拟实现

1.1 strlen的使用

  1. 功能:计算字符串的长度(不包括结束符 \0,也就是说必须以\0结束)
  2. 注意函数的返回值为size_t,类型是无符号的(易错)
  3. 包含头文件 <string.h>
cpp 复制代码
size_t strlen(const char *str);
//类型size_t

由于strlen笔者在之前的博客有过讲解,这里直接模拟实现

1.2 strlen模拟实现

cpp 复制代码
//计数器
#include <assert.h>
int my_strlen(const char* str)
{
	int count = 0;
	assert(str);//断言 确保str不为NULL
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}


//方式二

int main()
{
	const char* str = "abcdef";

	int r= my_strlen(str);
	printf("%d\n", r);
	return 0;
	
}

在这我仔细讲一下指针-指针的方式

cpp 复制代码
#include <stdio.h>
#include <assert.h>

int my_strlen(const char* str) {
    assert(str);
    const char* p = str; // 使用 const char* 以保持一致性
    while (*p != '\0') {
        p++;
    }
    return p - str; // 返回长度
}

int main() {
    const char* str = "abcdef";
    int r = my_strlen(str); // 修改为 int 类型
    printf("%d\n", r);
    return 0;
}
  • return p-str:
  • my_strlen 函数中,p 最初指向字符串的起始位置(即 str),然后通过 while 循环遍历字符串,直到遇到字符串的结束符 '\0'。此时,p 指向 '\0' 的位置。
  • p - str 计算的是 pstr 之间的距离(以字符为单位)。因为 p 指向字符串的结束符,而 str 指向字符串的起始位置,所以 p - str 的结果就是字符串的长度。

假设字符串是 "abcdef",它在内存中的布局如下:

cpp 复制代码
地址:   0x1000  0x1001  0x1002  0x1003  0x1004  0x1005  0x1006
内容:   'a'     'b'     'c'     'd'     'e'     'f'     '\0'
  • str 指向 0x1000(即字符 'a')。
  • p 在循环结束时指向 0x1006(即字符 '\0')。
  • 计算 p - str
    • 0x1006 - 0x1000 = 6,这就是字符串的长度。

因此,return p - str; 返回的是字符串的长度。

二、strcpy 的使用和模拟实现

2.1 strcpy的使用

cpp 复制代码
char *strcpy(char *dest, const char *src);
  • dest(destination) 是目标字符串的指针,复制的内容将存储在这里。
  • src (source)是源字符串的指针,内容将从这里复制。
  • strcpy 函数会将 src 字符串中的字符逐个复制到 dest 中,直到遇到字符串结束符 '\0',然后在 dest 的末尾也添加一个 '\0'
  • 注意:目标的空间一定要大,以确保能存放源字符串
  • 目标空间必须可修改
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello, World!";
    char dest[50]; // 确保目标数组足够大以容纳源字符串

    strcpy(dest, src); // 复制字符串

    printf("源字符串: %s\n", src);
    printf("目标字符串: %s\n", dest);

    return 0;
}

2.2 模拟实现

cpp 复制代码
char* my_strcpy(char* dest, const char* src)
{
    char* ret = dest;
    assert(dest != NULL && src != NULL);
    while ((*dest++ = *src++))
    {
        ;
    }
    return ret;
}

int main() {
    char src[] = "Hello, World!";
    char dest[50]; // 确保目标数组足够大以容纳源字符串

    my_strcpy(dest, src); // 使用自定义的 strcpy 函数

    printf("源字符串: %s\n", src);
    printf("目标字符串: %s\n", dest);

    return 0;
}

while ((*dest++ = *src++));

  1. 表达式的结构
  • *dest++ = *src++ 是一个赋值表达式,包含了以下几个部分:
    • *src++: 先获取 src 指针指向的字符,然后将 src 指针向后移动一个位置。
    • *dest++: 先获取 dest 指针指向的位置,然后将 src 中的字符赋值给这个位置,最后将 dest指针向后移动一个位置。
  1. 逐步解析
  • *src++:

    • *src:解引用 src 指针,获取当前指向的字符。
    • src++:在获取字符后,将 src 指针向后移动到下一个字符。
  • *dest++:

    • *dest:解引用 dest 指针,获取当前指向的目标位置。
    • dest++:在赋值后,将 dest 指针向后移动到下一个位置。
  • 赋值:

    • *dest++ = *src++:将 src 中的当前字符赋值给 dest 中的当前字符(赋值的顺序是从右到左),并同时更新两个指针。
  1. 循环条件
  • while ((*dest++ = *src++))
    • 这个 while 循环会持续执行,直到 *src 指向的字符为 '\0'(字符串结束符)。
    • *src'\0' 时,赋值表达式的结果为 0(因为 '\0' 的 ASCII 值为 0),因此 while循环结束。
  1. 整体功能
  • 这个 while 循环的整体功能是:
    • src 中逐个字符复制到 dest,直到遇到字符串结束符 '\0'
    • 在复制的过程中,destsrc 指针都会向后移动,以便指向下一个字符。
  1. 返回值
  • 循环结束后,my_strcpy 函数会返回 ret,即最初的 dest 指针,指向目标字符串的起始位置。

三、strcat函数的使用和模拟实现

cpp 复制代码
const char *strcat(char *dest,char *scr);
  • dest(destination) 是目标字符串的指针,追加的内容将存储在这里。
  • src (source)是源字符串的指针,内容将从这里追加。
  • strcpy 函数会将 src 字符串中的字符追加到 dest 中,直到遇到字符串结束符 '\0',然后在 dest 的末尾也添加一个 '\0'
  • 注意:目标的空间一定要大,以确保能存放源字符串
  • 目标空间必须可修改

3.1 strcat模拟实现

由于strcat函数和strcpy函数类似,这里直接开始模拟实现,可参照strcpy

cpp 复制代码
#include <stdio.h>
#include <assert.h>

char* my_strcat(char* dest, const char* src) {
    assert(dest != NULL && src != NULL);  // 确保指针不为 NULL

    // 找到目标字符串的末尾
    char* ret = dest;  // 保存目标字符串的起始地址
    while (*dest) {    // 遍历目标字符串,直到遇到 \'\\0\'
        dest++;
    }

    // 复制源字符串到目标字符串的末尾
    while ((*dest++ = *src++)) {  // 逐个字符复制
        ;
    }

    return ret;  // 返回目标字符串的起始地址
}

int main() {
    char dest[20] = "Hello";  // 目标字符串
        const char* src = "World!";  // 源字符串

        my_strcat(dest, src);  // 使用自定义的 strcat 函数

    printf("%s,\n", dest);  // 输出: Hello, World!
        return 0;
}

这里的 while (*dest) {
dest++; }

  • 由于我们是追加函数,实现在末尾追加源字符串的内容的话,我们肯定是要将指针指向'\0',否则会出现覆盖的情况

例如:

  • 假设 dest 原本是 "Hello",而 src" World"
  • 如果不移动 dest,那么在复制时,dest 会从 "H" 开始覆盖,最终结果将是 " World",而不是 "Hello World"

四、strcmp的使用和模拟实现

标准规定:

  • 参数:

    • str1:指向第一个字符串的指针。
    • str2:指向第二个字符串的指针。

返回值:

  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字
cpp 复制代码
int strcmp(const char *str1, const char *str2);

4.1 strcmp使用:

cpp 复制代码
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "abcdg";
	int result = strcmp(str1, str2);

    if (result < 0) {
        printf("%s is less than %s\n", str1, str2);
    }
    else if (result > 0) {
        printf("%s is greater than %s\n", str1, str2);
    }
    else {
        printf("%s is equal than %s\n", str1, str2);

    }

    return 0;
}

4.2 strcmp模拟实现:

cpp 复制代码
int my_strcmp(const char* str1, const char* str2)
{
    int ret = 0;
    assert(str1 != NULL && str1 != NULL);//判断是否为\0以及两个字符串所指向的值是否相等
    while (*str1 && (*str1 == *str2)) {
        str1++;
        str2++;
    }
    return *(unsigned char*)str1 - *(unsigned char*)str2;

}
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "abcdg";
	int result = my_strcmp(str1, str2);

    if (result < 0) {
        printf("%s is less than %s\n", str1, str2);
    }
    else if (result > 0) {
        printf("%s is greater than %s\n", str1, str2);
    }
    else {
        printf("%s is equal than %s\n", str1, str2);

    }

    return 0;
}

字符串比较:

  • 使用 while 循环逐个比较 str1str2 中的字符。
  • *str1*str2 分别表示当前字符。如果当前字符相等且 *str1 不为 '\0'(即字符串未结束),则继续比较下一个字符。
  • 在每次循环中,指针 str1str2 都向后移动一个字符。

返回比较结果

  • 当循环结束时,str1str2 指向的字符要么是不同的字符,要么是字符串的结束符 '\0'
  • 使用 *(unsigned char*)str1*(unsigned char*)str2 获取当前字符的 ASCII 值,并计算它们的差值。
  • 返回值的意义:
    • 如果 str1 小于 str2,返回一个负值。
    • 如果 str1 等于 str2,返回 0。
    • 如果 str1 大于 str2,返回一个正值。

五 、strstr的使用和模拟实现

  • 功能:函数返回字符串str2在字符串str1的出现的位置
  • 字符串的比较匹配不包含'\0',以'\0'为结束标志
cpp 复制代码
char * strstr(const char *str1,const char *str2);

str1------要搜索的主字符串;

str2------要查找的子字符串;

5.1 strstr使用

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    const char* haystack = "Hello, world!";
    const char* needle = "world";

    char* result = strstr(haystack, needle);
    if (result) {
        printf("Found '%s' at position: %ld\n", needle, result - haystack);
    }
    else {
        printf("'%s' not found in '%s'\n", needle, haystack);
    }

    return 0;
}

5.2 strstr模拟

cpp 复制代码
#include <stdio.h>

// 自定义 strstr 函数,用于在 str1 中查找 str2 的首次出现
char* strstr(const char* str1, const char* str2)
{
    char* cp = (char*)str1; // cp 指向 str1 的起始位置
    char* s1, * s2; // s1 用于遍历 str1,s2 用于遍历 str2

    // 如果 str2 为空字符串,直接返回 str1
    if (!*str2)
        return((char*)str1);
    
    // 遍历 str1
    while (*cp)
    {
        s1 = cp; // s1 指向当前 cp 位置
        s2 = (char*)str2; // s2 指向 str2 的起始位置
        
        // 比较 s1 和 s2 指向的字符,直到遇到不同的字符或到达字符串末尾
        while (*s1 && *s2 && !(*s1 - *s2))
            s1++, s2++; // 同时移动 s1 和 s2
        
        // 如果 s2 到达了字符串末尾,说明找到了 str2
        if (!*s2)
            return (cp); // 返回 str1 中 str2 的起始位置
        
        cp++; // 移动 cp,继续查找
    }
    
    // 如果没有找到 str2,返回 NULL
    return (NULL);
}

int main() {
    const char *haystack = "Hello, world!"; // 要搜索的字符串
    const char *needle = "world"; // 要查找的子字符串

    // 调用自定义 strstr 函数
    char *result = strstr(haystack, needle);
    
    // 检查结果并输出
    if (result) {
        printf("Found '%s' at position: %ld\n", needle, result - haystack); // 输出找到的位置
    } else {
        printf("'%s' not found in '%s'\n", needle, haystack); // 输出未找到的提示
    }

    return 0; // 程序结束
}

六、strtok的使用和模拟实现

cpp 复制代码
char* strtok (char * str,const char* sep);
//sep参数指向一个字符串,定义了用分隔符的字符集合
//str制定了一个字符串
  • strtok函数是用于 "将字符串分割成一系列标记"
  • strtok函数找到str中的下一个标记,会讲其用'\0'结尾 ,返回一个指向这个标记的指针

例如:

原:123456@abc.com

改后:123456**\0**abc.com

  • strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存他在字符串中的位置
  • strtok函数的第一个参数为NUNLL,函数将在同一字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回NULL指针
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, world! Welcome to C programming.";
    const char sep[] = " ,.!"; // 定义分隔符
    char *token;

    // 获取第一个子字符串
    token = strtok(str, sep);
    
    // 继续获取后续的子字符串
    while (token != NULL) {
        printf("Token: %s\n", token); // 输出当前的子字符串
        token = strtok(NULL, sep); // 获取下一个子字符串
    }

    return 0; // 程序结束
}

七、strerror函数

strerror函数用于"返回与错误码对应的错误信息字符串"

cpp 复制代码
char *strror(int errnum);//errnum为错误码的意思,
//这个错误码通常是由系统调用或库函数返回的。

需要包含头文件<errno.h>

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
    FILE *file = fopen("non_existent_file.txt", "r");
    if (file == NULL) {
        // 打印错误码
        printf("Error opening file: %d\n", errno);
        // 使用 strerror 获取错误信息
        printf("Error message: %s\n", strerror(errno));
    } else {
        fclose(file);
    }
    return 0;
}

在这里笔者会用一种方式让大家看明白strerror函数的返回

cpp 复制代码
#include<errno.h>
#include<string.h>
#include<stdio.h>
int main()
{
	int i;
	for (i = 0;i <= 10;i++)
	{
		printf("%s\n",strerror(i));
	}
	return 0;
}

在win11+vs2022环境下输出的结果:


相关推荐
黑听人17 分钟前
【力扣 困难 C】329. 矩阵中的最长递增路径
c语言·leetcode
时来天地皆同力.1 小时前
Java面试基础:概念
java·开发语言·jvm
hackchen1 小时前
Go与JS无缝协作:Goja引擎实战之错误处理最佳实践
开发语言·javascript·golang
JeffersonZU2 小时前
Linux/Unix 套接字Socket编程(socket基本概念,流程,流式/数据报socket,Unix domain socket示例)
linux·c语言·tcp/ip·udp·unix·gnu
铲子Zzz3 小时前
Java使用接口AES进行加密+微信小程序接收解密
java·开发语言·微信小程序
小小小新人121233 小时前
C语言 ATM (4)
c语言·开发语言·算法
Two_brushes.3 小时前
【linux网络】网络编程全流程详解:从套接字基础到 UDP/TCP 通信实战
linux·开发语言·网络·tcp/udp
小白学大数据3 小时前
R语言爬虫实战:如何爬取分页链接并批量保存
开发语言·爬虫·信息可视化·r语言
争不过朝夕,又念着往昔3 小时前
Go语言反射机制详解
开发语言·后端·golang
Azxcc03 小时前
C++异步编程入门
开发语言·c++