string.h
C 标准库 – <string.h> | 菜鸟教程 (runoob.com)
1 void *memchr(const void *str, int c, size_t n)
在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置。
在这个函数中,可以看到有void *作为形参,void *作为返回值。
这代表什么呢?
void *作为形参表示可以传入任意类型的指针;如果是返回值,则表示可以转成任意类型的指针。但是,通常情况下,输入输出的都是字符指针,传入其他类型的指针无操作意义。
输入时可以直接传入,但是输出时通常需要将void *强转成其他所需类型的指针。因为函数输出的结果总要是一个具体的类型。
这里的位置,指的就是目标字符所在的地址,即指针。
#include <stdio.h> #include <string.h> int main () { const char str[] = "be.happy.everyday"; const char ch = 'p'; //虽然第二个参数是int,但是可以直接传入char char *ret; ret = (char *)memchr(str, ch, strlen(str)); printf("result is %s\n", ret); printf("result is %c\n", *ret); return(0); }
运行结果:
2 | int memcmp(const void *str1, const void *str2, size_t n) 把 str1 和 str2 的前 n 个字节进行比较。 |
你还在用‘>’‘<’‘=’等比较字符串吗?
事实上,用大于小于比较字符串的方法是不对的。我们看一下两种常见的错误方法。
#include<stdio.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abc"; if (arr1 < arr2) { } if ("abcdef" < "abc") { } }
为什么两个字符串不能直接用大于小于号比较?
因为用大于小于号,比较的是字符串的首地址大小,而不是字符串的大小。如上述例子,比较的是"abcdef"中a的地址和"abc"中a的地址。为什么不能用数组名直接比较
因为数组名代表首元素地址(一般情况下),用数组名比较跟用字符串直接比较性质是一样的,所以用数组名比较也是不对的。
字符串比较大小的实质比较字符串的大小就是比较每个字符串中对应位置上的字符大小(ASC II码值大小),如果相同,就比较下一对,直到不同或者都遇到'\0'。
3 | void *memcpy(void *dest, const void *src, size_t n) 从 src 复制 n 个字符到 dest。 |
该函数返回一个指向目标存储区 str1 的指针,即返回结果是指向目标字符串。
我有个疑问,复制后是接续还是覆盖?
直接说结论:是覆盖。
通常,dest是一个未初始化的字符数组。
4 | void *memmove(void *dest, const void *src, size_t n) 另一个用于从 src 复制 n 个字符到 dest 的函数。 |
memmove和memcpy的作用都是内存拷贝,唯一的区别是,当内存发生局部重叠时:memmove保证了拷贝的结果是正确的,但是memcopy不一定是正确的。
但是memcpy比memmove速度快。
考虑下面两种内存重叠的情况:
- 第一种情况下,拷贝重叠的区域不会出现问题,内容均可以正确的被拷贝。
- 第二种情况下,问题出现在右边的两个字节,这两个字节的原来的内容首先就被覆盖了,而且没有保存。所以接下来拷贝的时候,拷贝的是已经被覆盖的内容,显然这是有问题的。
注意:是一个字符一个字符地复制的,所以第二种情况会出错。
在实际使用时,使用memmove是比memcpy更安全的。
更多参考:
memmove 和 memcpy的区别_nguliu的博客-CSDN博客_memmove
碰到个问题,我刚想把两个函数操作放一起比较,结果发现结果不对,原来是我两次都是基于原数据来计算的,事实上,运行第一个函数后,内存中的数据就已经变了。
#include <stdio.h> #include <string.h> int main() { char s[] = "123456789"; char *p1 = s; char *p2 = s+2; char *rp1 = (char *)memcpy(p2, p1, 5); //运行一次后,原来的数据已经变了 char *rp2 = (char *)memmove(p2, p1, 5); //第二次运行是在第一次基础上操作的 printf("Hello, World! %s\n", rp1); printf("Hello, World! %s\n", rp2); return 0; }
5 | void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。 |
复制到前n个字符,还是第n个字符?
#include <stdio.h> #include <string.h> int main() { char s[] = "behappy"; printf("Hello, World! %s\n", memset(s, 'a', 4)); return 0; }
注意,该值返回一个指向存储区 str 的指针。输入时str是什么类型的指针,输出的就是什么类型的指针,这里不必特意强制类型转换。
memset用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化。
6 | char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。 |
该函数返回一个指向最终的目标字符串 dest 的指针。
- dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
- src -- 指向要追加的字符串,该字符串不会覆盖目标字符串。
以上提到了两个关键点,一是目标数组要包含一个字符串,二是要用足够的容量来容纳。
经过测试,我们发现strcat的实现模式是将src中的所有字符(连同字符串最后的’\0’一起)加到dest字符串中第一个‘\0’的位置。
值得注意的是:
1.追加的字符串src和目标字符串dest中都必须要带有字符’\0’,并且追加字符串src必须以‘\0’结尾,否则追加过程无法顺利实现。
2.目标字符串空间必须足够大(足够容纳下追加字符串src的内容)。
3.目标空间必须可修改(前面不能加const并且不能说常量字符串)但是,在进行测试的时候,发现了一些问题。
1、
直接定义字符串可以追加。理论分析,s并没有足够的容量去容纳。但确实是追加上了。
#include <stdio.h> #include <string.h> int main() { char s[] = "behappy"; char t[] = "everyday"; printf("%s\n", strcat(s, t)); //behappyeveryday return 0; }
2、
目标字符串未初始化,且容量不足容纳源字符串,但还是追加上了
#include <stdio.h> #include <string.h> int main() { char s[1]; char t[] = "everyday"; printf("%s\n", strcat(s, t)); //everyday return 0; }
这是什么原因?
因为strcat函数是不安全的。然数组s的长度是1,但将t连接到s的后面时,不会进行越界检查,而是直接将s追加到t的后面。这样,就会占用不属于s的内存,所以运行程序时可能出现多种情况,比如:
1.
程序奔溃
2.
表现正常
3.
没奔溃,但程序出现莫名其妙的现象这3种可能都有可能出现,出现2实属侥幸,说不定下次运行时就会出现情况1或3。
最好是保证第一个空间长度足够,不足也会尝试继续往后放第二个字符串的字符,但是这种做法是十分危险的,可能会覆盖掉一些有用的数据,导致程序的不可预知的错误,也可能是尝试往一个可读不可写的存储空间写入数据则程序直接崩溃。
先要肯定一点:教程没有说错! 你在编程时必须要注意这一点:保证strcat的第一个参数有足够的空间!为什么没报错呢? 因为:这个函数的参数是指针类型,函数中也只是通过指针来读写这些内存,编译器无从得知这几块内存到底多大,所以编译阶段编译器不会报错。函数的行为大致就是先找到第一个参数所指的内存中字符串结尾的位置,然后从此处开始依次写入第二个参数中的字符...直到写完。函数也根本不知道第一个参数所指的内存空间到底够不够大,所以函数本身也不会对此检查。而在运行时,如果向第一个参数处写入了过多的字符,则有可能会引起问题,也有可能不会:假如第一个参数所指的内存空间后面的内存刚好也是可读写的,那么函数就继续向其中写入字符,虽然这已经是算写“越界”了,但函数不知道啊。 这样后面输出时就会把这字符串完整地输出。但假如第一个参数所指内存后面不可写,到函数写越界时就会引起运行时错误。所以这样的代码在很多时候不会触发运行时错误而运行出看似正确的结果,但这是非常错误的代码! 即便后续的内存是可写的,但也许其中存着其他数据,写越界就有可能覆盖这些数据,引起其他bug,这是严重的安全隐患! 其实所谓“缓冲区溢出漏洞”大致就是这样来的。
7 | char *strncat(char *dest, const char *src, size_t n) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。 |
n字符长度是指追加后整个的长度,还是指src要追加的长度?
#include <stdio.h> #include <string.h> int main() { char d[20] = "be happy"; char s[9] = " everyday"; printf("%s\n", strncat(d, s, 5)); //be happy ever return 0; }
经测试,n是指要源字符串要追加的字符长度。
strncat和strcat有何区别?
因为strcat是以'\0'作为结束标志的,所以strcat无法完成对自身的追加。
问题原因:在第一次循环的时候就自身的‘\0’就已经被第一个元素覆盖了,导致后面找不到'\0'所以无法退出循环,进入了死循环中。
这时候,就需要用到strncat,strncat的原理不同于strcat,strcat是利用'\0'来退出循环,strncat则是用长度来退出,因为有长度的限制,所以只要长度达到了就能结束,安全性有了更好的保证。
8 | char *strchr(const char *str, int c) 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。 |
该函数返回在字符串 str 中第一次出现字符 c 的位置,如果未找到该字符则返回 NULL。
和memchr相比,strchr不限制前n个字符了,而是在整个目标字符串中搜索。
memchr检测的是一段内存,strchr检测的是一个字符串,如果一段内存中有\0字符作为有效数据的话,显然不能用strchr去查找的。
strchr会停在\0,memchr不会。
strchr期望第一个参数以空值终止,因此不需要长度参数。 memchr的工作方式类似,但不期望内存块以空值终止,因此可以成功搜索\0字符。
9 | int strcmp(const char *str1, const char *str2) 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。 |
10 | int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字节。 |
该函数返回值如下:
- 如果返回值 < 0,则表示 str1 小于 str2。
- 如果返回值 > 0,则表示 str1 大于 str2。
- 如果返回值 = 0,则表示 str1 等于 str2。
12 | char *strcpy(char *dest, const char *src) 需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。 |
13 | char *strncpy(char *dest, const char *src, size_t n) 当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。 |
可用于给某声明过的数组进行初始化。
或者给某个数组整个地改变其内容。
#include <stdio.h> #include <string.h> int main() { char s[20] = "everydayyyy"; char t[] = "behappy"; printf("%s\n", strcpy(s, t)); //behappy return 0; }
直接复制到s,不管s里面有什么内容。
strcpy()函数是将一个字符串复制到另一块空间地址中的函数,‘\0’是停止拷贝的终止条件,同时也会将 '\0' 也复制到目标空间。
16 | size_t strlen(const char *str) 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。 |
计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
18 | char *strrchr(const char *str, int c) 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。 |
strchr是从第一次出现的位置,strrchr是最后一次出现的位置。注意区分。