系列文章目录
深度剖析:数据
深度剖析:递归
深度剖析:结构体
深度剖析:动态内存管理
深度剖析:文件操作
深度剖析:预处理
文章目录
前言
库函数string是我们经常使用也是非常重要的代码,我将会对常用的函数的用法和实现原理进行深度剖析
一、memset
1.调用结构
void *memset(void *str, int num, size_t len);
2.使用详解
memset:对每个字节进行初始化
原理详解:
- 以字节为单位设置内存
- 每个字节设置为一个值
- 设置len个字节
3.源码剖析
void *Memset(void *str, int num, size_t len) { //(变量, 值, 字节)
//以字节为单位设置内存, 每个字节设置为一个值,设置n个字节
assert(str); //防止传入空指针
char *start = str; //记录起始位置的地址
while (len--) { //对每个字节进行初始化
*(char *) str = num;
str = (char *) str + 1;
}
return start; //返回起始位置的地址
}
二、memcmp
1.调用结构
int memcmp(void const *s1, void const *s2, int len);
2.使用详解
memcmp:比较从 s1 和 s2 指针开始的 num 个字节
原理详解:
- 内存比较一个字节一个字节比较
- 判断每个字节在内存中存储的值是否相等
- 第一个字符串 > 第二个字符串,则返回 大于0 的数字
- 第一个字符串 = 第二个字符串,则返回 0
- 第一个字符串 < 第二个字符串,则返回 小于0 的数字
3.源码剖析
int Memcmp(void const *s1, void const *s2, int count) {
//比较从 s1 和 s2 指针开始的 num 个字节
assert(s1 && s2); //防止传入空指针
while (count-- && *(char *) s1 == *(char *) s2 && *(char *) s1 != '\0' && *(char *) s2 != '\0') { //判断每个字节存入内存中的值是否相等
s1 = (char *) s1 + 1;
s2 = (char *) s2 + 1;
}
return *(char *) s1 - *(char *) s2;
}
三、memcpy
1.调用结构
void *memcpy(void *dest, const void *src, size_t n);
2.使用详解
memcpy:从 src 复制 count 个字节到 dest
原理详解:
- 内存复制一个一个字节拷贝
- 将每个字节在内存中存储的值进行拷贝
- 不再局限于字符串的拷贝
3.源码剖析
void *Memcpy(void *dest, const void *src, size_t count) {
//内存复制:从 src 复制 count 个字节到 dest
assert(dest && src);
char *start = dest; //记录起始位置的地址
while (count--) { //拷贝的字节数
*(char *) dest = *(char *) src; //一个字节一个字节进行拷贝
dest = (char *) dest + 1;
src = (char *) src + 1;
}
return start; //返回起始位置的地址
}
四、memmove
1.调用结构
void *memmove (void *destination, const void *source, size_t num);
2.使用详解
memmove:从 src 复制 count 个字节到 dest
原理详解:
- 与memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理
3.源码剖析
void *Memmove(void *dest, void *src, int count) {
//内存复制:从 src 复制 count 个字节到 dest
assert(dest && src); //防止传入空指针
char *start = dest;
if (dest < src) { //前->后
while (count--) {
*(char *) dest = *(char *) src;
dest = (char *) dest + 1;
src = (char *) src + 1;
}
} else { //后->前
while (count--) {
*((char *) dest + count) = *((char *) src + count);
}
}
return start;
}
五、strlen
1.调用结构
size_t strlen(const char *str);
2.使用详解
strlen:计算字符串的长度
原理详解:
- 字符串以’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)
- 参数指向的字符串必须要以’\0’结束
- 注意函数的返回值为size_t,是无符号的
3.源码剖析
size_t Strlen(char *str) {
assert(str); //断言防止传入空指针
char *p = str; //创建一个可变指针
while(*p++); //遍历查找'\0'
return p - str; //返回两个地址的差
}
六、strtok
1.调用结构
char *strtok (char * str, const char * sep);
2.使用详解
strtok:用于分隔字符串
str
,并删除str
中含有sep
的字符
原理详解:
sep
参数是个字符串,定义了用作分隔符的字符集合- 第一个参数指定一个字符串,它包含了0个或者多个由
sep
字符串中一个或者多个分隔符分割的标记strtok
函数找到str
中的下一个标记,并将其用‘\0’
结尾,返回一个指向这个标记的指针。- 注:
strtok
函数会改变被操作的字符串,所以在使用strtok
函数切分的字符串一般都是临时拷贝的内容并且可修改。strtok
函数的第一个参数不为NULL
,函数将找到str中第一个标记,strtok
函数将保存它在字符串中的位置。strtok
函数的第一个参数为NULL
,函数将在同一个字符串中被保存的位置开始,查找下一个标记。- 如果字符串中不存在更多的标记,则返回
NULL
指针。
3.源码剖析
char* Strtok(char *s, const char *delim)
{
const char *spanp;
int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
* 跳过字符串首部的分隔符
*/
cont:
c = *s++;
for (spanp = delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
/*
*分割符后面没有字符串了
*/
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1; /*分割符后面还有字符串,将tok指向字符串首部(不包括分隔符)*/
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
* 循环字符串中的字符,直到找到分隔符或者结束符,并替换成结束符
*/
for (;;) {
c = *s++;
spanp = delim;
/*
*判断字符串中的某字符是否是分割符中的字符
*如果是,将分隔符替换成结束符并返回tok;
*如果不是,继续判断下一个字符
*/
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
七、strcpy
1.调用结构
char *strcpy(char *dest, const char *src);
2.使用详解
strcpy:把 src 所指向的字符串复制到 dest
原理详解:
- 源字符串必须以’\0’结束
- 会将源字符串中的’\0’拷贝到目标空间
- 目标空间必须足够大,以确保能存放源字符串(不够大也依然能全部拷贝,但会报错)
- 目标空间必须可变(不能是常量字符串)
3.源码剖析
char *Strcpy(char *dest, const char *src) {
//把 src 所指向的字符串复制到 dest
assert(dest && src); //防止传入空指针
char *start = dest; //记录起始位置
while ((*dest++ = *src++) != '\0'); //拷贝字符串
return start; //返回起始位置
}
八、strncpy
1.调用结构
char *strncpy (char *dest, const char *src, size_t n);
2.使用详解
strncpy:把 src 所指向的字符串复制到 dest,最多复制 n 个字符
原理详解:
- 拷贝num个字符从源字符串到目标空间
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个
3.源码剖析
char *Strncpy(char *dest, const char *src, int len) {
//把 src 所指向的字符串复制到 dest,最多复制 n 个字符
char *start = dest;
while (len-- && (*dest++ = *src++) != 0); // len先交付结果再--
if (len) { // 保护代码,后面全部补0
while (--len) { // len的位置有'\0'
*dest++ = '\0';
}
}
return start;
}
九、strcmp
1.调用结构
int strcmp(const char *str1, const char *str2);
2.使用详解
strcmp:把 str1 所指向的字符串和 str2 所指向的字符串进行比较
原理详解:
- 比较ASCII码值
- 第一个字符串 > 第二个字符串,则返回 大于0 的数字
- 第一个字符串 = 第二个字符串,则返回 0
- 第一个字符串 < 第二个字符串,则返回 小于0 的数字
3.源码剖析
int Strcmp(char const *s1, char const *s2) {
//比较:把 str1 所指向的字符串和 str2 所指向的字符串进行比较。
assert(s1 && s2); //防止传入空指针
while (*s1 == *s2 && *s1 != 0 && *s2 != 0) { //比较字符串
s1++;
s2++;
}
return *s1 - *s2; //返回比较结果
}
十、strncmp
1.调用结构
int strncmp(const char *str1, const char *str2, size_t n);
2.使用详解
strncmp:把 str1 和 str2 进行比较,最多比较前 n 个字节
原理详解:
- 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完
- 第一个字符串 > 第二个字符串,则返回 大于0 的数字
- 第一个字符串 = 第二个字符串,则返回 0
- 第一个字符串 < 第二个字符串,则返回 小于0 的数字
3.源码剖析
int Strncmp(char const *s1, char const *s2, int len) {
//比较:把 str1 和 str2 进行比较,最多比较前 n 个字节
assert(s1 && s2); //防止传入空指针
while (len-- && *s1 == *s2 && *s1 != '\0' && *s2 != '\0') { //比较 n 个字节的字符串
s1++;
s2++;
}
return *s1 - *s2; //返回比较结果
}
十一、strcat
1.调用结构
char *strcat(char *dest, const char *src);
2.使用详解
strcat:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾
原理详解:
- 两个字符串必须以
'\0'
结束- 目标空间必须有足够的大,能容纳下源字符串的内容
- 目标空间必须可修改
- 不可追加自己
- 可通过修改传入src的地址改变追加的起始位置
3.源码剖析
char *Strcat(char *dest, const char *src) {
//把 src 所指向的字符串追加到 dest 所指向的字符串的结尾 (不可追加自己)
assert(dest && src); //断言,防止传入空指针
char *start = dest; //记录字符串dest的起始位置用于返回起始地址
while (*dest) { //找到字符串dest的'\0'
dest++;
}
while (*src != '\0') { //将字符串src追加到字符串dest的结尾
*dest++ = *src++;
}
*dest = '\0'; //在结尾追加上'\0'
return start;
}
十二、strncat
1.调用结构
char *strncat(char *dest, const char *src, size_t n);
2.使用详解
strncat:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止
原理详解:
- 两个字符串必须以
'\0'
结束- 目标空间必须有足够的大,能容纳下源字符串的内容
- 目标空间必须可修改
- 不可追加自己
- 可通过修改传入src的地址改变追加的起始位置
3.源码剖析
char *Strncat(char *dest, const char *src, int len) {
//把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止 (不可追加自己)
assert(dest && src); //断言,防止传入空指针
char *start = dest; //记录字符串dest的起始位置用于返回起始地址
while (*dest) { //找到字符串dest的'\0'
dest++;
}
while (len--) { //将字符串src追加到字符串dest的结尾
if (*src == '\0') { //如果src结尾的'\0'也完成追加,则直接返回
return start;
}
*dest++ = *src++;
}
*dest = '\0'; //如果追加完成len个字节,则在最后追加上'\0'
return start;
}
十三、strchr
1.调用结构
char *strchr(char *s, char c);
2.使用详解
strchr:在参数 s 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的地址
原理详解:
- 遍历整个字符串查找指定字符
- 如果找到返回该字符的地址
- 如果没找到返回空指针NULL
3.源码剖析
char *Strchr(char *s, char c) {
//查找字符:在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的地址
assert(s); //防止传入空指针
while (*s != '\0' && *s != c) { //遍历字符串
++s;
}
return *s == c ? s : NULL; //找到了返回该字符的地址,没找到返回NULL
}
十四、strstr
1.调用结构
char *strstr(char const *s1, char const *s2);
2.使用详解
strstr:在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置
原理详解:
- 遍历整个字符串查找指定字符串
- 如果找到对应的字符,则以该字符为起点的一段字符进行字符串比较
- 如果相等返回这段字符串起点的地址
- 如果不想等则继续遍历直到结束
- 若最终未找到对应的字符串则返回空指针NULL
3.深度剖析
char *Strstr(char const *s1, char const *s2) {
//查找字符串:在字符串 s1 中查找第一次出现字符串 s2(不包含空结束字符)的位置
assert(s1 && s2); //防止传入空指针
char const *start1 = s1;
char const *start2 = s2;
while (*(s1 = start1++)) { //遍历s1
for (s2 = start2; *s1 == *s2; s1++, s2++) { //遍历s2与s1比较
if (*(s2 + 1) == '\0') { //如果s2与s1的一部分均相等
return (char *)start1; //返回start1记录的s1的地址
}
}
}
return NULL; //如果遍历s1均未找到则返回NULL
}