一、求字符串长度
strlen
定义:size_t strlen ( const char * str );
接收的参数是目标字符串首字符的地址,返回值类型是无符号整型
对size_t转到定义就知道它属于unsigned int的重命名类型,见下图:
- 字符串以隐藏的 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
- 参数指向的字符串必须要以 '\0'结束。
- 注意函数的返回值为size_t,是无符号的( 易错 )
显然,str2<str1,但为什么打印的结果却相反呢?这是因为strlen函数的返回值是无符号整型,两个无符号整型相减得到的结果自然也是无符号整型,因此编译器会认为内存中strlen(str2) - strlen(str1)的结果是无符号整型,会是一个非常大的正数 ,虽然会造成这样的漏洞,但函数本身的设计是合理的,因为长度本身不可能是负数,这就要求我们在使用时要注意,这里的判断可以直接用if(strlen(str2) >strlen(str1))。
- 学会strlen函数的模拟实现
既然是模拟实现,那么接收的参数类型以及返回类型就要和模拟对象一样,因此,对my_strlen函数的定义为:size_t my_strlen(const char*str),其实现见下列代码:
#include <stdio.h>
size_t my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str != '\0')
{
count++;
str++;
};
return count;
}
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (my_strlen(str2) >my_strlen(str1) )
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}
关于my_strlen()函数的实现除了上面的计数器版本,还有可以用递归和指针-指针来实现,详细见下面链接(在目录:指针运算下的指针-指针)
二、长度不受限制的字符串函数
strcpy:字符串拷贝
定义:char* strcpy(char * destination, const char * source );
接收的第一个参数是目标地的地址;第二个参数是源字符串地址,返回值是拷贝完成后目目标地的首字符地址。
- Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).【拷贝sorce指向的字符串到destination指向的数组,拷贝包括结束标志‘\0’,并且在此时结束拷贝】
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变,一般是一个数组。
- 学会模拟实现。
对my_strcpy函数的定义为:char* my_strcpy(char* dest,const char*sor),其实现见下列代码:
strcat:字符串追加
定义:char* strcat(char * destination, const char * source );
接收的第一个参数是目标字符串的地址;第二个参数是源字符串地址,返回值是追加完成后目目标地的首字符地址。
- Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.(将源字符串的副本追加到目标字符串。目标字符串的终止空字符‘\0’被源字符串的第一个字符覆盖,追加时包括源字符串的‘\0’,再由目标中两者的串联形成的新字符串。)
- 源字符串必须以 '\0' 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 字符串自己给自己追加,如何?
不可以,因为在自己追加自己时,首先要拿第一个字符来覆盖‘\0’,当字符全部拿完,需要‘\0’停下来的时候,发现‘\0’早就被覆盖了,因此没有办法停下来,就会陷入死循环。
- 学会模拟实现
对my_strcat函数的定义为:char* my_strcat(char* dest,const char*sor),其实现见下列代码:
strcmp:字符串比较
- This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.(此函数开始比较两字符串的第一个字符,如果它们彼此相等,则继续使用以下对,直到字符不同或达到终止空字符,找到不同字符后,ASCII码值较大的此字符串就大,若到达‘\0’则说明两字符串相等)。
- 标准规定: 第一个字符串大于第二个字符串,则返回大于0的数字 ;第一个字符串等于第二个字符串,则返回0 ;第一个字符串小于第二个字符串,则返回小于0的数字
- 那么如何判断两个字符串?
- 学会模拟实现
对my_strcmp定义为 int my_strcmp ( const char * str1, const char * str2 ),其实现见下列代码:
三、长度受限制的字符串函数介绍
strncpy
- Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.(将源字符串的字符复制到目标空间,如果在复制 num 个字符之前找到源 C 字符串的末尾,即‘\0’,则用零填充目标,直到向其写入了总共 num 个字符)
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
strncat
- Appends the first num characters of source to destination, plus a terminating null-character. If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.(将源字符串的第一个字符追加到目标空间字符串,包括终止空字符。如果源中 C 字符串的长度小于 num,则仅复制终止空字符及之前的内容。)
*我们还发现可以利用strncat函数来自己追加自己,这是strcat函数不能实现的。
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
-
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。

四、字符串查找
strstr
- Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.(返回指向 str1 中第一次出现的 str2 的指针,或者返回空指针NULL如果str2中没有str1.)
- 模拟实现strstr
对my_strstr定义为:char* my_strstr(const char*str1,const char*str2),其实现见下列代码:
strtok:切割字符串(比较特殊)
定义:char * strtok ( char * str, const char * sep );
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针
五、错误信息报告
strerror

