C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数.
strlen:求字符串长度
size_t strlen(const char *string);
strlen函数的返回值为size_t,是无符号的( 易错 )
strlen这个库函数需要引用头文件
举例:使用strlen函数求字符串长度
#include
#include
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这个结束标志。
举例:使用自定义函数my_strlen,模拟实现strlen函数,
#include
#include
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需要引用头文
注意点2:因为str的空间是不会被改的,所以在*前面加上const,使代码更加安全、健壮。
详情可点击下方查看我在CSDN中写的博客实用调试技巧篇里面有关于assert和const的详细讲解:assert和const详解
提示:strlen函数的返回值为size_t,是无符号的( 易错 )
举例证明:strlen函数的返回值为size_t(unsigned int-无符号整数),是无符号的
#include
#include
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
#include
#include
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;
}
strcpy:字符串拷贝
char* strcpy(char * destination, const char * source);
目的地 源头
原理:把源指针所指向的空间里面的数据拷贝到目的地指针所指向的空间里面
例:代码1:把字符串"hello"放入arr数组中去
代码2:
#include
#include
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:为什么目标空间必须可变?如图:
strcat:字符串追加(连接)
char * strcat(char * destination, const char * source);
把源字符串的字符串追加到目标字符串中去
返回值返回的是是目标空间的起始地址
例子
#include
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
printf("追加前:%s\n", arr1);
strcat(arr1, arr2);//将arr2的字符串追加到arr1中
printf("追加后:%s\n", arr1);
}
画图分析追加过程:
监视追加过程,判断源字符串的\0会不会也带过来,如下图:
使用自定义函数my_strcat, 模拟实现strcat函数
#include
#include
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标志了,就会导致死循环,导致程序崩溃。
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);
}
else
{
printf("p==q\n");
}
}
使用自定义函数my_strcmp, 模拟实现strcmp函数
#include
#include
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);
}
else
{
printf("p==q\n");
}
}
以上所讲的三个字符串函数strcpy、strcat、strcmp是长度不受限制的字符串
下面我们再来看三个长度受限制的字符串strncpy、strncat、strncmp
strncpy:拷贝n个字符
char * strncpy(char * destination, const char * source, size_t num);
目标字符串 源字符串 专门用来指定拷贝几个字符的
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0(即\0),直到num个。
举例说明:
#include
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;
}
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;
}
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;
}
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;
}
使用自定义函数my_strstr模拟实现strstr
#include
#include
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;
}
strtok:切割字符串
strtok:char * strtok(char * str, const char * sep);
第一个字符串的内容是由第二个字符串中的标记隔开的
#include
int main()
{
char arr[] = "[email protected] 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;
}
strerror:打印错误码对应的错误信息
strerror:它可以给我们报出来当前的错误信息以及怎么错的
使用库函数时,调用库函数失败时,都会设置错误码
#include
#include
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是一个全局的错误码,它相当于一个全局变量,想要使用它,需要包含头文件
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
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
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
#include
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;
}
memcpy:memory copy(内存 拷贝)
void * memcpy(void * destination, const void * source, size_t num);
例:把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;
}
使用自定义函数my_memcpy模拟实现memcpy函数
#include
#include
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
#include
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;
}
查看监视:
但是memmove函数可以处理内存重叠的情况,下面学习memmove这个函数
memmove:memory move(内存 调动)
void * memmove(void * destination, const void * source, size_t num);
//它的参数和返回值和memcpy一模一样
memmove的使用
#include
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memmove(arr + 2, arr, 20);
return 0;
}
那为什么memmove可以处理内存重叠的情况呢?
下面我们使用自定义函数my_memmove来模拟实现一下memmove这个函数来了解其原理吧
#include
#include
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;
}
int memcmp(const void * ptr1, const void * ptr2, size_t num);
#include
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返回的原理是一样的
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 20);//把arr数组的前20个字节全部设置为1.
//以字节为单位来设置内存
return 0;
}