1.长度不受限制的字符串函数
📓1.1 strlen(求字符串长度)
size_t strlen ( const char * str );
1.字符串是已 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包
含 ‘\0’ )。
2.参数指向的字符串必须要以 ‘\0’ 结束。
3.注意函数的返回值为size_t,在内存中的类型是typdef unsigned int 是一个是无符号的。
#include<stdio.h>
int main()
{
char ch[10]="abcdef";
printf("%d ",strlen(ch));//6
return 0;
}
因为统计的是字符串中\0之前出现的字符个数
int main()
{
char arr1[] = {'a','b','c'};
printf("%d\n", strlen(arr1));
char arr2[] = { 'a','b','c'};
printf("%d\n", strlen(arr2));
return 0;
}
大家注意看这两段代码,虽然定义的数组名不一样,但是里面放的内容是相同的,他们的长度是多少的?
心里想这不简单吗?这不就是3吗。
我们来看结果🏌🏿♂️
我们知道创建变量会在内存中开辟空间,但是这种创建方式我们不知道会把\0放在什么样的位置,即使是同样的内容,两次调试出来的结果都是随机值,我们再去求他的长度是完全没有意义的。
我们再来看一个代码,也就是我们提到的第3点
#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;
}
结果是什么呢?是大于还是小于?🧩
str1的长度为6,str2为3,strlen函数的返回值为size_t,在内存中的类型是typdef unsigned int 是一个是无符号的。3-6的结果不是-3,而是一个无符号的数,在二进制中符号位1代表负数,0代表正数,而一个无符号数永远都是大于0,所以这是一个恒为真的条件,结果会一直都是大于🧩
我们再来模拟实现一下strlen函数
//指针减指针
#include <stdio.h>
#include<string.h>
int my_strlen(char *s)
{
char * p = s;//这里我们定义一个指针变量指向起始位置
while (*p != '\0')
{
p++;//p一直往后推进,while循环也会在找到\0停止,
}
return p - s;//遇到\0以后,再减去起始位置就得到了字符的长度
}
int main()
{
char arr[100] = "abcdef";
printf("%d\n", my_strlen(arr));
return 0;
}
//计数器方式
#include <stdio.h>
#include<string.h>
int my_strlen(char* s)
{
int count = 0;
while (*s!= '\0')
{
count++;
s++;
}
return count;
}
int main()
{
char arr[100] = "abcdef";
printf("%d\n", my_strlen(arr));
return 0;
}
📔1.2 strcpy(整体拷贝字符串)
char* strcpy(char* destination,const char* source);
1.源字符串必须以 ‘\0’ 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。
int main()
{
char arr1[100] = "###########";
char arr2[] = "hello world";
strcpy(arr1, arr2);
printf("%s\n",arr1);
return 0;
}
通过监视我看到无论是空格还是\0我们都能拷贝过去,一定要注意区分目标和源头,是将源头的数据拷贝到目标中。
模拟实现一下strcpy函数
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest&&src);
while (*dest++=*src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "Pass1234";
char arr2 [10]= "world";
my_strcpy(arr1,arr2);
printf("%s\n",arr1);
}
目标字符串和源头字符串都不能为空指针,然后将每一个字符依次拷贝到目标空间,也包括’\0’,也可以拷贝到目标空间。
📒1.3 strcat(字符串连接)
char * strcat(char * destination,const char * source);
1.源字符串必须以 ‘\0’ 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。
4.字符串不能自己给自己追加。
注意看,是从目标字符串的结尾,也就是\0的位置开始追加,一直追加到源字符串的结尾位置,目标的\0告诉我们从哪开始追加,源字符串的\0告诉我们从哪里结束追加。
模拟实现一下strcat
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
//找到目标空间\0的位置
int* p = dest;
assert(dest&&src);
while (*dest)
{
dest++;
}
// 拷贝源文件数据到\0后面的空间
while (*dest++ = *src++)
{
;
}
return dest;
}
int main()
{
char arr1[20] = "hello \0XXXXXXX";
char arr2[10] = "world";
my_strcat(arr1, arr2);
printf("%s\n",arr1);
return 0;
}
第一步是先找到目标字符串最后的’\0’的位置,再将源头字符串依次拷贝到目标字符串的后面。
📕1.4 strcmp(字符串比较)
int strcmp(const char * str1,const char * str2);
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
比较的是对应位置上的字符的大小,而非长度,(记不住的话,可以查一查ASCII码表)
两个字符串都必须要有\0不然无法停止
strcmp的模拟实现
#include<stdio.h>
#include<assert.h>
#include<string.h>
int my_strcmp(const char*s1,const char*s2)
{
assert(s1 && s2);
while (*s1==*s2)
{
if (*s1 =='\0')
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
int main()
{
char arr1[] = "ajt";
char arr2[] = "ajj";
int ret = my_strcmp(arr1,arr2);
if (ret < 0)
printf("arr1<arr2\n");
else if (ret > 0)
printf("arr1>arr2\n");
else
printf("arr1=arr2\n");
printf("%d\n",ret);
return 0;
}
2.长度受限制的字符串函数
📗2.1 strncpy(字符串按字符个数拷贝)
char * strncpy(char * destination,const char * source,size_t num);
1.从源字符串中拷贝num个字符到目标字符串中
2.如果源字符串的长度小于目标字符串,会从后面补0,也就是补\0,直到num个
直接看代码
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[10] = "########";
char arr2[5] = "abcd";
strncpy(arr1,arr2,4);
printf("%s\n",arr1);
return 0;
}
我们从arr2向arr1中指定传4个字节大小的字符过去,原先目标空间占了8个字节大小,arr2传过去后,从起始位置向后占用了4个字节,后面的内容就是由原先arr1的内容和\0组成。
📘2.2 strncat(字符串按照字符个数追加)
1.strcat和strncat的区别在于,strcat无法自己追加自己,因为会覆盖掉\0,导致死循环
2.strncat可以控制需要追加的字符个数,有效的避免了上述问题
strncat的使用方法也是从目标空间的结束位置开始,但是追加的方式不一样,是追加指定的字符个数,strncat可以实现自己追加自己
这里采用strncat使得字符串自己追加自己,指定追加了3个字符,所以在使用的时候需要注意
📙2.3 strncmp(比较两个字符串前n个字符的大小)
int strncmp ( const char * str1, const char * str2, size_t num );
这里比较的是从起始位置开始往后的第3个字符的大小,返回的是小于0的数字,代表arr1是小于arr2的。
3.字符串查找函数
🗒3.1 strstr(在字符串中查找子串)
char * strstr(char * str1,const char * str2);
1.是在str1中查找str2是否存在,返回类型是char*
2.如果存在返回字符串第一次出现的起始位置,不存早就返回NULL
模拟实现strstr
#include<stdio.h>
#include<string.h>
char *my_strstr(char* str1, char* str2)
{
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
while (*p!='\0')
{
s1 = p;
s2 = str2;
while (*s1!='\0' && *s2!='\0' && * s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')//说明已经找到子串了
{
return p;//返回找到子串的位置
}
p++;
}
return NULL;//找不到子串
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* p = my_strstr(arr1, arr2);
if (p == NULL)
{
printf("不存在");
}
else
{
printf("%s\n", p);
}
return 0;
}
除了s1,s2指向总串和子串以外,还需要一个指针p来记录当前的位置。比方说,让s1和s2指向的字符比较一下,a和b不相等,s1往后走一步,p这时候发现,你们两个人都不相等,我在这还有什么用,所以他也往后走一步。这时候s1指向总串的第一个b,s2一看,你的b和我的b相等,我也往后走一步,于是s1++,s2++,走两步发现不对劲,怎么s1还指向一个b,s2已经到c了。s2只好回到起点的位置,而这时,p就会记录下当前的位置,说明这里不相等,从下一次循环的时候开始找。
需要注意两点
1.s1和s2都不能是\0,不然一次都不用找,直接空指针了
2.当子串遇到\0时,说明已经找完了,如果找到了就返回第一次找到的起始位置,没找到就是返回\0
🗓3.1 strtok(字符串分割)
char * strtok(char * str,const char *sep);
1.sep参数是个字符串,定义了用作分隔符的字符集合
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
6.如果字符串中不存在更多的标记,则返回 NULL 指针。
大家不用这么多的条条框框吓到,其实用起来是非常简单的,我是这样理解的,用指针指向一个特殊的标志,下次打印的时候就会以它为结束标志,遇到你了我就被斩断了,后面的内容就不打印了,如果后面还想以这个特殊标志为分隔符,那么第一个参数就是NULL。
int main()
{
char arr[100] = "azhen#love$laqiang";
char buf[100] = {0};
strcpy(buf,arr);
const char* p = "#$";//以#$为分隔符
char *str = strtok(buf,p);
printf("%s\n",str);//此时打印的就是分隔符前面的内容,azhenlove
str = strtok(NULL,p);
printf("%s\n",str);//love
str = strtok(NULL, p);
printf("%s\n", str);//laqiang
return 0;
}
必须要有一块新的空地来展示内容,所以是将arr中的内容都拷贝到了buf中,再利用指针来指向标志符号实现分割的效果。
4.错误信息报告函数
📑4.1 strerror(将错误码转换成错误信息)
char * strerror(int errnum);
1.strerror函数的作用就是将错误码给转化成错误信息。
2.在C语言中有一条全局的错误码errno,在程序运行过程中,只要库函数调用失败,我们就会把此处产生的错误码放入变量errno中。
3.返回值和参数:char * strerror ( int errnum );该函数的参数就是一个错误码,输入该错误码后,经函数内部处理,将该错误码转化成一条错误信息(类型是字符串)并将该错误信息(字符串)的地址返回。
这串代码是要在当前目录去打开一个名为test.txt的文件,但是我们没有创建过这个文件,所以报错,通过strerror来将错误代码转换成错误信息,并将其打印出来,注意头文件需要引用errno
📜4.1 perror(错误信息打印输出函数)
void perror ( const char * str );
1.perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。
2.在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。
3.当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。
4.perror=strerror+printf(个人观点)
和上述的情况是一样的错误,只不过这次可以直接利用perror打印出来,转换的话也会快一点,不会过多的浪费空间
5.字符分类函数
都是需要引用头文件#include<ctype.h>
我们举几个例子来试一下
6.内存函数
📓6.1 memcpy(内存拷贝函数)
void * memcpy( void * destination, const void * source, size_t num );
1.函数memcpy从source的位置开始向后复制num个字节的数据到。 destination的内存位置。
2.这个函数在遇到 ‘\0’ 的时候并不会停下来。
3.如果source和destination有任何的重叠,复制的结果都是未定义的。
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = { 0 };
memcpy(arr2,arr1,20);
int i = 0;
float ch1[] = {1.0f,2.0f,3.0f,4.0f};
float ch2[5] = {0.0};
memcpy(ch2,ch1,8);
return 0;
}
第一个传值我们传了20个字节从arr1到arr2中,整型大小一个是四个字节,等于传了5个数过来,float一个大小是4个字节,8哥个字节等于传了两个浮点数过来。
模拟实现一下
void* my_memcpy(void* dest, void* src, size_t num)
{
void* ret = dest;
assert(dest&&src);
while (num--)
{
*(char*)dest = *(char*)src;//强制类型转换为char*
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = {0};
my_memcpy(arr2,arr1,20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ",arr2[i]);
}
return 0;
}
只有强制转换为char*了,才能每次访问一个字节大小,最高限制是num,也就是指定传的大小,随着num的递减,,我们每次读写就会依次传到目标中
📔6.2memmove(内存拷贝函数可重叠)
void * memmove ( void * destination, const void * source, size_t num );
1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2.如果源空间和目标空间出现重叠,就得使用memmove函数处理。
(我理解的就是自己往自己的内存空间里加点原来的数据)
我来为大家解读一下
这个arr数组里面有1-10的数字,通过使用memmove函数,arr代表数组名,也就是下标为0的地方,从下标为5的地方开始接收来自源头的数据,传过来的字节大小是20,也就是5个元素,这五个元素就是从头往后依次的5个元素,最后打印出来。(若有不足之处,请指点出来,感谢)
模拟实现一下
void* my_memmove(void* dest, void* src, size_t num)
{
void* ret = dest;
assert(dest);
assert(src);
if (dest < src)//从前往后
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;//因为void*无法解引用,所以强制转换为char*类型,每次访问一个字节
src = (char*)src + 1;
}
}
else//从后往前
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1+2,arr1,20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ",arr1[i]);
}
return 0;
}
📒6.3memcmp(内存比较函数)
int memcmp(const void * ptr1,const void * ptr2,size_t num);
具体应该怎么用呢,这里就要看一下内存的存储了
int main()
{
int arr1[5]={1,2,3,4,5};
int arr2[5]={1,2,3,5,6};
int ret=memcmp(arr1,arr2,13);
printf("%d\n",ret);
return 0;
}
我们假设是以小端的方式存储的,上图🏌🏿♂️
arr1就是01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
arr2就是01 00 00 00 02 00 00 00 03 00 00 00 05 00 00 00 06 00 00 00
我们比较指定的第13个字节大小,04和05当然是05大了,所以返回的值是一个小于0的数字
📕6.4 memset(内存设置函数)
void * memset ( void * ptr, int value, size_t num );
1.以字节为单位来初始化,无论是整型还是字符都可以
2.对象的首地址,改成什么,改成几个字节
我们将数组的第一个元素的每一个字节改成了2,sizeof(int)代表的是一个字节大小。
7.🧳总结
这就是字符函数与内存函数的基本介绍,同样在文章中还有些许不足之处,欢迎大家指正。🏌🏿♂️