C语言----字符函数和字符串函数

在编程的过程中,我们要经常处理字符和字符串,为了方便操作字符和字符串,c语言标准库中提供的一系列库函数,接下来我们就开始学习与认识他们

1.字符分类函数

c语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的

这些函数的使用需要包含一个头文件:ctype.h

用islower举例

格式:int islower (int c)

为什么后面的是int c--用整型接受的,因为传过来的是字符或者对应的ASCII值

那么我们就用整型接受

返回值也是int

如果这个字符是小写字母的话,那么返回值就是一个非0数字

如果这个字符是一个大写字符的话,那么返回值就是0

总之:如果括号内的不是小写字母,那么这个函数就会返回一个0

是小写字母就返回一个非0数字

int main()
{
    int ret1 = islower('b');//2
    printf("%d\n", ret1);

    int ret2 = islower('A');//0
    printf("%d\n", ret2);


    int ret3 = islower('0');//字符0不是字母
    printf("%d\n", ret3);
    return 0;
}
int main()
{
    int ret1 = isdigit('A');//不是数字字符就返回0
    printf("%d\n", ret1);

    int ret2 = isxdigit('A');//A是16进制的字符--返回的就是非0值
    printf("%d\n", ret2);

    return 0;
}
//写一个代码,将字符串中的小写字母转换成大写字母,其他字符不变
int main()
{
    char arr[] = "I am a student";//末尾隐藏\0
    //需要遍历字符串--通过下标进行访问
    int i = 0;
    while (arr[i] != '\0')//不等于'\0'就一直遍历数组,直到遇到\0就停止遍历数组
    {
        //判断这个数组内的字符是不是小写字母
        if (islower(arr[i]))
            //如果不是小写字母,返回值就是0,是小写字母返回值就是非0数字,
            //这个函数是用来判断输入的字符是不是小写字母
        {
            //小写字母转大写字母的方法:
            //小写字母的ASCII-32=对应放入大写字母ASCII
            arr[i] = arr[i] - 32;
        }

        i++;//没有遇到'\0'就i++

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


//小写字母转大写字母出了这个toupper函数,还可以直接通过小写字母的ASCII-32就可以进行转换了

//那么我们对上面的问题进行改造一下
int main()
{
    char arr[] = "I am a student";//末尾隐藏\0
    //需要遍历字符串--通过下标进行访问
    int i = 0;
    while (arr[i] != '\0')//不等于'\0'就一直遍历数组,直到遇到\0就停止遍历数组
    {
        //判断这个数组内的字符是不是小写字母
        if (islower(arr[i]))
            //如果不是小写字母,返回值就是0,是小写字母返回值就是非0数字,
            //这个函数是用来判断输入的字符是不是小写字母
        {
            //小写字母转大写字母的方法:

            arr[i] = toupper(arr[i]);//小写字母通过toupper这个函数转换为大写字母了
        }

        i++;//没有遇到'\0'就i++

    }
    printf("%s", arr);
    return 0;
}
//对于这个循环还有一种判断是不是小写字母的方法
//if(arr[i]>='a'&&arr[i]<='z')
//在这个区间内的就都是小写字母了

这些字符分类函数主要是进行判断

2.字符转换函数

c语言提供两个字符转换函数

int tolower(int c);//将参数传进去的大写字母转小写

int toupper(int c);//将参数传进去的小写字母转大写

//int main()
//{
//    char ch = toupper('a');
//    printf("%c\n", ch);//打印出来的就是A,大写的A
//
//    //如果传进来的大写字母,那么输出的还是大写字母,不做判断
//    ch = tolower('A');
//    printf("%c\n", ch);//将大写字母转换为小写字母
//
//
//    return 0;
//}
//小写字母转大写字母出了这个toupper函数,还可以直接通过小写字母的ASCII-32就可以进行转换了

//那么我们对上面的问题进行改造一下
int main()
{
    char arr[] = "I am a student";//末尾隐藏\0
    //需要遍历字符串--通过下标进行访问
    int i = 0;
    while (arr[i] != '\0')//不等于'\0'就一直遍历数组,直到遇到\0就停止遍历数组
    {
        //判断这个数组内的字符是不是小写字母
        if (islower(arr[i]))
            //如果不是小写字母,返回值就是0,是小写字母返回值就是非0数字,
            //这个函数是用来判断输入的字符是不是小写字母
        {
            //小写字母转大写字母的方法:

            arr[i] = toupper(arr[i]);//小写字母通过toupper这个函数转换为大写字母了
        }

        i++;//没有遇到'\0'就i++

    }
    printf("%s", arr);
    return 0;
}
//对于这个循环还有一种判断是不是小写字母的方法
//if(arr[i]>='a'&&arr[i]<='z')
//在这个区间内的就都是小写字母了

3.strlen的使用和模拟实现

strlen格式:size_t strlen(const char * str);

统计的是\0之前的个数

strlen的返回值是size_t类型的

//
//int main()
//{
//    char arr1[] = "abcdef";
//    size_t len=strlen(arr1);//输出的数据是6
//    printf("%zd\n", len);
//
//
//    char arr2[] = { 'a','b','c','d','e','f' };//这里面是没有\0的
//    size_t len1 = strlen(arr2);
//    printf("%zd\n", len1);//输出数据是17
//    return 0;
//}
//注意函数的返回值是sizeof_t,是无符号的



int main()
{
    if (strlen("abc") - strlen("abcdef") > 0)
//另一种写法:if(strlen("abc")>strlen("abcdef"))

    {// size_t类型      size_t类型
        printf(">\n");
    }
    else
    {
        printf("<=\n");
    }
    return 0;
}
//输出的结果是>,为什么呢?
//当两个无符号整型进行计算的时候,算出来的结果还是无符号整型的结果
//如果想得到-3的话,那么我们需要将strlen("abc")和strlen("abcdef")强制类型转换为int 类型的数据

strlen的,模拟实现的三种方法

1.计数器的方法

2.指针~指针

size_t my_strlen(const char*str)//返回类型是sizeof_t,因为传过来的是字符串首元素的地址,那么我们用一个字符指针进行接收
{//前面加上const防止被修改
    //利用传过来的首元素的地址,我们遍历数组,统计\0之前的元素个数
    //只要不是\0就统计一个数字
    int count = 0;
    //因为str是指针变量,为了防止str是空指针,我们要进行断言一下
    assert(str != NULL);//如果str为空指针就报错

    while (*str != '\0')
    {
        count++;
        str++;//使指针++,向后走一步
    }
    return count;



}
int main()
{
    char arr[] = "abcdef";
    size_t len = my_strlen(arr);//自己创建一个函数来实现strlen函数
    //传过去一个数组名,字符串首元素的地址
    printf("%zd\n", len);
    return 0;
}
//第二种方法:指针-指针
//两个指针相减就能得到两个指针之间的元素个数了
size_t my_strlen(const char* str)
{
    char* start = str;
    assert(str != NULL);
    while (*str != '\0')
    {
        str++;//走到'\0'前面就停止了,那么到最后str的值是最后一个元素的地址
    }

    return str - start;//因为一开始将第一个元素的地址赋值给start了,那么现在指针相减,得到的就是两个指针之间的元素的个数了

}



int main()
{
    char arr[] = "abcdef";
    size_t len = my_strlen(arr);//自己创建一个函数来实现strlen函数
    //传过去一个数组名,字符串首元素的地址
    printf("%zd\n", len);
    return 0;
}
//这种写法可以不创建临时变量

//使用递归来实现
size_t my_strlen(const char* str)
{
    if (*str != '\0')
    { 
        return 1 + my_strlen(str + 1);
    }
    else
    {
        return 0;
    }
}

int main()
{
    char arr[] = "abcdef";
    size_t len = my_strlen(arr);//自己创建一个函数来实现strlen函数
    //传过去一个数组名,字符串首元素的地址
    printf("%zd\n", len);
    return 0;
}

4.strcpy的使用和模拟实现

功能:拷贝字符串

strcpy---cpoy string

strcpy在拷贝的过程中会将arr1里面的内容包括\0拷贝到arr2里面去

int main()
{
    char arr1[] = "hello world";
    char arr2[20] = { 0 };

    //现在想把arr1里面的hello world放到arr2里面去
    //我们可以用strcpy来实现
    strcpy(arr2, arr1);
    printf("%s\n", arr2);


    return 0;
}

注意注意:

被拷贝的字符串一定要包含'\0',保证strcpy遇到\0就能停止拷贝

1.源头的字符串中必须包含\0,没有\0,strcpy是不能结束的

2.目标空间必须足够大,以确保能存放原字符串

3.目标空间必须是可以修改的

int main()
{
    char arr1[] = "hello world";
    char* p = "xxxxxxxxxxxxxx";//常量字符串--不能修改

    strcpy(p, arr1);//会报错的
    return 0;
}
//模拟实现拷贝
char*my_strcpy(char*dest,const char*src)//传过来的是首元素的地址,返回值是目标空间的起始地址,所以就是char*
{
    //*dest指向的是arr2第
    // 一个元素,*src指向的是arr1第一个元素
    //保证这两个指针不为空指针
    /*assert(src != NULL);
    assert(dest != NULL);*/
    //另一种写法:
    assert(dest && src);//如果dest为空指针,那么括号内就为0,就是假的,就会报错

    char* ret = dest;//将dest存起来
    while (*src != '\0')
    {
        *dest=*src;
        //进行加加操作,换下一个字符和下一个位置进行交换
        src++;
        dest++;
    }//当这个循环结束的时候,\0还没有被拷贝进去
    //最后的时候,src已经++成为\0了,那么现在再次赋值就能将\0拷贝进去了
    *dest =* src;//这里处理的就是\0

    return ret;//直接返回目标空间的起始地址

}
int main()
{
    char arr1[] = "abcdef";//末尾隐藏\0
    char arr2[20] = { 0 };
    //1.直接打印arr2
    my_strcpy(arr2, arr1);
    printf("%s\n", arr2);


    //2.接收返回值
    char* p = my_strcpy(arr2, arr1);
    printf("%s\n", p);
    //直接将返回值放到打印里面也可以

    printf("%s\n", my_strcpy(arr2, arr1));
    return 0;
}


//这个函数返回的是char*,为的是实现链式访问

//strcpy函数返回的是字符串拷贝成功过后,目标空间的起始地址,
//返回值就是arr2的首元素的地址



//总结:
//将arr1拷贝到arr2后,我们可以通过三种方法直接打印arr2
//一种就是直接打印arr2
//还有一种就是根据这个拷贝函数的返回值进行打印
//返回值是一个地址,在函数的一开始我们就将目标函数赋值给另一个临时指针,那么这个临时指针就指向了arr2
//在拷贝过后,我们直接将这个临时指针返回,所以在函数的开头我们用char*
//在返回了临时指针后,我们在主函数就用再创建一个临时指针变量进行返回值的接受,
//因为这个临时指针变量指向的是arr2的其实元素,那么我们就可以用这个临时指针变量直接打印arr2
//有了字符串起始元素的地址,我们就能打印这个字符串了
//对函数部分进行改进,
//思考:能不能将拷贝\0和前面的字符串放在一起呢?

char*my_strcpy(char*dest,const char*src)
{

    assert(dest && src);

    char* ret = dest;
    while (*dest++ = *src++)//因为这里是后置++,所以先带进去数据进行解引用,再进行++
    {
     //arr1里面的字符通过*dest++ = *src++这个代码一个个拷贝到arr2里面去了
      //在最后,*src是\0拷贝过去了,然后因为while循环里面是\0,所以循环停止了
      // 但是\0拷贝到dest里面了 
        ;

    }//这个循环拷贝过去之后判断表达式的值
    //因为是后置++,所以延后产生

    return ret;

}

int main()
{
    char arr1[] = "abcdef";
    char arr2[20] = { 0 };

    my_strcpy(arr2, arr1);
    printf("%s\n", arr2);



    char* p = my_strcpy(arr2, arr1);
    printf("%s\n", p);


    printf("%s\n", my_strcpy(arr2, arr1));
    return 0;
}

5.strcat的使用和模拟实现

原字符串必须有\0结尾

目标字符串也得有\0,否则没办法知道从哪里开始

目标空间必须足够大,能容纳下字符串的内容

目标空间是可以进行修改的,而不是常量不能进行修改

int main()
{
    char arr1[20] = "hello ";
    char arr2[] = "world";

    //如何将arr2里面的字符串追加在arr1后面呢?
    strcat(arr1, arr2);//字符串追加
    printf("%s\n", arr1);
    return 0;
}

//这个函数是如何实现追加呢?
//先找到目标函数的末尾\0的位置
//再将原字符串拷贝过来

//要求目标字符串末尾有\0,因为后面要将原字符串拷贝过去,所以原字符串也要有\0
//要返回目标空间的起始地址
//目标空间是可修改的,但是源头需要进行限制,不能被修改
char* my_strcat(char* dest,const char* src)//返回类型为char*类型的
//dest指向的是arr1的首元素的地址
//src指向的是arr2首元素的地址
{
    char* ret = dest;//将起始位置存起来
    assert(dest && src);//进行断言,防止空指针
    //1.找目标空间的\0
    while (*dest != '\0')
    {
        dest++;
    }//while循环找到\0就停下来
    //2.拷贝----我们在字符串追加的时候,我们要将目标字符串末尾的\0覆盖掉
    while (*dest++ = *src++)//这个代码是进行字符串间的拷贝的,因为上面已经找到\0了,已经停止循环了,并且dest指向了arr1末尾的\0

    {
        ;//写个分号就行了,循环得有一个循环语句
    }

    return ret;//直接返回arr1的起始地址
}
int main()
{
    char arr1[20] = "hello ";
    char arr2[] = "world";

    //如何将arr2里面的字符串追加在arr1后面呢?
    my_strcat(arr1, arr2);//字符串追加
    printf("%s\n", arr1);
    return 0;
}

//这个函数是如何实现追加呢?
//先找到目标函数的末尾\0的位置
//再将原字符串拷贝过来

//要求目标字符串末尾有\0,因为后面要将原字符串拷贝过去,所以原字符串也要有\0



//总结: 我们应该先找到目标字符串末尾\0的位置,这样我们才好追加,
//切记:追加的时候我们要将目标字符串末尾的\0覆盖掉


//在这个追加函数我们用了两个循环,第一个循环是找到\0,第二个循环是将原字符串拷贝到目标字符串的后面去

我们是不能对一个数组自己进行追加的

6.strcmp的使用和模拟实现

strcmp是用来比较两个字符串的

返回值是int

用返回值来比较这两个字符串大小

比较的是对应位置上的字符,如果对应位置字符相等就比较下一对字符

比较的不是字符串的长度,而是对应位置上字符的大小

如果两个字符串相等,返回值就是0

如果前面的字符串大于后面的字符串,那么就会返回一个大于0的数字

如果是后面的字符串大于前面的字符,前面的字符小,就返回一个小于 0的数字

int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abq";
    int ret=strcmp(arr1, arr2);
    //printf("%d\n", ret);//输出的结果是-1,就说明arr1<arr2
    if (ret == 1)//仅仅在vs平台可以写返回值为1,其他的平台就不知道返回值是多少了
    {
        printf(">" );
    }
    else
    {
        printf("<=");
    }
    return 0;
}
//
//int main()
//{
//    char arr1[] = "abcdef";
//    char arr2[] = "abq";
//    int ret=strcmp(arr1, arr2);
//    //printf("%d\n", ret);//输出的结果是-1,就说明arr1<arr2
//    if (ret == 1)//仅仅在vs平台可以写返回值为1,其他的平台就不知道返回值是多少了
//    {
//        printf(">" );
//    }
//    else
//    {
//        printf("<=");
//    }
//    return 0;
//}


//int my_strcmp(const char*str1,const char*str2)//返回值是Int,并且加上const进行限制
//{
//    //我们一对一对字符进行比较
//    assert(str1 && str2);
//    while (*str1 == *str2)
//    {
//        if (*str1 == '\0')//如果str1在上一轮循环++到\0之后,就说明这两个字符串已经完全相等了
//        {
//            return 0;//两个字符串完全相等
//        }
//        str1++;
//        str2++;
//    }//两个字符相等我们就找下一对字符进行比较
//    if (*str1 > *str2)
//    {
//        return 1;
//    }
//    else//*str1 < *str2
//    {
//        return -1;
//    }
//}

//另一种写法
int my_strcmp(const char* str1, const char* str2)//返回值是Int,并且加上const进行限制
{
    //我们一对一对字符进行比较
    assert(str1 && str2);
    while (*str1 == *str2)
    {
        if (*str1 == '\0')//如果str1在上一轮循环++到\0之后,就说明这两个字符串已经完全相等了
        {
            return 0;//两个字符串完全相等
        }
        str1++;
        str2++;
    }//两个字符相等我们就找下一对字符进行比较
    return (*str1 - *str2);//直接返回他们相减的值

}
int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcdef";
    int ret=my_strcmp(arr1, arr2);
    printf("%d\n", ret);
    return 0;
}

