【C语言进阶技巧】探秘字符与字符串函数的奇妙世界

news2024/12/23 17:17:16

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界

  • 1. strlen函数
    • 1.1 strlen函数的使用介绍
    • 1.2 strlen函数的模拟实现
      • 1.2.1 计数法(使用临时变量)
      • 1.2.1 递归法(不使用临时变量)
      • 1.2.3 指针减指针的方法
  • 2. strcpy函数
    • 2.1 strcpy函数的使用介绍
    • 2.2 strcpy函数的模拟实现
  • 3. strcat函数
    • 3.1 strcat函数的使用介绍
    • 3.2 strcat函数的模拟实现
  • 4. strcmp函数
    • 4.1 strcmp函数的使用介绍
    • 4.2 strcmp函数的模拟实现
  • 5. strncpy函数
    • 5.1 strncpy函数的使用介绍
  • 6. strncat函数
    • 6.1 strncat函数的使用介绍
  • 7. strncmp函数
    • 7.1 strncmp函数的使用介绍
  • 8. strstr函数
    • 8.1 strstr函数的使用介绍
    • 8.2 strstr函数的模拟实现
  • 9. strtok函数
    • 9.1 strtok函数的使用介绍
  • 10. strerror函数
    • 10.1 strerror函数的使用介绍
      • 10.2 perror函数的使用介绍
  • 11. 其它字符函数
    • 11.1 字符分类函数
    • 11.2 字符转换函数

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界)

❤️博客主页: 小镇敲码人
🍏 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌞宿命论是那些毅力薄弱者的借口。——迪斯累里🍎🍎🍎
❤️在人生的进口处,天真地树立着两根柱子,一根写上这样的文字:善良之路;另一根上则这样警告:罪恶之路。再对走到路口的人说:选择吧。 💞 💞 💞

1. strlen函数

1.1 strlen函数的使用介绍

在这里插入图片描述
可以看见以下信息:

  • strlen函数的返回值是size_t也就是无符号的整形。
  • 它在使用时需要传一个char类型的指针。
  • 它的返回值是字符串的长度,不包括\0

下面一段代码将加深你对strlen函数的理解:

#include<stdio.h>
#include<string.h>
int main()
{
	if ((strlen("abc") - strlen("abcdef")) > 0)
	{
		printf("大于\n");
	}
	else
	{
		printf("小于\n");
	}
	return 0;
}

按照我们刚刚对strlen函数的理解,既然它是返回字符串的长度,那第一个字符串"abc"的长度是3,第二个字符串"abcdef"的长度是6,那么 3 − 6 3-6 36应该等于 − 3 -3 3,小于0,应该打印小于才对,那么结果是不是这样呢?我们来看运行结果:在这里插入图片描述
结果是大于是不是很诧异呢?这是因为strlen函数的返回值的类型是size_t的形式,也就是无符号整形,是不会出现负数的,无符号整形的减法也同样不会出现负数,就算相减得负数,负数在内存里面里面是以二进制补码的形式储存的,最高位的1原本是符号位,现在也变成二进制位了,因为没有符号位,所以它的补码也就是原码,这里我们给出-3的原、反、补码、以及它补码转换为十进制位的结果:
在这里插入图片描述

在这里插入图片描述
这里%u的形式去打印(strlen("abc") - strlen("abcdef"))可以看到和我们-3的补码直接无符号位转换成十进制位的结果是一样的,也间接的证明了这个式子确实是按照无符号来算的。
为了达到我们的目的,可以将strlen函数的返回值强制转换为int类型然后再去打印,请看如下代码:

#include<stdio.h>
#include<string.h>
int main()
{
	if (((int)strlen("abc") - (int)strlen("abcdef")) > 0)
	{
		printf("大于\n");
	}
	else
	{
		printf("小于\n");
	}
	return 0;
}

运行结果:
在这里插入图片描述

1.2 strlen函数的模拟实现

  • 计算字符串函数,遇见\0就停止计数,注意参数的设计参考cplusplus网站上面的参数说明。

1.2.1 计数法(使用临时变量)

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

// 函数:my_strlen
// 描述:计算字符串的长度
// 参数:
//   - str:要计算长度的字符串(以null结尾)
// 返回值:
//   - size_t:字符串的长度(不包括null终止符)
size_t my_strlen(const char* str)
{
    int count = 0;
    assert(str);  // 断言:确保传入的字符串指针不为NULL
    while (*str)
    {
        str++;
        count++;
    }
    return count;
}

int main()
{
    char arr[] = "abcdef";
    size_t sz = my_strlen(arr);
    printf("%u", sz);
    return 0;
}

1.2.1 递归法(不使用临时变量)

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

// 递归实现字符串长度计算
// 参数:
//   - str: 要计算长度的字符串(以null结尾)
// 返回值:
//   - size_t: 字符串的长度(不包括null终止符)
size_t my_strlen(const char* str)
{
    assert(str);  // 断言字符串不为空
    if (*str == '\0')
        return 0;  // 如果遇到null终止符,则返回0
    else
        return 1 + my_strlen(str + 1);  // 递归调用自身,并将字符串指针向后移动一位
}

int main()
{
    char arr[] = "abcdef";  // 声明一个字符数组 arr,并初始化为 "abcdef"
    size_t sz = my_strlen(arr);  // 使用自定义函数 my_strlen 计算 arr 的长度
    printf("%u", sz);  // 打印字符串的长度
    return 0;
}


