从msdn里可以查找这些库函数的使用及定义,并且,字符串相关的函数在使用时,需要包含头文件:
#include<string.h>
目录
一、求字符串长度的函数——strlen
要点:
1、字符串将'\0'作为结束标志,strlen函数返回的是在字符串中'\0'前面出现的字符个数(不包含'\0')
2、参数指向的字符串必须要以'\0'结束
3、注意函数的返回值是size_t,是无符号的 ,即:unsiged int,转入定义查看:
使用举例:
//使用strlen
int main()
{
int ret = strlen("abcdef");
printf("%d\n", ret);
return 0;
}
输出:6
注:字符串的末尾,系统会自动添加一个'\0'
错误示范:
int main()
{
char a[3] = { 'l','y','j' };
printf("%d\n", strlen(a));
return 0;
}
思考它的结果会是什么呢?
我在本次运行时,输出42,但是并不能保证我下一次也是42,为什么呢?
数组里面是是一个一个存放的字符,后面并没有'\0',strlen就会一直往内存后面去数,直到遇到'\0'为止,因此他的值是没有办法确定的。
有人就会去做实验,说,我不是每次运行出来都是一样的吗?两个问题,和我的结果一样吗?如果你再往代码加点东西,还一样吗?
如下:
int main()
{
char a[3] = { 'l','y','j' };
char b[5] = { "aagga" };
printf("%d\n", strlen(a));
return 0;
}
这个代码运行时,内存里还会存放数组b的内容,下面来看结果:
所以这种写法也没有意义呀,求不出来它的真实值
下面再来关注strlen使用的一个小细节:
int main()
{
if (strlen("abc") - strlen("abcdef")>0)
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
思考:输出什么?
正确输出:>
原因:因为strlen返回的是size_t,无符号整型,所以两个无符号整型相减还是无符号整型。
相减为-3,-3如果当作无符号数时,内存中存放的是它的补码,算出来将会是一个非常大的正数。
正确写法:
int main()
{
if (strlen("abc") >strlen("abcdef"))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
模拟实现strlen
代码1:
计数器的方法:
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* p)
{
assert(p);
size_t count = 0;
while (*p != '\0')
{
count++;
p++;
}
return count;
}
int main()
{
size_t len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
像库函数一样,返回值设置为size_t,原因是:计算长度不可能为负数,但是在使用时,注意一下,不要出现上述的bug,当然也可以设置为int,不影响。
将字符串传过去,实际上传的是首字符的地址,所以函数用字符指针来接收,又考虑到,我们只是数它的长度,并不会去改变指针里面的内容,所以我们用const 来限制他,更加安全。
接收指针,先断言指针是否为空,即利用assert,使用它时,需要包含头文件,<assert.h>
后续一个一个数,遇到'\0'结束即可,利用count计数。
方法二:
指针 - 指针
size_t my_strlen(const char* p)
{
assert(p);
char* str = p;
while (*p != '\0')
{
p++;
}
return p-str;
}
int main()
{
size_t len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
注:
指针-指针的绝对值得到是之间的元素,所以返回时,注意是尾指针-减去头指针,否则,它返回的是size_t,就会将其负数转化成一个很大的正数,或者你也可以将返回值设为int
方法三:
递归
size_t my_strlen(const char* p, int count )
{
if (*p != '\0')
{
count++;
my_strlen(++p, count);
}
else
return count;
}
int main()
{
size_t len = my_strlen("abcdef",0);
printf("%d\n", len);
return 0;
}
二、长度不受限制的字符串函数
1、字符串拷贝——strcpy
source——>源头,destination——>目的地
就是把源头指针所指向的空间的数据拷贝到目的地指针所指向的空间里面去
例:
int main()
{
char a[10] = { 0 };
strcpy(a, "name");
printf("%s", a);
return 0;
}
输出就是:name
注:
错误写法:a="name";——>a是数组名,是首元素地址,地址是编号,所以它是一个常量,不能给一个常量赋值吧,所以这种写法是错误的。所以需要使用字符串strcpy
细节:遇到'\0'拷贝结束(它是将'\0'也一并拷过来的),例:
错误示范1:
int main()
{
char a[10] = { 0 };
char b[3] = { 'l','y','j' };
strcpy(a, b);
printf("%s", a);
return 0;
}
找不到 '\0'的具体位置,运行出错啦!(越界了最后)
错误示范2:
int main()
{
char* p = "abcdef";
char arr[3] ="lyj";
strcpy(p, arr);
printf("%s\n", p);
}
这里的p所指向的是常量字符串,是不可被修改的,因此运行会出错的。(目标空间必须可变)
小结:
1、源字符串必须以'\0'结束
2、会将源字符串中的'\0'拷贝到目标空间
3、目标空间必须足够大,以确保能存放源字符串
4、目标空间必须可变
模拟:
char* my_strcpy(char* str1, const char* str2)
{
assert(str1 && str2);
char* p = str1;
do
{
*str1 = *str2;
str1++;
str2++;
} while (*str2 != '\0');
return p;
}
int main()
{
char a[100] = { 0 };
char b[] = "abcdef";
my_strcpy(a, b);
printf("%s\n", a);
return 0;
}
优化:
char* my_strcpy(char* str1, const char* str2)
{
assert(str1 && str2);
char* p = str1;
while (*str1++ = *str2++)
;
return p;
}
int main()
{
char a[100] = { 0 };
char b[] = "abcdef";
my_strcpy(a, b);
printf("%s\n", a);
return 0;
}
返回的是目的地的指针(模拟模拟,库函数是这样设置的),所以一开始需要用一个p来记录它。
2、字符串追加——strcat
使用举例:
int main()
{
char a[20] = "love ";
char b[10] = "you";
strcat(a, b);
printf("%s\n", a);
return 0;
}
输出:
模拟:
char* my_strcat(char* str1, const char* str2)
{
assert(str1 && str2);
char* p = str1;
//找到目标空间末尾
while (*str1!='\0')
str1++;
//拷贝
while (*str1++ = *str2++)
;
return p;
}
int main()
{
char a[20] = "bet ";
char b[10] = "on me";
my_strcat(a, b);
printf("%s", a);
return 0;
}
小结:
1、源字符串必须以'\0'结束
2、目标空间必须有足够的大,能容纳下源字符串的内容
3、目标空间必须可修改
注:这个函数是不可以字符串自己给自己追加。
原因:字符串末尾'\0'被覆盖,找不到'\0’停不下来,代码崩溃
3、字符串比较——strcmp
比较的是ASCII码
标准规定:
1、第一个字符串大于第二个字符串,则返回>0的数
2、第一个字符串小于第二个字符串,则返回<0的数
3、第一个字符串等于第二个字符串,则返回0
使用:
int main()
{
char a[20] = "abcde";
char b[20] = "abcdef";
int ret =strcmp(a, b);
if (ret > 0)
printf(">");
else if (ret == 0)
printf("=");
else
printf("<");
return 0;
}
输出:<
模拟:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1== *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else if (*str1 < *str2)
return -1;
}
int main()
{
char a[20] = "lyj";
char b[20] = "lyj";
int ret = my_strcmp(a, b);
if (ret > 0)
printf(">");
else if (ret == 0)
printf("=");
else
printf("<");
return 0;
}
优化:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
三、长度受限制的字符串函数
下面三个函数都是比上述的长度不受限制的字符串函数多了一个参数size_t count,来限制长度
1、字符串拷贝——strncpy
使用:
int main()
{
char a[10] = { 0 };
strncpy(a, "name",2);
printf("%s", a);
return 0;
}
如果源字符串的长度小于需要拷贝的长度,则会用'\0'来填补
模拟:
char* my_strncpy(char* str1, const char* str2,size_t count)
{
assert(str1 && str2);
char* p = str1;
int n = 0;//标记个数
while (*str1++ = *str2++)
{
n++;
if (count == n)
break;
}
while(count > n)
{
*str1++ = '\0';
n++;
if (n == count)
break;
}
return p;
}
int main()
{
char a[100] = { 0 };
char b[] = "abcdef";
my_strncpy(a, b,7);
printf("%s\n", a);
return 0;
}
2、字符串追加——strncat
使用:
模拟:
char* my_strncat(char* str1, const char* str2,size_t count)
{
assert(str1 && str2);
char* p = str1;
int n = 0;//标记个数
while (*str1!='\0')
str1++;
while (*str1++ = *str2++)
{
n++;
if (count == n)
break;
}
while(count > n)
{
*str1++ = '\0';
n++;
if (n == count)
break;
}
return p;
}
int main()
{
char a[20] = "bet ";
char b[10] = "on me";
my_strncat(a, b,4);
printf("%s", a);
return 0;
}
3、字符串比较——strncmp
使用:
int main()
{
char a[20] = "abcde";
char b[20] = "abcdef";
int ret =strncmp(a, b,3);
if (ret > 0)
printf(">");
else if (ret == 0)
printf("=");
else
printf("<");
return 0;
}
输出:=
模拟:
int my_strncmp(const char* str1, const char* str2,size_t count)
{
assert(str1 && str2);
int n = 1;//标记
while (*str1 == *str2)
{
if (n == count)
{
break;
}
else if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
n++;
}
return *str1 - *str2;
}
int main()
{
char a[20] = "ly";
char b[20] = "lyj";
int ret = my_strncmp(a, b,2);
if (ret > 0)
printf(">");
else if (ret == 0)
printf("=");
else
printf("<");
return 0;
}
输出:=
四、字符串查找
1、查找子串——strstr
看文档;
该函数返回的是子串在字符串里面的起始位置,如果没找到子串,则返回空指针
使用:
int main()
{
char a[20] = "abcdelyjhaha";
char b[20] = "lyj";
char* ret = strstr(a, b);
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
输出:
模拟:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
while (*p)
{
s1 = p;
s2 = str2;
while (*s1 != '\0'&&*s2!='\0'&&*s1==*s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)p;
}
p++;
}
return NULL;
}
int main()
{
char a[20] = "abcdelyjhaha";
char b[20] = "lyj";
char* ret = my_strstr(a, b);
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
分析:
2、切割字符串——strtok
1、Delimit参数是个字符串,定义了用作分割符的字符集合
2、第一个参数指定一个字符串,它包含了0个或多个由Delimit字符串中一个或多个分隔符分割的标记。
3、strtok函数找到strToken中的下一个标记,并将其用'\0'结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
4、strtok函数的第一个参数不为NULL,函数将找到strToken中第一个标记,strtok函数将保存他在字符串中的位置
5、strtok函数的第一个参数为NULL,函数将在同一个字符串中保存的位置开始,查找下一个标记
很绕的样子,使用一下,就明白了
例:
int main()
{
char a[30] = "lyj@xian.wenlixuyan&ruanjian";
const char* b = "@.&";//Delimit参数是个字符串,定义了用作分割符的字符集合
char tmp[30] = { 0 };
strcpy(tmp, a);//3\strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改
//strtok函数的第一个参数不为NULL,函数将找到strToken中第一个标记,strtok函数将保存他在字符串中的位置
char* ret=strtok(tmp, b);
printf("%s\n", ret);
//strtok函数的第一个参数为NULL,函数将在同一个字符串中保存的位置开始,查找下一个标记
ret = strtok(NULL, b);
printf("%s\n", ret);
ret = strtok(NULL, b);
printf("%s\n", ret);
ret = strtok(NULL, b);
printf("%s\n", ret);
return 0;
}
优化:
int main()
{
char a[30] = "lyj@xian.wenlixuyan&ruanjian";
const char* b= "@. & ";
char tmp[30] = { 0 };
strcpy(tmp, a);
char* ret = NULL;
for (ret = strtok(tmp, b); ret != NULL; ret = strtok(NULL, b))
{
printf("%s\n", ret);
}
return 0;
}
输出:
模拟:
char* my_strtok(char* str1, const char* str2)
{
assert(str2);
static int sz1 = NULL;
static int count = NULL;
static char* s1 = NULL;
static char* s2 = NULL;
int sz = 0;
if (str1 != NULL)//第一次进入。
{
sz1 = strlen(str1);//计算出str1中所有字符的个数
s2 = str1;//记录初始地址,等下找到分割符时,将这个地址返回。
sz = strlen(str2);
for (*str1; *str1 != 0; str1++)
{
for (int i = 0; i < sz; i++)
{
if (i == 0)
{
count++;
}
if (*str1 == *(str2 + i))
{
*str1 = 0;
s1 = str1;//记录这一次置0的位置。
return s2;
}
}
}
}
else
{
s2 = s1 + 1;
str1 = s2;
sz = strlen(str2);
for (*str1; *str1 != 0; str1++)
{
for (int i = 0; i < sz; i++)
{
if (i == 0)
{
count++;
}
if (*str1 == *(str2 + i))
{
*str1 = 0;
s1 = str1;//记录这一次置0的位置。
return s2;
}
}
}
if (count > sz1)
{
return NULL;
}
return s2;
}
}
int main()
{
char arr[30] = "lyj@xian.wenlixuyan&ruanjian";
char* p = "@.&";
char* str = NULL;
for (str = my_strtok(arr, p); str != NULL; str = my_strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}
五、错误信息报告——strerror
C语言的库函数,在执行失败的时候,都会设置错误码
如下:
(括号里面的错误码,C语言的库函数,在执行失败的时候,都会自动设置相应的错误码)
例如:
结束啦!后面还会出一期关于内存操作的函数,就可以不止是拷贝复制字符串啦!