在之前已经介绍过字符串操作函数是什么,这里弄出一个篇章重点介绍字符串有关的函数,以及部分函数的模拟实现。
这里的函数都能在cplusplus.com - The C++ Resources Network找到。
这些字符串有的和scanf
一样,vs系列会认为它们不安全,让用户使用它准备的加_s
的版本。解决方法是在代码开头加上这句:#define _CRT_SECURE_NO_WARNINGS 1
。
如果是c++
,更推荐使用string
这个被封装好的字符串类。
调用strlen
需展开头文件string.h
或cstring
(c++)。
原型:
size_t strlen ( const char * str );
返回类型size_t
是无符号整型,在vs2019是4个字节(unsigned int
),在Dev-cpp5.11是8个字节(unsigned long long
)。
参数str
指向的字符串必须要以 \0
结束。
str
可接受的实参有:
char
数组(成员中最好有\0
)。strlen
函数返回的是在字符串中 \0
前面出现的字符个数(不包含 \0
)。
案例:
#include
int main() {
const char* str1 = "abcdef";
char str2[] = "bbb";
printf("%u %u", strlen(str1), strlen(str2));
return 0;
}
#include
#include
size_t mystrlen1(const char* str)//循环计数
{
assert(str);//内部表达式布尔值判断为假时终止程序
size_t count = 0;
while (*str != '\0')
{
++count;
++str;
}
return count;
}
size_t mystrlen2(const char* str)//递归实现
{
assert(str);
if (*str != '\0')
return 1 + mystrlen2(str + 1);
return 0;
}
size_t mystrlen3(const char* str)//指针之差
{
char* First = (char*)str;
while (*str != '\0')
++str;
return str - First;
}
调用strcpy
需展开头文件string.h
或cstring
(c++)。
原型:
char * strcpy ( char * destination, const char * source );
strcpy
会将源字符串source
包括\0
,拷贝到目标空间destination
。
要求:
source
必须以 ‘\0’ 结束。destination
能存储的字符数要大于等于source
的有效字符串长度。destination
必须可变。使用示例:
#include
#include
int main() {
char a[20]={'\0'},b[15]="momoi";
strcpy(a,b);
printf("%s",a);
return 0;
}
#include
#include
#include
#include
char* mystrcpy(char* destination, const char* source)
{
char* start = destination;
assert(destination && source);
while (*destination++ = *source++);
return start;
}
int no;
void f1()
{
/*char* strcpy(char * destination, const char * source );//函数原型*/
/*sizeof(destination)>=sizeof(source)*/
char a[] = "hachiman\0yukino";
char b[] = "yui";
char* c = "iroha";
char d[] = { 'n','a','o','\0' };
strcpy(a, b);
//strcpy(c, b);//c不可变,而destination要求可变
//strcpy(b,a);//目标空间b空间不足,无法支撑函数运行
int i = 0;
for (i = 0; i < 15; i++)
{
if (a[i] == '\0')
printf("\\0");
else
printf("%c", a[i]);
}
printf("\n");
//char* p = strcpy(b, d);//strcpy返回b的地址
char* p = mystrcpy(b, d);//strcpy返回b的地址
printf("%s", p);
}
int main()
{
f1();
return 0;
}
调用strncpy
需展开头文件string.h
或cstring
(c++)。
原型:
char * strncpy ( char* destination, const char* source, size_t num );
拷贝num
个字符从源字符串到目标空间。
如果源字符串的长度小于num
,则拷贝完源字符串之后,在目标的后边追加\0
,直到num
个。
使用示例:
#include
#include
int main() {
char a[20] = { "abcoria"}, b[15] = "momoi";
strncpy(a, b,3);
printf("%s", a);
return 0;
}
输出:
momoria
#include
#include
#include
//using namespace std;
char* mystrncpy(char* destination, const char* source, size_t num) {
char* start = destination;
assert(destination && source);
while (*destination && *source && num) {
--num;
*destination++=*source++;
}
return start;
}
char* mystrcpy(char* destination, const char* source)
{
char* start = destination;
assert(destination && source);
while (*destination++ = *source++);
return start;
}
char a[51] = "Donald ", b[51] = "Trump ", c[51] = { '\0' };
int main() {
strcpy(c, a);//备份
printf("%s\n", a);
strncpy(a, b, 5);//演示效果
printf("%s\n", a);
mystrcpy(a, c);
printf("%s\n", a);
mystrncpy(a, b, 5);
printf("%s\n", a);
strcpy(a, c);
return 0;
}
调用strcat
需展开头文件string.h
或cstring
(c++)。
原型:
char * strcat ( char * destination, const char * source );
strcat
能将source
字符串加在destination
的末尾。
source
的第1个字符会覆盖destination
的\0
,新的\0
会在source
拷贝完成之后加上。
因此使用这个函数:
源字符串source
必须以 \0
结束。
目标空间destination
必须有足够的大,能容纳下源字符串source
的内容。
目标空间必须可修改。
使用案例:
#include
#include
int main() {
char a[20]={"midori"},b[15]="momoi";
strcat(a,b);
printf("%s",b);
return 0;
}
输出:
midorimomoi
在模拟实现时需要注意这种情况:
destination!=source
,否则会越界访问。
例如:
实现这个,只需要添加记录用的变量size_t len=strlen(source);
即可。也有别的办法。
模拟实现:
#include
#include
#include
#include
//不做处理的mystrcat
char* mystrcat(char* destination, const char* source)
{
char* init = destination;
assert(destination && source);
while (*destination != '\0')
++destination;
while (*destination++ = *source++);
return init;
}
//做处理的mystrcat
char* mystrcat2(char* destination, const char* source)
{
assert(destination && source);
char* init = (char*)source;
size_t len = 0;
while (*init) {//求source的长度
++init; ++len;
}
init = destination;
while (*destination != '\0')
++destination;
while (*source && len) {
*destination++ = *source++;
len--;
}
*destination = '\0';
return init;
}
int main()
{
char a[30] = "abc\0xyz";
char a2[30] = "abc\0xyz";
char b[30] = { 'd','e','\0' };
char* res = strcat(a, b);
printf("%s\n", res);
int i = 0;
for (i = 0; i < 8; i++)
{
if (a[i] == '\0')
printf("\\0");
else
printf("%c", a[i]);
}
res = mystrcat(a2, b);
printf("\n%s\n", res);
for (i = 0; i < 8; i++)
{
if (a2[i] == '\0')
printf("\\0");
else
printf("%c", a2[i]);
}
char a3[12] = "abcdefgh";
mystrcat2(a3, a3 + 5);
printf("\n%s", a3);
return 0;
}
调用strncat
需展开头文件string.h
或cstring
(c++)。
原型:
char * strncat ( char * destination, const char* source, size_t num );
strcat
能将source
字符串的前num
个字符加在destination
的末尾,source
的长度不够会补\0
。
source
的第1个字符会覆盖destination
的\0
,新的\0
会在source
拷贝完成之后加上。
使用示例
#include
#include
int main() {
char a[20] = { "Aim" }, b[15] = { "momoi" };
strncat(a, b, 3);
printf("%s\n", a);
strncat(a, b, 9);
printf("%s", a);
return 0;
}
#include
#include
#include
//using namespace std;
char* mystrncat(char* destination, const char* source, size_t num) {
assert(destination && source);
char* start = destination, * space = source;
while (*destination)
++destination;
while (*space && num) {
--num;
*destination++ = *space++;
}
return start;
}
char a[51] = "Donald_", b[51] = "Trump_", c[51] = { '\0' };
int main() {
strcpy(c, a);//备份
printf("%s\n", a);
strncat(a, b, 5);//演示效果
printf("%s\n", a);
strcpy(a, c);
printf("%s\n", a);
mystrncat(a, b, 5);
printf("%s\n", a);
strcpy(a, c);
return 0;
}
调用strcmp
需展开头文件string.h
或cstring
(c++)。
原型:
int strcmp ( const char * str1, const char * str2 );
比较str1
和str2
,从头开始枚举,遇到第1个不相同的字符时返回两个字符的ASCII码值的差(有的编译器的库函数是返回{-1,0,1}
),或两个字符串完全相同时返回0。
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
使用示例:
#include
#include
int main() {
char a[20] = { "momoi" }, b[8] = { "momoi" };
printf("%d\n", strcmp(a, b));
strcpy(a, "momo");
printf("%d\n", strcmp(a, b));
strcpy(a, "momoi0");
strcpy(b, "momoi");
printf("%d\n", strcmp(a, b));
strcpy(a, "momoi");
strcpy(b, "momp");
printf("%d\n", strcmp(a, b));
return 0;
}
输出结果之一:
0
-1
1
-1
#include
#include
#include
#include
int mystrcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2 && *s1 && *s2)
{
s1++;
s2++;
}
return (*s1 - *s2 > 0 ? 1 : (*s1 == *s2 ? 0 : -1));
}
void f3()
{
char a[] = "", b[] = "bbcde";
int res = strcmp(a, b);//VS只有1,0,1 三种返回结果
int res2 = mystrcmp(a, b);
printf("%d %d", res, res2);
}
int main()
{
f3();
return 0;
}
需展开头文件string.h
或cstring
(c++)。
原型:
int strncmp ( const char * str1, const char * str2, size_t num );
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
使用示例:
#include
#include
int main() {
char a[20] = { "momo" }, b[15] = { "momoi" };
printf("%d\n", strncmp(a,b,4));
strcpy(a, "momp");
printf("%d\n", strncmp(a, b, 4));
return 0;
}
在strcmp
的模拟实现的基础上,加无符号数num
,设置num!=1
的结束条件。这样即使前面的字符相同,也会比较最后一个字符。
#include
#include
#include
int mystrncmp(const char* s1, const char* s2, size_t num)
{
//不用担心实参的num为负数,负数的无符号数的补码一般都很大
assert(s1 && s2);
while (num != 1 && *s1 == *s2 && *s1 && *s2)
{
s1++;
s2++;
--num;
}
return (*s1 - *s2 > 0 ? 1 : (*s1 == *s2 ? 0 : -1));
}
int main() {
char a[20] = { "momo" }, b[15] = { "momoi" };
printf("%d\n", mystrncmp(a,b,4));
strcpy(a, "momp");
printf("%d\n", mystrncmp(a, b, -1));
return 0;
}
需展开头文件string.h
或cstring
(c++)。
原型有两个:
const char * strstr ( const char * str1, const char * str2 ); char * strstr (char * str1, const char * str2 );
一个用于其中一个字符串是常量字符串的情况,另一个则是不包含常量字符串的情况。
strstr
的功能是在str1
中查找str2
的位置并返回,找不到会返回NULL
。
要求两个指针都是字符串,否则会发生越界。
使用示例:
#include
#include
#include
#include
int main()
{
char a[30] = { "abcdef" };
char b[20] = { "bcd" };
int i = 0;
if (strstr(a, b) != NULL)
printf("yes\n");
else
printf("no\n");
return 0;
}
这个是暴力匹配,若用两个指针表示a
和b
的枚举进程,则a
、b
都会出现回退的情况,使得比较的次数等于两个字符串的长度相等。
char* mystrstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* p = (char*)str1;
char* s1 = (char*)str1, * s2 = (char*)str2;
while (*p != '\0' && p)
{
s1 = p;
s2 = (char*)str2;
while (*s1 == *s2 && (*s1 != '\0' && *s2 != '\0'))
{
s1++;
s2++;
}
if (*s2 == '\0')
return p;
p++;
}
if (*s1 == *s2 && *s2 == '\0')
return s1;
return NULL;
}
strchr
用于在一个字符串里查找一个字符第一次出现的位置,strrchr
则用于最后一个。
需展开头文件string.h
或cstring
(c++)。
原型:
char * strtok ( char * str, const char * delimiters );
sep
参数是个字符串,定义了用作分隔符的字符集合。
str
指定一个字符串,它包含了0个或者多个由sep
字符串中一个或者多个分隔符分割的标记。
strtok
函数找到str
中的下一个标记,并将其用 \0
结尾,返回一个指向这个标记的指针。(注:strtok
函数会改变被操作的字符串,所以在使用strtok
函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok
函数的第一个参数不为 NULL
,函数将找到str
中第一个标记,strtok
函数将保存它在字符串中的位置。
strtok
函数的第一个参数为 NULL
,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL
指针。
strtok
常用于将通过指定字符分隔的字符串拆分成若干个部分。例如邮箱、网址等。
#include
#include
#include
#include
int main()
{
char a[] = "@.", b[] = "[email protected]";
char* c = strtok(b, a);
while (c != NULL)
{
printf("%s\n", c);
c = strtok(NULL, a);
}
return 0;
}
输出:
0721
qq
com
分析:
循环前:
b=="0712'\0'qq.com"
c==&b[0]//标记'0'的地址
第一次循环:
b=="0712'\0'qq'\0'com"
c==&b[5]//标记'q'的地址
第二次循环:
b=="0712'\0'qq'\0'com"//因为在a中找不到可清理的,便无变化
c==&b[8]//标记'c'的地址
第三次循环:
b=="0712'\0'qq'\0'com"//因为在a中找不到可清理的,便无变化
c==NULL//strtok标记的a的各字符串首地址已用完,返回NULL
这说明strtok
内部存在静态指针,用于保存遍历进度。
c语言的库函数在调用失败时,会将错误码放在一个errno
的变量中,想了解库函数发生的错误信息时可将errno
中的错误码翻译。
strerror
就是返回错误码的信息,错误信息用字符串表示。
调用errno
变量,需要展开头文件errno.h
。
而调用strerror
,需要展开头文件string.h
。
原型:
char * strerror ( int errnum );
errno
的值对应不同的错误信息:
0:No error
1:Operation not permittd
2:No such file or directory
3:No such process
使用示例:
/* strerror example : error list */
#include
#include
#include
int main()
{
FILE* pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
printf("Error opening file unexist.ent: %s\n", strerror(errno));
return 0;
}
FILE*
是文件指针,fopen
函数是打开指定文件用的函数,unexist.ent
是文件名,r
表示打开的方式是只读。
这个代码尝试打开一个不存咋的文件失败,于是pFile
为空,errno
的值是2。
这里的内存操作函数同样需展开头文件string.h
或cstring
(c++)。
这个函数用的最多的场景是将一个数组的内容拷贝给另一个数组。
可以拆分成两个部分来理解:mem:memory,内存;cpy:copy,拷贝。所以这是一个内存操作函数,用于实现内存的拷贝。
原型:
void * memcpy ( void * destination, const void * source, size_t num );
将source
开头的一篇连续的空间的数据拷贝给destination
,其中num
是source
表示的连续的空间的大小。完成拷贝后,返回destination
的地址。
和strcpy
不同,即使遇到\0
也不会停下来。拷贝的方式是逐字节拷贝。
若source
和destination
有任何的重叠,复制的结果都是未定义的。
例如这个样例:
{1,2,3,4,5,6}
,将{1,2,3,4}
的部分拷贝给{3,4,5,6}
的部分,可以发现{3,4}
的部分重叠,这样会导致一个问题:拷贝的时候{1,2}
将原本的{3,4}
给覆盖,使得最终的结果变成了{1,2,1,2,1,2}
。但最终目标是{1,2,1,2,3,4}
。
部分编译器的memcpy
得到的结果是前者。
使用示例:
#include
#include
int main() {
int a[10] = { 0,1,2,3,4 };
int b[10] = { 1,1,4,5,1,4,0,7,2,1 };
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
printf("\n");
//数组b占用的内存是sizeof(int)*10,也就是40字节
memcpy(a, b, 40);
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
输出:
0 1 2 3 4 0 0 0 0 0
1 1 4 5 1 4 0 7 2 1
#include
#include
#include
void* mymemcpy(void* dest, const void* src,size_t count)
{
assert(dest && src);
void* init = dest;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return init;
}
memmove
和memcpy
的差别就是memmove
函数处理的源内存块和目标内存块是可以重叠的。
原型:
void * memmove (void * destination, const void * source, size_t num );
使用示例:
#include
#include
int main() {
int a[10] = { 0,1,2,3,4 };
int b[10] = { 1,1,4,5,1,4,0,7,2,1 };
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
printf("\n");
//数组b占用的内存是sizeof(int)*10,也就是40字节
memmove(a, b, 40);
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
这个样例:
{1,2,3,4,5,6}
,将{1,2,3,4}
的部分拷贝给{3,4,5,6}
的部分,可以发现{3,4}
的部分重叠,这样会导致一个问题:拷贝的时候{1,2}
将原本的{3,4}
给覆盖,使得最终的结果变成了{1,2,1,2,1,2}
。但最终目标是{1,2,1,2,3,4}
。
memmove
要杜绝这种情况的发生,因此模拟实现时可采用根据情况进行拷贝策略。
{1,2,3,4,5,6}
将{1,2,3,4}
拷贝给{3,4,5,6}
,使最终结果为{1,2,1,2,3,4}
。source
指针小于destination
指针,因此可以从后向前拷贝。{1,2,3,4,5,6}
将{3,4,5,6}
拷贝给{1,2,3,4}
,使最终结果为{3,4,5,6,5,6}
。source
指针大于destination
指针,因此可以从前向后拷贝。当source
指针和destination
指针分别指向不同内存的数组时,两种顺序用哪种都可以。
#include
#include
#include
#include
int no;
void* mymemmove(void* destination, const void* source, size_t num)
{
void* init = destination;
//The value of source is smaller than destination.
if ((int)source - (int)destination < 0)
{
while (num != 0)//源地址比目标地址小的时候从后向前拷贝
{
*((char*)destination + num - 1) = *((char*)source + num - 1);
--num;
}
}
The value of source is bigger than destination.
else//否则就按memcpy的方式从前向后拷贝
{
while (num != 0)
{
*((char*)destination) = *((char*)source);
destination = (void*)((char*)destination + 1);
source = (const void*)((char*)source + 1);
--num;
}
}
return init;
}
void f3()
{
//void* memmove(void* destination, const void* source, size_t num)
int a[] = { 1,2,3,4,5,6 }, i = 0, b[6] = { 0 };
memcpy(b, a, sizeof(a));
memmove(a + 2, a, 12);
for (i = 0; i < 6; i++)
printf("%d ", a[i]);
putchar(10);
memcpy(a, b, sizeof(b));
mymemmove(a + 2, a, 12);
for (i = 0; i < 6; i++)
printf("%d ", a[i]);
}
int main()
{
f3();//memmove模拟
return 0;
}
memory + set,内存设置函数。
用于将内存变成指定的值。
原型:
void * memset ( void * ptr, int value, size_t num );
ptr
是要设置的内存块的首地址。
value
是要统一设置成的值。
num
是要设置的内存块的字节数。
设置方式是获取value
的最后1个字节的内容,然后将ptr
指向的内存块的每个字节的内容都用value
覆盖。
比如若value
的值是 -1 ,则 -1的补码是0xffffffff,于是取value
的最后1个字节的内容0xff给所有ptr
表示的内存块赋值。
使用示例:
#include
#include
int main() {
int a[10] = { 1,1,4,5,1,4,0,7,2,1 };
memset(a, 0, 40);
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
return 0;
}