7.strncpy函数的使用

int main()
{
    char arr1[20] = "abcdef";
    char arr2[20] = { 0 };
    strncpy(arr2, arr1, 3);
    printf("%s", arr2);//打印的结果就是abc,只选择arr1里面的前三位字符进行拷贝
    return 0;
}



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

    strncpy(arr1, arr2, 3);
    printf("%s", arr1);//输出xxxdef
    return 0;
}

拷贝n个字符从原字符串到目标空间

如果原字符串的长度小于n的话,则拷贝完原字符串之后,在目标的后边追加0,直到凑齐n个

8.strncat函数的使用

可以用来给自己追加

在原有的基础上,可以选择性的追加n个字符

9.strncmp函数的使用

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

    int ret=strncmp(arr1, arr2, 3);//比较三个字符
    printf("%d", ret);//现在返回值就是0
    return 0;
}

指定几个字符进行比较久进行几个字符比较

总之来说还是比较方便的

10.strstr的使用和模拟实现

在一个字符串里面找子字符串,在一个字符串里面找另外一个字符串是否出现

如果要找的字符串出现一次以上,那么我们就返回第一次出现的位置

在str1中找str2

如果在str1中没有找到str2的话,就会返回一个空指针NULL

int main()
{
    char arr[] = "abcdefabcdef";
    char* p = "efab";//定义了一个指向字符串常量"efab"的指针p
    char* ret=strstr(arr, p);//返回的是e的地址
    //printf("%s", ret);//打印结果是efabcdef


    if (ret == NULL)
    {
        printf("不存在\n");
    }
    else
    {
        printf("%s\n", ret);
    }
    return 0;
}
char* my_strstr(const char* str1, const char* str2)//我们只是希望在str1中查找str2,并不希望将这两个字符串修改了,所以要加上const
{
    assert(str1 && str2);//保证两个指针不是空指针
    const char* s1 = NULL;//加上const限制住
    const char* s2 = NULL;
    const char* cur = str1;//一开始指向的是str1的起始位置的
    //*cur != '\0'简化如下:当*cur是\0我们就进不去循环了

    if (*str2 == '\0')//特殊情况,假如str2是空指针,那么我们直接返回str1
    {
        return str1;
    }
    while (*cur)//如果等于'\0'的话就说明这个字符串已经找完了
    {//只要*cur不是\0就能一直寻找要找的字符串
        //分别将起始位置赋值给s1和s2
        s1 = cur;
        s2 = str2; 
        //*s1!='\0'&& *s2!= '\0'简化如下,效果还是一样的
        //就是反正你*s1和*s2是\0这个循环就进不去,直接跳出来了
        while(*s1&& *s2&& * s1 == *s2)//判断两个指针指向位置的字符是否相等
        {
            //如果这一对字符相等的情况下我们就往后走判断下一对字符
            s1++;
            s2++;
        }
        if (*s2 == '\0')//说明我们已经在str1里面已经找完了字符串
        {
            return cur;//那么我们就直接返回str1中我们记录的cur的位置
        }

        cur++;//如果*s1!=*s2的话,就cur++换下一个字符,就跳出这个while循环了
        //再次循环就s1又被重新赋值了,但是s2仍然是被srt2赋值,

        //就是相等与我们在仅仅只是将str1的出发点进行了更换,但是str2的还没变
        //直到能在str1里面找到str2了,就是str2语言\0了,就说明已经在str1里面找到str2了

    //如果*s1不为\0,但是*s2已经是\0了,那么这个while循环我们就跳出来了

    }
    return NULL;//如果cur为\0就是我们已经不可能在str1中找到str2了,那我们直接返回空指针


}
int main()
{
    char arr[] = "abcdefabcdef";
    char* p = "bbs";
    const char* ret=my_strstr(arr, p);



    if (ret == NULL)//根据返回值进行判断str1中是否存在str2
    {
        printf("不存在\n");
    }
    else
    {
        printf("%s\n", ret);
    }
    return 0;
}
//如果在str1里面提前遇到了\0就说明这个字符串已经找完了还没遇到要找的字符串
//但是str2提前遇到\0的话,就说明我们已经找到了要找的字符串了




