字符串函数和内存函数
- 1.字符串函数介绍
- 1.1 strlen
- 1.2 strcpy
- 1.3 strcat
- 1.4 strcmp
- 1.5 strncpy
- 1.6 strncat
- 1.7 strncpy
- 1.8 strstr
- 1.9 strtok
- 1.10 strerror
- 1.11 字符分类函数
- 2.内存函数
- 2.1 memcpy
- 2.2 memmove
- 2.3 memcmp
- 3.函数的模拟实现
- 3.1 模拟实现strlen
- 3.2 模拟实现strcpy
- 3.3 模拟实现strcat
- 3.4 模拟实现strstr
- 3.5 模拟实现strcmp
- 3.6 模拟实现memcpy
- 3.7 模拟实现memmove
- 结语
1.字符串函数介绍
1.1 strlen
头文件<string.h>
获取字符串长度
返回 C 字符串 str 的长度。
C 字符串的长度由终止空字符确定:C 字符串的长度与字符串开头和终止空字符之间的字符数一样长(不包括终止空字符本身)。
这不应与保存字符串的数组的大小混淆。例如:
int main()
{char mystr[100] = "test string";printf("%d", strlen(mystr));return 0;
}
定义一个大小为 100 个字符的字符数组,但初始化 mystr 时使用的 C 字符串的长度仅为 11 个字符。因此,当 sizeof(mystr) 的计算结果为 100 时,strlen(mystr) 返回 11。
库中的声明如下:
_Check_return_
size_t __cdecl strlen(_In_z_ char const* _Str);
- 字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
- 参数指向的字符串必须要以 ‘\0’ 结束。
- 注意函数的返回值为size_t,是无符号的( 易错 )
1.2 strcpy
头文件<string.h>
复制字符串
将源指向的 C 字符串复制到目标指向的数组中,包括终止的 null 字符(并在该点停止)。
为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的 C 字符串(包括终止空字符),并且不应在内存中与源重叠。
int main()
{char str1[] = "Sample string";char str2[40];char str3[40];strcpy(str2, str1);strcpy(str3, "copy successful");printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3);return 0;
}
库中声明如下:
__DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(char*, __RETURN_POLICY_DST, __EMPTY_DECLSPEC, strcpy,_Out_writes_z_(_String_length_(_Source) + 1), char, _Destination,_In_z_ char const*, _Source)
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
1.3 strcat
头文件<string.h>
追加字符串
将源字符串的副本追加到目标字符串。目标中的终止空字符被源的第一个字符覆盖,并且在目标中由两者串联形成的新字符串的末尾包含一个空字符。
目标字符串和源字符串不得重叠。
int main()
{char str[80];strcpy(str, "these ");strcat(str, "strings ");strcat(str, "are ");strcat(str, "concatenated.");puts(str);return 0;
}
库中声明如下:
__DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(char*, __RETURN_POLICY_DST, __EMPTY_DECLSPEC, strcat,_Inout_updates_z_(_String_length_(_Destination) + _String_length_(_Source) + 1), char, _Destination,_In_z_ char const*, _Source)
- 源字符串必须以 ‘\0’ 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
1.4 strcmp
头文件<string.h>
比较两个字符串
将 C 字符串 str1 与 C 字符串 str2 进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续以下对,直到字符不同或达到终止空字符。
此函数执行字符的二进制比较。
返回一个整数值,该值指示字符串之间的关系:
int main()
{char key[] = "apple";char buffer[80];do {printf("Guess my favorite fruit? ");fflush(stdout);scanf("%79s", buffer);} while (strcmp(key, buffer) != 0);puts("Correct answer!");return 0;
}
库中声明如下:
_Check_return_
int __cdecl strcmp(_In_z_ char const* _Str1,_In_z_ char const* _Str2);
- 第一个字符串大于第二个字符串,则返回大于0的数字(vs中为固定值1)
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字(vs中为固定值-1)
1.5 strncpy
头文件<string.h>
从字符串中复制字符
将源的第一个字符数复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到总共写入 num 个字符为止。
如果源长度超过 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,不应将目标视为以空结尾的 C 字符串(这样读取它会溢出)。
目标字符串和源字符串不得重叠
int main()
{char str1[] = "To be or not to be";char str2[40];char str3[40];strncpy(str2, str1, sizeof(str2));strncpy(str3, str2, 5);str3[5] = '\0'; puts(str1);puts(str2);puts(str3);return 0;
}
库中声明如下:
__DEFINE_CPP_OVERLOAD_STANDARD_NFUNC_0_2_EX(char*, __RETURN_POLICY_DST, _ACRTIMP, strncpy, strncpy_s,_Out_writes_z_(_Size) char,_Out_writes_(_Count) _Post_maybez_, char, _Destination,_In_reads_or_z_(_Count) char const*, _Source,_In_ size_t, _Count)
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
1.6 strncat
头文件<string.h>
从字符串追加字符
将源的第一个数字字符追加到目标,外加一个终止空字符。
如果源中 C 字符串的长度小于 num,则仅复制终止空字符之前的内容。
int main()
{char str1[20];char str2[20];strcpy(str1, "To be ");strcpy(str2, "or not to be");strncat(str1, str2, 6);puts(str1);return 0;
}
库中声明如下:
__DEFINE_CPP_OVERLOAD_STANDARD_NFUNC_0_2_EX(char*, __RETURN_POLICY_DST, _ACRTIMP, strncat, strncat_s,_Inout_updates_z_(_Size) char,_Inout_updates_z_(_Count), char, _Destination,_In_reads_or_z_(_Count) char const*, _Source,_In_ size_t, _Count)
1.7 strncpy
头文件<string.h>
从字符串中复制字符
将源的第一个字符数复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到总共写入 num 个字符为止。
如果源长度超过 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,不应将目标视为以空结尾的 C 字符串(这样读取它会溢出)。
目标字符串和源字符串不得重叠
int main()
{char str1[] = "To be or not to be";char str2[40];char str3[40];strncpy(str2, str1, sizeof(str2));strncpy(str3, str2, 5);str3[5] = '\0'; puts(str1);puts(str2);puts(str3);return 0;
}
库中声明如下:
__DEFINE_CPP_OVERLOAD_STANDARD_NFUNC_0_2_EX(char*, __RETURN_POLICY_DST, _ACRTIMP, strncpy, strncpy_s,_Out_writes_z_(_Size) char,_Out_writes_(_Count) _Post_maybez_, char, _Destination,_In_reads_or_z_(_Count) char const*, _Source,_In_ size_t, _Count)
1.8 strstr
头文件<string.h>
查找子字符串
返回指向 str2 中第一次出现的 str1 的指针,如果 str2 不是 str1 的一部分,则返回一个空指针。
匹配过程不包括终止空字符,但它到此为止。
int main()
{char str[] = "This is a simple string";char* pch;pch = strstr(str, "simple");puts(pch);if (pch != NULL)strncpy(pch, "sample", 6);puts(str);return 0;
}
库中声明如下:
_VCRTIMP char _CONST_RETURN* __cdecl strstr(_In_z_ char const* _Str,_In_z_ char const* _SubStr);
1.9 strtok
头文件<string.h>
将字符串拆分为标记
对此函数的一系列调用将 str 拆分为标记,这些标记是由分隔符中的任何字符分隔的连续字符序列。
在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描令牌的起始位置。在后续调用中,该函数需要一个空指针,并使用最后一个令牌末尾之后的位置作为扫描的新起始位置。
为了确定标记的开头和结尾,该函数首先从起始位置扫描分隔符中未包含的第一个字符(该字符将成为标记的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,该字符将成为令牌的末尾。如果找到终止空字符,扫描也会停止。
令牌的此结尾将自动替换为空字符,并且令牌的开头由函数返回。
一旦在对 strtok 的调用中找到 str 的终止空字符,则对此函数的所有后续调用(以空指针作为第一个参数)都将返回空指针。
找到最后一个令牌的点由要在下一次调用中使用的函数在内部保留(不需要特定的库实现来避免数据争用)。
int main()
{char str[] = "- This, a sample string.";char* pch;printf("Splitting string \"%s\" into tokens:\n", str);pch = strtok(str, " ,.-");while (pch != NULL){printf("%s\n", pch);pch = strtok(NULL, " ,.-");}return 0;
}
库中声明如下:
_Check_return_ _CRT_INSECURE_DEPRECATE(strtok_s)
_ACRTIMP char* __cdecl strtok(_Inout_opt_z_ char* _String,_In_z_ char const* _Delimiter);
返回值
- 如果找到令牌,则指向令牌开头的指针。
- 否则为空指针。
- 当在正在扫描的字符串中到达字符串的末尾(即空字符)时,始终返回空指针。
1.10 strerror
头文件<string.h>
获取指向错误消息字符串的指针
解释 errnum 的值,生成一个字符串,其中包含描述错误条件的消息,就像由库的函数设置为 errno 一样。
返回的指针指向静态分配的字符串,程序不应修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。
strerror 生成的错误字符串可能特定于每个系统和库实现。
int main()
{FILE* pFile;pFile = fopen("unexist.ent", "r");if (pFile == NULL)printf("Error opening file unexist.ent: %s\n", strerror(errno));return 0;
}
我们可以给下面这段代码来看看vs的库中前20个错误信息提示是什么
int main()
{for (int i = 0; i < 20; i++){printf("%d:%s\n", i,strerror(i));}return 0;
}
1.11 字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
字符转换示例:
#include <stdio.h>
#include <ctype.h>
int main()
{int i = 0;char str[] = "Test String.\n";char c;while (str[i]){c = str[i];if (isupper(c))c = tolower(c);putchar(c);i++;}return 0;
}
2.内存函数
2.1 memcpy
头文件<string.h>
复制内存块
将字节数的值从源指向的位置直接复制到目标指向的内存块。
源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。
该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。
为避免溢出,目标和源参数指向的数组大小应至少为字节数,并且不应重叠
struct {char name[40];int age;
} person, person_copy;int main()
{char myname[] = "Pierre de Fermat";/* using memcpy to copy string: */memcpy(person.name, myname, strlen(myname) + 1);person.age = 46;/* using memcpy to copy structure: */memcpy(&person_copy, &person, sizeof(person));printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);return 0;
}
库中声明如下:
void* __cdecl memcpy(_Out_writes_bytes_all_(_Size) void* _Dst,_In_reads_bytes_(_Size) void const* _Src,_In_ size_t _Size);
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
2.2 memmove
头文件<string.h>
移动内存块
将字节数的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样,允许目标和源重叠。
源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。
该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。
为避免溢出,目标参数和源参数指向的数组的大小应至少为字节数。
int main()
{char str[] = "memmove can be very useful......";memmove(str + 20, str + 15, 11);puts(str);return 0;
}
库中声明如下:
_VCRTIMP void* __cdecl memmove(_Out_writes_bytes_all_opt_(_Size) void* _Dst,_In_reads_bytes_opt_(_Size) void const* _Src,_In_ size_t _Size);
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
2.3 memcmp
头文件<string.h>
比较两个内存块
将 ptr1 指向的内存块的前 num 字节数与 ptr2 指向的第一个字节数进行比较,如果它们都匹配,则返回零,如果不匹配,则返回一个不同于零的值,表示哪个值更大。 请注意,与 strcmp 不同,该函数在找到空字符后不会停止比较。
int main()
{char buffer1[] = "DWgaOtP12df0";char buffer2[] = "DWGAOTP12DF0";int n;n = memcmp(buffer1, buffer2, sizeof(buffer1));if (n > 0) printf("'%s' is greater than '%s'.\n", buffer1, buffer2);else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);return 0;
}
库中声明如下:
int __cdecl memcmp(_In_reads_bytes_(_Size) void const* _Buf1,_In_reads_bytes_(_Size) void const* _Buf2,_In_ size_t _Size);
- 比较从ptr1和ptr2指针开始的num个字节
- 返回值
返回一个整数值,该值指示内存块内容之间的关系:
3.函数的模拟实现
3.1 模拟实现strlen
三种方式:
方式1:
//计数器方式
int my_strlen(const char * str)
{int count = 0;while(*str){count++;str++;}return count;
}
方式2:
//不能创建临时变量计数器
int my_strlen(const char * str)
{if(*str == '\0')return 0;elsereturn 1+my_strlen(str+1);
}
方式3:
//指针-指针的方式
int my_strlen(char *s)
{char *p = s;while(*p != ‘\0’ )p++;return p-s;
}
3.2 模拟实现strcpy
参考代码:
char *my_strcpy(char *dest, const char*src)
{ char *ret = dest;assert(dest != NULL);assert(src != NULL);while((*dest++ = *src++)){;}return ret;
}
3.3 模拟实现strcat
参考代码:
char *my_strcat(char *dest, const char*src)
{char *ret = dest;assert(dest != NULL);assert(src != NULL);while(*dest){dest++;}while((*dest++ = *src++)){;}return ret;
}
3.4 模拟实现strstr
char* strstr(const char* str1, const char* str2)
{char* cp = (char*)str1;char* s1, * s2;if (!*str2)return((char*)str1);while (*cp){s1 = cp;s2 = (char*)str2;while (*s1 && *s2 && !(*s1 - *s2))s1++, s2++;if (!*s2)return(cp);cp++;}return(NULL);
}
3.5 模拟实现strcmp
参考代码:
int my_strcmp(const char* src, const char* dest)
{int ret = 0;assert(src != NULL);assert(dest != NULL);while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)++src, ++dest;if (ret < 0)ret = -1;else if (ret > 0)ret = 1;return(ret);
}
3.6 模拟实现memcpy
参考代码:
void* my_memcpy(void* dst, const void* src, size_t count)
{void* ret = dst;assert(dst);assert(src);while (count--) {*(char*)dst = *(char*)src;dst = (char*)dst + 1;src = (char*)src + 1;}return(ret);
}
3.7 模拟实现memmove
参考代码:
void* my_memmove(void* dst, const void* src, size_t count)
{void* ret = dst;if (dst <= src || (char*)dst >= ((char*)src + count)) {while (count--) {*(char*)dst = *(char*)src;dst = (char*)dst + 1;src = (char*)src + 1;}}else {dst = (char*)dst + count - 1;src = (char*)src + count - 1;while (count--) {*(char*)dst = *(char*)src;dst = (char*)dst - 1;src = (char*)src - 1;}}return(ret);
}
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!