C语言菜鸟成长之旅:《字符串函数篇》

前言

本篇博客主要本人是对C语言的字符串相关函数的所学知识的一个总结,本质上算是一篇学习笔记,如果我分享的内容能够帮助到你,那再好不过。

求字符串长度的函数

strlen

函数声明:size_t strlen ( const char * str );

功能介绍:测量以 \0 为结束标志的字符串长度,长度不包括 \0

注意点:

  • 参数指向的字符串必须要以\0结束。
  • 注意函数的返回值为size_t,是无符号整型( 易错 )

以一个错误的代码为示范,该代码输出结果永远为大于

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

解释:

在计算机中CPU只有加法逻辑,减法运算就是两个数的补码进行相加,所以
strlen(“abc”) - strlen(“abcdef”)
= 3 - 6
= 3 + (-6)
= 00000000000000000000000000000011 + 00000000000000000000000000000110
= 11111111111111111111111111111101
对于一个无符号数来说,原码反码补码相同这段代码永远也不会打印 “小于或等于”。
如果想要实现打印“小于或等于也非常简单,只需要转换一下类型即可
修改后的条件为:(int)strlen("abc") - (int)strlen("abcdef")

strlen函数的模拟实现的三种方法:

  1. 遍历计数
#include <assert.h>
size_t myStrlen(const char* str)
{
	assert(str);
	size_t len = 0;
	while (*str++)
	{
		len++;
		str++}
	return len;
}
  1. 指针 - 指针
#include <assert.h>
size_t myStrlen(const char* str)
{
	assert(str);
	const char* begin = str;
	while (*str)
	{
		str++;
	}
	return str - begin;
}

指针 - 指针图解

  1. 函数递归
#include <assert.h>
size_t myStrlen(const char* str)
{
	assert(str);
	return *str == '\0' ? 0 : 1 + myStrlen(str + 1);
}

以上三种模拟实现中均用到了断言函数assert,使用该函数有两个原因:

  1. 应对函数接收到NULL,而引发程序崩溃的问题。
  2. 相对更见方便找到引发程序错误的位置。

演示assert
断言不通过会直接返回错误的位置,能够剩下不少找bug的时间,对断言函数有兴趣的可以去cplusplus了解一下,这里附上链接:assert

长度不受限制的字符串函数

strcpy

函数声明:char* strcpy(char * destination, const char * source );

功能介绍:将source指向的字符串复制到destination指向的数组中,包括结束的\0字符(并在该点停止)。

注意点:

  • 源字符串必须以\0结束。

  • 函数会将源字符串中的\0拷贝到目标空间。

  • 目标空间必须足够大,以确保能存放源字符串,否则会报错。
    【举例】
    目标空间小于源字符串引发报错

  • 目标空间必须可变,否则无法拷贝。
    【举例】
    目标空间不可变引发报错

strcpy函数的模拟实现:

#include <assert.h>
char* myStrcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while (*src)
	{
		*dest++ = *src++;
	}
	*dest = *src;
	return ret;
}

strcat

函数声明:char * strcat ( char * destination, const char * source );

功能介绍:将源字符串追加到目标字符串。destination中的结束\0字符被source的第一个字符覆盖,并且在destination新字符串的末尾包含一个\0字符。

注意点:

  • 源字符串必须以\0结束。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 字符串自己给自己追加,如何?—— 报错,无法编译!
    理由:两个指针指向同一块空间,字符追加导致字符串变长,造成类似死循环的后果,最后越界访问,程序崩溃。
    在这里插入图片描述

strcat函数的模拟实现:

#include <assert.h>
char* myStrcat(char* dest, const char* src)
{
    assert(dest && src);

    char* ret = dest;
    while (*dest)    //目标空间指向'\0'
        dest++;
    while (*dest++ = *src++)    //从'\0'开始复制
        ;
    return ret;
}

strcmp

功能介绍:比较字符串大小的函数,本质上比较对应位置上的字符的ASCII码值大小。

函数声明:int strcmp(const char* str1, const char* str2)

规则规定:
函数返回值规定

strcmp函数的模拟实现:

int myStrcmp(const char* str1, const char* str2)
{
    assert(str1 && str2);
    while (*str1 && *str2 && *str1++ == *str2++)
    {
        ;
    }
    return *str1 - *str2;
}

长度受限制的字符串函数

strncpy

函数声明:char * strncpy ( char * destination, const char * source, size_t num );

功能介绍:大致功能与 strcpy 函数相似,增加了第三个参数 num ,该函数只会拷贝 num 个字符。

注意点:

  • secure指向的字符串长度 < num 时,在末尾填充\0 直到写入 num 个字符
    在这里插入图片描述

  • secure指向的字符串长度 >= num 时,函数拷贝完num个字符后不会在末尾添加\0,且该函数不强制要求目标空间字符串以\0结束。
    -

  • 由于该函数不强制要求目标空间字符串以\0结束,有可能造成溢出、非法访问等问题在这里插入图片描述