//总结:
/*
我们在这个模拟函数中,我们最重要的就是创建了一个cur来不断重新定义来找的位置

假如在第一次寻找的过程中,我们没找到,那么cur就进行++操作,然后s1=str2重新赋值,
就是我们将要找的字符串的指针重新定义在首元素,但是cur一直在往后走,
直到s2走到\0,就是说明我们已经在str1内找到str2了

那么如果找到了的话,我们就将cur现在的地址return 回去,就是说明我们在str1中cur处可以找到str2了


*/

我们当前写的strstr函数的实现不是最优的算法

KMP算法---字符串中找字符串---效率更高,但是实现更难

11.strtok函数的使用

charstrtok(charstr,const char *sep)

1.sep参数指向一个字符串,定义了用作分隔符的字符集合

2.第一个参数指定了一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记

3.strtok函数找到str中的下一个标记,并将用\0结尾,返回一个指向这个标记的指针。(注意:strtok会改变被操作符的字符串,所以被strtok函数切分的字符串一般都是临时拷贝的内容并且可以修改)

4.strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,就是第一个分隔符,strtok函数将保存他在字符串中的位置

5.strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置,查找下一个标记。

6.如果字符串中不存在更多的标记,则返回NULL指针,就是说明这个字符串已经被找完了,再没有任何的分隔符了,已经尽数转化为\0了

