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

【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 字符转换函数

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

1. strlen函数

1.1 strlen函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第1张图片
可以看见以下信息:

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

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

#include
#include
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,应该打印小于才对,那么结果是不是这样呢?我们来看运行结果:【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第2张图片
结果是大于是不是很诧异呢?这是因为strlen函数的返回值的类型是size_t的形式,也就是无符号整形,是不会出现负数的,无符号整形的减法也同样不会出现负数,就算相减得负数,负数在内存里面里面是以二进制补码的形式储存的,最高位的1原本是符号位,现在也变成二进制位了,因为没有符号位,所以它的补码也就是原码,这里我们给出-3的原、反、补码、以及它补码转换为十进制位的结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第3张图片

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第4张图片
这里%u的形式去打印(strlen("abc") - strlen("abcdef"))可以看到和我们-3的补码直接无符号位转换成十进制位的结果是一样的,也间接的证明了这个式子确实是按照无符号来算的。
为了达到我们的目的,可以将strlen函数的返回值强制转换为int类型然后再去打印,请看如下代码:

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

运行结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第5张图片

1.2 strlen函数的模拟实现

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

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

#include
#include

// 函数: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
#include

// 递归实现字符串长度计算
// 参数:
//   - 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
#include

// 指针-指针实现字符串长度计算
// 参数:
//   - 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函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第6张图片
从图中可以看出库函数strcpy的几个重要信息:

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

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

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

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

运行截图:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第7张图片

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

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
#include 

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

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

    return 0;
}

进行调试,如果发现监视的窗口,字符串arr2拷贝到arr1中后,也在'x'中间,多了\0,就说明确实strcpy函数在拷贝时会将\0拷贝过去,如图所示:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第8张图片
可以看到,确实在字符串"abcdef"'x'之间多了一个\0,这是目标字符串原先并不存在的,说明strcpy函数将\0也拷贝过来了。

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

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

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

#include 
#include 

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

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

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

运行截图:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第9张图片
可以看到程序崩了,说明这种做法是非法的。

2.2 strcpy函数的模拟实现

  • 参数和返回值设计参考库函数的函数声明。
#include 
#include 

// 函数: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 
#include 

// 函数: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函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第10张图片
从上面图片我们可以知道以下几点:

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

下面演示strcat函数的使用:

#include 
#include 

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;
}

运行结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第11张图片

3.2 strcat函数的模拟实现

#include 
#include 

// 函数: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就不会被覆盖,所以打印只会打印目标字符串部分,遇到这种错误,我们可以自行调试解决。
运行截图:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第12张图片

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

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;
}

运行结果:【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第13张图片
可以看到程序确实直接崩了。

4. strcmp函数

4.1 strcmp函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第14张图片

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

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

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;
}

运行结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第15张图片

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

4.2 strcmp函数的模拟实现

#include 
#include 

// 函数: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;
}

运行结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第16张图片

5. strncpy函数

5.1 strncpy函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第17张图片

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

下面是字符串arr1的调试结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第18张图片

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

6. strncat函数

6.1 strncat函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第20张图片

  • strcat函数的区别就是长度受限制了,其它条件依然要满足。
  • 从目标字符串的\0开始追加,目标字符串必须空间足够大,且目标字符串要有\0
  • 当你让我追加的字符个数大于源字符串的长度时,strncat函数追加完源字符串就结束了。
  • strcncat函数不在乎\0,它会在你让我追加的字符(除\0外)后面默认加上一个\0,因此这种带长度限制的函数也更加安全。
    下面一段代码来演示strcnpy函数的使用:
#include
#include
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的调试结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第21张图片
我们设置的追加4个字符过去,实际上只追加了两个,可能你会好奇,那个\0不是从源字符串追加的吗?实际上,并不是,请看下面一段代码:

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

我们再来看arr1被追加后的结果,调试是这样的:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第22张图片
上述代码中我们只追加了两个字符过去,还是字符串arr1中还是多出了一个结束标志\0,说明应该是函数帮你默认添加的,与源字符串的\0无关,所以我们可以知道,strncat函数不关心源字符串里面的\0,因为追加字符长度的限制,它最多只会追加完源字符串除\0以外的部分。

7. strncmp函数

7.1 strncmp函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第23张图片

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

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

#include 
#include 

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;
}

运行结果:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第24张图片

8. strstr函数

8.1 strstr函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第25张图片

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

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

#include 
#include 

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;
}

运行结果:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第26张图片
如果子字符串是’\0’时,它的运行结果是这样的:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第27张图片

8.2 strstr函数的模拟实现

#include 
#include 

// 函数: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函数的思路:
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第28张图片

运行结果:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第29张图片
我们模拟实现的my_strstr更接近于库里面的实现,如果你想让算法更优,可以考虑KMP匹配算法。

9. strtok函数

9.1 strtok函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第30张图片

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

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

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

#include
#include 

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;
}

运行结果:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第31张图片

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

#include 

int main()
{
    char str[] = "abc#[email protected]";  // 声明一个字符数组 str,并初始化为 "abc#[email protected]"
    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;
}

运行结果:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第32张图片
下图是程序运行结束后copy内放的字符,希望帮助你更好理解strtok函数的工作机制:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第33张图片

10. strerror函数

10.1 strerror函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第34张图片

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

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

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第35张图片

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

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

#include 
#include 

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

运行结果:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第36张图片

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

#include 
#include 

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,但是我们没有创建它,打印错误信息是这样的:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第37张图片
这里我们打开同路径的文件夹,试图创建一个叫做data.txt的文件:

鼠标右击箭头所指处:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第38张图片

然后是这样的:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第39张图片

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

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第40张图片
然后我们右击鼠标点新建创建一个文本文件data.txt,如图:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第41张图片

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

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第42张图片

可以看到程序仍然打印出了错误信息,意思是:没有类似的文件,那这是为什么呢?刚刚我们明明已经在同路径底下创建了文件data.txt呀,注意问题就出在这个扩展名txt上,有些系统是默认隐藏扩展名了的,需要自己设置才能看得见,
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第43张图片

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

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第44张图片

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

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第45张图片
可以看到这次没有打印错误信息,文件应该成功被打开了。

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

10.2 perror函数的使用介绍

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第46张图片

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

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

#include 
#include 

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删掉,运行结果是这样的:

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第47张图片

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 
#include 

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行是我们自己输入的字符串):

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第48张图片
【C语言进阶技巧】探秘字符与字符串函数的奇妙世界_第49张图片

你可能感兴趣的:(C语言进阶篇,c语言,开发语言,算法,程序人生,青少年编程,笔记)