1.2.3 指针减指针的方法

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

// 指针-指针实现字符串长度计算
// 参数:
//   - str: 要计算长度的字符串(以null结尾)
// 返回值:
//   - size_t: 字符串的长度(不包括null终止符)
size_t my_strlen(const char* str)
{
    assert(str);  // 断言字符串不为空
    char* ret = str;  // 保存初始的字符串指针位置
    while (*str)
    {
        str++;  // 指针后移,直到遇到null终止符
    }
    return str - ret;  // 计算指针移动的距离,即字符串的长度
}

int main()
{
    char arr[] = "abcdef";  // 声明一个字符数组 arr,并初始化为 "abcdef"
    size_t sz = my_strlen(arr);  // 使用自定义函数 my_strlen 计算 arr 的长度
    printf("%u", sz);  // 打印字符串的长度
    return 0;
}

2. strcpy函数

2.1 strcpy函数的使用介绍

在这里插入图片描述
从图中可以看出库函数strcpy的几个重要信息:

  • strcpy函数是实现字符串的拷贝功能的。
  • strcpy函数有两个参数,一个参数是目标的字符串的起始地址,另一个参数是源头字符串的起始地址。
  • strcpy函数的返回值也是一个指针,返回目标字符串的起始地址。
    我们需要知道它使用中的几个常见问题:
  1. 目标字符串的长度不能小于源字符串的长度,否则将其拷贝过去编译器就会报错,请看如下代码:
#include <stdio.h>
#include <string.h>

int main() {
    char arr1[3] = " ";
    char arr2[] = "hello bit";

    // 使用 strcpy 函数将 arr2 的内容复制到 arr1 中
    // 注意,arr1 的大小要足够容纳 arr2 的内容,以避免缓冲区溢出
    strcpy(arr1, arr2);

    printf("%s", arr1);
    return 0;
}

运行截图:
在这里插入图片描述

  1. 源字符必须包含\0,否则编译器不知道什么时候拷贝停止,会报错,请看如下代码:
#include <stdio.h>
#include <string.h>

int main() {
    char arr1[20] = "xxxxxxxxx";
    char arr2[] = { 'a', 'b', 'c', 'd', 'e', 'f' };

    // 使用 strcpy 函数将 arr2 的内容复制到 arr1 中
    // 注意,arr1 的大小要足够容纳 arr2 的内容,以避免缓冲区溢出
    strcpy(arr1, arr2);

    printf("%s", arr1);
    return 0;
}

调试后arr1数组的结果:
在这里插入图片描述
可以看到arr1后面的拷贝出现了问题,编译器也报了错,所以由于字符拷贝函数的结束标志是\0,所以一定要在源字符串后面加上\0,如果源字符串是以单个数组的形式进行储存的,需要手动加上\0

  1. strcpy函数在拷贝时,源字符串末尾的\0也会拷贝到目标字符串中。
#include<stdio.h>
#include <string.h>

int main() {
    char arr1[] = "xxxxxxxxx";
    char arr2[] = "abcdef";

    // 使用 strcpy 函数将 arr2 的内容复制到 arr1 中
    // 注意,arr1 的大小要足够容纳 arr2 的内容,以避免缓冲区溢出
    strcpy(arr1, arr2);

    return 0;
}

进行调试,如果发现监视的窗口,字符串arr2拷贝到arr1中后,也在'x'中间,多了\0,就说明确实strcpy函数在拷贝时会将\0拷贝过去,如图所示:
在这里插入图片描述
可以看到,确实在字符串"abcdef"'x'之间多了一个\0,这是目标字符串原先并不存在的,说明strcpy函数将\0也拷贝过来了。

  1. 字符串拷贝不能拷贝到常量字符串中。
    如:不能通过指向常量字符串的指针来修改常量字符串,这是未定义行为。
    下面是一段引用,希望帮助你理解为什么不能修改。

char* str = "abcd" ;
假设可以修改*str = "abc" 等价于"abcd" = "abc",这显然是非法的,因为常量直接被修改,用strcpy函数也是一个道理。

下面一段代码,让你理解修改通过strcpy函数直接修改指针存储的常量字符串在C语言中是非法的:

#include <stdio.h>
#include <string.h>

int main() {
    char* arr1 = "abcdef";
    char arr2[] = "xxxx";

    // 尝试将 arr2 的内容复制到 arr1
    // 这里会导致错误,因为 arr1 指向一个字符串字面量,是只读的,不能进行修改
    strcpy(arr1, arr2);

    printf("%s", arr1);
    return 0;
}

运行截图:
在这里插入图片描述
可以看到程序崩了,说明这种做法是非法的。

2.2 strcpy函数的模拟实现

  • 参数和返回值设计参考库函数的函数声明。
#include <stdio.h>
#include <assert.h>

// 函数:my_strcpy
// 描述:将源字符串复制到目标字符串
// 参数:
//   - dest:目标字符串指针
//   - src:源字符串指针(以null结尾)
// 返回值:
//   - char*:目标字符串的指针
char* my_strcpy(char* dest, const char* src)
{
    char* ret = dest;
    assert(dest && src);  // 断言:确保目标字符串和源字符串的指针不为NULL
    while (*src != '\0')
    {
        *dest++ = *src++;
    }
    *dest = *src;//\0
    return ret;
}

