字符串

概念:字符串就是一串0个或多个的字符,并且以一个位模式全为0的NUL字节结尾。因此字符串中间不能出现NUL字节。

0和‘\0’表示的含义是一样的,唯一的区别是0是四个字节,‘\0’是一个字节,不要混淆和‘0’的却别,‘0’是一个ASII码48的字符而已。

0标志字符串的结束,但他不是字符串的一部分。

计算字符串长度的时候不包括这个0

字符串以数组的形式存在,以数组或指针的形式访问,更多的是以指针的形式。

字符串常量

char *s = “hello, world!”;

s是一个指针,初始化为指向一个字符串常量。

由于这个常量所在的地方(不深究在哪里),所以实际上s是一个const char *s,由于历史的原因,编译器接受不带const的写法

不要试图对s所指的字符串写入,否则会导致严重的后果。

如果需要修改字符串,就应该用数组的形式
比如: cahr s[]=hello, world!;

指针或数组的选择

既然指针和数组都可以表示字符串,那么应该怎么选择呢

如果内存是动态分配的话,用指针。
如果字符串不需要修改,用指针。
如果字符串需要作为函数传递,用指针。(用数组也行,但是既然都是以指针的形式传递,为什么不用指针呢)

如果要处理一个字符串,用指针(备注,处理指的是连接复制等)
如果要构造一个字符串,用数组

char*是字符串吗?

char_可以表达字符串,但不一定是字符串,就好像int _也是可以是字符串,但是不一定是字符串,它还可以是一个数的指针。

cahr_的本意是指向字符的指针,可能指向的是字符数组(就像int _)一样

只有它指的字符数组的结尾有0,才能说他是字符串

比如:char p【】=“”;
引号里面什么都没有,也没有空格,但是他是字符串,因为数组的长度是1,尽管什么都没有,但是字符串有都以0结尾,空串也不列外。

字符串标准库

<string.h> 有几个常用方法来处里字符串。

string.h下的方法都是以str打头的

先来看看有什么常用的:
(要看清楚传进来哪些参数是可以改变的,哪些是不可以改变的,还有哪些参数本身就是不可改变的,符不符合参数的要求)

//求字符串长度
size_t strlen(char const *string);

//长度不受限制的字符串函数的复制
char _strcpy(char _dst,char const _src);
//长度受限制的字符串函数的复制
char _strncpy(char _dst, char const _src, size_t len);

//长度不受限制的字符串函数的连接
char _strcat(char _dst, char const _src);
//长度受限制的字符串函数的连接
char _ strncat(char _dst, char const _src,size_t len);

//长度不受限制的字符串函数的比较
int strcmp(char const_s1,char const _s2);
//长度受限制的字符串函数的比较
int strncmp(char const_s1,char const _s2,size_t len);

//查找特定字符
//(1)查找遇到ch第一次出现的位置
char _strchr(char const _str, int ch);
//(2)查找ch最后一次出现的位置
char _strrchr(char const _str, int ch);

//查找的不是特定字符,而是任意一组字符第一次出现的位置
char _strpbrk(char const _str, char const *group);
标准库没有strrpbk函数,需要手动实现

//查找一个子串
char _strstr(char const _s1, char const *s2);
标准库没有strrstr函数,需要手动实现

//查找一个字符串的前缀
//返回str起始部分匹配group中任意字符的字符数统计
size_t strspn(char const *str, char const *group);
//返回str起始部分不匹配group中任意字符的字符数统计
size_t strcspn(char const *str, char const *group);

//查找标记
char _strtok(char _str, char const *sep);

解释:
size_t是一个无符号的整数类型,他是在头文件stddef.h中定义的
函数参数有const修饰,说明该参数不会被修改。

函数分析

长度

size_t strlen(char const *string);

用来计算字符串的长度。注意事项,返回的是一个无符号数,进行运算需要留心无符号数和无符号数的计算还是一个无符号数。
比如:
if(strlen(x)>=strlen(y))…

if(strlen(x)-strlen(y)>=0)…
前者可以按照预想(判断长短)来执行,后者不行,因为strlen(x)-strlen(y)的结果绝对不会为负的。

注意:无符号计算结果打印是可以出现为负数,但是系统会判定他是正数!

那么如果是一个无符号数和有符号数的计算呢?可能会产生一个奇怪的结果。
所以,我们要把无符号数转成有符号数就能根据正负来判断长短了。
比如:
if(strlen(x)>=10)

if(strlen(x)-10>=0)
前者可以按照设想得到结果,后者不行,后者可能会给出奇奇怪怪的结果,解决的办法就是把strlen的返回值强制转为int。

复制