//int main()
//{
//    char arr[] = "abdjskgb@vsfkv.net";
//    char buf[256] = { 0 };
//    strcpy(buf, arr);//把arr数据拷贝到buf内//abdjskgb\0vsfkv\0net
//    char sep[] = "@.";//char*sep= "@."
//    char* ret=strtok(buf, sep);
//    printf("%s\n", ret);//输出结果就是abdjskgb
//
//    char* ret1 = strtok(NULL, sep);
//    printf("%s\n", ret1);//输出结果就是vsfkv
//
//
//    char* ret2 = strtok(NULL, sep);
//    printf("%s\n", ret2);//输出结果是net
//
//
//    return 0;
//}
//strtok的返回值是buf的第一个标记的指针
//当这个函数返回的是一个空指针的时候,就说明这个函数已经找完了

//上面这种写法必然是错误的,我们必须先知道提供的字符串需要切割几段



int main()
{
    char arr[] = "abdjskgb@vsfkv.net";
    char buf[256] = { 0 };
    strcpy(buf, arr);//把arr数据拷贝到buf内//abdjskgb\0vsfkv\0net
    char *sep = "@.";//分割符
    char* ret = NULL;
    //上个代码的buf只在strtok里面进行一次,后面都是空指针
    //我们利用了for循环的特点,初始化只执行一次

    //就是说只有第一次传的是buf,后面传的都是NULL
    for (ret = strtok(buf, sep); ret != NULL;ret=strtok(NULL,sep))
    {
        printf("%s\n", ret);
    }
    //只要ret不等于NULL这个循环就一直执行
    //strtok(buf, sep)的返回值是第一个切割符前面的字符串的地址,并将其赋值给ret,

    //每次循环都会运行ret=strtok(NULL,sep),将新获得的返回值赋值给ret,然后每次循环就从新位置开始
    return 0;
}