strncpy函数的模拟实现

char* myStrncpy(char* destination, const char* source, size_t num)
{
    assert(destination && source);

    char* ret = destination;
    size_t i = 0;
    for (i = 0; *source && i < num; i++)
        *destination++ = *source++;

    for (i = 0; i < num; i++)    //源字符串长度 < num 才执行
        *destination++ = '\0';

    return ret;
}

strncat

函数声明:char * strncat ( char * destination, const char * source, size_t num );

功能介绍:将指定的 num 个字符从source 追加到 destination

注意点:

  • source指向的字符串长度 < num 时,追加完source指向的字符串后,自动在末尾添加终止字符\0
    在这里插入图片描述
  • source指向的字符串长度 >= num 时,追加完 num个字符后会自动在末尾添加终止字符\0
    在这里插入图片描述

strncat的模拟实现

char* myStrncat(char* destination, const char* source, size_t num)
{
    assert(destination && source);

    char* ret = destination;
    
    while (*destination)    //目标空间指向'\0'
        destination++;

    for (size_t i = 0; *source && i < num; i++)//从'\0'开始复制
    {
        *destination++ = *source++;
    }
    *destination = '\0';

    return ret;
}

strncmp

函数声明:int strncmp ( const char * str1, const char * str2, size_t num );

功能介绍:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用以下对,直到字符不同,直到达到终止的空字符,或者直到两个字符串中的num字符匹配,以先发生者为准。

规则规定:
在这里插入图片描述

strncmp函数的模拟实现

int myStrncmp(const char* str1, const char* str2, size_t num)
{
    assert(str1 && str2);

    size_t i = 0;
    for (i = 0; i < num-1 && *(str1 + i) == *(str2 + i); i++)
    {
        ;
    }

    return *(str1 + i) - *(str2 + i);
}

对指针有了解的还可以有这种写法

int myStrncmp(const char* str1, const char* str2, size_t num)
{
    assert(str1 && str2);

    size_t i = 0;
    for (i = 0; i < num - 1 && str1[i] == str2[i]; i++)
    {
        ;
    }

    return str1[i] - str2[i];
}

字符串查找

strstr

函数声明:char * strstr ( const char * str1, const char * str2 );

功能介绍:字符串查找子字符串,找到返回第一次出现的地址,否则返回NULL

注意点:

  • 匹配过程不包括终止空字符,但到此为止

strstr函数的模拟实现:

char* myStrstr(char* str1, char* str2)
{
    char *cur = str1;
    char *s1 = NULL;
    char *s2 = NULL;

    while (*cur)
    {
        s1 = cur;
        s2 = str2;

        if (*str2 == '\0') //默认设定
            return str1;

        while(*s1 && *s2 && *s1 == *s2)
            s1++, s2++;

        if (*s2 == '\0')
            return cur;
        
        cur++;
    }
    return NULL;
}

strtok

功能介绍:以指定字符为分隔符,分割字符串。

函数声明:char* myStrstr(char* str1, char* str2)

注意点:

  • sep参数是个以\0字符串,定义了用作分隔符的字符集合。
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用 \0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回NULL 指针。

文字苍白,代码为例,我们来看下面的代码例子:
在这里插入图片描述
这样写虽然能够完成将字符串分割的功能,但是如果arr很长、分割字符很多时,就变得非常麻烦,下面介绍一种便捷的写法:
在这里插入图片描述
函数使用相关细节:

有一次我很好奇,把分割字符集写成空字符串会怎样,我的猜想是会直接返回NULL, 但实际上不是。
在这里插入图片描述
函数参数sep写成字符串的形式之后容易下意识的忽略掉了字符串中的\0,看似没有分割字符,但是其实还有\0。例子中以一个空字符串作为字符集按道理说第一次调用函数就应该返回NULL输出应该全部都是空,但实际上第二次之后才是全为空。
另外,在查证过程中,发现了一片对strtok函数研究颇深的的博客,想了解更多的可以去看看。
推荐:《到处是“坑”的strtok()—解读strtok()的隐含特性》

错误信息报告

strerror

函数声明:char * strerror ( int errnum );

功能介绍:

  • C语言中定义了一个全局变量errno,每当有程序错误发生就会把errno修改为错误信息的对应信息码。
  • 该函数的作用就是获取信息码对应的错误信息,错误信息为一个字符串。
  • 返回的指针指向静态分配的字符串,程序不应修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。
  • strerror 生成的错误字符串可能特定于每个系统和库实现。

以下代码为是使用for循环测试数字0~9所代表的错误信息是什么:
在这里插入图片描述

除了strerror外,库里面还有一个功能作用相似的函数perror

使用该函数需要包含头文件stdio.h

函数声明:void perror ( const char * str );

功能介绍:解释全局变量errno指代的错误信息并将其打印;参数为一个用户自定义的字符串,能够添加辅助性的文字提示
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值