int main()
{
    char arr1[] = "xxxxxx";
    char arr2[] = "abcde";
    printf("%s", my_strcpy(arr1, arr2));
    return 0;
}

上述代码可以这样优化:

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

// 函数:my_strcpy
// 描述:将源字符串复制到目标字符串
// 参数:
//   - dest:目标字符串指针
//   - src:源字符串指针(以null结尾)
// 返回值:
//   - char*:目标字符串的指针
char* my_strcpy(char* dest, const char* src)
{
    char* ret = dest;
    assert(dest && src);  // 断言:确保目标字符串和源字符串的指针不为NULL
    while (*dest++ = *src++)
    {
        ;
    }
    return ret;
}

int main()
{
    char arr1[] = "xxxxxx";
    char arr2[] = "abcde";
    printf("%s", my_strcpy(arr1, arr2));
    return 0;
}
  • 注意:用const修饰源字符串是由于它只用作拷贝,而不会被修改,将其变为常量字符串,防止它被修改。
  • char*类型返回目标字符串的起始地址,这种情况直接可以%s形式打印,注意到my_strcpy函数的返回值作为printf函数的参数,这种访问方式又叫链式访问。

3. strcat函数

3.1 strcat函数的使用介绍

在这里插入图片描述
从上面图片我们可以知道以下几点:

  • strcat函数的功能是实现字符串的追加。
  • strcat函数的两个参数都是字符指针,源字符串不可修改。
  • strcat函数先找到目标字符串\0的位置,然后从这个位置开始追加源字符串直到找到源字符串的\0才会停止。
  • strcat的源字符串和目标字符串都要有\0,这样它才能正常工作,且目标字符串的总长度应该大于两者之和,否则编译器就会报错。

下面演示strcat函数的使用:

#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[20] = "hello ";  // 声明一个大小为 20 的字符数组 arr1,并初始化为 "hello "
    char arr2[] = "world";  // 声明一个字符数组 arr2,并初始化为 "world"

    // 使用 strcat 函数将字符串 arr2 追加到字符串 arr1 的末尾
    // strcat 函数会将 arr2 的内容追加到 arr1 的末尾,并返回指向 arr1 的指针
    strcat(arr1, arr2);

    printf("%s", arr1);  // 打印拼接后的字符串 arr1

    return 0;
}

运行结果:
在这里插入图片描述

3.2 strcat函数的模拟实现

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

// 函数:my_strcat
// 描述:将源字符串追加到目标字符串的末尾
// 参数:
//   - dest:目标字符串指针
//   - src:源字符串指针(以null结尾)
// 返回值:
//   - char*:目标字符串的指针
char* my_strcat(char* dest, const char* src)
{
    char* ret = dest;
    assert(dest && src);  // 断言:确保目标字符串和源字符串的指针不为NULL

    // 找到目标字符串的末尾位置,即null终止符的位置
    while (*dest)
    {
        dest++;
    }

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

    return ret;
}

int main()
{
    char arr1[20] = "hello ";
    char arr2[] = "world";
    printf("%s\n", my_strcat(arr1, arr2));
    return 0;
}

下面是得到目标字符串\0位置的错误代码:

 while(*dest++)
 {
    ;
 }

这段代码如果后面没有进行dest--操作的话,目标字符串原先的\0就不会被覆盖,所以打印只会打印目标字符串部分,遇到这种错误,我们可以自行调试解决。
运行截图:
在这里插入图片描述

  • 注意:无论是我们自己实现的my_strcat还是库函数strcat都允许,字符串自己给自己追加,因为源字符串会把目标字符串的\0给覆盖掉,从而源字符串也就没有\0,函数无法停止,程序会崩,如果你不信,请看如下代码:
#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[20] = "hello ";  // 声明一个大小为 20 的字符数组 arr1,并初始化为 "hello "
 
    // 使用 strcat 函数将字符串 arr1 追加到字符串 arr1 的末尾
    // strcat 函数会将 arr1 的内容追加到 arr1 的末尾,并返回指向 arr1 的指针
    strcat(arr1, arr1);

    printf("%s", arr1);  // 打印拼接后的字符串 arr1

    return 0;
}

运行结果:在这里插入图片描述
可以看到程序确实直接崩了。

4. strcmp函数

4.1 strcmp函数的使用介绍

在这里插入图片描述

从上面图片我们可以得到以下信息:

  • strcmp函数用作字符串的比较。它的比较规则和返回值是这样的:字符逐一比较当遇见第一个不同的字符时,如果前一个字符串的字符大于后一个字符串的字符,返回一个大于0的数。如果前一个字符串的字符小于后一个字符串的字符,返回一个小于0的数。如果一直没有遇见不同的字符,直到两者同时遇见\0,说明这两个字符相等,返回等于0。
  • strcmp函数的两个参数都是字符指针,const修饰表示这两个字符串只用作比较但不可修改,它的返回值是一个int型的值。
    下面通过一段代码来演示strcmp函数的使用:
#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[] = "abc";  // 声明一个字符数组 arr1,并初始化为 "abc"
    char arr2[] = "abcd";  // 声明一个字符数组 arr2,并初始化为 "abcd"
    char arr3[] = "abc";  // 声明一个字符数组 arr3,并初始化为 "abc"
    char arr4[] = "ab";  // 声明一个字符数组 arr4,并初始化为 "ab"

    // 使用 strcmp 函数比较字符串的大小,并打印结果
    printf("%d\n%d\n%d\n", strcmp(arr1, arr2), strcmp(arr1, arr3), strcmp(arr1, arr4));

    return 0;
}

