C - 字符串函数

本文详细介绍了C语言中用于处理字符串的一些关键函数,包括strlen用于计算字符串长度,strcpy用于字符串拷贝,strcat用于字符串追加,strcmp用于比较两个字符串,以及strncpy、strncat、strncmp、strstr、strtok和strerror等函数的用途和基本实现原理。通过示例代码和解析库函数的实现,帮助读者深入理解这些函数的工作方式。
摘要由CSDN通过智能技术生成

目录

 一、strlen(求字符串的长度)

二、strcpy(字符串拷贝)

三、strcat(追加字符串)

四、strcmp(比较两个字符串)

五、strncpy(有限制的字符串拷贝)

六、strncat(有限制的字符串追加)

七、strncmp(比较两个字符串的前n个字符)

八、strstr(判断一个字符串是不是另一个字符串的子字符串)

九、strtok(拆分字符串)

十、strerror(返回函数调用的错误信息)

总结


前言: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

总结

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值