通常用于,当一个任务执行失败后,我们想知道原因,这个函数就可以帮我们打印原因
见下例 :
六、字符操作
字符转换函数:
返回值和参数都是整型,是因为大小写字母在内存中都以ASCII码值存储,ASCII码是整型。
实例:将字符串中的大写字符换成相应的小写字母后打印出来
七、内存操作函数
memcpy:不同内存空间数据的拷贝
void * memcpy ( void * destination, const void * source, size_t num );
空指针可以接收返回任意类型数据的地址,体现其相比于strcpy应用的广泛性。三个参数分别是,拷贝目标空间的地址,源数据开始的地址,要拷贝的数据数量,单位是字节,返回值是拷贝完成后目标空间的地址。
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
注意:拷贝的时候一定要保证目标空间足够大。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的
C语言中规定,memcpy,只需要实现不同内存空间之间的数据拷贝即可,不同编译器对其内部设计有差异,因此如果要对同一块内存空间的数据(可能出现重叠)进行处理,不同编译器下效果是不同的,如下例:
这种有重叠的情况我们可以看到VS编译器中可以达到我们想要的效果,但如果换一个编译器就可能出现arr1[]={1,2,1,2,1,2,10}的情况,原因如下:
因此为了我们的代码在任何环境下都能正确运行,我们通常不用memcpy来处理同一块内存空间的数据,而用memmove函数来实现,重叠内存之间的数据拷贝,见以下讲解。
memmove:同一块内存空间数据的拷贝
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
我们来模拟实现memmove的功能:
在设计之前,我们首先应该考虑到同一块内存空间中拷贝数据的实现方法以及所有情况,因此会有以下考虑:
1、拷贝的实现,根据原memmove中参数num的意义,可知从源数据起始地址拿数据的时候是一个字节一个字节拿取的,直到拿完num个字节,而字节又恰好是所有数据类型中的最小单位,自然而然我们可以将接收到的指针全部强制转换为char*,之后我们通过一个字节一个字节的访问,可以实现任意字节大小内容的拷贝。
2、拷贝的情况可大分为两类,有重叠内容,和无重叠内容,而有重叠内容中,又分为前面内容拷贝到后面和后面内容拷贝到前面,因此可以细分为三类,通过观察,我们发现,对于前面内容拷贝到后面的情况,我们按字节从后往前拷贝就不会出现覆盖有效数据的情况,而对于后面内容拷贝到前面,恰恰相反,按字节从前往后拷贝不会出现覆盖有效数据的情况,对于无重叠内容的拷贝,则不用关心拷贝顺序,可以与上面两种情况的任意一种归为一类。
考虑到这些情况,我们就可以设计my_memmove函数了:
void* my_memmove(void* dest, const void* sor, int num)
{
char* s1 = (char*)dest;
char* s2 = (char*)sor;
//首先判断是三种情况
if (dest > sor)//说明是前面的内容拷贝到后面,应该按字节从后往前拷贝
{
while (num--)
{
*(s1 + num) = *(s2 + num);
}
}
//下面的逻辑就是按字节从前往后拷贝
else
{
while (num--)//先判断后--,一共拷贝num次
{
*s1 = *s2;
s1++;
s2++;
}
}
return dest;
}
int main()
{
int arr1[] = { 1,2,5,7,3,9,10 };
my_memmove(arr1 + 2, arr1, 16);//前面内容拷贝到后面
//my_memmove(arr1 , arr2, 16);//后内容拷贝到前面
int i = 0;
for (i = 0; i <7; i++)
{
printf("%d ", arr1[i]);
}
}
memcmp:按字节依次比较内存中的顺序
- 比较从ptr1和ptr2指针开始的num个字节
- 按字节比较内存中两组数据内容,直到找到不同或结束,返回值同strcmp
memset:填充内存块
void * memset ( void * ptr, int value, size_t num );
- 将 ptr 所指向的内存块的num个字节数设置为指定值(解释为无符号字符)。
- 第二个参数,要设置的值,该值作为 int 传递,但该函数使用此值的无符号 char 转换来填充内存块。
因此此段代码不能实现把arr2数组内容全部填充为1 。
对于字符串和内存函数的使用还有两点需要注意:
1、因为在操作某些内容时,我们不想让原内容改变,所以保险起见,我们通常把如字符串等不能被改变的量用const修饰,尤其是在模拟实现这些函数时,参数类型最好也像原函数一样适当用const来修饰。
2、在使用传来的指针参数之前,我们最好使用断言assert来确保传来的指针都不是空指针,再继续向下运行,本篇博文中后面的例子,我都忘记加了,大家不要学我~
下面放一个正确使用的例子:
#include <stdio.h>
size_t my_strlen(const char* str)
{
assert(str);//断言:确保不是空指针
int count = 0;
while (*str != '\0')
{
count++;
str++;
};
return count;
}
int main()
{
const char* str1 = "abcdef";//用const来修饰不能被改变的量
const char* str2 = "bbb";
if (my_strlen(str2) >my_strlen(str1) )
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}