运行结果:
在这里插入图片描述

  • 注意:VS编译器上面默认大于0的数字返回1。
                                             等于0返回0。
                                            小于0的数字返回-1。

4.2 strcmp函数的模拟实现

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

// 函数:my_strcmp
// 描述:比较两个字符串的大小
// 参数:
//   - str1:要比较的第一个字符串指针(以null结尾)
//   - str2:要比较的第二个字符串指针(以null结尾)
// 返回值:
//   - int:如果 str1 大于 str2,则返回一个正数;如果 str1 小于 str2,则返回一个负数;如果 str1 等于 str2,则返回 0
int my_strcmp(const char* str1, const char* str2)
{
    assert(str1 && str2);  // 断言:确保 str1 和 str2 的指针不为NULL

    while (*str1 == *str2)
    {
        if (*str1 == '\0')
            return 0;

        str1++;
        str2++;
    }

    return *str1 - *str2;
}

int main()
{
    int ret = my_strcmp("bvq", "bcq");

    if (ret > 0)
        printf("大于>:\n");

    printf("%d\n", ret);

    return 0;
}

运行结果:
在这里插入图片描述

5. strncpy函数

5.1 strncpy函数的使用介绍

在这里插入图片描述

  • strncpystrcpy的区别在于前者多了一个参数,这个参数的类型是size_t类型的,可以控制源字符串里面的具体几个字符拷贝到目标字符串中而不一定是全部。
  • 如果源字符串的长度小于num,则拷贝完源字符串后,函数会在目标字符串内追加\0,直到num个。
    下面我们演示以下strncpy函数的使用:
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcxxxx";
	char arr2[] = "def";
	strncpy(arr1, arr2, 5);
	printf("%s\n", arr1);
}

下面是字符串arr1的调试结果:
在这里插入图片描述

  • 注意:\0也算作源字符串里面的一个字符,我们也可以让strncpy函数不把\0拷贝过去,这样打印的结果是这样:
    在这里插入图片描述
    可以看到结果是defxxxx,只拷贝了三个字符过去,这种指定具体拷贝几个字符的函数,不会再因为源字符串没有\0而使程序运行发生崩溃,所以这种函数比原先的strcpy函数更加安全。
  • 注意:目标字符串的长度不能小于num,否则编译器会报错,如果目标字符串没有\0,那么%s形式打印时就会出现乱码,因为%s打印是遇见\0才停止。

6. strncat函数

6.1 strncat函数的使用介绍

在这里插入图片描述

  • strcat函数的区别就是长度受限制了,其它条件依然要满足。
  • 从目标字符串的\0开始追加,目标字符串必须空间足够大,且目标字符串要有\0
  • 当你让我追加的字符个数大于源字符串的长度时,strncat函数追加完源字符串就结束了。
  • strcncat函数不在乎\0,它会在你让我追加的字符(除\0外)后面默认加上一个\0,因此这种带长度限制的函数也更加安全。
    下面一段代码来演示strcnpy函数的使用:
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcdef\0yyyyyyyy";
	char arr2[] = { 'x','x','\0','\0'};
	printf("%s", strncat(arr1, arr2, 4));
	return 0;
}

我们在字符串arr1的后面主动加上\0,然后后面加上非\0字符是为了调试方便,让我们弄明白strncat\0是如何加的,下面是字符串arr1的调试结果:
在这里插入图片描述
我们设置的追加4个字符过去,实际上只追加了两个,可能你会好奇,那个\0不是从源字符串追加的吗?实际上,并不是,请看下面一段代码:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcdef\0yyyyyyyy";
	char arr2[] = { 'x','x','\0','\0'};
	printf("%s", strncat(arr1, arr2, 2));
	return 0;
}

我们再来看arr1被追加后的结果,调试是这样的:
在这里插入图片描述
上述代码中我们只追加了两个字符过去,还是字符串arr1中还是多出了一个结束标志\0,说明应该是函数帮你默认添加的,与源字符串的\0无关,所以我们可以知道,strncat函数不关心源字符串里面的\0,因为追加字符长度的限制,它最多只会追加完源字符串除\0以外的部分。

7. strncmp函数

7.1 strncmp函数的使用介绍

在这里插入图片描述

  • 和其它两个长度受限制的字符函数一样,strncmp也多了一个参数num,用来限制比较的长度。
  • 比较到出现两个不一样的字符或者一个字符串结束或者num个字符全部比较完,注意:长度num只是我最多比较的字符个数,如果在这之前已经比较出结果了,那就不会再比较下去了。

下面一段代码,希望能让你更好的理解strncmp函数:

#include <stdio.h>
#include <string.h>

int main()
{
    char str[][5] = { "R2D2","C3PO","R2A6" };  // 声明一个二维字符数组 str,包含三个字符串
    int n;

    puts("Looking for R2 astromech droids ...");  // 打印提示信息

    for (n = 0; n < 3; n++)
    {
        // 使用 strncmp 函数比较字符串的前两个字符,如果相等则打印该字符串
        if (strncmp(str[n], "R2xx", 2) == 0)
        {
            printf("found %s\n", str[n]);  // 打印找到的字符串
        }
    }

    return 0;
}

运行结果:
在这里插入图片描述