//总结:我们定义一个数组arr,里面带有分隔符
// 再定义一个空数组,将带有分隔符的数组拷贝过来,在后面的过程,我们都是用这个拷贝的数组
// 
// char* ret = NULL;:定义了一个指向分割后子字符串的指针。
//char *sep = "@."; 定义了分隔符字符串,包含 @ 和 .。



/*针对这个循环进行更加详细的解释

ret = strtok(buf, sep)是初始化部分,
在循环开始之前,strtok被调用,使用buf和sep来分割字符串,并返回第一个子字符串的指针
这个指针被赋值给ret,作为循环的起始点

循环条件:ret != NULL   这表示只要strtok返回的指针不是NULL,就能继续执行循环体,
因为strtok在没有更多子字符串可供分割时会返回NULL,所以在没有更多子字符串可供分割时会结束

迭代部分:ret = strtok(NULL, sep)   
在每次循环迭代时,strtok(NULL, sep)被调用,告知strtok继续从上一次的位置继续分割字符串
并返回下一个子字符串的指针,这个指针被赋值给ret,作为下一次循环的起始点


这个循环的条件保证了每次循环迭代都能够正确地从输入字符串中分割出一个子字符串,
并且在没有更多子字符串可供分割时结束循环。

*/


//strtok可以把一个字符串切成一段一段的,每切一次就将起始地址返回去

