C语言学习笔记(浙大翁恺版)第十周 字符串函数(2)

目录

10.2.1 单字符的输入输出

putchar

getchar

string.h

strlen

strlen代替函数

strcmp

strcmp代替函数

strcpy

strcpy代替函数

strcat

安全版本

strchr

strstr


10.2.1 单字符的输入输出

putchar

int putchar(int c); 输出的不是char类型而是int类型,但是不能接收4字符

向标准输出写一个字符(在标准输出里输出一个字符)

返回写了几个字符,EOF(-1)表示写失败,返回1为正常

getchar

int getchar(void);

从标准输入读入一个字符

返回其读到的字符。返回类型也是int,这是为了返回EOF(-1),来表示输入结束。我们来解释一下

#include<stdio.h>
int main(int argc,char const *argv[])
{
    int ch;
    
    while((ch = getchar()) != EOF)
    {
        putchar(ch);
    }

    printf("EOF\n");
    return 0;
}

 (Mac上是CtrlD,而Windows是CtrlZ)

而既然getchar只读一个字符,我们输入一个字符之后它并没有自动终止读入,而是在用户按下回车之后才终止。

原因如图: 

在我们的程序和我们输入与屏幕之间有shell代为处理数据,用户输入之后,shell会将输入的内容填入缓冲区,然后进行行编辑处理。shell把缓冲区里的东西输进程序。

输入的数据会在不同程序下对其进行不同的处理。其中的CtrlC会让shell直接关闭程序,而CtrlD在shell收到后,在末尾填一个-1,程序读到-1,程序就正常结束了。遇到\0,则等待用户继续输入,因此程序并不会停止读入。

string.h

strlen

size_t strlen (const char *s):可以返回s的字符串长度(不算结尾的0,sizeof会计算0)。

里边的const表示这不会对字符串进行任何更改。

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

int main(int argv,char const *argv[])
{
    char a[] = "Hello";
    printf("strlen = %lu\n",strlen(s));
    printf("sizeof = %lu\n",sizeof(s));
    
    return 0;
}

 这个函数是怎么实现的?我们来写一个替代函数试一下!

strlen代替函数

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


//遍历数组,当数组元素为0时表示结束,用下标代替元素个数,得到字符串长度
size_t mylen(const char* s)
{
    int index = 0;
    while(s[index] != '\0')
    {    
        index++;
    }
    return index;
}

int main(int argv,char const *argv[])
{
    char a[] = "Hello";
    printf("strlen = %lu\n",mylen(a));
    printf("sizeof = %lu\n",sizeof(a));
    
    return 0;
}

 结果和上面一样,证明了这样做可以替代strlen。

strcmp

  • int strcmp(const char *s1,const char *s2); 用来比较两个字符串,返回值有三种情况
  • 0:两字符串相等,s1==s2
  • 1:前者较大,s1>s2
  • -1:后者较大,s1<s2
#include<stdio.h>
#include<string.h>

int main(int argv,char const *argv[])
{
    char s1[] = "abc";
    char s2[] = "abc";
    
    printf("strlen = %d\n",strcmp(s1,s2));
    
    
    return 0;
}

运行结果为0。但这个比较式不能直接用于if的条件判断,如:

if( strcmp(s1,s2) )
{ }

 因为if是当条件满足时才运行,返回一个0会直接跳过。因此需要这样做:

if( strcmp(s1,s2)==0 )
{ }

那么是否能用这样的方式表示两个字符串相等呢?

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

int main(int argv,char const *argv[])
{
    char s1[] = "abc";
    char s2[] = "abc";

    printf("%d\n",s1==s2);//可行吗?


    printf("strlen = %d\n",strcmp(s1,s2));
    
    
    return 0;
}

当然不行,要知道,放在格式化输出符后面比较数组,实质是比较两个数组的地址是否相同。而两个数组的地址一定是不相同的。

我们再通过一些例子来看看这个函数的实质。

1.

char s1[] = "abc";
char s2[] = "bbc";

比较结果为-1,表示后者大于前者。 也就表示a比b小,与ASCII码中的大小顺序相对应。

2.

char s1[] = "abc";
char s2[] = "Abc";

比较结果为32,表示前者大于后者。也就是a比A大32,ASCII码 'a'-'A'=32。对应正确。

3.

char s1[] = "abc";
char s2[] = "abc ";//(多一个空格)

比较结果为-32,表示后者大于前者。这是为什么?我们知道空格的ASCII码为32,和这个有关吗?

当然是有的。其原理如图:

 因此其原理是:将两个数组中第n个元素ASCII码相减(第一个数组减第二个数组),返回结果。

所以我们可以写出替代函数:

strcmp代替函数

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

//遍历两个数组,下标相同的元素相减并返回得到的结果。
int mycmp(const char* s1,const char* s2)
{
    int idx = 0;
    while(1)
    {
//当二者不相等时,跳出循环,直接返回差值。
        if(s1[idx] != s2[idx])
        {
            break;
        }
//当二者相等,但s1已经结束,也要跳出循环,直接返回差值
        else if(s1[idx] == '\0')
        {
            break;
        }
//相等的情况下,idx递增,继续判断
        idx++;
    }


    return s1[idx] - s2[idx];
}




int main(int argv,char const *argv[])
{
    char s1[] = "abc";
    char s2[] = "abc";

    printf("%d\n",s1==s2);//可行吗?


    printf("strlen = %d\n",mycmp(s1,s2));
    
    
    return 0;
}

改进一下程序:

int mycmp(const char* s1,const char* s2)
{
    int idx = 0;
//当比较的元素不相等,且s1没结束,就不退出循环,将之前的条件反过来并且合并
    while( s1[idx] == s2[idx] && s1[idx] != '\0')
    {
       idx++;
    }


    return s1[idx] - s2[idx];
}