8. strstr函数

8.1 strstr函数的使用介绍

在这里插入图片描述

  • strstr函数的功能是实现字符串的查找,它会返回子字符串第一次出现的位置,如果找不到就返回空指针(NULL)
  • 特别的,如果子字符串是'\0',那么函数会返回源字符串的起始地址,因为'\0'被认为是字符串的终止符,当子字符串遍历到'\0'时,strstr函数会认为已经找到子字符串了,所以直接返回源字符串的起始地址。
  • 注意:无论是源字符串还是子字符串都需要给它添加字符串终止符'\0'

请看下面代码,希望它帮助你理解strstr函数的功能:

#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[] = "abcdefabcdef";  // 声明一个字符数组 arr1,并初始化为 "abcdefabcdef"
    char arr2[] = "def";  // 声明一个字符数组 arr2,并初始化为 "def"
    char* ret = strstr(arr1, arr2);  // 使用 strstr 函数在 arr1 中查找 arr2 的出现位置

    if (ret != NULL)
        printf("%s\n", ret);  // 如果找到了 arr2,则打印找到的位置及其后面的内容
    else
        printf("找不到\n");  // 如果找不到 arr2,则打印 "找不到"

    return 0;
}

运行结果:

在这里插入图片描述
如果子字符串是’\0’时,它的运行结果是这样的:

z

8.2 strstr函数的模拟实现

#include <stdio.h>
#include <string.h>

// 函数:my_strstr
// 描述:在一个字符串中查找另一个子字符串的起始位置
// 参数:
//   - str1:源字符串
//   - str2:要查找的子字符串
// 返回值:
//   - char*:子字符串在源字符串中的起始位置的指针,如果找不到则返回 NULL
char* my_strstr(char* str1, char* str2)
{
    char* cp = str1;  // 用于遍历源字符串的指针
    char* s1 = str1;  // 用于遍历以源字符串不同位置为起始位置的字符串与子字符串是否匹配的指针
    char* s2 = str2;  // 用于遍历子字符串的指针

    if (*str2 == '\0')  // 如果子字符串为空,则直接返回源字符串的起始位置
        return str1;

    while (*cp)  // 遍历源字符串
    {
        s1 = cp;  // 重置s1为遍历过程中开始匹配的起始位置
        s2 = str2;  // 重置子字符串的指针

        while (*s1 && *s2 && *s1 == *s2)  // 比较源字符串和子字符串的字符
        {
            s1++;  // 源字符串指针后移
            s2++;  // 子字符串指针后移
        }

        if (*s2 == '\0')  // 如果子字符串遍历完了,说明完全匹配
            return cp;  // 返回源字符串开始匹配的起始位置

        cp++;  // 源字符串指针后移
    }

    return NULL;  // 找不到子字符串,返回 NULL
}

int main()
{
    char arr1[] = "abcdefabcdef";  // 声明一个字符数组 arr1,并初始化为 "abcdefabcdef"
    char arr2[] = "def";  // 声明一个字符数组 arr2,并初始化为 "def"
    char* ret = my_strstr(arr1, arr2);  // 使用自定义的 my_strstr 函数在 arr1 中查找 arr2 的出现位置
    if (ret != NULL)
        printf("%s\n", ret);  // 如果找到了 arr2,则打印找到的位置及其后面的内容
    else
        printf("找不到\n");  // 如果找不到 arr2,则打印 "找不到"

    return 0;
}

下面我们以画图的形式来分析以下模拟实现strstr函数的思路:
在这里插入图片描述

运行结果:

在这里插入图片描述
我们模拟实现的my_strstr更接近于库里面的实现,如果你想让算法更优,可以考虑KMP匹配算法。

9. strtok函数

9.1 strtok函数的使用介绍

在这里插入图片描述

当你需要把字符串abc#bbb.net@777"中的abcbbbnet777拿出来时,你可以使用字符串函数strtok

  • strtok函数的功能是将有特定分隔符的字符串一段段的分隔出来。
  • 第二个参数delimiters是分隔符的意思,定义了用作分隔符的字符集合。
  • 第一个参数指定一个字符串,它包含了0个或多个delimiters中的分隔符的标记,这些分隔符可能只有delimiters中的一种。
  • 当你传一个非空字符串copystrtok,它会找到str中的第一个标记,并把它用\0结尾,并返回这一段字符串的起始位置,并且strtok有记忆功能,它能保存这个被置为\0位置。(注意:由于strtok函数会改变被操作的字符串,所以使用strtok切分的字符串一般是临时拷贝的内容并且可以修改)
  • 当第一个参数为NULL时,strtok将从上一次被保存的下一个不为\0的位置开始,查找下一个标记,找到之后的做法同上。
  • 如果字符串中不存在更多的标记,我们就返回NULL

下面一段代码来演示strtok函数怎样来使用:

#include<stdio.h>
#include <string.h>