//每次就直接将分隔符前面的字符串切割下来,并将切割符变为\0,就是\0后面的字符将不进行访问


//函数会找到第一个分隔符,并记住位置,下次找就从这个位置开始找

12.strerror函数的使用

strerror可以将错误对应的错误信息字符的地址返回

strerror 函数可以把参数部分错误码对应的错误信息的字符串地址返回来。

在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头⽂件中说明的,C语⾔程序启动的时候就会使⽤⼀个全局的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表⽰没有错误,当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会将对应的错误码,存放在errno中,⽽⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。

//int main()
//{
//    for (int i = 0; i < 10; i++)
//    {
//        printf("%d:%s\n", i,strerror(i));
//    }
//    return 0;
//}

//0:No error
//1:Operation not permitted
//2 : No such file or directory
//3 : No such process
//4 : Interrupted function call
//5 : Input / output error
//6 : No such device or address
//7 : Arg list too long
//8 : Exec format error
//9 : Bad file descriptor



int main()
{
    //打开文件
    FILE*pf=fopen("date.txt", "r");//r---读,以读文件的形式打开文件,如果文件不存在就是打开失败
    //打开失败就返回一个空指针

    if (pf == NULL)//打开失败
    {
        printf("打开文件失败,原因是%s",strerror(errno));//为了使用errno我们要包含头文件#include <errno.h>
        return 1;//打开失败我们就直接结束进程
    }
    else
    {
        printf("打开成功\n");
        fclose(pf);//关闭文件
        pf = NULL;
    }
    return 0;
}
//打开文件失败,原因是No such file or directory

