字符串函数,字符函数和内存函数
一、字符串函数
1.strlen
1.1strlen库函数文档
它的参数类型是constchar*类型的,这是考虑到求字符串长度不会改变原字符串的大小。
返回类型是size_t类型的,这是因为计算的长度不可能为负数。
这个函数返回的是字符串的长度,传入一个地址,计算的是\0字符之前的长度。
1.2strlen的使用及注意事项
- 字符串以\0作为结束标志,strlen函数返回的是\0前面出现的字符个数
- 参数指向的字符串必须以\0结束
- 注意函数的返回类型是size_t,是无符号的
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcd\0efg";
char arr3[] = { 'a','b','c'};
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
printf("%d\n", strlen(arr3));
return 0;
}
注意函数的返回类型是size_t,是无符号的
int main()
{
if (strlen("abcd") - strlen("abcdefgh") > 0)
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
1.3strlen的模拟实现
在这里我们用三种方法来实现:计数器、递归、指针-指针
//模拟实现strlen
//计数器
#include<assert.h>
int my_strlen1(const char* str)
{
int count = 0;
assert(str);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
//递归
// 不允许创建临时变量
int my_strlen2(const char* str)
{
assert(str);
if (*str != '\0')
{
return 1 + my_strlen2(str + 1);
}
else
{
return 0;
}
}
//指针-指针
int my_strlen3(const char* str)
{
assert(str);
const char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "hello";
printf("%d\n", my_strlen1(arr));
printf("%d\n", my_strlen2(arr));
printf("%d\n", my_strlen3(arr));
return 0;
}
2.strcpy
2.1strcpy的库函数文件
- 这个函数有两个参数chardestination ,const char source源头空间不必被修改,所以加上const修饰。
- 它的功能是将source处的字符拷贝到destination处
- 返回类型是char*,返回destination处的地址。
2.2strcpy的使用及注意事项
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
int main()
{
char arr1[] = "circle";
char arr2[20] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
会将源字符串中的 ‘\0’ 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。
2.3strcpy的模拟实现
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
3.strcat
3.1strcat的库函数文件
- 这个函数有两个参数chardestination ,const char source源头空间不必被修改,所以加上const修饰。
- 它的功能是将source处的字符追加到destination的后面。
- 返回类型是char*,返回destination处的地址。
3.2strcat的使用以及注意事项
- 源字符串必须以 ‘\0’ 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 字符串自己给自己追加,会陷入死循环。
3.3strcat的模拟实现
#include<assert.h>
#include<stdio.h>
#include<string.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//找到\0的位置
while (*dest != '\0')
{
dest++;
}
//追加
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "world";
char arr2[20] = "hello ";
printf("%s\n", my_strcat(arr2, arr1));
return 0;
}
3.4strcat自己给自己追加
不能实现自己对自己追加,会陷入死循环。
假设下面的字符串对自己追加的话
\0会被修改为a,后面的都会覆盖过去,这样我们就永远找不到\0的位置,最终导致分配的空间被使用完,使用了未分配的空间而崩溃
下面是我们模拟用strcat自己对自己追加后的效果
我们可以看到似乎没有出现这个问题,这个是vs对这个问题进行了优化,但是在其他的ide上可能就会出现这个死循环问题,导致程序出现bug
4.strcmp
4.1strcmp的库函数文件
- 这个函数的两个参数都是const char*的,因为我们只会查看,并不会修改。
- 这个函数的作用是比较两个字符串的大小,比较规则是从第一个字符开始依次比较,一个字符一个字符的比较,谁的ASCII值大谁就大,如果相等就比较后一个字符。
- 这个函数的返回类型是int类型的,第一个字符串大于第二个字符串,则返回大于0的数字 第一个字符串等于第二个字符串,则返回0 第一个字符串小于第二个字符串,则返回小于0的数字,
- 在vs中,大于返回1,等于返回0,小于返回-1。但在其他编译器上不一定成立,
4.2strcmp的使用
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcd";
char arr2[] = "abc";
printf("%d\n", strcmp(arr1, arr2));
return 0;
}
4.3 strcmp的模拟实现
#include<assert.h>
#include<stdio.h>
#include<string.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
//在一些编译器上
//return *str1 - *str2;
}
int main()
{
char arr1[] = "abcd";
char arr2[] = "abc";
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}
4.4注意事项(函数不安全的原因)
strcpy,strcat,ctrcmp这些函数都是不安全的函数,因为它们时长度不受限制的函数,如果目标空间不够大,就会出现问题。
所以我们使用时,会在开头使用一个预处理指令
#define _CRT_SECURE_NO_WARNINGS
而为了让这些函数相对安全些,我们有了长度受限制的函数strncpy,strncat,strncmp
5.strncpy
5.1strncpy的库函数文件
- 这个函数有三个参数chardestination, const charsource, size_t num。
- char*destination是目标空间的地址。
- const char*source是源头的地址,这个地址不可修改。
- size_t num是要拷贝几个字节,类型是size_t,因为拷贝的字节个数不是负数。
- 这个函数的意思是拷贝source的前num个字节到destination中,返回类型是char*返回的是destination的地址。
5.2strncpy的使用以及注意事项
- 拷贝num个字符从源字符串到目标空间。
- 如第一个图所示,如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
- 如图2所示,不会将最后的\0拷过来,而是需要几个拷贝几个
5.3 strncpy的模拟实现
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
//num为0导致的结束,不用加\0
while (num && (*dest++ = *src++))
{
num--;
}
//源头字符串已经拷贝完了,num还没为0导致的结束,我们需要添加\0
if (num)
{
while (--num)
{
*dest++ = '\0';
}
}
return ret;
}
int main()
{
char arr1[] = "hello";
char arr2[] = "xxxxxxxxxx";
printf("%s\n", my_strncpy(arr2, arr1, 10) );
return 0;
}
6.strncat
6.1strncat的库函数文件
- 这个函数的参数和返回类型和strncpy的是一样的,
- 不同的是函数的功能是追加source的前num个字符到destination中
- 追加num个字符后还需在后面补充一个\0
6.2strncat的使用以及注意事项
- 对于这个函数,我们需注意的是追加之后后面会补充一个\0,如第一个图所示。
- 即使num超出了source的范围,也只会补充一个\0,而不是多个,如第二个图所示
6.3strcat的模拟实现
#include<assert.h>
#include<stdio.h>
#include<string.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
//找到\0的位置
while (*dest != '\0')
{
dest++;
}
//追加
while (num--)
{
*dest++ = *src++;
//追加到\0停止
if ((*dest++ = *src++) == '\0')
{
return ret;
}
}
//num超出了source的范围,也只会补充一个\0
*dest = '\0';
return ret;
}
int main()
{
char arr1[] = "xxx\0xxxxxx";
char arr2[] = "hello";
printf("%s\n", my_strncat(arr1, arr2, 10));
return 0;
}
7.strncmp
7.1strncmp的库函数文件
函数的功能是比较前n个字符的大小,返回一个值,因为不会修改字符串的内容,所以参数加上const修饰
如果str1大,返回大于0的数
如果str1小,返回小于0的数
如果相等,返回0
在这里插入图片描述
7.2strncmp的使用以及注意事项
比较到两个字符不一样或一个字符串结束或者num个字符比完
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcfda";
int ret = strncmp(arr1, arr2, 3);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcfda";
int ret = strncmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcde";
char arr2[] = "abc";
int ret = strncmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}
8.strstr
8.1strstr的库函数文件
- 这个函数有两个参数,都是const char* 类型的
- 功能是子str1中查找是否存在str2字符串 返回一个const char*型的地址,
- 如果存在,则返回在str1中第一次出现str2的地址,
- 如果不存在,则返回NULL
8.2strstr的使用以及注意事项
#include<stdio.h>
#include<string.h>
int main()
{
char arr1 []= "abcdefg";
char arr2 []= "bcd";
char* ret = strstr(arr1, arr2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("找不到\n");
}
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char arr1 []= "abcdefg";
char arr2 []= "acd";
char* ret = strstr(arr1, arr2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("找不到\n");
}
return 0;
}
8.3 strstr的模拟实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0')
{
return (char*)str1;
}
char* s1 = NULL;
char* s2 = NULL;
char* cp = str1;
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;
}
cp++;
}
return NULL;
}
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "bcd";
char* ret = my_strstr(arr1, arr2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("找不到\n");
}
return 0;
}
9.strtok
9.1strtok的库函数文件
- 这个函数有两个参数,str是目标字符串,需要被修改
- delimiters是一个字符串,用作分隔符的集合。
- 函数的功能是在str字符串中找到delimiters这个集合中的任意一个字符,将最先出现的这个字符修改为\0,并且返回这个分割好的字符串的地址,
- 并且内部有一个静态变量记录之前切割好的地址。
- 这个str可以是null,如果传的是空指针,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
9.2strtok的使用以及注意事项
- sep参数是个字符串,定义了用作分隔符的字符集合 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
//strtok
int main()
{
char arr[] = "joshua@say.yes/net";
char buf[30] = { 0 };
strcpy(buf, arr);//strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
const char* p = "@./";//sep参数是个字符串,定义了用作分隔符的字符集合
char* str = strtok(buf, p);
printf("%s\n", str);//joshua
//strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
//中的位置。
str = strtok(NULL, p);//say
printf("%s\n", str);
//strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
//记
str = strtok(NULL, p);//yes
printf("%s\n", str);
str = strtok(NULL, p);//net
printf("%s\n", str);
//如果字符串中不存在更多的标记,则返回 NULL 指针。
return 0;
}
这样重复利用一个函数可能有点繁琐,我们可以用一个for循环来实现上面的多次调用
int main()
{
char arr[] = "joshua@say.yes/net";
char buf[30] = { 0 };
strcpy(buf, arr);
const char* p = "@./";
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n",str);
}
}
10.strerror
10.1strerror的库函数文件
- 这个函数会接受一个整形的数字,这个数字是一个错误码,然后返回一个字符串的地址,我们通过打印这个字符串来显示我们的错误信息。
- c语言运行时如果发生错误,就会将错误码放在errno这个变量中,这个函数可以将错误码翻译成字符串。
10.2strerror的使用以及注意事项
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
return 0;
}
#include<errno.h>
int main()
{
//打开文件
// //打开文件的时候,如果文件的打开方式是"r"
// //文件存在则打开成功,文件不存在打开失败
// //打开文件失败的话,会返回NULL
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//读写文件
//关闭文件
fclose(pf);
return 0;
}
10.3 perror的使用
- perror和strerror很相似,但是perror可以自己将错误信息打印出来。
- perrror=printf+strerror
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 0;
}
fclose(pf);
return 0;
}
二、字符函数
1.字符分类函数
2.字符转换函数
这两个函数不会改变字符本身的值,而是返回转换后的值
3.利用字符函数,将字符串中的大写字母改为小写打印出来
#include<stdio.h>
#include<string.h>
#include<ctype.h>
int main()
{
char arr[] = "Our Dawn Is Hotter Than Day";
int i = 0;
int len = strlen(arr);
for (i = 0; i < len; i++)
{
if (isupper(arr[i]))
{
arr[i] = tolower(arr[i]);
}
printf("%c", arr[i]);
}
return 0;
}
三、内存函数
我们已经有了字符串的操作函数,但是这些函数只能用于字符串,但我们有时需对整形数组等进行类似字符串的操作,这时内存函数就起作用了。
1.memcpy
1.1 memcpy的库函数文档
- 这个函数有三个参数,第一个参数voiddestination ,是目标空间的起始地址,第二个参数const voiddestination ,是源头空间的起始地址,size_t num是拷贝num个字节。
- 这个函数的功能是将source的前num个字节拷贝到destination中。
- 返回一个void类型的指针,指针指向destiantion。
- 由于我们不知道我们要用这个函数比较什么类型的数据,所以指针的类型都是void*。
1.2 memcpy的使用及注意事项
- memcpy拷贝是以字节为单位的。
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 '\0’的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[8] = { 0 };
memcpy(arr2, arr1, 20);
return 0;
}
在这里我们可以清晰地看到拷贝是是以字节为单位的,而拷贝17个字节就可以将前5个数拷贝过去,是小端存储导致的。
1.3模拟memcpy函数
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;//这里将void*类型的指针全部强转为char*类型,是因为拷贝是是以字节为单位
dest = (char*)dest+1;//而char类型刚好只占一个字节,解引用时刚好访问一个字节,一次拷贝一个字节
src = (char*)src + 1;//+1时刚好跳过一个字节
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[8] = { 0 };
my_memcpy(arr2, arr1, 20);
return 0;
}
1.4memcpy的缺陷
想要将1 2 3 4 5拷贝到3 4 5 6 7 的地方,想要达到的效果是1 2 1 2 3 4 5 8 9 10
但是实际打印出的效果是1 2 1 2 1 2 1 8 9 10,这是因为拷贝1 2 到3 4处时3 4 的数据已经被修改为1 2了,这是再拷贝3 4 处的数据到5 6内存处,也就是将1 2拷贝到5 6处
为了避免这样的场景,我们可以从从后面开始拷贝,
可是如果想要将3 4 5 6 7 拷贝到 1 2 3 4 5处,从后面开始拷贝也会失败
这时又需要从前向后面拷贝
这个似乎需要分情况,经过我们的分析,我们可以得出以下结论
当然对于以上几种情况,我们可以进行一次简化。
如果是这样拷贝,那么我们的目标就实现了。
而这个功能其实在memmove函数中就已经实现了
2.memmove
2.1 memmove的库函数文档
这个函数的参数和功能与memcpy一样,但是它可以实现自己拷贝自己
2.2 memmove的使用及注意事项
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
- 在vs上memcpy和memmove没有任何区别。但是其他编译器上不一定
2.3模拟memmove函数
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (src > dest)
{ //从前向后拷贝
while (num--)
{
*(char*)dest = *(char*)src;//这里将void*类型的指针全部强转为char*类型,是因为拷贝是是以字节为单位
dest = (char*)dest + 1;//而char类型刚好只占一个字节,解引用时刚好访问一个字节,一次拷贝一个字节
src = (char*)src + 1;//+1时刚好跳过一个字节
}
}
else//从后向前拷贝
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
//这里从后向前拷贝需要找到被拷贝的最后一个字符,这时首字符的地址加上num就可以得到最后一个字符的地址了
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[8] = { 0 };
my_memmove(arr2, arr1, 20);
return 0;
}
3.memcmp
3.1 memcmp的库函数文档
这个函数的功能是比较ptr1和ptr2两个指针指向空间的前num个字节
ptr1>ptr2 返回大于0的数
ptr1=ptr2 返回0
ptr1<ptr2 返回小于0的数
3.2 memcmp的使用及注意事项
int main()
{
int arr1[] = { 1,2,3 };
int arr2[] = { 1,2,5 };
int ret = memcmp(arr1, arr2, 9);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[] = { 1,2,3 };
int arr2[] = { 1,2,5 };
int ret = memcmp(arr1, arr2, 8);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[] = { 1,2,7 };
int arr2[] = { 1,2,5 };
int ret = memcmp(arr1, arr2, 12);
printf("%d\n", ret);
return 0;
}
4.memset
4.1 memset的库函数文档
这是一个内存设置函数
将ptr空间的前num个字节,以字节为单位 设置为value
4.2 memset的使用及注意事项
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "hello world";
memset(arr, 'm', 5);
printf("%s\n", arr);
memset(arr + 6, 'n', 4);
printf("%s\n", arr);
return 0;
}
注意·:单位是字节
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 40);//这样不可以将数组的每个元素改为0
return 0;
}
总结
本节讲解了常用的字符串函数、字符函数和内存函数的使用,以及一些重要函数的模拟实现。
希望对你有所帮助。