文章目录
- 字符函数和字符串函数
- 函数介绍
- 1.strlen
- 模拟实现strlen
- 2.strcpy
- 3.strcat
- 模拟实现 strcat
- 4.strcmp
- 模拟实现 strcmp
- 5.strncpy
- 6.strncat
- 7.strncmp
- 8.strstr
- 模拟实现 strstr
- 9.strtok
- 10.strerror:
- 字符分类函数
- 字符转换函数
- 内存操作函数
- 1.memcpy
- 模拟实现memcpy
- 2.memmove
- 模拟实现 memmove
- 3.memcmp:内存比较
- 4.memset:内存设置函数
字符函数和字符串函数
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数.
函数介绍
1.strlen
strlen:求字符串长度
size_t strlen(const char *string);
strlen函数的返回值为size_t,是无符号的( 易错 )
strlen这个库函数需要引用头文件<string.h>
举例:使用strlen函数求字符串长度
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "hello";
char arr2[] = { 'a', 'b', 'c' };
char arr3[] = { 'a', 'b', 'c', '\0' };
int len1 = strlen(arr1);//参数指向的字符串是以\0结束的,结果为5
int len2 = strlen(arr2);//参数指向的字符串不是以\0结束的,结果为随机值
int len3 = strlen(arr3);//参数指向的字符串是以\0结束的,结果为3
printf("len1=%d\n", len1);//5
printf("len2=%d\n", len2);//17
printf("len3=%d\n", len3);//3
return 0;
}
总结:
使用strlen求字符串长度必须注意的两个条件才能正确求出结果
1.字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
2.参数指向的字符串必须保证以 ‘\0’ 结束,也就是说参数指向的字符串中必须有\0这个结束标志。
模拟实现strlen
举例:使用自定义函数my_strlen,模拟实现strlen函数,
#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{
int count = 0;//计数器
assert(str != NULL);
while (*str != '\0')
{
count++;
str++;
}
printf("count=%d\n", count);
return count;
}
int main()
{
char arr[] = "hello";
int len = my_strlen(arr);
printf("len=%d\n", len);
return 0;
}
注意点1:* str表示对指针进行解引用,对指针进行解引用就要保证指针要是有效的指针,为了使代码更加完善,就需要使用assert对指针进行断言,使str不能等于空指针,这就保证了指针的有效性,使使用* str更加安全,同时assert需要引用头文<assert.h>.
注意点2:因为str的空间是不会被改的,所以在*前面加上const,使代码更加安全、健壮。
详情可点击下方查看我在CSDN中写的博客实用调试技巧篇里面有关于assert和const的详细讲解:assert和const详解提示:strlen函数的返回值为size_t,是无符号的( 易错 )
举例证明:strlen函数的返回值为size_t(unsigned int-无符号整数),是无符号的
#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,但是在算数层面无符号数-无符号数算出的还是无符号数,-3如果被当成一个无符号数来解读的话,那它就没有符号位这个概念了,这时-3放到内存里的补码会解读成一个纯粹的无符号数,而这时候它的补码就是真正的原码,原码被解读之后将是一个非常大的正数,这个数一定是大于0的。所以结果为>。
但是,我们模拟实现的my_strlen函数这里返回的是整型,是有符号整型,使用它得到的结果就是<=
代码如下:
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strlen(const char* str)
{
int count = 0;//计数器
assert(str != NULL);
while (*str != '\0')
{
count++;
str++;
}
printf("count=%d\n", count);
return count;
}
int main()
{
if (my_strlen("abc") - my_strlen("abcdef")>0)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
2.strcpy
strcpy:字符串拷贝
char* strcpy(char * destination, const char * source);
目的地 源头
原理:把源指针所指向的空间里面的数据拷贝到目的地指针所指向的空间里面
例:代码1:把字符串"hello"放入arr数组中去
代码2:
#include<stdio.h>
#include<string.h>
int main()
{
char* str = "#####################";
printf("拷贝前:%s\n", str);
char* p = "hello";
strcpy(str, p);
printf("拷贝后:%s\n", str);
return 0;
}
打印结果:
- 注意:strcpy拷贝的时候会把源字符串里面的\0也带过去
- strcpy的功能:会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 使用strcpy需要注意的地方
1.源字符串必须以 ‘\0’ 结束。
2.目标空间必须足够大,以确保能存放源字符串。
3.目标空间必须可变。
疑问1:为什么目标空间必须足够大?
如果目标空间不够大,虽然能拷贝过去,但是会导致程序崩溃,也就是说目标空间放不下源字符串的数据,源字符串将目标空间撑爆了!!如下图:
疑问2:为什么目标空间必须可变?如图:
3.strcat
strcat:字符串追加(连接)
char * strcat(char * destination, const char * source);
把源字符串的字符串追加到目标字符串中去
返回值返回的是是目标空间的起始地址
例子
#include<stdio.h>
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
printf("追加前:%s\n", arr1);
strcat(arr1, arr2);//将arr2的字符串追加到arr1中
printf("追加后:%s\n", arr1);
}
画图分析追加过程:
监视追加过程,判断源字符串的\0会不会也带过来,如下图:
- 使用strcat需要注意的地方
1.源字符串必须以 ‘\0’ 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。
模拟实现 strcat
使用自定义函数my_strcat, 模拟实现strcat函数
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
//因为源字符串的空间是不会被改的,而目标字符串的空间会被该,所以可在源字符串前加上const,使代码更加健壮、安全
char* ret = dest;
assert(dest && src);//解引用操作要保证指针的有效性,需要加assert,条件是dest不等于空指针,并且src不等于空指针
//1.先找到目标字符串中的\0
while (*dest)
{
dest++;
}
//2.将源字符串追加到目标字符串,并且把\0也包含到里面去
while (*dest++ = *src++)
{
;
}
return ret;//返回的是目标空间的起始地址
}
int main()
{
char arr1[30] = "wangpengfei ";
char arr2[] = "bamingli";
printf("追加前:%s\n", arr1);
my_strcat(arr1, arr2);//将arr2的字符串追加到arr1中
printf("追加后:%s\n", arr1);
return 0;
}
思考:字符串可以自己给自己追加字符串吗?
举例说明
int main()
{
char arr[20] = "abcd";
strcat(arr, arr);
printf("%s\n", arr);
return 0;
}
不可以,因为追加时\0被覆盖了,把d拿过去之后,找不到后面的\0标志了,就会导致死循环,导致程序崩溃。
4.strcmp
strcmp字符串比较
int strcmp(const char * str1, const char * str2);
比较两个字符串的大小
有返回值,返回值是int
例子:
int main()
{
char* p = "abc";
char* q = "abcdef";
if (p > q)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
上面这个代码是错的,字符串和字符串之间不能直接比较大小!!!
strcmp这个函数比较的是对应位置的字符的ASCII码值,
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串小于第二个字符串,则返回小于0的数字
第一个字符串等于第二个字符串,则返回0
int main()
{
int ret1 = strcmp("abcd", "aabc");
int ret2 = strcmp("abbb", "acbb");
int ret3 = strcmp("aaa", "aaa");
printf("ret1=%d\n", ret1);
printf("ret2=%d\n", ret2);
printf("ret3=%d\n", ret3);
return 0;
}
使用库函数strcmp比较两个字符串的大小
int main()
{
char* p = "abcdef";
char* q = "abbb";
int ret = strcmp(p, q);//比较p指向的字符串和q指向的字符串
if (ret > 0)
{
printf("p>q\n");
}
else if (ret < 0)
{
printf("p<q\n");
}
else
{
printf("p==q\n");
}
}
模拟实现 strcmp
使用自定义函数my_strcmp, 模拟实现strcmp函数
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* s1,const char* s2)
{
assert(s1&&s2);
while (*s1 == *s2)//相等就往后走
{
if (*s1 == '\0')//判断其中一个是否等于\0,当然其中一个等于\0说明另外一个也等于\0,反之亦然,如果碰到\0就不往后比了,如果没碰到,就继续++
{
return 0;
}
s1++;
s2++;
}
/*if (*s1 > *s1)
{
return 1;
}
else
{
return -1;
}*/
return *s1 - *s2;//二者直接相减也可判断于0,或小于0
}
int main()
{
char* p = "abcdef";
char* q = "abbb";
int ret =my_strcmp(p, q);//比较p指向的字符串和q指向的字符串
if (ret > 0)
{
printf("p>q\n");
}
else if (ret < 0)
{
printf("p<q\n");
}
else
{
printf("p==q\n");
}
}
以上所讲的三个字符串函数strcpy、strcat、strcmp是长度不受限制的字符串
下面我们再来看三个长度受限制的字符串strncpy、strncat、strncmp
5.strncpy
strncpy:拷贝n个字符
char * strncpy(char * destination, const char * source, size_t num);
目标字符串 源字符串 专门用来指定拷贝几个字符的
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0(即\0),直到num个。
举例说明:
#include<stdio.h>
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "ghink";
char arr3[10] = "hello";
char arr4[] = "her";
strncpy(arr1, arr2, 5);//2表示长度,专门用来指定拷贝几个字符的
strncpy(arr3, arr4, 4);
printf("%s\n", arr1);//ghinkf
printf("%s\n", arr3);//her
return 0;
}
6.strncat
strncat:追加n个字符
char * strncat(char * destination, const char * source, size_t num);
举例说明:
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
char arr3[20] = "wangpengfei ";
char arr4[] = "bamingli";
strncat(arr1, arr2, 5);
strncat(arr3, arr4, 5);
printf("%s\n", arr1);//hello world
printf("%s\n", arr3);//wangpengfei bamin
return 0;
}
- 如果源字符串的长度小于等于于num,则拷贝完源字符串之后,在目标的后边追加0(即\0),如arr1
- 如果源字符串的长度大于num,则按照让追加的个数。在追加完成后在最后一个字符后面加\0,如arr3
7.strncmp
strncmp:比较n个字符串
int strncmp(const char * str1, const char * str2, size_t num);
int main()
{
char* p = "abcdef";
char* q = "abcqerto";
int ret1 = strcmp(p, q);//比较字符串的大小
int ret2 = strncmp(p, q, 3);//比较字符串中前3个字符
printf("%d\n", ret1);
printf("%d\n", ret2);
return 0;
}
8.strstr
strstr:一个字符串中找另一个字符串
const char * strstr(const char * str1, const char * str2);
char * strstr(char * str1, const char * str2);
strstr函数的使用
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bc";
int NULL = 0;
//在arr1中查找是否包含arr2数组,如果存在,返回的是arr2第一次出现在arr1中的位置,如果不存在,返回的是空指针
char* ret=strstr(arr1,arr2);
if (ret == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了:%s\n", ret);
}
//如果是bcq就会打印没找到
return 0;
}
模拟实现 strstr
使用自定义函数my_strstr模拟实现strstr
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1,const char* str2)
{
assert(str1&&str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
if (*str2 == '\0')
{
return (char*)str1;//强制类型转换
}
while (*cp)
{
s1 = cp;
s2 = str2;// 执行cp++之后会给将str2的值重新赋给s2
while (*s1&&*s2&&(*s1 == *s2))//*s1&&*s2:表示s1和s2都不等于\0
//两个字符串都没有被查完并且各种又都相等的时候执行循环
{
s1++;
s2++;
}
//当s1 这个字符串被查完了、或者s2这个字符串遇到\0了、或者*s1不等于*s2了,跳出循环之后
//执行if语句,条件符合时说明找到了,就返回cp
if (*s2 == '\0')
{
return (char*)cp;
}
cp++;
}
//如果第一层循环走完了都没有执行return语句,就说明找不到了,就返回空指针
return NULL;
}
int main()
{
char arr1[] = "abcdefab";
char arr2[] = "bc";
//在arr1中查找是否包含arr2数组,如果存在,返回的是arr2第一次出现在arr1中的位置,如果不存在,返回的是空指针
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了:%s\n", ret);
}
return 0;
}
9.strtok
strtok:切割字符串
strtok:char * strtok(char * str, const char * sep);
第一个字符串的内容是由第二个字符串中的标记隔开的
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
- strtok具有记忆功能
#include<stdio.h>
int main()
{
char arr[] = "123@baidu.com com\0hehe";
char* p = "@ . \0";//\0不能做分隔符,因为遇到\0就就结束了
char tmp[20] = {0};
strcpy(tmp, arr);//将数据拷贝一份处理arr中的内容,这样就不会对原数据有影响了
char* ret = NULL;
// ret=strtok(tmp, p);tmp将找到的第一个标记改为\0并记住它在字符串中的位置
// printf("%s\n", ret);
//
// ret=strtok(NULL, p);//传空指针是从刚刚记住的那个位置开始找
// printf("%s\n", ret);
//
// ret=strtok(NULL, p);
// printf("%s\n", ret);
但这样写效率不高,如果要切的字符串很多就会很太麻烦
//可以写成循环
for (ret = strtok(tmp, p); ret != NULL; ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
10.strerror:
strerror:打印错误码对应的错误信息
strerror:它可以给我们报出来当前的错误信息以及怎么错的
使用库函数时,调用库函数失败时,都会设置错误码
#include<stdio.h>
#include<errno.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));
printf("%s\n", strerror(5));
//各自作为错误码的时候,strerror函数会把错误码翻译成错误信息,并且返回错误信息所对应的那个字符串的意思
FILE* pf = fopen("test.txt","r");//fopen:打开一个文件用fopen函数,r:以读的形式打开;FILE*是一个指针类型
if (pf == NULL)//当文件没有创建时就会调用失败
{
printf("%s\n", strerror(errno));//当fopen库函数调用失败的时候,他就会把那个错误码放到errno这个变量里面去
//然后把这个错误码对应的错误信息打印出来
//errno是一个全局的错误码,它相当于一个全局变量,想要使用它,需要包含头文件<errno.h>
return 1;//调用失败,返回
}
//如果调用成功,即成功打开文件时再往下走
//...
fclose(pf);//关闭文件
pf = NULL;//将文件赋值为空指针
return 0;
}
这些错误信息都是C语言设置好了的
打印输出结果:
分析代码:FILE* pf = fopen(“test.txt”,“r”);
字符分类函数
字符分类函数:如果它的参数符合条件返回非0的值,如果不符合就返回0
iscntrl:是否为控制字符,如果是,返回非0的值,如果不是返回0 isspace:是否为空白字符,空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ isdigit:是否为十进制数字 0~9 isxdigit:是否为十六进制数字,包括所有十进制数字,小写字母af,大写字母AF islower:是否为小写字母a~z isupper:是否为大写字母A~Z isalpha:是否为字母a~ z或A~Z isalnum:是否为字母或者数字,a~ z,A~ Z,0~9 ispunct:是否为标点符号,任何不属于数字或者字母的图形字符(可打印) isgraph:是否为任何图形字符 isprint:是否为任何可打印字符,包括图形字符和空白字符
下面举几个例子 如:isdigit:判断一个字符是不是数字字符,如果是数字字符返回非0的值,如果不是,返回0
#include<stdio.h>
int main()
{
char ch1 = '&';
char ch2 = '2';
int ret1 = isdigit(ch1);
int ret2 = isdigit(ch2);
printf("ret1=%d\n", ret1);//0
printf("ret2=%d\n", ret2);//4
return 0;
}
判断一个字符是不是小写字符
#include<stdio.h>
int main()
{
char ch1 = 'A';
char ch2 = 'b';
int ret1 = islower(ch1);
int ret2 = islower(ch2);
printf("ret1=%d\n", ret1);//0
printf("ret2=%d\n", ret2);//2
return 0;
}
- 其他字符分类函数的使用基本类似,不要忘了引用头文件
- 虽然不使用库函数也能判断出这些结果,但是使用库函数不仅可以直接写,不需要自己写;还会使代码更加统一,易于阅读
字符转换函数
int tolower(int c);//大写转小写
int toupper(int c);//小写转大写
例:把含有大写的字母转为小写
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[20] = { 0 };
scanf("%s", arr);
int i = 0;
while (arr[i] != '\0')
{
if (isupper(arr[i]))
{
arr[i] = tolower(arr[i]);
}
printf("%c", arr[i]);
i++;
}
return 0;
}
内存操作函数
1.memcpy
memcpy:memory copy(内存 拷贝)
void * memcpy(void * destination, const void * source, size_t num);
- void* destination:目的地
- void * source:源头,把源头指向空间的数据拷贝到目的地指向的空间里面去
- size_t num:从源头开始要拷贝多少个字节的空间,即要拷贝的数据的宽度和大小,数据量的大小
- memcpy,返回类型为void*,表示它是去具体类型的指针,可以得知它不挑类型,什么类型的都可以拷贝
例:把arr1的前5个字符拷贝到arr2里面去
int main()
{
int arr1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);//前20个字节,即前五个字符
return 0;
}
模拟实现memcpy
使用自定义函数my_memcpy模拟实现memcpy函数
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest,const void * src,size_t num)
{
void* ret = dest;
assert(dest && src);
//void*的指针不能直接进行解引用操作,也不能进行++,--操作,因为它是无具体类型的指针,不确定它会操作几个字节
//处理num个字节
while (num--)
{
*(char*)dest = *(char*)src;//可强制类型转换为char*之后再解引用,一个字节一个字节的进行拷贝
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, 20);//前20个字节,即前五个字符
return 0;
}
查看监视:
把arr中的1 2 3 4 5的数据放到arr中3 4 5 6 7的空间里去改如何写呢?
要求:
放之前是1 2 3 4 5 6 7 8 9 10
放之后是这样的1 2 1 2 3 4 5 8 9 10
但结果是什么呢?
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void * src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
my_memcpy(arr+2, arr, 20);
return 0;
}
查看监视:
- 说明:在C语言标准中memcpy只要实现了不拷贝就可以了,但在vs中使用memcpy函数时它既可以拷贝不重叠的内存,也可以拷贝重叠内存
- 而我们自己模拟实现的my_memcpy是按照C语言对memcpy这个库函数所规定的标准(只拷贝不重叠内存即可)来实现的。
但是memmove函数可以处理内存重叠的情况,下面学习memmove这个函数
2.memmove
memmove:memory move(内存 调动)
void * memmove(void * destination, const void * source, size_t num);
//它的参数和返回值和memcpy一模一样
memmove的使用
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memmove(arr + 2, arr, 20);
return 0;
}
查看监视:
- 由结果可以得出memmove可以处理内存重叠的情况
模拟实现 memmove
那为什么memmove可以处理内存重叠的情况呢? 下面我们使用自定义函数my_memmove来模拟实现一下memmove这个函数来了解其原理吧
#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, const void*src, size_t num)
{
void* ret = dest;
assert(dest&&src);
if (dest < src)
{
//从前向后拷贝
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//从后向前拷贝
while (num--)//20 19...
{
*((char*)dest + num)=*((char*)src+num);
}
}
return ret;
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memmove(arr + 2, arr, 20);
return 0;
}
查看监视:
3.memcmp:内存比较
int memcmp(const void * ptr1, const void * ptr2, size_t num);
#include<stdio.h>
int main()
{
float arr1[] = { 1.0, 2.0, 3.0, 4.0 };
float arr2[] = { 1.0, 3.0 };
int ret=memcmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}
memcmp与strcmp返回的原理是一样的
4.memset:内存设置函数
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 20);//把arr数组的前20个字节全部设置为1.
//以字节为单位来设置内存
return 0;
}
查看内存: