目录
1、求字符串长度函数
1.1、strlen
2、字符串拷贝(cpy)、拼接(cat)、比较(cmp)函数
2.1、长度不受限制的字符串函数
2.1.1、strcpy
2.1.2、strcat
2.1.3、strcmp
2.2、长度受限制的字符串函数
2.2.1、strncpy
2.2.2、strncat
2.2.3、strncmp
3、字符串查找函数
3.1、strstr
3.2、strtok
4、错误信息报告函数
4.1、strerror
4.2、perror
5、字符函数
5.1、字符分类函数
5.2、字符转换函数
5.2.1、tolower
5.2.2、toupper
6、内存操作函数
6.1、memcpy
6.2、memmove
6.3、memset
6.4、memcmp
1、求字符串长度函数
1.1、strlen
- strlen用于求字符串长度。
- 包含头文件<string.h>。
- 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
- 参数指向的字符串必须要以 '\0' 结束。
注意:
1、函数的返回值为size_t,是无符号的( 易错 )
2、因为strlen返回的是 '\0' 前面的字符个数,如果字符串中间本身就一个'\0',那么返回的值就会返回字符串中的'\0'之前的字符个数。
例如:"abc\0def" 这个字符串,使用strlen函数会返回3。
【使用方式】
int main()
{
char arr[] = "Hello hacynn";
int ret = strlen(arr);
printf("%d\n", ret);
return 0;
}
【运行结果】
【易错提醒】
请问ret的值是多少?
int ret = strlen("abc") - strlen("abcdef");
答案是3,因为函数的返回值为size_t,是无符号的整型。
【模拟实现strlen】
int my_strlen(char* arr)
{
int count = 0;
while (*arr != '\0')
{
count++;
arr++;
}
return count;
}
int main()
{
char arr[] = "Hello hacynn";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
2、字符串拷贝(cpy)、拼接(cat)、比较(cmp)函数
2.1、长度不受限制的字符串函数
2.1.1、strcpy
- strcpy用于拷贝字符串,将字符串2拷贝到字符串1当中。
- 包含头文件<string.h>。
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
【使用方法】
int main()
{
char arr1[] = "Hello hacynn";
char arr2[20] = { 0 };
strcpy(arr2,arr1);
printf("%s\n", arr2);
return 0;
}
【运行结果】
【模拟实现strcpy】
char* my_strcpy(char* dest,const char* src)
{
char* ret = dest;
while (*dest = *src)
{
dest++;
src++;
}
return ret;
}
int main()
{
char arr1[] = "Hello hacynn";
char arr2[20] = { 0 };
my_strcpy(arr2,arr1);
printf("%s\n", arr2);
return 0;
}
2.1.2、strcat
- strcat用于拼接两个字符串,将字符串2拼接到字符串1末尾。
- 包含头文件<string.h>。
- 源字符串必须以 '\0' 结束(保证找得到目标空间的末尾),在拷贝时会把源字符串的 '\0 '也拷贝过去。
- 目标空间必须有足够的大,能容纳下源字符串的内容,并且还可以被修改。
注意:
不能字符串自己追加自己,因为当自己追加自己的时候,追加的过程中会将目标字符串的 '\0' 覆盖掉,而有因为此时目标字符串就是源字符串,就会导致源字符没有 '\0' ,将会一直拼接下去导致死循环。
虽然有些环境中该函数可以完成自己拼接自己,但是C语言的标准中并未规定strcat可以自己拼接自己,所以这个函数最好不要使用在自己拼接自己的情况下。如果真有自己追加自己的场景,建议使用strncat函数,这个函数将在下文进行讲解。
【使用方式】
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "hacynn" ;
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
【运行结果】
【模拟实现strcat】
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
//找到目标空间的末尾
while (*dest != '\0')
{
dest++;
}
//数据追加
while (*dest = *src)
{
dest++;
src++;
}
return ret;
}
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "hacynn" ;
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
2.1.3、strcmp
- strcmp用于比较两个字符串。
- 包含头文件<string.h>。
- 误区:该函数不是比较字符串长度的,而是比较对应位置上字符的大小(ASCII)。
- 标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
【使用方式】
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abz";
if (strcmp(arr1, arr2) > 0)
printf(">\n");
else
printf("<=\n");
return 0;
}
【运行结果】
【模拟实现strcmp】
int my_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abz";
if (my_strcmp(arr1, arr2) > 0)
printf(">\n");
else
printf("<=\n");
return 0;
}
2.2、长度受限制的字符串函数
- 就是可以限制操作个数的字符串函数。
- 包含头文件<string.h>。
2.2.1、strncpy
- 区别仅与strcpy差一个参数,记录要操作的个数。
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
- 因为拷贝个数由用户自己决定,因此\0没有被拷贝过来的可能性也是有的。
【使用方式】
int main()
{
char arr1[] = "Hello hacynn";
char arr2[20] = { 0 };
strncpy(arr2, arr1, 5); //拷贝前五个字符 ,此时拷贝\0后arr2中并不会有\0
printf("%s\n", arr2);
return 0;
}
【运行结果】
【特殊情况】
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。如下:
int main()
{
char arr1[] = "Hello";
char arr2[20] = "xxxxxxxxxxxxxxxxx";
strncpy(arr2, arr1, 10); //此时10大于arr1的元素个数,就会在后添加0直至够10个
printf("%s\n", arr2);
return 0;
}
2.2.2、strncat
- 区别也仅与strcat差一个参数,记录要操作的个数。
- 使用strncat追加,当结束追加时,就算没到\0,也会在末尾追加一个\0。
- 如果源字符串的长度小于num,则追加完源字符串之后,会自动停止追加。注意此处与strncpy的区别。
- 包含头文件<string.h>。
【使用方式】
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "hacynn" ;
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}
【运行结果】
2.2.3、strncmp
- 区别也仅与strcmp差一个参数,记录要操作的个数。
- 包含头文件<string.h>。
【使用方式】
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcz";
if (strncmp(arr1, arr2, 3) > 0) //只比较前三个字符
printf(">\n");
else if (strncmp(arr1, arr2, 3) == 0)
printf("=\n");
else
printf("<\n");
return 0;
}
【运行结果】
3、字符串查找函数
3.1、strstr
- 查找一个字符串中是否存在与另一个字符串当中,即找子串。
- 返回一个指向str1中第一个出现str2的指针,如果str2不是str1的一部分,则返回一个空指针NULL。
- 包含头文件<string.h>。
【使用方式】
可以看到,即使是有两个字串 ,也只会返回第一次出现的地址。
int main()
{
char arr1[] = "abcdefghidef"; //def出现了两次
char arr2[] = "def";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
printf("找不到\n");
else
printf("%s\n", ret);
return 0;
}
【运行结果】
【模拟实现strstr】
const char* my_strstr(const char* str1, const char* str2)
{
if (*str2 == '\0')
return str1;
char* pc = str1; //pc用于记录开始匹配的位置
while (*pc)
{
char* s1 = pc; //遍历str1指向的字符串
char* s2 = str2; //遍历str2指向的字符串
while (*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
return pc;
pc++;
}
return NULL;
}
int main()
{
char arr1[] = "abcdefghidef";
char arr2[] = "def";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
printf("找不到\n");
else
printf("%s\n", ret);
return 0;
}
【图解】
3.2、strtok
比较奇葩的一个函数
char * strtok ( char * str, const char * delimiters );
- 切割字符串函数,例如hacynn@nash.com,当切割标记是@和 . 时,通过三次合理的使用可以切割出三个字符串:hacynn nash com
- 包含头文件<string.h>。
- delimiters参数是个字符串,定义了用作分隔符的字符集合。
- 第一个参数指定一个字符串,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针
【使用方式】
int main()
{
char arr[] = "hacynn@nash.com";
char buf[200] = { 0 }; //因为strtok会改变被操作字符串,
//所以拷贝一个临时变量来操作
strcpy(buf, arr);
char* p = "@.";
char* s = strtok(buf, p); //参数不为NULL,找到第一个标记
printf("%s\n", s);
s = strtok(NULL, p); //参数为NULL,找到下一个标记
printf("%s\n", s);
s = strtok(NULL, p); 参数为NULL,找到下一个标记
printf("%s\n", s);
return 0;
【运行结果】
【使用方式优化 】
在实际开发中,我们不一定知道这个字符串是怎样的,这个字符串需要切割几次的,因此手动设置切割几次将代码写死的方式是不可取,而应该使用以下的方式进行自动切割。
int main()
{
char arr[] = "hacynn@nash.com.hahaha@abcd";
char buf[200] = { 0 };
strcpy(buf, arr);
char* p = "@.";
char* s = NULL;
for (s = strtok(buf, p); s != NULL; s = strtok(NULL, p))
{
printf("%s\n", s);
}
return 0;
}
这里巧妙的运用了for函数的初始化部分只执行一次的特点,而strtok也只需要第一次传地址,其他时候都只需要传NULL就行。
【优化后的运行结果】
4、错误信息报告函数
4.1、strerror
- strerror函数是将错误码翻译成错误信息,返回错误信息的字符串起始地址。
- 包含头文件<string.h>。
- C语言中使用库函数的时候,如果发生错误,就会将错误码放在errno的变量中,errno是一个 全局变量,可以直接使用。
【错误码举例】
int main()
{
int i = 0;
for ( i = 0; i < 10; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
每一个错误码都对应一个错误信息
【使用方式】
以打开文件为例子,fopen以读的形式打开文件,当文件存在时打开成功,文件不存在时打开失败,并返回空指针。可以利用这个来设置一个打开失败时的错误信息告知。
int main()
{
FILE* pf = fopen("add.txt", "r"); //当前文件路径中并没有add.txt文件,打开失败
if (pf == NULL)
{
printf("打开文件失败,原因是:%s\n", strerror(errno));
return 1;
}
else
{
printf("打开文件成功\n");
}
return 0;
}
【运行结果】
4.2、perror
- perror也是用于翻译错误信息 ,但与strerror不同的是,perror会直接打印错误码所对应的错误信息。而perror中传递的字符串参数就是自定义显示信息的部分,打印的结果就是 自定义显示信息:错误信息
- 包含头文件<stdlib.h>
- 可以简单理解为:perror = printf + strerror 即翻译又打印
【使用方式】
int main()
{
FILE* pf = fopen("add.txt", "r");
if (pf == NULL)
{
perror("打开文件失败"); //注意:此处是perror,不是printf。
return 1;
}
else
{
printf("打开文件成功\n");
}
return 0;
}
【运行结果】
5、字符函数
5.1、字符分类函数
字符分类函数使用非常简单,由于篇幅受限,在这里不就一一列举了 ,只需要把下面的图看懂就行。
5.2、字符转换函数
5.2.1、tolower
这个函数听名字就知道是用于将大写字母转换成小写字母,而这类函数唯一需要注意的就是函数有返回值,返回类型为int,因此在使用的时候最好使用一个int ret接收返回值。
int main()
{
int ret = tolower('A');
printf("%c\n", ret);
}
5.2.2、toupper
小写字母转大写字母,其他注意点与tolower一致。
6、内存操作函数
上文讲到的字符串函数只适用于字符串,但是内存中的数据不仅仅只有字符,这就导致这些函数有很大的局限性。因此需要有一个能够对所有类型的数据都适用的函数,这就是内存操作函数的出现的原因。下面我们来学习一下内存操作函数。
6.1、memcpy
- 函数memcpy从source的位置开始向后拷贝num个字节的数据到destination的内存位置。
- 包含头文件<string.h>
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
- 因为C语言标准中并未规定memcpy能适用于重叠内存的拷贝,因此不重叠内存的拷贝才使用memcpy,而重叠内存的拷贝使用接下来讲解的memmove函数。
【使用方式】
使用memcpy拷贝整型数据。
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5 };
memcpy(arr1, arr2, sizeof(int) * 5);
int i = 0;
for ( i = 0; i < 5; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
【运行结果】
【模拟实现memcpy】
void* my_memcpy(void* dest, const void* src, size_t sz)
{
void* ret = dest;
while (sz)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
sz--;
}
return ret;
}
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5 };
my_memcpy(arr1, arr2, sizeof(int) * 5);
int i = 0;
for ( i = 0; i < 5; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
6.2、memmove
- memmove的参数和功能与memcpy完全一致。
- 包含头文件<string.h>
- 唯一有区别的就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 因此当出现重叠内存的拷贝时,就使用memmove函数处理。
【模拟实现memmove】
void* my_memmove(void* dest, const void* src, size_t sz)
{
void* ret = dest;
if (dest < src)
{
while (sz)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
sz--;
}
}
else
{
while (sz--)
{
*((char*)dest + sz) = *((char*)dest + sz);
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5 ,6,7,8,9,10 };
my_memmove(arr1, arr1+2, sizeof(int) * 5);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
6.3、memset
- 将ptr所指向空间的前num个字节设置为指定值value。
- 包含头文件<string.h>
【使用方式】
int main()
{
char arr[] = "hello world";
memset(arr + 6, 'x', 3);
printf("%s\n", arr);
return 0;
}
【运行结果】
6.4、memcmp
- 比较ptr1和ptr2前num个字节的内容。
- 包含头文件<string.h>
- 标准规定:
ptr1大于ptr2,则返回大于0的数字。
ptr1等于ptr2,则返回0。
ptr1小于ptr2,则返回小于0的数字。
【使用方式】
int main()
{
int arr1[] = { 1,2,3,4,5,6,7 };
int arr2[] = { 1,2,3,7 };
int ret = memcmp(arr1, arr2, sizeof(int) * 3);
printf("%d\n", ret);
}
【运行结果】
如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!
如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!
如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!