strerror---将错误码对应的错误信息的字符串的起始地址返回

int main()
{
    //打开文件
    FILE*pf=fopen("date.txt", "r");//r---读,以读文件的形式打开文件,如果文件不存在就是打开失败
    //打开失败就返回一个空指针

    if (pf == NULL)//打开失败
    {
        printf("打开文件失败,原因是:%s\n",strerror(errno));//为了使用errno我们要包含头文件#include <errno.h>
        perror("打开文件失败,原因是");
        return 1;//打开失败我们就直接结束进程

    }
    else
    {
        printf("打开成功

perror--将errno中错误信息直接打印出来

perror函数线打印str指向的字符串,再打印冒号,再打印空格,再打印错误码对应的信息

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多模态融合:CLIP

CLIP的全称是Contrastive Language-Image Pre-Training&#xff0c;中文是对比语言-图像预训练&#xff0c;是一个预训练模型&#xff0c;简称为CLIP。CLIP一共有两个模态&#xff0c;一个是文本模态&#xff0c;一个是视觉模态&#xff0c;分别对应了Text Encoder和Image Enco…

资源不是问题,极空间全自动小雅Alist以及Emby全家桶部署教程,同时实现自动更新

资源不是问题&#xff0c;极空间全自动小雅Alist以及Emby全家桶部署教程&#xff0c;同时实现自动更新 哈喽小伙伴&#xff0c;我是Stark-C~ 在上次更新了极空间虚拟机教程之后&#xff0c;终于有小伙伴催更了小雅Alist的搭建方案。作为当前市面最强、最大、最全的影视资源合…

MySQL-----排序 GROUP BY

在我们对数据进行分析的时候&#xff0c;通常会根据一个或多个列对结果集进行分组&#xff0c;从而得到我们想要的结果。例如&#xff1a;统计考某一门课程的学生信息等。 而MySQL的GROUP BY 语句根据一个或多个列对结果集进行分组。同时&#xff0c;我们也可以使用 COUNT, SUM…

Kubernetes 集群架构

etcd 集群状态存储&#xff1a;etcd 存储所有 Kubernetes 对象的状态&#xff0c;例如部署、pod、服务、配置映射和机密。配置管理&#xff1a;集群配置的更改存储在 etcd 中&#xff0c;允许 Kubernetes 管理和维护集群的所需状态。 注意&#xff1a;etcd 可能位于 kube-syst…

GBDT算法超参数评估

GBDT&#xff08;Gradient Boosting Decision Tree&#xff09;算法是一种强大的机器学习技术&#xff0c;广泛应用于分类、回归等任务。然而&#xff0c;为了充分发挥其性能&#xff0c;超参数的合理设置至关重要。超参数&#xff0c;如学习率、树的最大深度、子样本比例等&am…

尝试使用blazor(二)Blazor WebAssembly(WASM)与Server之间有什么区别?

要使用Blazor&#xff0c;你得先选择一种模式&#xff0c;因为它有两种模式。Blazor网络框架允许将Razor组件以不同的方式托管。它们可以在ASP.NET Core&#xff08;Blazor Server&#xff09;中在服务器端运行&#xff0c;也可以在基于WebAssembly的.NET运行时在浏览器中在客户…

便捷生活,从便民平台开始

想要生活更轻松、更便捷吗&#xff1f;那就来试试我们的便民平台吧&#xff01;生活中的琐事总是让人头疼不已&#xff0c;但有了我们的便民平台&#xff0c;一切问题都迎刃而解&#xff01; 咸阳便民平台的张总说&#xff1a;无论您是需要家政服务、维修安装&#xff0c;还是寻…

支持微信支付宝账单,极空间Docker部署一个开箱即用的私人账本『cashbook』

支持微信支付宝账单&#xff0c;Docker部署一个开箱即用的私人账本『cashbook』 哈喽小伙伴好&#xff0c;我是Stark-C~ 不知道屏幕前的各位富哥富姐们有没有请一个专业的私人财务助理管理自己的巨额资产&#xff0c;我不是给大家炫耀&#xff0c;我在月薪300的时候就已经有了…

ICC2:如何获取get_xx -filter后可用的属性有哪些?

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 report_attribute -app -class cell $instname 这种直接告诉你指定cell有哪些属性&#xff0c;以及对应的值是什么 或者直接用list_attribute也可以 list_attribute -help可以…

STM32自己从零开始实操05:接口电路原理图

一、TTL 转 USB 驱动电路设计 1.1指路 延续使用芯片 CH340E 。 实物图 原理图与封装图 1.2数据手册重要信息提炼 1.2.1概述 CH340 是一个 USB 总线的转接芯片&#xff0c;实现 USB 与串口之间的相互转化。 1.2.2特点 支持常用的 MODEM 联络信号 RTS&#xff08;请求发送&…

不吃饭也要搞懂的 git 命令

昨天睿哥布置了一个任务给我&#xff0c;让我学习一下 Git 的一些命令。 我问睿哥&#xff0c;到底我们在实际开发中用哪些命令会比较多&#xff0c;睿哥是这样回答我的&#xff1a; 而且他推荐我用 IDEA 自带的那个 Git 面板来执行 git 命令&#xff0c;他说直接敲命令太麻烦…

WPF学习(2)--类与类的继承2-在窗口的实现

一、代码分析 1.Animal.cs 1.1 代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace AnimalNamespace {public class Animal{public string Name { get; set; }public int Age { get; set…

【乐吾乐2D可视化组态编辑器】条件变化,触发告警动画

条件触发告警动画 乐吾乐2D可视化组态编辑器地址&#xff1a;https://2d.le5le.com/ 如图所示&#xff0c;左侧文本图元数值一直在变化&#xff0c;当数值大于等于50的时候&#xff0c;右侧矩形图元执行告警动画&#xff0c;当数值小于50的时候&#xff0c;右侧图元恢复正常。…

Blender雕刻建模_UV展开

UV展开的标准&#xff1a;展平&#xff0c;不重叠&#xff0c;均匀展开 ZenUV插件 切到边模式 -Mark&#xff0c;标记缝合边 -Unmark&#xff0c;取消标记 -Unmark All&#xff0c;全部取消标记 -Mirror Seams&#xff0c;镜像缝合边 -Zen Unwrap&#xff0c;全部展开 纹…

Python私教张大鹏 Vue3整合AntDesignVue之Checkbox 多选框

何时使用 在一组可选项中进行多项选择时&#xff1b; 单独使用可以表示两种状态之间的切换&#xff0c;和 switch 类似。区别在于切换 switch 会直接触发状态改变&#xff0c;而 checkbox 一般用于状态标记&#xff0c;需要和提交操作配合。 案例&#xff1a;多选框组件 核心…

Django中使用下拉列表过滤HTML表格数据

在Django中&#xff0c;你可以使用下拉列表&#xff08;即选择框&#xff09;来过滤HTML表格中的数据。这通常涉及两个主要步骤&#xff1a;创建过滤表单和处理过滤逻辑。 创建过滤表单 首先&#xff0c;你需要创建一个表单&#xff0c;用于接收用户选择的过滤条件。这个表单可…

Laravel 6 - 第十九章 模型文件

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

基于JSP的超市管理系统

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP MyBatis 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 员工管理界面图 管…

香港户口需要什么条件?有学历要求吗?最新香港落户途径详解!

香港户口需要什么条件&#xff1f;有学历要求吗&#xff1f;最新香港落户途径详解&#xff01; 由于香港放开“落户”窗口&#xff0c;想去香港发展或者想拿香港身份的朋友都想抓住这个机会赶紧申请。 只是&#xff0c;香港户口办理是有条件的&#xff0c;而且有学历要求&…

方法分享 |公网IP怎么指定非433端口实现https访问

公网IP可以通过指定非443端口实现HTTPS访问。在网络配置中&#xff0c;虽然HTTPS协议默认使用443端口&#xff0c;但没有规定不能在其他端口上实施HTTPS服务。使用非标准端口进行HTTPS通信需要正确配置服务器和SSL证书&#xff0c;并确保客户端能够连接到指定的端口。下面说明如…