C字符串库函数(一)【自用】

本文介绍了C语言中的几个关键字符串库函数:strlen()、strcpy()、strcat()、strcmp()和strstr()。详细阐述了每个函数的作用、使用注意事项,并提供了函数模拟实现的多种方法。例如,strlen()返回字符串的长度,不包含'';strcpy()用于字符串拷贝,要求目标空间足够大且可变;strcat()追加字符串,要求目标字符串以''结束;strcmp()比较字符串,返回值取决于ASCII码值;strstr()查找子串,返回子串首次出现的地址。

1. strlen()

size_t strlen ( const char* str );

  • 函数返回的是字符串不含\0的字符个数
  • 参数指向的字符串必须要以\0结束
  • 函数的返回类型是size_t,无符号整型,不是int

🍀strlen()返回不带’\0’的字符个数

char str[] = "abcde";
int num = strlen(str);

num的值是5。

其实str数组的组成是这样的。它有6个元素,最后一个元素'\0'我们看不到,而strlen也不会计算这个'\0'

请添加图片描述

🍀参数指向的字符串必须以’\0’结束

不含'\0'会出错的
请添加图片描述

🍀函数的返回类型是size_t,不是int

其中第三点是一个易错点。strlen返回的起始不是int型,而是无符号整型unsigned long

在我的编译器中可以看到,这段程序可以运行起来,但是有报错,不能用%d,而是%lu

平常的应用中可能无伤大雅,但是我们来看这个例子

int main()
{    
    if(strlen("abc") - strlen("abcdef") > 0)
    {
        printf(">\n");
    }
    else
    {
        printf("<=\n");
    }
    return 0;
}

"abc"是3个字符,"abcdef"是6个字符,3-6 = -3,应该输出<=

然而实际上呢

)

因为strlen(“abc”)返回的是 无符号的3

strlen(“abcedf”)返回的是 无符号的6

最后的结果-3如果按照无符号数来算将是一个很大的数,else语句永远不会被执行

这里我们就要改进一下,进行强制类型转化才能得到想要的

🍀模拟strlen()函数

🍎 非递归方法

size_t my_strlen(const char* str)
{
  int count = 0;
  while(*str != '\0')
  {
    count++;
    str++;
  }
  return count;
}

🍎 递归方法

size_t my_strlen(const char* str)
{
  while(*str != '\0')
  {
    return my_strlen(str + 1) + 1;
  }
  return 0;
}

🍎 指针-指针

size_t my_strlen(const char* str)
{
  char* start = str;
  while(*str != '\0')
  {
    str++;
  }
  return (str-start)/sizeof(char);
}

2. strcpy()

char* strcpy( char* destination , const char* source );

  • 将source指向空间中包括’\0’在内的字符串拷贝到destination所指空间中
  • 源字符串必须以'\0'结束
  • 会将源字符串中的'\0'拷贝到目标空间
  • 目标空间必须足够大,以确保能存放源字符串
  • 目标空间必须可变
  • 返回目标空间的起始地址

🍀源字符串必须以'\0'结束

int main()
{    
    char arr1[20] = {0};
    char arr2[] = {'a','b','c'};//应改为{'a','b','c','\0'}
    strcpy(arr1,arr2);
    return 0;
}

arr2没有'\0',这段代码会让程序崩溃

🍀会将源字符串中的'\0'拷贝到目标空间

int main()
{
    char arr1[20] = "XXXXXXXX";
    char arr2[] = "abcde";
    strcpy(arr1,arr2);
    printf("%s\n",arr1);
}

比如这段代码,最开始arr1和arr2数组是这样的

而strcpy语句执行后

可以看到’\0’也被拷贝进了arr1里

🍀目标空间必须足够大

arr1不够大,报错力~。因为arr2有4个元素,arr1[2]是肯定不够的

在有的编译器中(比如vs),目标空间如果没有源字符串空间大也是能强行塞进去的,不过程序仍然是崩溃的,因为arr1的栈空间被破坏了,所以作为程序员,我们必须让目标空间足够大!

🍀目标空间必须可变

int main()
{    
    char* arr1 = "Atsuki";
    char arr2[] = {'a','b','c','\0'};
    strcpy(arr1,arr2);
    printf("%s\n", arr1);
    return 0;
}