int main()
{
    char str[] = "abc#bbb.net@777";  // 声明一个字符数组 str,并初始化为 "abc#bbb.net@777"
    char copy[30];  // 声明一个字符数组 copy,用于复制 str
    strcpy(copy, str);  // 将 str 复制到 copy 中
    char dmr[] = "#.@";  // 分隔符字符串,包含了 '#'、'.' 和 '@'
    char* ret = strtok(copy, dmr);  // 使用 strtok 函数分割 copy,获取第一个子字符串

    printf("%s\n", ret);  // 打印第一个子字符串
    ret = strtok(NULL, dmr);  // 继续使用 strtok 函数分割剩余部分,获取下一个子字符串
    printf("%s\n", ret);  // 打印第二个子字符串
    ret = strtok(NULL, dmr);  // 继续使用 strtok 函数分割剩余部分,获取下一个子字符串
    printf("%s\n", ret);  // 打印第三个子字符串
    ret = strtok(NULL, dmr);  // 继续使用 strtok 函数分割剩余部分,获取下一个子字符串
    printf("%s\n", ret);  // 打印第四个子字符串

    return 0;
}

运行结果:

在这里插入图片描述

观察到strtok函数除了第一次实参不同是str,其余的都相同,因此上述代码我们可以这样修改:

#include <string.h>

int main()
{
    char str[] = "abc#bbb.net@777.open";  // 声明一个字符数组 str,并初始化为 "abc#bbb.net@777.open"
    char copy[30];  // 声明一个字符数组 copy,用于复制 str
    strcpy(copy, str);  // 将 str 复制到 copy 中
    char dmr[] = "#.@";  // 分隔符字符串,包含了 '#'、'.' 和 '@'

    for (char* ret = strtok(copy, dmr); ret != NULL; ret = strtok(NULL, dmr))
        printf("%s\n", ret);  // 打印每个分隔出的子字符串

    return 0;
}

运行结果:

在这里插入图片描述
下图是程序运行结束后copy内放的字符,希望帮助你更好理解strtok函数的工作机制:

在这里插入图片描述

10. strerror函数

10.1 strerror函数的使用介绍

在这里插入图片描述

strerror 函数主要用于处理系统调用或库函数返回的错误码,将其转换为对应的错误信息字符串。这些错误码通常是由操作系统或库函数定义的,用于标识不同类型的错误情况。
具体而言,strerror 函数可以处理包括但不限于以下类型的错误:

  1. 系统错误:例如文件操作失败、进程创建失败、网络连接错误等与操作系统相关的错误。
  2. 库函数错误:例如内存分配失败、打开文件失败、格式化字符串错误等与特定库函数相关的错误。
  3. 线程错误:例如线程创建失败、线程同步操作失败等与多线程编程相关的错误。
  4. 套接字错误:在网络编程中,套接字操作可能返回错误码,strerror 函数可将其转换为对应的错误信息。
  5. 其他错误:某些特定的库函数或系统调用可能返回自定义的错误码,strerror 函数也可以处理这些自定义的错误码。
  • 需要注意的是,strerror 函数处理的是系统或库函数返回的错误码,而不是编译器报告的语法错误。编译器报告的语法错误通常由编译器自身处理,并生成相应的错误信息。
  • 它的参数是整数类型的errnum,程序在运行时发生了各种错误,会将错误码存放到errno这个变量中,errno是C语言提供的一个全局变量,这个值是不断被更新的,所以你应该及时的查看自己程序运行中的错误。
  • 在VS2019的编译器上,可以看到errno是一个预定义的宏,放在errno.h这个头文件里面。

在这里插入图片描述

  • 它的返回值是错误码的首字符的地址,类型是char*类型的指针。

下面有一段代码,希望加深你对strerror函数功能的理解:

#include <stdio.h>
#include <string.h>

int main()
{
    for (int i = 0; i < 10; i++)
    {
        printf("%d:%s\n",i, strerror(i));  // 打印错误码对应的错误信息字符串
    }
    return 0;
}

运行结果:

在这里插入图片描述

下面是一个打开文件操作的实例,希望可以帮助你更好的了解到及时查看自己的错误码的重要性:

#include <stdio.h>
#include <string.h>

int main()
{
    //C语言中可以操作文件
    //操作文件的步骤:
    // 1.打开文件
    // 2.读/写文件
    // 3.关闭文件
    FILE* pf = fopen("data.txt", "r");  // 打开名为 "data.txt" 的文件,以只读方式打开
    if (pf == NULL)
    {
        printf("fopen:%s\n", strerror(errno));  // 如果文件打开失败,打印错误信息
        return 1;
    }

    // 读取文件内容
    // ...

    fclose(pf);  // 关闭文件
    return 0;
}

我们试图打开一个同路径的文件data.txt,但是我们没有创建它,打印错误信息是这样的:

在这里插入图片描述
这里我们打开同路径的文件夹,试图创建一个叫做data.txt的文件:

鼠标右击箭头所指处:

在这里插入图片描述

然后是这样的:

在这里插入图片描述

点"打开所在文件夹"后,应该是这样的:

在这里插入图片描述
然后我们右击鼠标点新建创建一个文本文件data.txt,如图:

在这里插入图片描述

那么既然已经成功的创建出了data.txt文件程序是不是就可以正常运行了呢,我们来看程序的运行结果:

在这里插入图片描述

可以看到程序仍然打印出了错误信息,意思是:没有类似的文件,那这是为什么呢?刚刚我们明明已经在同路径底下创建了文件data.txt呀,注意问题就出在这个扩展名txt上,有些系统是默认隐藏扩展名了的,需要自己设置才能看得见,
在这里插入图片描述

看我们这里实际上后面是默认加上了扩展名,你再加上就相当于文件名为data.txt.txt,显然我们打开data.txt文件是打不开的,这里如果没有错误信息的提醒,我们很难想到是文件扩展名没有显示导致文件不能打开,这里我们改一下文件名:

