目录
一、字符串函数
1.strlen
(1)strlen的库函数文档
(2)strlen的模拟实现
(3)strlen的注意事项
2.strcpy
(1)strcpy的库函数文档
(2)strcpy的使用以及注意事项
(3)strcpy的模拟实现
3.strcat
(1)strcat的库函数文档
(2)strcat的使用以及注意事项
(3)strcat的模拟实现
(4)strcat自己对自己追加
4.strcmp
(1)strcmp库函数文档
(2)strcmp的使用
(3)strcmp的模拟实现
注意事项(函数不安全的原因)
5.strncpy
(1)strncpy库函数文档
(2)strncpy的使用以及注意事项
(3)strncpy的模拟实现
6.strncat
(1)strncat的库函数文档
(2)strncat的使用以及注意事项
(3)strncat的模拟实现
7.strncmp
(1)strncmp的库函数文档
(2)strncmp的使用
8.strstr
(1)strstr库函数文档
(2)strstr的使用
(3)strstr的模拟实现
9.strtok
(1)strtok的库函数文档
(2)strtok的使用以及注意事项
10.strerror
(1)strerror的库函数文档
(2)strerror的使用
(3)perror的使用
二、字符函数
1.字符分类函数
2.字符转换函数
3.利用字符函数,将字符串中的大写字母改为小写打印出来
三、内存函数
1.memcpy
(1)memcpy的库函数文档
(2)memcpy的使用以及注意事项
(3)模拟memcpy函数
2.memmove
(1)memcpy的缺陷
(2)memmove的库函数文档
(3)memmove的模拟实现
(4)memmove的使用以及注意事项
3.memcmp
(1)memcmp的库函数文档
(2)memcmp的使用
4.memset
(1)memset的库函数文档
(2)memset的使用
总结
一、字符串函数
1.strlen
(1)strlen的库函数文档
如下图所示,是strlen在库函数中的文档。
它的参数是const char*类型的,这是考虑到它不会被改变的原因
返回类型是size_t类型的,这是考虑到它计算的是长度不会是负数的原因
这个函数返回的是一个字符串的长度,传入一个地址,计算的是\0字符之前的长度
(2)strlen的模拟实现
在这里我们采用三种方式来实现:循环计数、递归、指针减指针
#include<stdio.h>
#include<assert.h>
//循环计数
int my_strlen1(const char* str)
{
assert(str);
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
//递归
int my_strlen2(const char* str)
{
assert(str);
if (*str == NULL)
{
return 0;
}
return 1 + my_strlen2(str + 1);
}
//指针减指针
int my_strlen3(const char* str)
{
assert(str);
const char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen3(arr);
printf("%d\n", len);
return 0;
}
(3)strlen的注意事项
1.字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
2.参数指向的字符串必须要以 '\0' 结束
3.注意函数的返回值为size_t,是无符号的
关于第三点注意事项,我们在这里着重说一下
#include<stdio.h>
#include<string.h>
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
这段代码的运行结果是
肯定与我们的设想是不一样的,因为strlen("abc")计算出来的是3,strlen("abcdef")计算出来的是6,虽然看上去好像3-6是-3,应该是<=,但是要主要这里的都是无符号的,计算出来的结果也是无符号的。所以这个数其实在内存中来看是一个很大的数。还是大于0的,所以是>
2.strcpy
(1)strcpy的库函数文档
它的参数是char* destination 和const char* source,destination的意思是目标空间的地址,
source的意思是源头空间的地址,这个地址里面的值是不可以被修改的。
功能是将source处的字符串拷贝到destination处
返回类型是char*,意思是将destination处的地址返回
(2)strcpy的使用以及注意事项
1.源字符串必须以 '\0' 结束。
2.会将源字符串中的 '\0' 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变。
在下面这段代码中
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "xxxxxxxxxxxxxxxxxx";
char arr2[] = "hello world";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
我们直接从调试里面查看数组的值
在下面这段代码中
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "xxxxxxxxxxxxxxxxxx";
char arr2[] = "hello world";
strcpy(arr2, arr1);
printf("%s\n", arr1);
return 0;
}
出现了错误,这是因为,arr2的空间不是足够大。
在下面这段代码中
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "xxxxxxxxxxxxxxxxxx";
char arr2[] = "hello world";
strcpy(p, arr2);
printf("%s\n", p);
return 0;
}
程序直接崩溃,这是因为char* p是不可以被修改的
(3)strcpy的模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "xxxxxxxxxxxxx";
char arr2[] = "hello world";
char* p = my_strcpy(arr1, arr2);
printf("%s\n", p);
return 0;
}
运行结果为
3.strcat
(1)strcat的库函数文档
1.它的参数是char* destination和const char* source,与strcpy是一样的。
2.它的功能是将source处的字符串追加到destination后面
3.它的返回类型是char*,返回destination的地址
(2)strcat的使用以及注意事项
1.源字符串必须以 '\0' 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。
4.字符串自己给自己追加,会陷入死循环
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20] = "hello ";
char* p = strcat(arr, "world");
printf("%s\n", arr);
return 0;
}
运行结果为
(3)strcat的模拟实现
思路就两步:
1.找到\0字符的位置
2.追加
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//1.找到'\0'字符
while (*dest != '\0')
{
dest++;
}
//2.追加
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr[20] = "hello ";
char* p = my_strcat(arr, "world");
printf("%s\n", arr);
return 0;
}
(4)strcat自己对自己追加
这个其实是不可以的,会造成死循环的现象
假如说下面的字符串自己对自己追加的话
\0会被修改为a,后面的都会覆盖过去,这样我们就永远也找不到\0了,最终导致分配的空间用完, 使用了未分配的空间而崩溃
下图是我们模拟出来的strcat的自己对自己追加后的效果
如果是官方的库里面的话,似乎没有出现这个问题,这个是visual studio对这个问题进行了优化,在一些其他的ide上可能就会出现这个死循环问题,导致程序出现bug
4.strcmp
(1)strcmp库函数文档
1.这个函数有两个参数都是const char*类型的,因为我们不会进行修改,我们只会进行查看
2.这个函数的作用是比较两个字符串的大小,比较规则是:从第一个字符依次开始比较,一个字符一个字符比较,谁的ASCII码值大,谁就大。如果相等则比较后一个字符。
3.这个函数的返回值是int类型,如果str1大于str2,则返回一个大于0的数,如果相等则返回0,如果小于则返回小于0的一个数
4.在vs环境下:大于返回1,等于返回0,小于返回-1。但是在其他编译器上不一定成立
(2)strcmp的使用
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
(3)strcmp的模拟实现
#include<stdio.h>
#include<assert.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[] = "abcefg";
char arr2[] = "abc";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
注意事项(函数不安全的原因)
strcpy,strcat,strcmp这些函数都是不安全的函数,因为他们都是长度不受限制的函数,如果目标空间不是很大,则会出现问题。所以我们vs上使用这些函数需要使用开头的那个预处理指令
#define _CRT_SECURE_NO_WARNINGS 1
而为了让这些更安全,我们就有了strncpy,strncat,strncmp这些长度受限制的函数
5.strncpy
(1)strncpy库函数文档
这个函数有三个参数,char* destination,const char* source和size_t num,前两个参数的意思是目标空间的地址和源头的地址,num的意思是要拷贝几个字节
意思是拷贝前num个字节到destination中
返回类型是char*返回destination的地址
(2)strncpy的使用以及注意事项
1.拷贝num个字符从源字符串到目标空间。
2.如下图1所示,如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
3.如下图2所示,我们在使用的时候要注意,不会将最后的\0给拷贝进来,需要拷贝几个字符就拷贝几个字符.
(3)strncpy的模拟实现
需要注意的事项是,由于拷贝受两个条件控制
如果num为0导致的结束的话,也就是拷贝了一部分,那么我们就不会补充\0
如果是由于源头字符串拷贝完了,但是num还没结束导致的结束,那么我们就要补充\0,要注意我们这个num是少减了一次1的,所以要前置--
第一个循环中,num和赋值的操作是不可以进行交换顺序的,这是因为&&的短路现象
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
while (num && (*dest++ = *src++))
{
num--;
}
if (num)
{
while (--num)
{
*dest++ = '\0';
}
}
return ret;
}
int main()
{
char arr[] = "xxxxxxxxxxxxxxxxxxxxx";
char* p = my_strncpy(arr, "hello", 10);
printf("%s\n", p);
return 0;
}
6.strncat
(1)strncat的库函数文档
这个的参数和返回类型与strncpy是一样的
不同的是函数的功能是追加source的前n的字符
这前n个字符追加后是需要补充一个\0的
(2)strncat的使用以及注意事项
1.对于这个函数,我们需要注意的就是追加之后后面会补充一个\0的
2.即便超出了source的范围,也只是补一个\0,不会补充多个\0
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "xxx\0xxxxxxxxxxxxxxx";
char* p = strncat(arr, "hello", 3);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "xxx\0xxxxxxxxxxxxxxx";
char* p = strncat(arr, "hello", 10);
return 0;
}
(3)strncat的模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
while (num--)
{
if ((*dest++ = *src++) == '\0')
{
return ret;
}
}
*dest = '\0';
return ret;
}
int main()
{
char arr[] = "xxx\0xxxxxxxxxxxxxxx";
char* p = my_strncat(arr, "hello", 3);
return 0;
}
7.strncmp
(1)strncmp的库函数文档
函数的功能是比较前n个字符的大小,返回一个值。
如果str1大,则返回大于0的数
如果str2大,则返回小于0的数
如果相等,则返回0
(2)strncmp的使用
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完
#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "abcdef";
char* p2 = "abcfda";
int ret = strncmp(p1, p2, 3);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "abcdef";
char* p2 = "abcfda";
int ret = strncmp(p1, p2, 4);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "abdef";
char* p2 = "abcfda";
int ret = strncmp(p1, p2, 4);
printf("%d\n", ret);
return 0;
}
8.strstr
(1)strstr库函数文档
这个函数有两个参数,都是const char* 类型的
功能是子str1中查找是否存在str2字符串
返回一个const char* 类型的地址,如果存在,则返回在str1中第一次出现str2的地址,如果不存在,则返回NULL
(2)strstr的使用
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "bcd";
const 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[] = "abcdef";
char arr2[] = "bcdf";
const char* ret = strstr(arr1, arr2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("找不到\n");
}
return 0;
}
(3)strstr的模拟实现
我们在这里采用的是暴力循环遍历的方法。当然也可以使用KMP算法,在此不做介绍了
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0')
{
return (char*)str1;
}
const char* s1 = NULL;
const char* s2 = NULL;
const 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[] = "abcdef";
char arr2[] = "bcdef";
char* ret = my_strstr(arr1, arr2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("找不到\n");
}
return 0;
}
9.strtok
(1)strtok的库函数文档
这个函数有两个参数,str是目标字符串。它是需要被修改的
delimiters是一个字符串,定义了用作分隔符的集合
函数的功能是,在str字符串中找到,delimiter这个集合中的任意一个字符,然后将最先出现的这个字符修改为\0,并且返回分割好的这个字符串的地址,并且内部有一个静态变量记录之前切割的位置
这个str可以是一个null,如果传的是空指针的话,那么就在后面的字符串中找到集合中的某个元素,并且修改为\0,然后返回这个字符串。
(2)strtok的使用以及注意事项
第二个参数sep是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由第二个字符串sep中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "192.168.120.86#56789";
char sep[] = ".#";
char buf[30] = { 0 };
strcpy(buf, arr);
char* ret = NULL;
for (ret = strtok(buf, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n",ret);
}
return 0;
}
10.strerror
(1)strerror的库函数文档
这个函数它会接受一个整型的数字,这个数字是一个错误码,然后返回一个字符串的地址,我们可以打印出这个字符串的地址,显示我们的错误信息
C语言在运行时,如果发生错误,就会将错误码存放到errno
这个变量中,而这个函数可以将错误码翻译成字符串
(2)strerror的使用
#include<stdio.h>
#include<string.h>
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
return 0;
}
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
fclose(pf);
return 0;
}
(3)perror的使用
perror和strerror很相似,区别就是它是可以自己打印出错误信息的
perror在打印错误信息之前,会先打印出自定义的信息。
perror==printf+strerror
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 0;
}
fclose(pf);
return 0;
}
二、字符函数
对于这部分的函数,我们用的很少,当我们想要使用的时候,直接去文档搜索一下用法即可
1.字符分类函数
函数 | 如果它的参数符合下列条件则返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格' ',换页'\f',换行'\n',回车'\r',制表符’\t‘,垂直制表符’\v‘ |
isdigit | 十进制数0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 小写字母a~z,或者A~Z |
isalnum | 字母或数字,a~z,A~Z,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
2.字符转换函数
注意:这两个函数不会改变自身本来的值,是返回转换后的值
大写转小写 | int tolow(int c) |
小写转大写 | int toupper(int c) |
3.利用字符函数,将字符串中的大写字母改为小写打印出来
#include<stdio.h>
#include<string.h>
#include<ctype.h>
int main()
{
char arr[] = "I Have An Apple";
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)memcpy的库函数文档
它有三个参数,第一个参数是目标空间的起始地址,第二个参数第源头空间的地址,第三个变量是拷贝num个字节
我们这个函数的功能是将source的前num个字节拷贝到destination中
返回一个指针指向dest处的指针,这个指针是void类型的
(2)memcpy的使用以及注意事项
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1 + 2, 20);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1 + 2, 17);
return 0;
}
17个字节可行的原因是因为小端存储造成的
(3)模拟memcpy函数
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const 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;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1 + 2, 20);
return 0;
}
2.memmove
(1)memcpy的缺陷
在memcpy函数中,我们可以将一个地址的前n个字节复制到一个地址动。但是如果我们自身对自身进行拷贝的话,会出现一些问题
假如下图是src和dest的地址
那么按照我们的思路,最终的现象就是下图所示的
我们发现会出现1212这样似乎处于一个循环的情景,这与我们的字符串追加造成的现象是极其相似的。而且strcpy中也会出现这样的情况。为了避免出现这个现象,我们可以这样做,从后向前拷贝。
但是这样的话,如果dest又在src前面的话,又不符合我们的想法了,又会陷入一种类型于死循环的情况
这时候我们就需要从前向后拷贝了
于是我们发现,这个似乎需要分情况,经过我们的分析,我们可以得出以下结论
当然对于这四种情况,我们可以进行一次简化
如果是这样拷贝,那么我们的目标就实现了
而这个功能其实在memmove函数中就已经实现了
(2)memmove的库函数文档
这个函数的参数与memcpy是一样的,功能也是一样的,唯一不同的就是,可以自己拷贝自己了
(3)memmove的模拟实现
#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(src && dest);
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;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memmove(arr1, arr1 + 2, 20);
return 0;
}
(4)memmove的使用以及注意事项
1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2.如果源空间和目标空间出现重叠,就得使用memmove函数处理。3.在vs上memcpy和memmove没有任何区别。但是其他编译器上不一定
3.memcmp
(1)memcmp的库函数文档
它的功能是比较ptr1和ptr2两个指针所指向空间的前num个字节
ptr1>ptr2返回大于0的数
ptr1==ptr2返回0
ptr1<ptr2返回小于0的数
(2)memcmp的使用
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[] = { 1,2,3 };
int arr2[] = { 1,2,5 };
int ret = memcmp(arr1, arr2, 9);
printf("%d", 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", 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", ret);
return 0;
}
4.memset
(1)memset的库函数文档
它是一个内存设置函数
将ptr空间中的前num个字节,以字节为单位设置为value
(2)memset的使用
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "hello world";
memset(arr, 'x', 5);
printf("%s\n", arr);
memset(arr + 6, 'y', 5);
printf("%s\n", arr);
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 40);
return 0;
}
总结
本节讲解了最常使用的字符串函数,内存函数,字符函数的使用,以及一些重要的函数的实现
如果对你有帮助,不要忘记点赞加收藏哦!!!
想获得更多优质内容,一定要关注我哦!!!