C和C指针小记(十四)-字符串、字符和字节

时间:2022-06-26 10:34:30

1、字符串

C语言没有字符串数据类型,因为字符串以字符串常量的形式出现或存储于字符数组中.
字符串常量和适用于那些程序不会对他们进行修改的字符串.
所有其他字符串都必须存储于字符串数组或动态分配的内存中.
字符串是一个或多个字符,并且以一个位模式全0 的NUL字节结尾
头文件 string.h 包含了使用字符串函数所需的原型和声明.

2、字符串长度

用库函数 strlen 计算字符串长度
size_t strlen(char const *string);
size_t 是一个无符号整型,在stddef.h中定义.
警告:

if( strlen( x ) >= strlen( y ) ) //1
if( strlen( x ) - strlen( y ) >= 0)//2

第一条语句可以正常工作
第二天语句永远是真. 因为strlen的结果是个无符号数,所以操作符 >= 左边的表达式也将是无符号数,而无符号数绝对不可能是负的.
把strlen的返回值强制转换为int可以消除这个问题,但是不推荐.

//计算字符串参数的长度
size_t strlen (char const *string){
    int length;
    for( length = 0; *string++ != '\0';)
        length += 1;
    return length;
}

3、不受限制的字符串函数

最常用的字符串函数都是“不受限制”的,就是说他们值是通过寻找字符串参数结尾的NUL字节来判断它的长度.
这些函数一般都指定一块内存用于存放结果字符串.在使用这些函数时,程序员必须保证结果字符串不会溢出这块内存.

3.1 复制字符串

char strcpy(char det, char const *src);
这个函数把参数src字符串复制到dst参数.如果参数src和dst在内存中出现重叠(也就是两者相同),其结果是为定义的.
由于dst参数将进行修改,所以它必须是个字符数组或者一个指向动态分配内存的数组指针,不能使用字符串常量.
目标参数的之前的内容将被覆盖并丢失.即使新的字符串比dst原先的内存更短,由于新字符串是以NUL字节结尾,所以老字符串最后剩余的几个字符也会被有效地删除
例子:

char message[] = "Original message";
...
if(...)
    strcpy(message, "Different" );

如果if条件为真,并且顺利复制,数组将包含以下的内容:
C和C指针小记(十四)-字符串、字符和字节

第一个NUL字节后面的几个字符再也无法被字符串函数访问,因此从实现的角度看,他们都已经丢失了.
注意:
程序员必须保证目标字符数组的空间足以容纳需要复制的字符串.如果字符串比数组长,多余的字符仍被复制, 它们将覆盖原先存储于数组后面的内存空间的值.
scrcpy无法解决这个问题,因为它无法判断目标字符的长度.

3.2 链接字符串

想要把一个字符串添加(连接)到另一个字符串的后面,你可以使用strcat函数.它的原型如下:
char strcat( char dst, char const *src);
strcat函数要求dst参数原先已经包含了一个字符串(可以是空字符串).它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置.如果src 和 det 的位置发生重叠,其结果是为定义的.
警告:
程序员必须保证目标字符数组剩余的空间足以保存整个源字符串

3.3 函数的返回值

strcpy和strcat都返回他们第1个参数的一份拷贝,就是一个指向目标字符数组的指针.这样可以方便的嵌套调用这些函数
如:
strcat( strcpy(dst, a), b);
strcpy首先执行.它把字符串从a复制到dst并返回dst.然后这个返回值成为strcat函数的第一个参数, strcat 函数把b 添加到 dst后面.

3.4 字符串的比较

比较两个字符串涉及对两个字符串对应的字符逐个进行比较,知道发现不匹配为止.那个最先不匹配的字符中较“小”(也就是所在字符集中的序数较小)的那个字符所在的字符串被认为“小于”另外一个字符.如果其中一个字符串是另外一个字符串的前面一部分,那么它被认为“小于”另外一个字符串,因为它的NUL结尾字节出现得更早.这种比较被称为“**词典比较**”.对于只包含大写字母或小写字母的字符串比较,,这种比较过程所给出的结果总是和我们日常所用的字母顺序比较相同.

strcmp原型:
int strcmp(char const s1, char const s2);
当s1 > s2 时,返回值大于0 ,当s1 == s2 时,返回值等于0, 当 s1 < s2时,返回值小于0;
注意:
if( strcmp(a, b) ) 不要误以为if条件为真.这个条件表达式 当a==b 的时候返回值是0.所以if语句为假.更好的方法是将strcmp的返回值与0比较
标准中规定,s1 > s2 时,返回值大于0 ,当s1 == s2 时,返回值等于0, 当 s1 < s2时,返回值小于0.但并没有规定 s1 > s2 时返回值是 -1, s1 < s2 时返回值是1
由于strcmp并不修改它的任何一个参数,所以不存在溢出字符数组的危险.但是必须保证它和其他不受限制的字符串函数一样,strcmp 函数的字符串参数必须以一个NUL 字节结尾.如果不这样,strcmp就可能对参数后面的字节进行比较,这样的比较结果没有意义

4、长度受限的字符串函数

标准库还包含了一些函数,他们以一种不同的方式处理字符串.这些函数接受一个显式的长度参数,用于限定进行复制或比较的字符数. 这些函数提供了一种方便的机制,可以防止难以预料的长字符串从它们的目标数组溢出.