char _strcpy(char _dst,char const *src);

注意:
1)第一个参数是目标参数,即把src复制给dst,因为dst将会被覆盖修改,所以dst应该是一个可修改的字符串,即数组形式表示,src不会被修改,它可以是字符串常量,也可以是数组,也可以是指针

2)既然是数组,就有空间限制,我们要保证复制的字符串不会超过数组范围。因为数组是不会自动扩大的,所以当你复制的内容超过了数组范围,它仍将执行复制,但是会侵占数组后面的内存位置,修改那里的变量。

3)如果src和dst位置(地址)发生重叠,结果是未定义的。经测试出现错误:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTkhi1bk-1585747998280)($resource/d48f3799e251896201a98b64949c8d3.png)]
比如:
char _string1 = “hello world!”;
char ch[] = “hello world!”;
char _string2 = “hello##”;

strcpy(ch,string2);//复制成功!ch 变成 hello world!hello##
strcpy(string1,string2)//程序无法运行,string1是一个常量引用,它无法被修改!


char _strncpy(char _dst, char const *src, size_t len);
多了一个size_t的参数,这个参数是一个无符号数,表示向dst写入src的len个字符。那么就有几个问题:

1)len大于src怎么办,已经超过了src的范围:此时会向dst写入len个字符,多出原数组的地方用NUL字节来补充
2)len小于等于src怎么办,就是len在src的范围之内:此时会直接向dst写入len个字符,但是不会自动补充NUL字节,也就是说,原字符串可能经过复制变成非字符串(以NUL字节结尾的才是字符串)。这个非字符串还是会有NUL的,在哪里呢,可能在几百个字符后面吧,你非要找还是能找出来的。所以说在使用strncpy时,一定要确保字符串最终以NUL字节结尾,必须手动补充NUL字节。(虽然有些地方可能不用补,但是补了总没错)
3)目标数组溢出的问题

注意:
1)len是从一开始的,并不是采用数组的下标法
2)如果src和dst位置相同,结果是未定义的。

所以最好不要自身复制自身。

连接字符串

char _strcat(char _dst, char const *src);

stract函数要求dst原先包含了一个字符串,**至少包含一个‘\0’,**函数会找到这个‘\0’,把src的一份拷贝添加到这个位置。

注意:
dst原字符串被覆盖,并非返回新字符串,所以要注意数组的是否越界,不要破坏其他地方的数据。

char _ strncat(char _dst, char const *src,size_t len);

strncat 和 strncpy 有一代女不同,strncat总会在结尾加上NUL字节的。所以它连接的个数是len+1,1就是末尾的‘\0’

字符串比较

两个字符串的比较是逐个字符进行比较的,两个参数都不会被改变。

前者大于后者,返回大于0的数,前者小于后者,返回小于0的数,两者相等,返回0,这里的大于0或者小于0 的数,不一定是1和-1。。

strcmp对字符串的检查也会检查NUL字节,所以两个比较的字符串必须含有NUL字节,不然比较是没有意义的,天晓得没有NUL字节结尾的字符串他的NUL在哪里,这样的比较没有意义。

strncmp最多比较len个字符,如果在len个字符之前遇到不相等的字符,返回结果,如果len个字符都相等,返回0。

查找

查找一个字符

char _strchr(char const _str, int ch);

第二个参数是一个整型,但是包含了一个字符值(就是可以用字符来做参数传递),查找str中第一次出现ch的位置。

找到后返回该位置的指针,找不到返回NULL指针。

函数区分大小写。

char _strrchr(char const _str, int ch);

查找最后一次出现ch的位置,返回该位置的指针,找不到返回NULL指针。函数区分大小写。

char *string="12345";
	char *s=strchr(string,'4');
	printf("%p\n",string);
	printf("%p\n",s);

输出:
007D5940
007D5943
查找任何几个字符

char _strpbrk(char const _str, char const *group);

函数返回一个指向sre中第一个匹配group中任何一个字符的位置。如果未找到,返回一个NULL指针。

char string[20]="Hello there, honey.";
char *ans = strpbrk(string,"aeiou");

返回位置是string+1,因为这个位置是第二个参数第一次出现。

函数区分大小写。

该函数没有配备查找最后一次出现的位置,需要自己手动实现。

查找一个子串

char _strstr(char const _s1, char const *s2);

函数查找s1中第一次出现s2的位置,返回该位置的指针。如果s2没有完整的出现在s1中,返回NULL指针。,如果s2是一个空串,返回s1。

strstr也没有配备查找最后一次出现的方法。

需要手动实现。比如:

/*

在字符串s1中查找字符串s2最后出现的位置,并返回该位置的指针

*/

