本章重点:
重点介绍处理字符和字符串的库函数的使用和注意事项
目录
一,函数介绍
1,1 strlen
求字符串长度,只能是字符串
原函数
size_t strlen ( const char * str );
注意;
- 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' )。
- 参数指向的字符串必须要以 '\0' 结束
- 注意函数的返回值为size_t,是无符号的( 易错 )
- 学会strlen函数的模拟实现
#include <stdio.h>
int main()
{
const char*str1 = "abcdef";
const char*str2 = "bbb";
if(strlen(str2)-strlen(str1)>0)
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}
//这个程序结果为:str2>str1
1,2 strcpy
复制字符串
原函数
char* strcpy(char * str2, const char * str1 ); //把str1指向的字符串复制到str2里面去。
注意:
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。不能是常量字符串。
int main()
{
char*arr1 = "wangwu";
char arr2[] = "zhang";
strcpy(arr1, arr2);
printf("%s", arr1);
//这个代码是错误的
//arr1是常量字符串不能被修改的。
//如果目标函数小于原字符串。也会复制过去
//但是会破坏栈区。会报错。
return 0;
}
1,3 strcat
追加字符串
char * strcat ( char * str2, const char * str1 );把str1追加到str2后面去。
注意:
- 在使用的时候str1和str2指向的字符串里面必须都要有\0
- 目标空间(str2) 必须足够大
- 目标空间必须可修改
- 注意字符串不能自己给自己追加。
1,4 strcmp
比较两个字符串
int strcmp ( const char * str1, const char * str2 );
注意
- strcmp比较的不是字符串的长度
- 比较的是字符串中对应位置上的元素的ascll值,如果相同就比较下一对,直到遇到不同的或者都远到\0.
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字。
注意:
以上strcmp,strcpy, strcat,是长度不受限制的字符串函数,这里都没有让说他执行多少个字符,一般都是直接执行到 \0.。他们在使用的时候就会有一定的风险。比如会出现溢出,非法访问,程序会崩溃等等。
以下三个函数strncmp,strncpy,strncat。是长度受限制的字符串函数。在输入参数的时候会比上面的多输入一个count参数,使用起来也会相对安全。
1,5 strncpy
也是拷贝字符串,但是加了一个参数。
char * strncpy ( char * destination, const char * source, size_t num);
//就是将前count个字符从原字符串复制到目标字符串。
注意:
- 注意num也是不能随意填写的,num应该比目标函数小,否则会形成越界访问。
- num如果比原字符串大,后面会默认补\0。直到num个。
- 这里也是傻瓜模式的最佳,不会管字符串结尾是否以\0结尾的。
这就是一个常见错误用法:后面没有\0 没有文件结束标志。
1,6 strncat
追加字符串,
char * strncat ( char * destination, const char * source, size_t num );
//用源字符串给在目标字符串后面追加count个字符串。
注意:
- 这里也要注意count的大小。
- 当count大于源字符串的时候,后面不会默认追加 \0的,和strcpy不一样,
- 这里字符串就可以自己给自己追加了,这里不会出现无限覆盖的问题。
1,7strncmp
也是比较两个字符串前前count个字符。
int strncmp ( const char * str1, const char * str2, size_t num );
//只是比较count个字符。不管后面的是否相等。
注意:
- 如果在count前已经比较出来就不用再比较了。
拓展:
如果真的是不用strcmp比较两个字符串,直接用< 和 > 比较会发生什么?
int main()
{
char arr1[10] = "abcdefg";
char arr2[10] = "sfdas";
if (arr1 > arr2)//这里比较的是两个字符串地址的大小。
if ("abcd" > "abcde")//这里也是比较两个常量字符串地址的大小。
return 0;
}
1.8 strsr
看一个字符串是不是另一个字符串的子串
char * strstr ( const char *str1, const char * str2);//在str1里面找str2.
//如果str2再str1里面出现了,他会返回str2再str1里面第一次出现的地址。
//如果没有出现,即不是字串。就返回NULL。
注意:
- 返回的指针。
- 返回的第一次出现的地址。
- 模拟实现。(这里可以去研究一下kmp算法。)
char* my_strstr(const char* str1,const char* str2)
{
assert(str1 && str2);
while (*str1 != '\0')
{
if (*str1 == *str2)
{
const char* s1 = str1;
const char* s2 = str2;
while (*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)str1;
}
}
str1++;
}
return NULL;
}
1.9 strtok
切割字符串。
char * strtok ( char * str, const char * sep );
//sep是一个字符串,定义了用作分隔符的字符集合。(存放分隔符的)
//str是一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标。(要分割的字符串)
注意:
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
举例:
int main()
{
char arr1[100] = "zxuefeng$lxuep%ing*";
char arr2[10] = "@#$%^*";
//strtok(arr1, arr2);//只找第一个
//strtok(NULL, arr2);//是从保存好的位置开始向后继续找。
//可以多次调用。
//printf("%s", arr1);
printf("%s\n", strtok(arr1, arr2));//zxuefeng
printf("%s\n", strtok(NULL, arr2));//lxuep
printf("%s\n", strtok(NULL, arr2));//ing
printf("%s\n", strtok(NULL, arr2));//(NULL)
return 0;
}
当我们不知道字符串里面有多少分隔符要去掉的时候就要用到循环了
int main()
{
char arr1[100] = "avsv$svasv$asv&vdqu*svsv@avv(";
char buf[200] = { 0 };
strcpy(buf, arr1);
const char* sep = "!@#$%^&*(";
for (char* str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
//结果为:
// avsv
// svasv
// asv
// vdqu
// svsv
// avv
return 0;
}
//此时无论有多少分隔符都能去掉。
1,10 strerror
返回错误码对应的错误信息。
char * strerror ( int errnum );
//返回的是错误信息的起始地址。
//参数就是输入一个错误码。
举例:
拓展:
1,错误码(errno) 头文件:<errno.h>
库函数在使用失败的时候,会留下一个错误码,这个错误码是一个全局变量 errno (错误码),一旦一个库函数使用或者调用失败的时候就会把错误信息对应的错误码,放在全局变量errno里面去。错误码可以是1,2,3,4,5......。可以用strerror函数 可以返回错误码所对应的错误信息的起始地址,就可以直接打印错误信息printf("%s", strerror(errno));
错误码是可以被更新的。每一个错误码对应一个错误信息。
2,malloc 头文件:<stdlib.h>
malloc这个函数是向堆区申请内存的,malloc(40) 就是向堆区申请40字节的内存。返回40个字节的起始地址。这个函数返回类型是void* 的类型所以在使用的时候要强制类型转换。int * p = (int*)malloca(40); malloc函数在申请开辟空间失败的时候会返回一个空指针,并且把错误信息对应的错误码放到全局变量errno里面去。想知道错误原因的时候,就可以用strerror返回的错误信息起始地址来打印错误信息。
int main() { int* p = (int*)malloc(40);//这里是可以返回申请成功的。 if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } return 0;//所以返回0 }
当malloc函数使用失败的时候就会返回NULL。
int main() { int* p = (int*)malloc(INT_MAX);//这里申请的空间太大了 //会申请失败会返回一个NULL if (p == NULL) { printf("%s\n", strerror(errno)); //结果为:Not enough space(没有足够的空间) return 1;//返回1 } return 0; }
3,INT_MAX 头文件<limits.h>
是指整型最大值(2147483647),
4, perror 头文件<stdlib.h>
void perror( const char *string );
p这里相当于打印错误信息。perror("自定义错误名称(自定义的信息)")。这个函数会更加主动。如果你想打印错误信息的时候就很好。和strerror 各有各的好处。
int main() { int* p = (int*)malloc(INT_MAX);//这里申请的空间太大了 //会申请失败会返回一个NULL if (p == NULL) { perror("malloc"); //结果为:malloc: Not enough space //这里的 ‘:’是perror函数自己加上去的。 //malloc是自己写的 也可也写成别的字符 return 1;//返回1 } return 0; }
二,字符操作函数
2,1 字符分类函数
函数 如果他的参数符合下列条件就返回真。参数:ascll值 或者 字符。
iscntrl
任何控制字符
isspace
空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
isdigit <ctype.h>
十进制数字 0~9。 输入一个字符的asll值,如果是数字字符,就会返回一个非零值,否则就会返回0。
isxdigit
十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower
判断小写字母a~z 如果是小写就会返回非零,不是就返回0;int islower( int c );
isupper
大写字母A~Z
isalpha
字母a~z或A~Z
isalnum
字母或者数字,a~z,A~Z,0~9
ispunct
标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph
任何图形字符
isprint
任何可打印字符,包括图形字符和空白字
2,2 字符转换函数
小写转大写 int toupper (int c); 头文件:<stdilb.h>或者<ctype.h>
输入一个小写字符会被转换成大写,并输出大写字符的ascll值。如果已经是大写了,他转换后还是一个大写字符。如果输入的不是字母,者不会对这个字符做任何改变,会将原来字符的ascll值原封不动的返回。
大写转小写 int tolower ( int c ); 头文件:<stdlib.h>或者<ctype.h>
输入一个大写字符会被转换成小写,并输出小写字符的ascll值。如果已经是小写了,他转换后还是一个小写字符。如果输入的不是字母,者不会对这个字符做任何改变,会将原来字符的ascll值原封不动的返回。
举例:
#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;
}
//答案:test string.
三,内存操作函数
3,1 memcpy
内存复制。
void * memcpy ( void * dest, const void * src, size_t num );
//和strncpy很相似。但是void*不同。
//num单位是字节,就是复制多少字节过去。
//dest就是目标空间的起始地址。src是源空间的起始地址。
//返回的是一个地址void*类型的地址。
注意:
- 函数memcpy从src的位置开始向后复制num个字节的数据到dest的内存位置。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果src和dest有任何的重叠,复制的结果都是未定义的。
- 拷贝的时候应该是不重叠的拷贝。
举例:
int main()
{
int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };
int arr2[5];
memcpy(arr2, arr1, sizeof(arr1[0]) * 5);
for (int i = 0; i < 5; i++)
{
printf("%d", arr2[i]);
}
//结果为:01234
return 0;
}
模拟实现:
void* memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* mid = dest;
//for (int i = 0; i < count; i++)
//{
// *(char*)dest = *(char*)src;
// dest = (char*)dest + 1;
// //dest++;
// src = (char*)src + 1;
// //src++;
//}
while (count--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
return mid;
}
//注意强制类型转换,不是永久性的转换。
注意:这个代码还是有一定问题的。这个只是模拟实现的。在重叠内存拷贝的时候是未定义的。虽然vs这个编译器是可以实现重叠内存拷贝但是,不能保证任何环境下都能重叠拷贝。C语言的语法未规定,memcpy是否可以重叠拷贝。在VS里面把他写的强大了。
3,2 memmove
重叠内存拷贝。
void * memmove ( void* dest, const void * src, size_t num );
//参数和memcpy一样。
注意:
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。C语言规定memmove可以重叠拷贝。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
模拟实现:
void* my_memmove(void* dest, const void* src, size_t count)
{
char* mid = dest;
if (dest < src)
{
//前->后
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//后->前
while (count--)
{
//
//dest = (char*)dest + count;
//src = (char*)src + count;
//*(char*)dest = *(char*)src;//这个是错误代码。
*((char*)dest + count) = *((char*)src + count);
}
}
return mid;
}
3.3memcmp
比较从ptr1和ptr2指针开始的num个字节
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
//ptr1大于ptr2的时候返回大于0的数。
//ptr1小于ptr2的时候返回小于0的数。
//相等的话返回0。
注意:
- 在处理的时候细分到每一个字节去比较,
- 注意大小端字节序存储。
举例:
int main()
{
int arr1[] = { 1,2,3,4, 5 };
int arr2[] = { 1,2,3,4,0x11223305 };
int ret = memcmp(arr1, arr2, 17);//0
int mid = memcmp(arr1, arr2, 18);//-1
printf("%d\n", ret);
printf("%d\n", mid);
return 0;
}
3.4 memset
内存设置
void *memset( void *dest, int c, size_t count );
//dest就是目标空间的起始地址。
//c就是想要把目标空间值修改为c
//count是想要改多少字节。
注意:
- 他是以字节为单位来初始化的
- c为十六进制的数据最大值为FF
举例:
int main()
{
int arr[] = { 1,2,3,4,5,6 };
memset(arr, 0, 24);
for (int i = 0; i < 6; i++)
{
printf("%d", arr[i]);//000000
}
return 0;
}