目录
八、strstr(判断一个字符串是不是另一个字符串的子字符串)
前言:C语言本身是没有字符串类型的,字符串通常放在常量字符串或者字符数组中。
了解函数的网站链接:Forum - C++ Forumhttps://legacy.cplusplus.com/forum/
一、strlen(求字符串的长度)
应用举例:
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "apple";
const char* str2 = "watermelon";
printf("%d %d\n", strlen(str1), strlen(str2));
return 0;
}
输出:5 10
了解了strlen的应用之后,那strlen具体是如何实现的呢?
我们先自己构造一下:
方法1:计数器
int my_strlen(const char* p)
{
int count = 0;
while (*p)
{
p++;
count++;
}
return count;
}
方法二:递归(不创建临时变量)
int my_strlen(const char* s)
{
if (*s == '\0')
{
return 0;
}
else
{
return 1+my_strlen(s+1);
//注意!此处不能写 s++,这样写会先使用后++,调用的时候,调用的是s,而非s+1,陷入死循环。
//写出 ++s 经过测试是可行的,但s+1更保险。
}
}
方法三:指针-指针(两个指针相减,得到两个指针之间元素的个数)
int strlen(const char* n)
{
const char* m = n;
while (*m!='\0')
{
m++;
}
return m - n;
}
与库中的实现方式对比一下:
我电脑的打开路径:D:\Windows Kits\10\Source\10.0.19041.0\ucrt\string\arm
打开方式:我用的是Notepad++(需自行下载),用别的也行。
size_t __cdecl strlen (const char * str)
{
const char *eos = str;
while( *eos++ ) ;
return( eos - str - 1 );
}
我定义的函数返回类型是int,库中定义的函数返回类型是size_t。
size_t是一种无符号整数类型。它是一种能够以字节表示任何对象大小的类型:size_t是sizeof运算符返回的类型,在标准库中广泛用于表示大小和计数。
size_t在使用时需要注意:size_t被设计的足够大,如果size_t作为实参过大,形参为int(取值范围为-2147483648~+2147483647),接受受到限制,可能导致错误。
库中使用size_t,因为字符串的长度不会是负数。
在第三种指针-指针实现方法中,m最终指向的是\0,而库中的eos指向的是\0后的地址。因为即使eos指向的内容已经为\0,逻辑判断为假,不再进行循环,但while中的表达式为*eos++,++依然会进行,所以eos指向的是\0后的地址。
二、strcpy(字符串拷贝)
应用举例:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = { "my heart" };
const char* str2 = "go on";
char str3[] = { "go on" };
printf("%s\n", strcpy(str1,str2));
printf("%s\n", strcpy(str1,str3));
return 0;
}
输出:go on
go on
了解了strcpy的应用之后,那strcpy具体是如何实现的呢?(以下为库中的写法)
char* strcpy(char* dest, const char* src)
{
char* ret = dest;
while (*ret++ = *src++);
return dest;
}
因为dest在完成while循环后,dest储存的地址会发生变化,所以需要先创建一个变量ret,用变量ret进行地址访问,避免dest的地址发生改变,使返回的地址保持有效。
在进行while循环时,即使逻辑判断为假,但\0已经被赋给了dest。
三、strcat(追加字符串)
应用举例:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = {"my herat"};
char str2[20] = { "go on" };
printf("%s\n", strcat(str1, str2));
return 0;
}
输出:
my heratgo on
了解了strcat的应用之后,那strcat具体是如何实现的呢?(以下为库中的写法)
char* strcat(char* dest, const char* src)
{
char* ret = dest;
while (*ret != '\0')
{
ret++;
}
while ((*ret++ = *src++) != '\0');
return dest;
}
四、strcmp(比较两个字符串)
应用举例1:
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "abcde";
const char* str2 = "abcef";
printf("%d\n", strcmp(str1, str2));
return 0;
}
输出:
-1
应用举例2:
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "my";
const char* str2 = "goon";
printf("%d\n", strcmp(str1, str2));
return 0;
}
输出:
1
字符串比较,与字符串的长度无关,是按顺序进行逐个字符的比较,不相同时(大于或者小于)就会停止比较。
应用举例2:
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "ice";
const char* str2 = "ice";
printf("%d\n", strcmp(str1, str2));
return 0;
}
输出
0
了解了strcmp的应用之后,那strcmp具体是如何实现的呢?(以下为库中的写法)
int __cdecl strcmp( const char* src, const char* dst )
{
int ret = 0;
while ((ret = *(unsigned char*)src - *(unsigned char*)dst) == 0 && *dst)
{
++src, ++dst;
}
return ((-ret) < 0) - (ret < 0); // (if positive) - (if negative) generates branchless code
}
五、strncpy(有限制的字符串拷贝)
应用举例1:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = { "my herat" };
char str2[] = { "go on" };
printf("%s\n", strncpy(str1, str2, 2));
return 0;
}
输出:
go herat
应用举例2:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = { "my herat" };
char str2[] = { "go on" };
printf("%s\n", strncpy(str1, str2, 8));
return 0;
}
输出:
go on
了解了strncpy的应用之后,那strncpy具体是如何实现的呢?(以下为库中的写法)
char * __cdecl strncpy (char * dest,const char * source,size_t count)
{
char *start = dest;
while (count && (*dest++ = *source++) != '\0') /* copy string */
count--;
if (count) /* pad out with zeroes */
while (--count)
*dest++ = '\0';
return(start);
}
六、strncat(有限制的字符串追加)
应用举例1:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "my herat" };
char str2[20] = { "go on" };
printf("%s\n", strncat(str1, str2, 2));
return 0;
}
输出:
my heartgo
应用举例2:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "my herat" };
char str2[20] = { "go on" };
printf("%s\n", strncat(str1, str2, 8));
return 0;
}
输出:
my heartgo on
了解了strncat的应用之后,那strncat具体是如何实现的呢?
我们先自己构思一下:
char* my_strncat(char* str1, const char* str2, int n)
{
char* tmp = str1;
while (*tmp != '\0')
{
tmp++;
}
while (n && (*tmp++ = *str2++)!='\0')
{
n--;
}
return str1;
}
库中的写法:
char * __cdecl strncat ( char * front,const char * back,size_t count )
{
char *start = front;
while (*front++)
;
front--;
while (count--)
if ((*front++ = *back++) == 0)
return(start);
*front = '\0';
return(start);
}
七、strncmp(比较两个字符串的前n个字符)
应用举例1(先发生字符不同):
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "abcdef" };
char str2[20] = { "abdefg" };
printf("%d\n", strncmp(str1, str2, 6));
return 0;
}
输出:
-1
应用举例2(先发生到达\0):
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "abcdef" };
char str2[20] = { "abc" };
printf("%d\n", strncmp(str1, str2, 6));
return 0;
}
应用举例3(先发生n个字符匹配完成):
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "abcdef" };
char str2[20] = { "abcefg" };
printf("%d\n", strncmp(str1, str2, 3));
return 0;
}
输出:
\0
了解了strncmp的应用之后,那strncmp具体是如何实现的呢?(以下为库中的写法)
int __cdecl strncmp(const char *first,const char *last,size_tcount)
{
size_t x = 0;
if (!count)
{
return 0;
}
/*
* This explicit guard needed to deal correctly with boundary
* cases: strings shorter than 4 bytes and strings longer than
* UINT_MAX-4 bytes .
*/
if( count >= 4 )
{
/* unroll by four */
for (; x < count-4; x+=4)
{
first+=4;
last +=4;
if (*(first-4) == 0 || *(first-4) != *(last-4))
{
return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4));
}
if (*(first-3) == 0 || *(first-3) != *(last-3))
{
return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3));
}
if (*(first-2) == 0 || *(first-2) != *(last-2))
{
return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2));
}
if (*(first-1) == 0 || *(first-1) != *(last-1))
{
return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1));
}
}
}
/* residual loop */
for (; x < count; x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char *)first - *(unsigned char *)last);
}
first+=1;
last+=1;
}
return 0;
}
八、strstr(判断一个字符串是不是另一个字符串的子字符串)
应用举例1:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "abcdef" };
char str2[20] = { "efg" };
printf("%s\n", strstr(str1, str2));
return 0;
}
输出:
(null)
应用举例2:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = { "abcdef" };
char str2[20] = { "bcd" };
printf("%s\n", strstr(str1, str2));
return 0;
}
输出:
bcdef
了解了strstr的应用之后,那strstr具体是如何实现的呢?(可能并非库中的写法)
char* my_strstr(const char* str1, const char* str2)
{
char* cp = (char*)str1;//"const char *" 类型的值不能用于初始化 "char *" 类型的实体,强制转换为char*类型
char* s1, *s2;
if (!*str2)
return ((char*)str1);
while (*cp)
{
s1 = cp;
s2 = (char*)str2;
while (*s1 && *s2 && !(*s1 - *s2))//逻辑反操作 !
{
s1++;
s2++;
}
if (!*s2)
return cp;
cp++;
}
return NULL;
}
九、strtok(拆分字符串)
应用举例:
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = {"This-is,a sample string."};
const char* p = "-,";
char tmp[30] = { 0 };
strcpy(tmp, str);
char* ret = NULL;
ret = strtok(tmp, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
return 0;
}
输出:
This
is
a sample string.
这样的写法太麻烦了,如果字符串很长, 标记字符很多,那一个一个地写效率太低了。
如何提高代码效率呢?可以使用for循环。
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = {"This-is,a sample string."};
const char* p = "-,";
char tmp[30] = { 0 };
strcpy(tmp, str);
char* ret = NULL;
for (ret = strtok(tmp, p); ret != NULL; ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
strtok函数是如何保存上次划分字符的位置呢?
使用了static 修饰变量。(static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。不同于函数内部的普通局部变量,函数内部的普通局部变量出函数即销毁。)
十、strerror(返回函数调用的错误信息)
应用举例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");//打开test.txt,并阅读
if (pf == NULL)//打开失败时,会返回空指针
{
printf("%s\n", strerror(errno));//我电脑时没有test.txt这个文件,看返回错误信息是否与事实一致
return 1;//如果无法打开test.txt,就停止,后序操作不必进行。
}
fclose(pf);//关闭文件
pf = NULL;//放置空指针
return 0;
}
输出:
No such file or directory //没有这样的文件或目录
与之相似的函数:perror(打印错误信息)
与strerror区别:strerror只把错误码转换为错误信息,是否打印取决于使用者,如果需要打印,需要:printf("%s\n", strerror(errno));
perror把错误码转换为错误信息,并且打印出来(包含自定义的信息)。
应用举例:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");//打开test.txt,并阅读
if (pf == NULL)
{
perror("fopen");//我电脑时没有test.txt这个文件,看返回错误信息是否与事实一致
return 1;//如果无法打开test.txt,就停止,后序操作不必进行。
}
fclose(pf);//关闭文件
pf = NULL;//放置空指针
return 0;
}
输出:
fopen: No such file or directory
总结