#include<string.h>

char *
my_strrstr(char const *s1,char const *s2)
{
register char*last;
register char*current;
/*
把指针初始化为我们已经找到的前一次匹配的位置。
*/
last = NULL;
/*
旨在第二个字符串不为空时才进行查找,如果s2为空,返回NULL
*/

if(s2 != '\0'){
/*
查找s2在s1第一次出现的位置
*/
current= strstr(s1,s2);
}
/*
每次找到字符串时,让指针指向他的起始位置,然后查找该字符串下一个匹配的位置。
*/
while(current != NULL){
last=current;
current = strstr(last+1,s2);
}
}
/*返回只想我们找到的最后一次匹配的起始位置的指针*/
return last;
}

查找字符串前缀

size_t strspn(char const *str, char const *group);

返回str起始部分匹配group中任意字符的字符数。如果group包含了空白符,那么会返回str起始部分的空白符个数。str下一个字符是第一个非空白符。

也可以理解为,函数返回 str1 中第一个不在字符串 group 中连续出现的字符下标。

函数会根据group中的字符数,与str的匹配程度进行返回。

比如:str=“1124345”;
当group=“123”时,返回的下标是3,也就是4的位置,因为1和2是连着的,所以他会自动跟进匹配,str中的2和3不是连续的,所以没有继续返回三的位置。其实他就是查找前缀,group中有多少个是和str连续匹配的,就返回下一个位置的下标呗,如果不是连续的,怎么叫前缀呢,直接查找单个字符的位置好了。

不过需要注意的是,他会计算前缀连续的个数:
比如str=“123333345”;
group=“123”;
那么返回的位置不是“123”后一个位置,而是把3都数完的位置,也就是下标7,数字4的位置

同理,当group=“13456”时,只有1是连续匹配的,所以返回的下标位置是2,也就是第三个数字2。

size_t strcspn(char const *str, char const *group);

函数返回 str1 开头连续都不含字符串 group 中字符的字符数
(实验结果未名,作此标注)

内存操作

下面5个函数用来处理非字符串内部包含NUL的情况。因为NUL在中间,所以无法用字符串函数来处理他,那些函数会在遇到第一个NUL就停止工作。

void *memcpy(void *dst,void const *src, size_t length);
void *memmove(void *dst,void const *src, size_t length);
void *memcmp(void const *a,void const *b, size_t length);
void *memchr(void const *a,int ch, size_t length);
void *memset(void *a,int ch, size_t length);

每个原型都包含了一个显式的参数说明需要处理的字节数,但和strn带头的函数不同,这几个遇到NUL时不会停止工作。

memcpy

memcpy从src的起始位置复制length个字节到dst的内存起始位置,可以用这种方法复制任何类型的值。length参数指定了复制的长度(以字节计)。同样输入dst和src以任何形式发生重叠,结果是未定义的。

例子:
memcpy(temp,values,sizeof(valuse));//temp和values都是整型数组

前两个参数并不需要类型转换,因为参数的要求是一个空指针,任何类型的指针都可以转为空指针。

如果数组只有部分内容需要复制,那么复制的数量必须在length参数中指明。对于长度大于一个字节的数据,要确保数量和数据类型长度相乘。

例子:
memcpy(saved_answers,answers,count * sizeof(answers[0]));//归根结底是对复制字节数的确定

这种技巧也可以用来复制结构和结构数组。

memmove

memmove,使用方法与memcpy类似,不过它可以允许源和目标操作数重叠。

工作原理:
把源操作数复制到一个临时位置,这个临时位置不会与源或目标操作数重叠,然后再把它从临时位置复制到目标操作数。

memmove和memcpy的区别:
1)两个都可以用来复制数据
2)时间上比memcpy慢一些
3)如果源目标和目标参数可能存在重叠,使用memmove

也就是说:如果对时间没有要求,如果不确定源和目标参数是否会重叠,优先使用memmove

memcmp
memcmp对两段内存的内容进行比较,两段内存分别起始a和b,共比较length个字节

这些值按照无符号字符逐字节进行比较,函数返回类型和strcmp一样,正数,a大于b,负数,a小于b,0,两者相等。

注意:
memcmp是根据无符号字节进行比较,如果memcmp用于比较不是单字节的数据,比如整型或浮点数时,给出的结果时不可预料的。

memchr

memchr从a的起始位置开始查找ch第一次出现的位置,返回一个指向该位置的指针,它共查找length个字节。如果length个字节内找不到该字符,返回一个NULL指针。

memset

memset把a开始的length个字节都设置为字符值ch

例子:
memset(buffer,0,SIZE);

把buffer前SIZE个字节全部初始化为0;