如下:
char strncmp( char dst, char const src, size_t len);
char
strcnat( char dst, char const src, size_t len);
char strncmp( char s1, char const *s2, size_t len);
对于strncpy 它总是正好向 dst 写入 len 个字符. 如果 strlen(src) 的值小于 len, dst 数组就用额外的NUL 字节填充到len长度.如果 strlen(src) 的值大于或等于 len,那么只有len个字符串被复制到dst中.注意!它的结果将不会以NUL字节结尾

注意:
strncpy 调用的结果可能不是一个字符串,因此字符串必须以 NUL字节结尾.如果在一个需要字符串的地方(如strlen函数的参数)使用了一个不是以NUL结尾的字符序列,它将会继续拷贝字符序列长度之外的字符,直到它发现一个NUL字节为止.所以这是strlen这时候返回的是一个随机数.如果函数视图访问系统分配给这个程序以外的内存范围,程序就会崩溃
如何避免这样的错误:
这个问题只有当你使用strncpy 函数创建字符串,然后或者对他们使用以str开头的库函数,或者在printf中使用%s格式码打印它们时才会发生.在使用不受限制的函数之前,你首先必须确定字符串实际上是以NUL字节结尾的

char buffer[BSIZE];
strncpy( buffer, name, BSIZE);
buffer[BSIZE - 1] = '\0';

如果name的内容可以容纳于buffer中,那么最后的那个赋值语句没有任何效果和影响.但是,如果name太长,这条语句能能够保证 buffer 中字符串是以 NUL 结尾的.
以后对这个数组使用strlen 或其他不受限制的字符串函数将能够正确的工作.

5、字符串查找

5.1 查找一个字符串

char *strchr( char const *str, int ch );
char *strrchr( char const *str, int ch );

注意,我们之前说过的,第二个参数是个整型类型,但是它包含了一个字符值. strchr 在字符串中查找 ch 第一次出现的位置,找到后函数返回一个指向该位置的指针.
strrchr 返回最后一次出现的位置.
strchr 和 strrchr 区分大小写.

5.2 查找任意几个字符

char *strpbrk( char const *str, char const *group );

这个函数返回一个指向 str 中第1个匹配group 中任何一个字符的字符位置,如果未找到匹配,函数返回一个NULL 指针.区分大小写

char string[20] = "Hello there, honey.";
char *ans;
ans = strpbrk( string, "aeiou");
//ans 所指向的位置是string + 1  , 第二个参数中的e第一次出现在 第一个字符串中

5.3 查找一个子串

char *strstr( char const *s1, char const *s2 );
该函数查找 在s1 中 s2  第一次出现的起始位置,并返回一个指向该位置的指针. 如果s2没有完整的出现在s1中的任何地方,返回一个NULL 指针.如果第二个参数是空字符串,函数返回s1.

标准库中并不存在 strrstr 或者 strrpbrk 函数

//在字符串 s1 中查找字符串 s2 最右出现的位置,并返回一个指向该位置的指针
#include <string.h>
char *my_strrstr( char *const *s1, char const *s2 ){
    register char *last;
    register char *current;
    //把指针初始化为我们已经找到的前一次匹配位置
    last = NULL;
    //只在第2个字符串不为空时才进行查找,如果s2 为空, 返回NULL
    if( *s2 != '\0' ){
        //查找s2 在 s1 中第一次出现的位置
        current = strstr(s1, s2);
        //我们每次找到字符串时,让指针指向它的起始位置,然后查找该字符串下一个匹配位置.
        while( current != NULL ){
            last = current;
            current = strstr(last + 1, s2 );
        }
    }
    //返回指向我们找到的最后一次匹配的起始位置的指针
    return last;
}

6、高级字符串查找

strspn 和 strcspn 函数用于在字符串的起始位置对字符计数.
size_t strspn( char const str, char const group );
size_t strcspn( char const str, char const group );
group 字符串指定一个或者多个字符. strspn 返回 str 起始部分匹配 group 中任意字符的字符数.
如果group 包含了空格,制表符等空白字符,那么这个函数将返回 str 起始部分空白符的数目.

strspn 返回字符串s1中第一个不在字符串s2中出现的字符下标.

    unsigned long len1, len2;
    char buffer[] = "25,142,330,Smith,J,239-4123";
    len1 = strspn(buffer, "0123456789");// buffer 中 第一个不在 “0123456789” 出现的字符是‘ , ’ 它在buffer 的第2个位置(以0开始), 也就是buffer 的0-1 都在 “0123456789” 中
    len2 = strspn(buffer, ",0123456789");//buffer 中 第一个不在 “,0123456789” 出现的字符串是 ‘S’ 它在buffer 的第 11个位置, 也就是说 buffer 的 0-11 都在 “,0123456789” 中
    
    printf("len1 :%ld,len2: %ld \n",len1,len2);//2 , 11 
size_t strcspn (const char *s, const const *reject );
strcspn() 从参数 s 字符串的开头开始计算连续的字符,而这些字符完全不在参数 reject 所指的字符串中. 简单的说,若 strcspn() 返回的数值为n, 则代表字符串 s 开头连续有n个字符都不含字符串 reject 内的字符.
返回值: 返回字符串s 开头连续不含字符串reject内的字符数目.
#include <string.h>
int main(){
    char *str = "Linux was first developed for 386/486-based pcs. ";
    printf("%lu\n", strcspn(str, " "));// 只计算到 “ ”(空格)出现 所以返回到“Linux”的长度 5
    printf("%lu\n", strcspn(str,"/-"));//返回到 ’/‘ 或 ‘-’出现, 33
    printf("%lu\n",strcspn(str,"1234567890"));//返回到 3 出现, 30
}