字符函数、字符串函数、内存函数
如果想深度理解,请访问:https://s3.bmp.ovh/imgs/2024/08/10/9094bd873e0661bf.jpg
1、strlen函数
在 <string.h>
中定义
size_t strlen(const char* str)
-
参数:
str
指向要检查的空终止字符串的指针 -
返回值: 空终止字符串
str
的长度
1.1 使用
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str1) > strlen(str2))
{
printf(">");
}
else
{
printf("<");
}
return 0;
}
1.2 模拟实现
// strlen的模拟实现
# include<stdio.h>
# include<assert.h>
//int my_strlen(char* str)
//{
// assert(str);
// char* ret = str;
// while (*ret != '\0')
// {
// ret++;
// }
// return ret - str;
//}
//int my_strlen(char* str)
//{
// int count = 0;
// assert(str);
// while (*str)
// {
// count++;
// str++;
// }
// return count;
//}
int my_strlen(char* str)
{
assert(str != NULL);
if (*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
int main()
{
char* str = "abcdef";
int ret = my_strlen(str);
printf("%d\n", ret);
return 0;
}
一共使用了三种方法:1、指针—指针 2、计数器 3、递归函数
2、strcpy函数
在 string.h
中定义
char* strcpy(char* dest, const char* src)
描述: 复制 src
所指向的空终止字节字符串,包含‘\0’,到首元素为 dest
所指的字符数组。
若 dest
数组长度不足则行为未定义。若字符串重叠则行为未定义。若 dest
不是指向字符数组的指针或 src
不是指向空终止字节字符串的指针则行为未定义。
参数:
dest
:指向要写入的字符数组的指针, 英文destination的缩写src
:指向要复制的空终止字节字符串的指针,英文srcouse的缩写
返回值:
dest
的副本
2.1 使用
int main()
{
char dest[] = "abcdef";
char str[] = "ghj";
size_t len = strlen(strcpy(dest, str));
printf("%zd\n", len);
printf("%s\n", dest);
}
2.2 模拟实现
// strcpy的使用和模拟实现
// char* strcpy(char * destination, const * source)
// | |
// 目标空间 源字符串
# include<stdio.h>
# include<assert.h>
char* my_strcpy(char* dest, const char* str)
{
char* ret = dest;
assert(dest && str);
while (*dest++ = *str++)
{
;
}
return ret;
}
int main()
{
char dest[] = "abcdef";
char str[] = "ghj";
my_strcpy(dest, str);
size_t len = strlen(my_strcpy(dest, str));
printf("%zd\n", len);
printf("%s\n", dest);
}
3、strcat 函数
在 string.h
中定义
char* strcat(char* dest, const char* src)
描述: 将 src
所指向的空终止字节字符串的副本追加到 dest
所指向的空终止字节字符串的末尾。字符 src[0]
替换 dest
末尾的空终止符。产生的字节字符串是空终止的。
若目标数组对于 src
和 dest
的内容以及空终止符不够大,则行为未定义。若字符串重叠,则行为未定义。若 dest
或 src
不是指向空终止字节字符串的指针,则行为未定义。
参数:
dest
:指向要后附到的空终止字节字符串的指针src
:指向作为复制来源的空终止字节字符串的指针
返回值:
返回 dest
的副本。
3.1 strcat的使用
# include<stdio.h>
# include<assert.h>
int main()
{
char arr1[20] = "hello ";
size_t len = strlen(strcat(arr1, "world"));
printf("%zd\n", len);
printf("%s\n", arr1);
return 0;
}
3.2 strcat模拟实现
# include<stdio.h>
# include<assert.h>
char* my_strcat(char* dest, const char* str)
{
char* ret = dest;
assert(dest && str != NULL);
// 第一步:找到\0
while (*dest != '\0')
{
dest++;
}
// 第二步:复制到目标字符串尾部
while (*dest++ = *str++)
{
;
}
return ret;
}
size_t my_strlen(const char* str)
{
assert(str != NULL);
if (*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
int main()
{
char arr1[20] = "hello ";
// 找到目标空间的第一个'\0'
// 然后从这个\0开始追加原字符串
// 源字符串的内容,包括\0都会追加到目标空间
size_t len = my_strlen(my_strcat(arr1, "world"));
printf("%zd\n", len);
printf("%s\n", arr1);
return 0;
}
4、strcmp函数
在 string.h
中定义
int strcmp(const char* lhs, const char* rhs)
;
描述: 以字典序比较两个空终止字节字符串。结果的符号是被比较的字符串中首对不同字符(都转译成 unsigned char)的值间的差的符号。若 lhs 或 rhs 不是指向空终止字节字符串的指针,则行为未定义。
参数:
rhs
lhs
-指向要比较的空终止字符串的指针
返回值:
根据地址,lhs和rhs一个一个的比较,lhs 先于 rhs 出现则为负值。若 lhs 与 rhs 比较相等则为零。若字典序中 lhs 后于 rhs 出现则为正值。
4.1 strcmp的使用
# include<stdio.h>
# include<assert.h>
int main()
{
char dest[] = "abcdef";
char str[] = "ghj";
int ret = strcmp(dest, str);
if (ret > 0)
printf(">");
if (ret < 0)
printf("<");
else
printf("=");
return 0;
}
4.2 strcmp的模拟实现
模拟实现strcmp 函数
第一个字符串大于第二个字符串时,返回大于零的数字
第一个字符串等于第二个字符串时,返回等于零的数字
第一个字符串小于第二个字符串时,返回小于零的数字
//
# include<stdio.h>
# include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2 != NULL);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char dest[] = "abcdef";
char str[] = "ghj";
int ret = my_strcmp(dest, str);
if (ret > 0)
printf(">");
if (ret < 0)
printf("<");
else
printf("=");
return 0;
}
接下来,我们将讲解另一个种类的,strncpy,strncmp,strncat。 可以看到和之前将讲解的函数的差别是多了一个n,这两种函数到底有什么区别呢?
5、strncpy函数
在 string.h
中定义
char* strncpy(char* dest, const char* src, size_t count);
描述:
复制 src
所指向的字符数组的至多 count
个字符(包含空终止字符,但不包含后随空字符的任何字符)到 dest
所指向的字符数组。
若在完全复制整个 src
数组前抵达 count
,则结果的字符数组不是空终止的。
若在复制来自 src
的空终止字符后未抵达 count
,则写入额外的空字符到 dest
,直至写入总共 count
个字符。
若字符数组重叠,若 dest
或 src
不是指向字符数组的指针(包含若 dest
或 src
为空指针),若 dest
所指向的数组大小小于 count
,或若 src
所指向的数组大小小于 count
且它不含空字符,则行为未定义。
参数:
dest
: 指向要复制到的字符数组的指针src
:指向复制来源的字符数组的指针count
:要复制的最大字符数
返回值:
返回 dest
的副本
5.1 strncpy使用
# include<stdio.h>
# include<string.h>
# include<assert.h>
int main()
{
char arr1[] = "asdfghjk";
char arr2[] = "++++";
strncpy(arr1, arr2, 1);
printf("%s\n", arr1);
return 0;
}
5.2 strncpy模拟实现
strncp 的使用和模拟实现
# include<stdio.h>
# include<string.h>
# include<assert.h>
char* my_strncpy(char* destination, const char* source, size_t num)
{
assert(destination && source != NULL);
char* ret = destination;
int count = (int)strlen(source);
while (num)
{
if (count > 0)
*destination++ = *source++;
else
*destination++ = '\0';
num--;
count--;
}
return ret;
}
int main()
{
char arr1[] = "asdfghjk";
char arr2[] = "++++";
// strncpy(arr1, arr2, 1);
// 模拟实现strncpy
my_strncpy(arr1, arr2, 7);
printf("%s\n", arr1);
return 0;
}
6、strncat函数
在 string.h
中模拟实习
char* strncat(char* dest, const char* stc, size_t count);
描述:
将来自 src
所指向的字符数组的至多 count
个字符,追加到 dest
所指向的空终止字节字符串的末尾,若找到空字符则停止。字符 src[0] 替换位于 dest
末尾的空终止符。总会在末尾追加终止空字符(故函数可写入的最大字节数是 count+1)。
若目标数组没有对于 dest
和 src
的前 count
个字符加上终止空字符的足够空间,则行为未定义。若源与目标对象重叠,则行为未定义。若 dest
不是指向空终止字节字符串的指针,或 src
不是指向字符数组的指针,则行为未定义。
参数:
dest
:指向要后附到的空终止字节字符串的指针src
:指向作为复制来源的字符数组的指针count
: 要复制的最大字符数
返回值:
返回 dest
的副本。
注:
因为 strncat
需要在每次调用时找到 dest
的结尾,故用 strncat
将多个字符串连接成一体是低效的
6.1 strncat使用
# include<stdio.h>
int main()
{
char arr1[20] = "abcdefg";
char arr2[] = "hujfdjs";
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}
6.2 strncat模拟实现
strncat函数的使用和模拟实现
# include<stdio.h>
# include<assert.h>
# include<string.h>
char* my_strncat(char* destination, const char* source, size_t num)
{
assert(destination && source != NULL);
char* ret = destination;
int count = (int)strlen(source);
while (*destination)
{
destination++; // 这里要格外注意,不可以将++放在while中
}
while (num)
{
if (count >= 0)
*destination++ = *source++;
else
*destination++ = '\0';
num--;
count--;
}
return ret;
}
int main()
{
char arr1[20] = "abcdefg";
char arr2[] = "hujfdjs";
//strncat(arr1, arr2, 3);
// strncat 函数的模拟实现
my_strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}
7、strncmp函数
在 string.h
中定义
int strncmp(const char* lhs, count char* rhs, size_t count);
描述:
比较两个可能空终止的数组的至多 count 个字符。按字典序进行比较。不比较后随空字符的字符。
结果的符号是被比较的数组中首对字符(都转译成 unsigned char)的值间的差的符号。
若出现越过 lhs 或 rhs 结尾的访问,则行为未定义。若 lhs 或 rhs 为空指针,则行为未定义。
参数:
lhs,rhs
-指向要比较的可能空终止的数组的指针count
- 要比较的最大字符数
返回值:
若地址中 lhs
先于 rhs
出现则为负值。
若 lhs
与 rhs
比较相等,或若 count 为零,则为零。
若地址中 lhs
后于 rhs
出现则为正值。
7.1 strncmp使用
# include<stdio.h>
int main()
{
char arr1[20] = "abcdefg";
char arr2[] = "abcfdjs";
int ret = strncmp(arr1, arr2, 4);
if (ret > 0)
printf(">");
if (ret < 0)
printf("<");
else
printf("=");
return 0;
}
7.2 strncmp模拟实现
// strncmp函数的使用和模拟实现
# include<stdio.h>
# include<string.h>
# include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
while(*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
num--;
if (num == 0)
return 0;
}
return *str1 - *str2;
}
int main()
{
char arr1[20] = "abcdefg";
char arr2[] = "abcfdjs";
int ret = my_strncmp(arr1, arr2, 4);
if (ret > 0)
printf(">");
if (ret < 0)
printf("<");
else
printf("=");
return 0;
}
8、strstr函数
在 string.h
中定义
char* strstr(const char* str, const char* substr);
描述:
查找 `substr` 所指的空终止字节字符串在 `str` 所指的空终止字节字符串中的首次出现。不比较空终止字符。若 `str` 或 `substr` 不是指向空终止字节字符串的指针,则行为未定义。
参数:
str
:指向要检验的空终止字节字符串的指针substr
:指向要查找的空终止字节字符串的指针
返回值:
指向于 str
中找到的子串首字符的指针,或若找不到该子串则为空指针。若 substr
指向空字符串,则返回 str
。
8.1 strstr使用
# include<stdio.h>
# include<string.h>
int main()
{
char arr1[20] = "cmdfgabcdrty";
char arr2[] = "abc";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
printf("没有找到。\n");
else
printf("找到了:%s\n", ret);
return 0;
}
8.2 strstr模拟实现
// strstr函数的使用和模拟实现
# include<stdio.h>
# include<assert.h>
# include<string.h>
char* my_strstr(const char* destination, const char* source)
{
assert(destination && source != NULL);
const char* str1 = destination;
const char* str2 = source;
const char* nul = destination; // 标记起始地址
while (*nul)
{
str1 = nul;
str2 = source;
while (*str1 && *str2 && *str1 == *str2)
{
str1++;
str2++;
}
if (*str2 == 0)
return nul;
nul++;
}
return NULL;
}
int main()
{
char arr1[20] = "cmdfgabcdrty";
char arr2[] = "abc";
// char* ret = strstr(arr1, arr2);
// strstr函数的模拟实现
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
printf("没有找到。\n");
else
printf("找到了:%s\n", ret);
return 0;
}
9、strtok 函数
在 string.h
中定义
char* strtok(char* str, const char* delim);
描述:
寻找 str
所指向的空终止字节字符串中的下个记号。由 delim
所指向的空终止字节字符串鉴别分隔字符。
此函数被设计为进行多次调用以从同一字符串获得相继的记号。
-
若
str
不是空指针,则调用被当做strtok
对此特定字符串的首次调用。函数搜索首个不含于delim
的字符。 -
若找不到这种字符,则
str
中完全没有记号,而函数返回空指针 -
若找到这种字符,则它是记号的起始。然后函数从该点搜索首个含于
delim
的字符。 -
若找不到这种字符,则
str
只有一个记号,而将来对strtok
的调用将返回空指针 -
若找到这种字符,则用空字符 ‘\0’ 替换它,并将指向下个字符的指针存储于静态位置,以为后继调用所用
-
若
str
为空指针,则将调用当做对strtok
的后继调用,函数从先前调用中它剩下的位置开始。行为如同将先前存储的指针作为 str 传递。若
str
或delim
不是指向空终止字节字符串的指针,则行为未定义。
参数:
str
: 指向要记号化的空终止字节字符串的指针delim
:指向标识分隔符的空终止字节字符串的指针
返回值:
返回指向下个记号起始的指针,或若无更多记号则返回空指针。
注解:
此函数是破坏性的:它写入 ‘\0’ 字符到字符串 str
的元素。特别是,不能以字符串字面量为 strtok
的首参数。每次对 strtok
的调用都会修改静态对象:它不是线程安全的。不同于大多数其他记号化器,strtok
中的分隔符能对于后继记号不同,而且甚至能依赖于先前记号的内容。
// strtok的使用
# include<stdio.h>
# include<assert.h>
# include<string.h>
int main()
{
char arr1[] = "zpengwei@yeah.net";
char arr2[] = "192@168#110.123";
char sep[] = "@#.";
char buf[40] = { 0 };
strcpy(buf, arr1);
char* p = NULL;
for (p = strtok(buf, sep); p != NULL; p = strtok(NULL, sep))
{
printf("%s\n", p);
}
return 0;
}
10、strerror函数
在 string.h
中定义
char* strerror( int errnum );
描述:
返回指向系统错误码 errnum
的文本表示的指针,它等同于 perror() 会打印的描述。
errnum
通常从 errno
变量获得,不过函数接受可任何 int 类型值。字符串的内容特定于本地环境。
程序必须不修改返回的字符串,但对 strerror
函数的后继调用可能重写该字符串。不要求 strerror
为线程安全。实现可以返回指向静态只读字符串字面量的不同指针,或反复返回指向静态缓冲区的同一指针,strerror
放置字符串于该缓冲区中。
返回值:
-
指向对应 errno 错误码
errnum
的空终止字节串的指针。 -
若整个消息完整存储于
buf
则为零,否则为非零。
// strerror函数的使用
# include<stdio.h>
# include<string.h>
# include<errno.h>
int main()
{
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%2d : %s\n",i, strerror(i));
}
return 0;
}
//int main()
//{
// FILE* pf = fopen("test.txt", "r");
// if (pf == NULL)
// {
// perror("文件打开失败,原因是");
// }
// return 0;
//}
//int main()
//{
// FILE* pf = fopen("test.txt","r");
// if (pf == NULL)
// {
// printf("打开失败的原因是:%s\n", strerror(errno));
// }
// return 0;
//}
内存函数
动态内存分配允许程序在运行时根据需要分配内存,而不是在编译时固定大小。这使得程序能够处理不确定大小的数据结构,如动态数组、链表等。
11、内存函数
在 string.h
中定义
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
11.1memcpy使用
# include<stdio.h>
# include<string.h>
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = {5,6,7};
memcpy(arr2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
11.2 memcpy模拟实现
// memcpy用函数的使用和模拟实现
// memcpy 是按字节复制,不考虑数据的类型或内容。这个函数在遇到 '\0' 时候不会停下
// strncpy 是按字符复制,并且会考虑字符串的结束符。
// void* memcpy(void * destination, const void * source, sizeof_t num)
# include<stdio.h>
# include<string.h>
# include<assert.h>
void* my_memcpy(void* destination, const void* source, size_t num)
{
assert(destination && source != NULL);
void* ret = destination;
while (num--)
{
*(char*)destination = *(char*)source;
(char*)destination += 1;
(char*)source += 1;
}
return ret;
}
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = {5,6,7};
//memcpy(arr2, arr1, 5 * sizeof(int));
//memcpy函数的模拟实现
my_memcpy(arr2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
12、memmove函数
在 string.h
中定义
void* memmove( void* dest, const void* src, size_t count);
描述:
从 src
所指向的对象复制 count
个字节到 dest
所指向的对象。两个对象都被转译成 unsigned char 的数组。对象可以重叠(这是与memcpy的重要区别):如同复制字符到临时数组,再从该数组到 dest
一般进行复制。 若在 dest 数组末尾之后发生访问则行为未定义。若 dest
或 src
为非法或空指针则行为未定义。
参数:
dest
:指向复制目标对象的指针src
:指向复制来源对象的指针count
: 要复制的字节数
返回值:
返回 dest
的副本,本质为更底层操作的临时内存地址,在实际操作中不建议直接使用此地址,操作完成以后,真正有意义的地址是dest本身。
12.1 memmove使用
# include<stdio.h>
# include<string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 5,6,7 };
memove(arr1+3, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
return 0;
}
12,2 memmove模拟实现
memmove函数的模拟实现, memmove和memcpy相差无几,
唯一的区别是memmove可以很好地处理destination和source相同地情况
也即是说 memmove支持重叠内存区域
# include<stdio.h>
# include<string.h>
# include<assert.h>
void* my_memove(void* des, const char* sour, size_t num)
{
assert(des && sour != NULL);
void* ret = des;
if ((char*)des >= (char*)sour + num || (char*)sour >= (char*)des + num)
{
while (num--)
{
*(char*)des = *(char*)sour;
(char*)des += 1;
(char*)sour += 1;
}
}
else
{
(char*)des = (char*)des + num ;
(char*)sour = (char*)sour + num ;
while (num--)
{
*(--(char*)des) = (*(--(char*)sour));
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 5,6,7 };
my_memove(arr1+3, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
12,2 memmove模拟实现
memmove函数的模拟实现, memmove和memcpy相差无几,
唯一的区别是memmove可以很好地处理destination和source相同地情况
也即是说 memmove支持重叠内存区域
# include<stdio.h>
# include<string.h>
# include<assert.h>
void* my_memove(void* des, const char* sour, size_t num)
{
assert(des && sour != NULL);
void* ret = des;
if ((char*)des >= (char*)sour + num || (char*)sour >= (char*)des + num)
{
while (num--)
{
*(char*)des = *(char*)sour;
(char*)des += 1;
(char*)sour += 1;
}
}
else
{
(char*)des = (char*)des + num ;
(char*)sour = (char*)sour + num ;
while (num--)
{
*(--(char*)des) = (*(--(char*)sour));
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 5,6,7 };
my_memove(arr1+3, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
return 0;
}
未完待续!!!!