在这里插入图片描述

然后再看程序运行的结果:

在这里插入图片描述
可以看到这次没有打印错误信息,文件应该成功被打开了。

  • C语言文件的相关知识我们后续会以博客的形式继续输出,还不太了解也不需要慌张,这里主要是明白及时查看错误信息的重要性。

10.2 perror函数的使用介绍

在这里插入图片描述

  • perror函数在头文件'stdio.h'里面·,它主要用于当前错误信息的打印,它的使用方法是给它传递一个字符串参数,该字符串作为错误消息的前缀,然后根据当前 errno值打印相应的错误信息。
  • 而strerror 函数的使用方法是传递一个错误码作为参数,然后返回对应的错误信息字符串。它可以帮助我们理解和处理发生的错误。
  • 总结来说,perror函数主要用于打印当前错误信息,而 strerror 函数用于将错误码转换为对应的错误信息字符串。它们的区别在于输出方式和返回结果的不同。

上述代码做以下修改,希望帮你更好的理解这个函数:

#include <stdio.h>
#include <string.h>

int main()
{
    //C语言中可以操作文件
    //操作文件的步骤:
    // 1.打开文件
    // 2.读/写文件
    // 3.关闭文件
    FILE* pf = fopen("data.txt", "r");  // 打开名为 "data.txt" 的文件,以只读方式打开
    if (pf == NULL)
    {
        printf("fopen:%s\n", strerror(errno));  // 如果文件打开失败,打印错误信息
        perror("fopen");
        //fopen :错误信息
        return 1;
    }

    // 读取文件内容
    // ...

    fclose(pf);  // 关闭文件
    return 0;
}

我们将文件data.txt删掉,运行结果是这样的:

在这里插入图片描述

11. 其它字符函数

11.1 字符分类函数

函数如果它的参数符合以下条件就返回真
iscntrl任何控制字符
isspace空白字符:空格 ' ',换页 '\f',换行 '\n',回车 '\r',制表符 '\t' 或垂直制表符 '\v'
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母 a~f,大写字母 A~F
islower小写字母 a~z
isupper大写字母 A~Z
isalpha字母 a~zA~Z
isalnum字母或数字,a~ zA~ Z0~9
ispunct标点符号,不属于数字、字母或空白字符的图形字符
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符
  • 如果上述函数判断为真,就返回一个非0值,判断为假就返回0。

11.2 字符转换函数

  1. int tolower(int c); 这个函数是将大写字母转换为小写字母。
  2. int upper(int c);这个函数是将小写字母转换为大写字母。

请看下面代码,让你知道如何使用上述常见函数:

#include <stdio.h>
#include <ctype.h>

int main()
{
	printf("%d\n", isupper('a'));  // 打印结果:0,因为 'a' 不是大写字母
	printf("%d\n", isdigit('1'));  // 打印结果:非0值,因为 '1' 是数字
	printf("%c\n", tolower('A'));  // 打印结果:'a',将大写字母 'A' 转换为小写字母
	printf("%c\n", tolower('s'));  // 打印结果:'s',小写字母 's' 保持不变

	char arr[20] = { 0 };
	gets(arr);  // 读取用户输入的字符串,遇到空格停止

	char* p = arr;
	while (*p)
	{
		if (isupper(*p))  // 判断当前字符是否为大写字母
		{
			*p = tolower(*p);  // 将大写字母转换为小写字母
		}
		p++;
	}
	printf("%s\n", arr);  // 打印转换后的字符串
	return 0;
}

运行结果(其中第5行是我们自己输入的字符串):

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/761336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rust

文章目录 rustCargoCreating a rust project How to Debug Rust Programs using VSCodebasic debuggingHow to pass arguments in Rust debugging with VS Code. References rust Cargo Cargo is a package management tool used for downloading, compiling, updating, and …

Linux---gdb

Linux调试器-gdb使用 GDB&#xff08;GNU调试器&#xff09;是一个在多种操作系统&#xff08;包括Linux&#xff09;上使用的功能强大的调试器。它允许开发者对程序进行调试&#xff0c;以便找出程序中的错误、理解程序的执行过程和进行性能分析。 程序的发布有两种&#xf…

想要学习编程,有什么推荐的书籍吗?

编程是以计算机程序的形式创建创新解决方案的艺术&#xff0c;用于解决各个领域不同的问题&#xff0c;从经典的数学难题和日常生活问题到天气预报以及寻找和理解宇宙中的新奇观。 尽管编程和编码通常可以互换使用&#xff0c;但编程不仅仅是编码。编码代表编程的这一部分&…

mysql数据库 索引

目录 1.定义 2.作用 3.索引使用场景 4.索引分类 5.案例 普通索引 唯一索引 主键索引 组合索引 全文索引 删除索引 1.定义 索引是一个排序的列表 在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址 ### 可以当作目录 2.作用 方便定位信息 做…

我终于成功登上了JS 框架榜单,并且仅落后于 React 4 名!

前言 如期而至&#xff0c;我独立开发的 JavaScript 框架 Strve.js 迎来了一个大版本5.6.2。此次版本距离上次大版本发布已经接近半年之多&#xff0c;为什么这么长时间没有发布新的大版本呢&#xff1f;主要是研究 Strve.js 如何支持单文件组件&#xff0c;使代码智能提示、代…