甚至可以扔掉额外参数

int mycmp(const char* s1,const char* s2)
{
//条件是一样的,舍弃掉额外变量之后,需要用指针来控制遍历和比较。
//而且传进来的就是指针,可以直接用
    while( *s1 == *s2 && *s1 != '\0')
    {
        s1++;
        s2++;
    }


    return *s1 - *s2;
}

因此在处理字符串时,要么把它当作数组,把参数作为下标,要么就把它当作指针,移动指针实现自增,指针加减实现返回值。

strcpy

  • char* strcpy(char *resitrict dst, const char *restrict src); 作用是将src的字符串拷贝到dst
    • 可以看到参数表里,目的地址在前而源地址在后
  • restrict表示src和dst不重叠(C99)
  • 返回dst的目的:让strcpy能够再参与别的运算,链接代码

 

用strcpy复制一个字符串:

//因为动态分配内存不清楚要复制的内容大小,用一个strlen函数获取
//因为strlen得到的是内容长度,不包含结尾的0,所以再加一就可以得到正确的长度
char* dst = (char*)malloc(strlen(src)+1);

strcpy(dst,src);

strcpy代替函数

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

//复制字符串,需要将固定位置的源地址复制到定义的位置
char* mycpy(char* dst,const char* src)
{
    int idx=0;
//挨个将源地址的每个字符复制到新地址,因此当源地址的值为0时退出循环

//条件也可以写成 while(src[idx]),最后一位是0符合退出条件,自动退出
    while(src[idx] != '\0')
    {
        dst[idx] = src[idx];
        idx++;
    }
//当源地址复制到最后一位就已经退出,0还没有复制过去,因此退出后再补上
    dst[idx] = '\0';
    return dst;
}

int main(int argc,char const *argv[])
{
    char s1[] = "abc";
    char s2[] = "abc";
    
    strcpy(s1,s2);
    return 0;
}

 指针版(仅函数部分改动)

char* mycpy(char* dst,const char* src)
{
    char* rst = *dst;

//一样,也可以写成while(*src)
//甚至再简化一下,整个循环只剩 while( *dst++ = *src++ )再加一个分号

    while(*src != '\0')
    {
        *dst = *src;
        *dst++;
        *src++;
    }
    *dst = '\0';

//此时不能再返回dst,因为dst在过程中累加,值变化了
    return rst;
}

strcat

  • char* strcat(char* restrict s1, const char *restrict s2);
  • 作用:把s2拷贝到s1后面,接成一个长的字符串
  • 返回s1

 strcpy和strcat都有安全问题:目的地数组都可能会空间不足

安全版本

//n代表了能拷贝过去的最大字符数量限制
char* strncpy(char* restrict dst, const char *restrict src,size_t n);
//n代表了能连上的最大字符数量限制
char* strncat(char* restrict s1, const char *restrict s2,size_t n);
//n可以限制比较的个数,n代表前n个
int strncmp(const char *s1,const char *s2,size_t n);

 

strchr

  • char *strchr(const char*s, int c); 用来在字符串里找字符,返回一个指针指向你找的字符(就是参数表里的c)
  • char *strrchr(const char *s,int c); 用来从右往左找字符
  • 返回NULL表示没找到

举个例子,在hello中寻找l,l出现了两次,如何分别寻找第一个和第二个?

我们先寻找一下‘l’这个字符

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

int main()
{
    char s[] = "hello";
    char *p  = strchr(s,'l');
    
    printf("%s\n",p);
    
    return 0;
}

将*p打印出来,结果是‘llo’,依据定义可以知道,返回的指针p指向了l,所以此时输出p会将l之后的字符打印出来

因此想要查找第二个l,只需要让指针移动一位

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

int main()
{
    char s[] = "hello";
    char *p  = strchr(s,'l');
//这里是在查找出来的llo中继续查找l,但是指定字符串为p+1,也就是在lo中查找 
    p = strchr(p+1,'l');

    printf("%s\n",p);
    
    return 0;
}

此时输出的就是lo 

那么查找到需要的字符串之后,想要将其复制到别处,该如何做?

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

int main(int argc,char const *argv[])
{
    char s[] = "hello";
    char *p = strchr(s,'l');
//新建一个t指针,把查找到的l之后的内容赋给t
    char *t = (char*)malloc(strlen(p)+1);
//从p复制到t
    strcpy(t,p);
    printf("%s\n",t);

    free(t);
//将malloc动态分配来的指针释放,free函数包含在stdlib中
    return 0;
}

如果我们想要得到查找到的字符之前的一段,该如何做?这里有一个小技巧:先把查找到的字符位置处更换为0,此时输出这个字符串,就得到需要的部分,然后再将其换回原来的字符,其他功能仍然可以正常运行,数组改动前后也没有变化。

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

int main(int argc,char const *argv[])
{
    char s[] = "hello";
    char *p = strchr(s,'l');

//建立一个临时变量c,指针指向了'l',赋给c,所以c='l'
    char c = *p;
    *p = '\0';
//*p = '0'之后,此时s这个字符串只有he'\0',将其拷贝到t再输出t就得到he

    char *t = (char*)malloc(strlen(p)+1);
    strcpy(t,p);
    *p = 'c';
//将p还原回去,打印t
    printf("%s\n",t);
    free(t);

    return 0;
}

 

strstr

  • char *strstr(const char *s1,const char *s2);作用是在字符串里找字符串
  • char *strcasestr(const char *s1,const char *s2); 和上面一样,但是忽略大小写


版权声明:本文为m0_56661338原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。