这样一段代码程序也是崩溃的,因为arr1是字符指针,字符指针指向的是常量字符串,不可变~

🍀模拟strcpy()函数

🍎 常规方法

//返回类型是char*,因为函数返回值是目标空间起始地址
char* my_strcpy(char* dest, const char* src)
{
  char* ret = dest;//保存目标空间的起始地址作返回值,因为后面dest就变了
  while(*src != '\0')
  {
    *dest = *src;
    dest++;
    src++;
  }
  *dest = *src;//当*src为'\0'时,循环已经结束,'\0'还未拷贝过去,因此有了这一句
  return ret;
}

🍎 改进

char* my_strcpy(char* dest,const char* src)
{
  char* ret = dest;
  while(*dest++ = *src++)
  {
    ;
  }
  return ret;
}
//这段代码妙在循环条件,改进前'\0'是在循环外赋值的,这个方法直接达到了循环完成所有拷贝
//赋值表达式的值是右侧表达式的值
//当*src == '\0'时,*dest = *src这个表达式的值为0,循环结束

3. strcat()

char* strcat ( char* destination, const char* source) ;

  • 在目标字符串后追加源字符串的拷贝。目标字符串的'\0'被源字符串的第一个字符覆盖,形成的新字符串的末尾包括'\0'
  • 源字符串必须以'\0'结束
  • 目标空间必须足够大,能容纳下源字符串的内容
  • 目标空间必须可修改

🍀strcat()从目标字符串'\0'处开始追加,所以源字符串里必须有'\0'

最后arr1里A覆盖了原本的'\0',但是这个数组放不下拼接后的数组,所以目标空间一定要主动设定到足够大

如果我们让字符串里本来就有'\0'呢?

源字符串的拷贝从第一个\0就开始插入了。

🍀模拟strcat()函数

char* my_strcat(char* dest, char* src)
{
    assert(dest && src);
    char* ret = dest;
    //找目标空间的'\0'
    while(*dest )
    {
        dest ++;
    }
    //拷贝
    while((*dest++ = *src++))
    {
        ;
    }
    return ret;
}

4. strcmp()

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

  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字

🍀strcmp函数的比较原理

strcmp函数比较的不是字符串长度,而是比较字符串对应位置上的字符的大小(ascii码值),如果相同,就比较下一对,直到不同或者都遇到\0

ret1,一直比较到arr1的a与arr2的'\0'比较,最终得出arr1大,正数

ret2,比较到arr2的s和arr3的a,最终得出arr2大,正数

ret3,和ret2一样, 但是ts小,所以得出一个负数

🍀strcmp的返回值为>0,<0或0,具体的值视编译器而定

我用的sublime返回的就是两个字符ascii码相减的值

而如果是vs就会返回1,-1

🍀模拟strcmp函数

int my_strcmp(const char* s1, const char* s2)
{
    assert(s1 && s2);
    while(*s1 == *s2)
    {
        if(*s1 == '\0')
        {
            return 0;//相等
        }
        s1++;
        s2++;
    }
    return *s1 - *s2;
}

🍀字符串直接用>. <比较,比较的是什么?

是地址

char arr1[] = "abcde";
char arr2[] = "abc";
if(arr1 < arr2)
{
  //数组名是数组首元素地址,所以这段代码比较的是两个数组的地址
}
if("abc" < "abcde")
{
  //字符串也有自己的地址,是字符串首字母的地址
}

5. strstr()

char* strstr( const char* str1, const char* str2 );

  • 判断str2是不是str1的子串
  • 返回str2第一次出现在str1中的首字母地址,如果str2不是str1的子串返回空指针
char str1[] = "abcdefgcdeg";
char str2[] = "cde";
char* ret = strstr(str1,str2);

比如这段代码,str1中有两个"cde",strstr返回的就是第一次出现的cde的c的地址

请添加图片描述
看这个结果。因为ret是第一个cde的首字母地址,所以%s顺着这个地址把它后面的字符都打印出来了。

🍀模拟strstr函数

char* my_strstr(char* str1,char* str2)
{
    assert(str1 && str2);

    char* s1 = str1;
    char* s2 = str2;

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

        while(*s1 && *s2 && (*s1 == *s2))
        {
            s1++;
            s2++;
        }
        if(*s2 == '\0')
        {
            return cur;
        }
        cur++;
    }
    return NULL;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值