[洛谷]P8662 [蓝桥杯 2018 省 AB] 全球变暖(dfs)

读题不规范&#xff0c;做题两年半&#xff01; 注意&#xff1a;被海水淹没后的陆地应用另一个字符表示&#xff0c;而不是把它变为海洋&#xff0c;这个点可以便利&#xff0c;但不能被当作起点&#xff0c;不然就只有 36 分。 ACocde: #include<bits/stdc.h> using…

nodejs 下载地址 阿里云开源镜像站

nodejs 下载地址 阿里云开源镜像站 https://mirrors.aliyun.com/nodejs-release/ 我们下期见&#xff0c;拜拜&#xff01;

STM32(HAL库)通过ADC读取MQ2数据

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 ADC外设配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 ADC数据采集 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方式对M…

【java】JMeter进行web测试

JMeter进行web测试 1.对网页进行负载测试新建线程组添加默认 HTTP 请求属性添加cookie支持添加HTTP请求添加监听器以便于查看结果登录网站 2. 测试本地web项目3. 其他使用 URL 重写处理用户会话使用标题管理器 参考JMeter用户手册 https://jmeter.net/usermanual/build-web-te…

PC与(VMware)linux的IP互通

一、环境 二、NAT模式 NAT&#xff08;Network Address Translation&#xff09;网络地址转换&#xff0c;允许一个整体机构以一个公用IP地址出现在Internet上&#xff0c;即把内部私有网络地址翻译成合法网络IP地址的技术。家用路由器一般都是NAT模式。让虚拟系统借助NAT&…

14、vivado打不开工程源码

卡在Initializing Language Server. 解决办法&#xff1a;下图&#xff0c;然后重启软件

微软开源了一个 助力开发LLM 加持的应用的 工具包 semantic-kernel

在首席执行官萨蒂亚纳德拉&#xff08;Satya Nadella&#xff09;的支持下&#xff0c;微软似乎正在迅速转变为一家以人工智能为中心的公司。最近微软的众多产品线都采用GPT-4加持&#xff0c;从Microsoft 365等商业产品到“新必应”搜索引擎&#xff0c;再到低代码/无代码Powe…

数学分析:面积和微分形式

这是面积的推广&#xff0c;这里引出了格拉姆矩阵&#xff0c;有了这个&#xff0c;我们得到的矩阵总是方阵&#xff0c;可以绕过雅可比矩阵不存在的问题。能得到通用的积分换元公式。 其实任何时候&#xff0c;从几何意义去理解总是更加自然。考虑一个平面上的坐标&#xff0c…

centos7安装 mongodb

一、rpm安装 1.1、配置MongoDB Enterprise的yum 源文件 [mongodb-enterprise] nameMongoDB Enterprise Repository baseurlhttps://repo.mongodb.com/yum/redhat/$releasever/mongodb-enterprise/3.4/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://www.mongodb.org/static/pgp…

数据结构(王道)——数据结构之 串

一、串的数据结构 串的定义&#xff1a; 串和线性表对比&#xff1a; 串的基本操作 串的比较思路 字符集编码含义 串定义总结&#xff1a; 二、串的存储结构 定义一个串的方式 定长顺序存储和堆分配存储 定长顺序存储的方案&#xff1a; 堆分配存储的方案&#xff1a; 基本操…

vscode远程连接提示:过程试图写入的管道不存在(删除C:\Users\<用户名>\.ssh\known_hosts然后重新连接)

文章目录 复现过程原因解决方法总结 复现过程 我是在windows上用vscode远程连接到我的ubuntu虚拟机上&#xff0c;后来我的虚拟机出了点问题&#xff0c;我把它回退了&#xff0c;然后再连接就出现了这个问题 原因 本地的known_hosts文件记录服务器信息与现服务器的信息冲突了…

Spring MVC异常处理【单个控制异常处理器、全局异常处理器、自定义异常处理器】

目录 一、单个控制器异常处理 1.1 控制器方法 1.2 编写出错页面 1.3 测试结果 二、全局异常处理 2.1 一个有异常的控制器类 2.2 全局异常处理器类 2.3 测试结果 三、自定义异常处理器 3.1 自定义异常处理器 3.2 测试结果 往期专栏&文章相关导读 1. Maven系列…

【并发编程二十二】通过进程名字杀死进程TerminateProcess

【并发编程二十二】通过进程名字杀死进程TerminateProcess 一、代码二、杀进程失败时&#xff0c;检查1、OpenProcess的参数2、UpdatePrivilege提升优先级3、以管理员方式&#xff0c;启动进程 三、启动进程失败1、由于找不到vcruntime140d.dll&#xff0c;无法继续执行代码。2…

ScaleBit 与 NFTScan 达成安全生态合作伙伴关系

7 月初&#xff0c;ScaleBit 与 NFT 基础设施服务商 NFTScan 正式达成安全生态战略合作&#xff0c;携手促进 NFT 生态的安全性。 NFTScan 作为全球领先的 NFT 基础设施服务商&#xff0c;旨在为 Web3 用户和开发者提供专业的一站式 NFT 基础设施服务。用户可以通过它搜索、查看…

3. CSS-定位

absolute和relative依据什么定位? relative依据自身定位,absolute 依据最近一层的定位元素定位 (定位元素是指开启了absolute relative fixed的父元素,没有就是根元素body) 居中对齐的实现方式:详情看这篇博客