字符串
前言:
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的.
字符串通常放在 常量字符串中或者字符数组中。
字符串常量 适用于那些对它不做修改的字符串函数.
strlen
strlen是一个求字符串长度的函数,遇到\0后停止
函数原型
size_t strlen(const char* );
注意:
- 返回值是size_t,size_t就是无符号整型
- strlen里面存放的是字符指针类型
- 遇到\0就停止,长度不包含\0
使用形式:
int main()
{
//字符指针
char* p = "abcdef";
size_t sz = strlen(p);//注意strlen返回的是size_t
printf("%u\n", sz);//size_t是无符号整形使用%u
//字符数组
char arr[] = "abcdefg";
sz = strlen(arr);
printf("%u\n", sz);
return 0;
}
错误的使用:
char arr[] = { 'a','b','c' };
size_t sz= strlen(arr);
printf("%d", sz);
结果:19,显然是错误的
这样的字符数组,因为没有以\0结尾,所以strlen会一直向下查找,直到找到\0为止。
所以,在使用的时候,我们要十分注意这个用法,字符串一定要含有\0
无符号整形的应用:
#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
因为strlen的结果是size_t是无符号整形,两个size_t的无符号整形相减之后,还是以无符号整形的形式出现的,无符号整形就是没有负数的,所以是大于0的。
模拟实现strlen
//使用计数器来计算
int my_strlen1(char* arr)
{
int count = 0;
while ((*arr++) != '\0')
{
count++;
}
return count;
}
//使用指针相减来计算
int my_strlen2(char* arr)
{
char* end = arr;
while ((*end) != '\0')
end++;
return end - arr;
}
//使用递归来实现
int my_strlen3(char* arr)
{
if (*arr == '\0') return 0;
else return 1 + my_strlen3(arr + 1);
}
int main()
{
char arr[] = "abcdef";
printf("%d\n", my_strlen1(arr));
printf("%d\n", my_strlen2(arr));
printf("%d\n", my_strlen3(arr));
return 0;
}
strcpy
字符串拷贝函数,简而言之就是字符串届的赋值,将A的字符串赋值到B的字符串中,并且覆盖了B的字符
函数原型:
char* strcpy(char* destination,const char* source);
把source指向的字符拷贝到destination指向的字符中去,(包括’\0’),并且要注意destination的数组长度要足够长以包含source的元素
注意
- source字符串必须包含\0
- destination的长度必须足够长
- 拷贝的时候也拷贝了\0
- 遇到\0就停止拷贝了
源字符串包含\0(覆盖)
正确的做法:
int main()
{
char source[] = "1";
char destination[100] = "abc";
strcpy(destination, source);
printf("%s", destination);
return 0;
}
错误的做法:
char source[] = { '1','2' };
char destination[] = "abcdef";
strcpy(destination, source);
printf("%s", destination);
原因就是source里面没有\0,它会一直向后寻找直到找到\0为止,所以打印了不属于本数组的内容。
遇到\0就停止
char source[] = "abc\0edf";
char destination[] = "xxxxxxxxxxxxxxxxx";
strcpy(destination, source);
printf("%s", destination);
目标字符串足够长
对于我们的目标字符串,他是为了承载我们的source的函数的,所以我们的destination的字符串长度必须足够长。
char source[] = "abcdef";
char destination[] = "1";
strcpy(destination, source);
printf("%s", destination);
这样的代码是会报错的
目标字符串可修改
char source[] = "abcdef";
char* destination = "12345678";//目标字符串是字符串常量,无法修改会报错
strcpy(destination, source);
printf("%s", destination);
这个程序会报错
这个就说明了,目标字符串必须是变量,不能是字符串常量,字符串常量无法修改。
char* my_strcpy(char destination[],const char source[])//注意:返回值要是char*
{
char* ret = destination;
assert(source&&destination);//断言,防止source和destination为NULL指针,因为下文解引用了
while (*destination++ = *source++)
;
return ret;
}
int main()
{
char source[] = "123456";
char destination[] = "xxxxxxxxxxxxxxxxxxx";
printf("%s", my_strcpy(destination, source));
return 0;
}
strncpy
函数原型:
char* strncpy(char* destination,const char* source,size_t num);
注意:
- 将
num
个source
中的字符拷贝到destination
中去,不是全部拷贝了- 如果
num
大于source
的字符个数,就补充\0,直到等于num
为止num
的值不要超过destination
的空间大小
补充\0
char arr1[] = "abcdef";
char arr2[] = "12";
strncpy(arr1, arr2, 4);
printf("%s", arr1);
上面那个就是自动补充的\0.
模拟实现
char* my_strncpy( char* arr1, const char* arr2, size_t num)
{
char* ret = arr1;//记录头指针,方便返回
while (num)
{
if (*arr2 != '\0')//如果源字符串还没有达到\0
{
*arr1 = *arr2;//就把源字符串的值赋予目标字符串中
arr2++; arr1++;
num--;
}
else//源字符串没有值了,但是还没有结束
{
*arr1 = '\0';//就把\0赋予目标字符串
num--;
}
}
return ret;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "12";
printf("%s", my_strncpy(arr1, arr2, 4));
return 0;
}
strcat
函数原型:
char* strcat(char* destination,const char* source);
工作原理:
将source
的字符拼接到destination
的后面。source
的第一个值会覆盖destination
的\0
,对destination
进行拼接,直到找到\0
为止
易错点:
源字符串必须以 ‘\0’ 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
下面是对易错点进行示范:
目标字符串足够长
char source[] = "abc";
char destination[] = "123";
strcat(destination, source);
printf("%s", destination);
这样是不对的,会报错
但是,如果加长目标字符串的长度,就不会报错
char source[] = "abc";
char destination[100] = "123";//将目标字符串的空间增大
strcat(destination, source);
printf("%s", destination);
源字符串须含有\0
错误示范:
char source[] = { 'a','b','c' };
char destination[100] = "123";
strcat(destination, source);
printf("%s", destination);
如果source不含有\0,strcat函数就会一直寻找\0直到找到为止,所以会出现上面的乱码。
目标字符串必须可修改
目标字符串不可以是常量字符串,因为常量字符串不可以写入,不可以修改
char source[] = "abc";
char* destination = "123";//常量字符串不可以修改,会报错
strcat(destination, source);
printf("%s", destination);
自己给自己追加
自己不可以给自己追加,会报错
char destination[] = "123";
strcat(destination, destination);
printf("%s", destination);
//会报错
但是,如果是新创建一个数组去保存,即使是相同的元素,也是不会报错的。
所以,下回如果你想要在一个字符串后面添加一个相同的字符串,就要新创建一个数组了,就像下面这样:
同时使用strcpy函数和strcat函数才可以完成自己对自己的追加
char arr1[] = "123";
char arr2[10] = "";
strcpy(arr2, arr1);
strcat(arr2, arr1);
printf("%s", arr2);
模拟实现
char* my_strcat(char* destination, const char* source)
{
assert(destination && source);
char* pd = destination;
char* ps = source;
//找到destination的\0位置
while (*pd != '\0')
pd++;
//从那开始进行赋值
while (*pd++=*ps++)
{
;
}
return pd;
strncat
函数原型:
char* strncat(char* destination,const char* source,size_t num)
工作原理:
从下面的例子中可以看出来,strncat
的作用:
char arr1[20] = "abc\0xxxxxxxx";
char arr2[] = "123";
strncat(arr1, arr2, 2);
printf("%s", arr1);
结果是abc12
如果num过长,也只能拷贝到\0为止,不能再继续拷贝了
char arr1[20] = "adc\0xxxxxxxx";
char arr2[] = "123";
strncat(arr1, arr2, 6);
printf("%s", arr1);
工作原理
strncat
遇到arr1
的\0后,将arr2
的元素连接上去。并在最后自动补上\0
如果
num
的值大于arr2
的值,把arr2
的值的内容拷贝完成后,直接在函数的后面加入\0即可
模拟实现
char* my_strncat(char* arr1, const char* arr2, size_t num)
{
char* p1 = arr1;
char* p2 = arr2;
//找到arr1[]的‘\0'
while (*p1!='\0')
{
p1++;
}
//开始把arr2的元素接到arr1上面
while ((*p1++ = *p2++) && num-1)//当num-1等于0或者接到arr2的\0为止
num--;
*p1 = '\0';//为字符串的末尾接上\0
return arr1;
}
int main()
{
char arr1[20] = "adc";
char arr2[] = "123";
printf("%s", my_strncat(arr1,arr2,2));
return 0;
}
strcmp
这个函数是字符串比较函数。
注意:
比较的是内容,不是长度。
如果字符串的每一个字符的内容都相等,就返回0.
如果一个字符串的某一个字符大于相同位置的某一个字符,就返回1
如果一个字符串的某一个字符小于相同位置的某一个字符,就返回-1
(注意不是看长度,不是长返回1,短就返回-1)
比较内容
char* arr1 = "123456";
char* arr2 = "124";
printf("%d", strcmp(arr1, arr2));
这道题,就可以充分的看出来,在‘3’和‘4’比较的时候,虽然arr1的长度比arr2的长度大。但是‘3’的ASCII比‘4’的小。所以返回的还是-1。
模拟实现
int my_strcmp(const char* arr1, const char* arr2)
{
while (*arr1 == *arr2&&*arr1!='\0'&&*arr2!='\0')
{
arr1++; arr2++;
}
return *arr1 - *arr2;
}
int main()
{
char* arr1 = "124";
char* arr2 = "123";
printf("%d", my_strcmp(arr1, arr2));
}
strncmp
函数原型:
int strncmp(const char* destination,const char* source,size_t num);
工作原理:
这个strncmp
的工作原理和strcmp
的工作原理十分相同,num
是多少就比较多少元素
模拟实现:
int my_strncmp(const char* arr1, const char* arr2, size_t num)
{
while (num && (*arr1 == *arr2))
{
if (*arr1 == '\0')
return 0;
num--;
arr1++; arr2++;
}
return *arr1 - *arr2;
}
int main()
{
char arr1[] = "1234";
char arr2[] = "12";
printf("%d", my_strncmp(arr1, arr2,5));
return 0;
}
strstr
函数原型:
char* strstr(const char* arr1 ,const char* arr2);
工作原理:
在arr1
中找arr2
,如果找到了,就返回arr2
在arr1
中第一次出现的地方的地址;如果没找到,就返回NULL
char arr1[] = "1 2 3 2 4 5";
char arr2[] = "2";
printf("%s", strstr(arr1, arr2));
模拟实现
!!!
char* my_strstr(const char* arr1, const char* arr2)
{
char* str1 = (char*)arr1;
char* str2 = (char*)arr2;
char* cp = str1;
while (*cp)
{
str1 = cp;
str2 = arr2;
while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2)
{
str1++;
str2++;
}
if (*str2 == '\0')//如果将目标元素全部遍历完成后
return cp;//就返回找到的
cp++;
}
return NULL;//返回NULL
}
strtok
函数原型:
char* strtok(char* str,const char* seq);
工作原理:
- strtok是一个字符串切割函数,如果见到seq中的一些切割字符,就把str根据切割字符成很多小部分。
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改
变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针
具体实现:
int main()
{
char arr1[] = "E1:T4:U7:78:34,90.";
char arr2[100] = "";
char seq[] = ":,";
strcpy(arr2, arr1);//一般使用的是拷贝的临时变量
printf("%s\n", arr1);
char* str = NULL;
//初始变量
for (str=strtok(arr2, seq); str!= NULL; str=strtok(NULL, seq))
{
printf("%s\n", str);
}
return 0;
}
strerror
函数原型:
char* strerror(int errnum);
工作原理:
- 根据
errnum
的内容找到错误码所对应的信息 - 返回的是错误码所对应的信息的地址
具体实现
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));
return 0;
}
在C语言内部存在一个全局变量:errno
,它就是专门用来记录错误码的。
可以利用strerror
来打印errno
所对应的错误信息。
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
printf("%s\n", strerror(errno));
else
printf("have found it\n");
return 0;
perror
跟strerror
很相同的一个函数还有perror
,并且perror
更加高效。
相当于:printf+strerror
内存函数
memcpy
函数原型:
void* memecpy(void* destination,const void* source,size_t count);
工作原理:
- 和strcpy的作用相似,就是拷贝目标数组中的内容。不同的是:strcpy只是拷贝字符串,但是memcpy可以拷贝任意不同类型的数值
- 注意:count一定是字节数,不是元素个数
具体实现:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[100] = {0};
memcpy(arr2, arr,10*sizeof(int));
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
模拟实现(都转化为char*)
注意:
对于这种void*类型的函数,如果想要把它转化为我们的目标类型,是很复杂的。
所以,我们就采取了一个非常nice的方法。
就是把它们都转化为char*类型,这样无论是什么类型的数字,我们都可以采用一次只调用一个字节的方法。
我们查字节数,这样就轻松解决了问题了。
void* my_memcpy(void* arr1, const void* arr2,size_t count){ char* ret = arr1;//先 保存arr1的首地址 while (count--) { *(char*)arr1 = *(char*)arr2;//将元素都转化成char*类型的,这样的话就可以遍历count啦 arr1 = (char*)arr1 + 1;//把它们都转化成char*类型。我们就可以按照字节的顺序一次只跳过一个字节 arr2 = (char*)arr2 + 1; } return ret;}int main(){ int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[100] = { 0 }; my_memcpy(arr2, arr, 10 * sizeof(int)); for (int i = 0; i < 10; i++) { printf("%d ", arr2[i]); } return 0;}
memmove
工作原理:
和
memcpy
的工作原理一样,只是解决了memcpy
的覆盖的问题。
比如说,下面这个:
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memcpy(arr1 + 2, arr1, 16); for (int i = 0; i < 10; i++) { printf("%d ", arr1[i]); }
memcpy
的缺点就是它在变化的太快了。不能将原来的元素赋值,反而将新的元素赋值了,但是这不是我们想要看到的
模拟实现
下面我们就来实现一下不会随着改变而改变的memmove
函数
void* my_memmove2(void* des, const void* souc, size_t count){ void* ret = des; if (des > souc && (char*)des <= (char*)souc + count - 1) {//从后向前拷贝 while (count--) { *((char*)des + count) = *((char*)souc + count); } } else//从前向后拷贝 { while (count--) { *(char*)des = *(char*)souc; des = *(char*)des + 1; souc = *(char*)souc + 1; } } return ret;}
memcmp
函数原型:
int memcmp(const void* str1,const void* str2,size_t num);
工作原理:
还是以字节数num
进行比较的.
如果所比较的字节的内存不相等,就返回相应的值
如果所比较的字节的内存相等,就继续比较下一个内存的字节。
memset
函数原型:
void memset(void* arr,int x,size_t count);
工作原理:
将arr的count个字节,全部赋予x
具体实现:
int arr[] = { 16,8,4,2,0 }; memset(arr, 1, 20); return 0;
模拟实现:
void* ret = arr1; while (count--) { *((char*)arr1 + count) = (char)x; } return ret;