在这篇博客会讲解以下函数:strlen,strcpy,strcat,strcmp,strncpy,strncat,strncmp,strstr,strtok,memcpy,memmove,memset,memcmp以及字符函数。
strlen
函数讲解:计算字符串的长度
size_t strlen(const char * str );
strlen这个函数的参数是一个字符指针,返回的是一个无符号的整数;
需要注意以下几点 :
1.字符串已经 '\0' 作为结束标志, strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数。2. 参数指向的字符串必须要以 '\0' 结束。3.函数的返回值是一个无符号整数。
其中第三点要注意一下:下面这段代码是错误的
if(strlen(str1) - strlen(str2) > 0)
{
printf("str1长一些\n");
}
因为strlen的返回值是无符号整数,两个无符号整数相减还是无符号整数,永远会输出str1长一些。
模拟实现
int my_strlen(const char* str)
{
int count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
除了上述计数器方法,还有递归和指针减指针的方法。
strcpy
函数讲解:把sourse字符串复制给destination字符串
char* strcpy(char* destination, const char* source);
strlcpy这个函数的参数是两个字符指针,返回的是destination这个字符指针;
需要注意以下几点 :1.源字符串必须以 '\0' 结束。2.会将源字符串中的 '\0' 拷贝到目标空间。3.目标空间必须足够大,以确保能存放源字符串。4.目标空间必须可以被改变。
其中第四点要注意一下:下面这段代码是错误的
int main()
{
char* p = "abcdefghi";
char arr2[20] = "hehe";
strcpy(p, arr2);
return 0;
}
因为p指针指向的是一个字符串常量的首元素,目标空间不能被修改。
模拟实现
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while ((*dest++ = *src++))
{
;
}
return ret;
}
asset()函数是断言函数当assert里面的判断为假时程序就会报错,提高了程序的安全性。
strcat
函数讲解:把sourse字符串追加到destination字符串的末尾
char* strcat(char* destination, const char* source );
strcat这个函数的参数是两个字符指针,返回的是destination这个字符指针;
需要注意以下几点 :
1.源字符串必须以 '\0' 结束。2.目标空间必须有足够的大,能容纳下源字符串的内容。3.目标空间必须可修改。4.字符串自己给自己追加,这个行为是未被定义的。
其中第四点要注意一下:下面这段代码的行为是不被定义的
int main()
{
char str[20] = { "abd" };
strcat(str, str);
printf("%s\n", str);
return 0;
}
在C语言标准中使用strcat函数追加自己的的行为是未定义的,所以在某些编译器上这段代码可能会报错。
模拟实现
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while (*dest)
{
dest++;
}
while ((*dest++ = *src++))
{
;
}
return ret;
}
这段代码,先让dest指针指向字符串的末尾,在进行和字符串拷贝类似的操作。
strcmp
函数讲解:比较两个字符串的大小
int strcmp (const char* str1, const char* str2 );
strcmp这个函数的参数是两个字符指针,返回的是一个整形;
函数返回值的具体信息:
1.第一个字符串大于第二个字符串,则返回大于 0 的数字。2.第一个字符串等于第二个字符串,则返回 0。3.第一个字符串小于第二个字符串,则返回小于 0 的数字。
程杰《大话数据结构》中有讲到串的比较
串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。(在C语言中字符串用的是ASCII编码)1.相等的情况:给定两个串: S="a1a2…an″, T="b1b2…bm", 当且仅当n=m时,且a1=b1,a2=b2…an=bm时,我们认为S与T这两个串相等。2.不等的情况:给定两个串: S="a1a2…an″, T="b1b2…bm", 当满足以下条件之一时,我们认为S <T。(1) n < m,且ai = bi(i = 1,2,3...n)(2) 存在某个k <= min(n, m),使得 ai = bi(i = 1,2,3...k-1),ak < bk。
书本中的定义其实有点太公式化而不太好理解,以下是我的总结:
字符串的比较可以看错一对循环的字符比较 (若字符相等则比较下一对字符)
我们从字符串比较的终止条件来判断字符串的大小:
终止条件1:字符串比较未达到两个字符串的末尾 ('\0')
这个情况时,当有一个字符串对应的字符大于另一个字符串的时,大的那一个对应的字符串大。
终止条件2:字符串比较到两个字符串的末尾 ('\0')
这个情况时,两个字符串相等。
模拟实现
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
strncpy
函数讲解:与strcpy类似,不过这个函数可以选择复制的长度。
char* strncpy(char* destination, const char* source, size_t num);
strncpy这个函数的参数是两个字符指针和一个无符号整形(复制的长度),返回的是destination这个字符指针;
需要注意以下几点 :1.拷贝 num 个字符从源字符串到目标空间。通过观察vs编译器中strncpy的使用(读者可以自己观察试试):1.如果源字符串的长度小于 num ,则拷贝完源字符串之后,在目标的后边追加 0 ,直到 num 个。2.不管怎么样只会复制num个数,不会主动加\0
模拟实现
char* my_strncpy(char* dest, const char* src, size_t num)
{
int is_end = 0;
for (int i = 0; i < num; i++)
{
if (is_end == 1)
{
*(dest + i) = '\0';
}
else
{
if (*(src + i) != '\0')
{
*(dest + i) = *(src + i);
}
else
{
*(dest + i) = '\0';
is_end = 1;
}
}
}
return dest;
}
strncat
函数讲解:与strcat类似,不过这个函数可以选择追加的长度。
char* strncat(char* destination, const char* source, size_t num);
strncat这个函数的参数是两个字符指针和一个无符号整形(追加的长度),返回的是destination这个字符指针;
通过观察vs编译器中strncat的使用(读者可以自己观察试试):1.strncat 遇到\0就会停止追加,要是没遇上就会自动加一个\0。
模拟实现:
char* my_strncat(char* dest, const char* src, size_t num)
{
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
int is_end = 0;
for (size_t i = 0; i < num; i++)
{
if (*(src + i) != '\0')
{
*(dest + i) = *(src + i);
}
else
{
*(dest + i) = '\0';
is_end = 1;
break;
}
}
if (is_end == 0)
{
*(dest + num) = '\0';
}
return ret;
}
strncmp
函数讲解: 与strcmp类似,不过这个函数有比较长度的限制。
int strncmp(const char* str1, const char* str2, size_t num);
strncmp这个函数的参数是两个字符指针和一个无符号整形(比较的长度),返回的是一个整形,整形的意义和strcmp的返回值一样。
通过观察vs编译器中strncmp的使用(读者可以自己观察试试):
1.比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
模拟实现:
//比较简单,和strcmp类似。读者可以自己尝试
strstr
函数讲解:在一个字符串中寻找另一个字符串
char* strstr (const char* str1, const char* str2);
这个函数的参数是两个字符指针;返回的是一个字符指针,如果在str1中找到了str2,则会返回str1中的那段串的第一个字符;如果没找到,则会返回空指针。
模拟实现
char* my_strstr(const char* arr1, const char* arr2)
{
assert(arr1 && arr2);
char* cp = (char*)arr1;
char* s1 = NULL;
char* s2 = NULL;
while (*cp)
{
s1 = cp;
s2 = (char*)arr2;
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;
}
cp++;
}
return NULL;
}
关键:用cp指针记录每次寻找,没找到则指针加1;
strtok
函数讲解:用一个字符集合分割字符串。
char* strtok (char* str, const char* sep);
函数的参数是两个字符指针,返回值也是字符指针。
需要注意以下几点:
1. sep 参数是个字符串,定义了用作分隔符的字符集合。2. str 参数指定一个字符串,它包含了 0 个或者多个由 sep 字符串中一个或者多个分隔符分割的标 记。3. strtok 函数找到 str 中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok 函数会改变被操作的字符串,所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容 并且可修改。)4. strtok 函数的第一个参数不为 NULL ,函数将找到 str 中第一个标记, strtok 函数将保存它在字符串 中的位置。5. strtok 函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。6. 如果字符串中不存在更多的标记,则返回 NULL 指针。
模拟实现:无
memcpy
函数讲解:这是可以看作一个拷贝函数,与strcpy类似,但不仅限于拷贝字符串。
void* memcpy(void* destination,const void* source, size_t num);
函数的参数是两个void*类型的指针,和一个代表复制的字节长度的无符号整形。返回值也是一个void*的指针,是目标的起始空间;
需要注意以下几点:
1. 函数 memcpy 从 source 的位置开始向后复制 num 个字节的数据到 destination 的内存位置。2. 复制的长度只与num相关,在遇到 '\0' 的时候并不会停下来,3. 如果 source 和 destination 有任何的重叠,复制的结果都是未定义的(可能成功,也可能失败)。
模拟实现:
void* my_memcpy(void* dest, void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
这个模拟实现的关键 是把void*类型 强制类型转换 成char* 进行操作;
memmove
函数讲解:看名字可以知道这个函数可以理解为内存移动函数。这个函数与memcpy类似,只不过source和destination有重叠,也可以进行复制。
void* memmove(void* destination, const void* source, size_t num);
需要注意以下几点:
1. 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2. 在复制时,如果源空间和目标空间出现重叠,就得使用memmove函数处理。
模拟实现:
void* my_memmove(void* dest, void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
关键:根据在src中要复制的元素,要在修改前就要复制,来决定时从后往前复制还是从后往前复制(画图即可弄明白)。
memset
函数讲解:把内存中的每一个字节赋值一个相同的值。
void* memset(void* ptr, int value, size_t num);
ptr指针:指向的内存块地址; value:赋的值(可以是字符,原因:字符以ASCII码的形式存储);num:需要修改的字节数。
需要注意以下几点:
1. 是修改内存中的每一个字节为value,单位为一个字节。
2. int arr[10] = { 0 };memset(arr, 1, sizeof(arr));
这段代码并不能把arr数组中每个元素赋值为1, 而赋值是一个很大的数。
3. 如果value是0,则无这方面的问题。
模拟实现:无
memcpy
函数讲解:与strcpy类似,但又不仅限于比较字符串。
int memcmp(const void * ptr1, const void * ptr2, size_t num);
无符号的num:比较的字节数; int返回值:与strcpy类似;
需要注意以下几点:
1. 比较从ptr1和ptr2指针开始的num个字节