C风格字符串基本操作

与字符串相关的基本操作包括strlen, strcpy, strcat, strcmp, strchr, strspn, strcspn, strpbrk, strstr, strtok等。在有些平台上,strcpy等使用较多的操作可能直接使用汇编代码编写,本文采用C语言来编写这些函数,然后说说与其相关的一些内容。

strlen

strlen这个函数用于计算C风格字符串的长度,而C风格字符串的结束标志是一个NULL字符,通过计算起始处到结束位置的字符数量即可求得长度。起初,我以为在这里采用末尾指针与起始指针相减的方法来计算长度,而不使用计数会提高效率,因为可以避免在每次比较后访问内存两次。然而,在我写了一段代码进行测试验证后,我发现我错了。反汇编后发现,后一种方法的汇编指令更短一点,自然执行时间更短。虽然访问一次内存需要的时间比执行一条指令要多,但是在有着多级缓存的CPU上,难以确定一次访问内存跟几条指令相当,不通过实际比较难以确定那种效率更高。可以肯定的是,使用汇编语言编写优化过的肯定效率更高。

size_t strlen(const char * str)
{
	const char * i = str;
	while (*i) i++;
	return i - str;
}

strnlen

这个函数是strlen的扩展,可以通过第二个参数指定字符串的最大值,这样就能避免由于错误参数引起的一系列问题(可能会引起程序崩溃)。

size_t strnlen(const char *str, size_t maxlen)
{
	const char *i = str;
	while (*i && maxlen--) {
		i++;
	}
	return (i - str);
}

strcpy

字符串拷贝是一个使用很频繁的函数,它可以使用rep movsb指令来实现,这里还是使用C语言来实现。值得注意的问题是源地址空间和目的地址空间有可能重叠,这样需要分情况进行出来。

char *strcpy(char *dst, const char *src)
{
	char *o = dst;
	if (src > dst) {
		while (*src) *dst++ = *src++;
		*dst = '\0';
	} else {
		const char *i = src;
		while (*i++);
		dst = dst + (i - src);
		while (i >= src) *dst-- = *i--;
	}
	return o;
}

strncpy

strncpy就像strnlen一样,通过额外的参数指定了字符串的最大长度,可以避免一些通过strcpy实施的栈溢出攻击。

char *strncpy(char *dst, const char *src, size_t n)
{
	char *o = dst;
	if (src > dst) {
		while (*src && n--) *dst++ = *src++;
		*dst = '\0';
	} else {
		const char *i = src;
		while (*i++ && n--);
		dst = dst + (i - src);
		while (i >= src) *dst-- = *i--;
	}
	return o;
}

strcat

这个函数是在目的字符串末尾追加字符,可以转换为strcpy操作。

char *strcat(char *dst, const char *src)
{
    char *str = dst;
	while (*str++);
	strcpy(str, src);
	return dst;
}

strncat

strncat也可转换为strncpy操作。

char *strncat(char *dst, const char *src, size_t n)
{
    char *str = dst;
	while (*str++);
	strncpy(str, src,  n);
	return dst;
}

strcmp

strcmp返回两个字符串比较的结果,返回值是首个不同字符的差值,相同则返回0。

int strcmp(const char *str1, const char *str2)
{
	const unsigned char *s1 = (const unsigned char *)str1;
	const unsigned char *s2 = (const unsigned char *)str2;
	int delta = 0;

	while (*s1 || *s2) {
		delta = *s2 - *s1;
		if (delta)
			return delta;
		s1++;
		s2++;
	}
	return 0;
}

strncmp

有额外参数指定最大长度的比较也是有必要的。

int strncmp(const char *str1, const char *str2, size_t n)
{
	const unsigned char *s1 = (const unsigned char *)str1;
	const unsigned char *s2 = (const unsigned char *)str2;
	int delta = 0;

	while ((*s1 || *s2) && n--) {
		delta = *s2 - *s1;
		if (delta)
			return delta;
		s1++;
		s2++;
	}
	return 0;
}

strchr

strchr用于查找指定字符在字符串中首次出现的位置。

char *strchr(const char *str, int value)
{
	char *i = (char *)str;
	while (*i) {
		if (*i == value)
			return i;
		i++;
	}
	return 0;
}

strrchr

strrchr用于反向查找指定字符首次出现的位置。

char *strrchr(const char *str, int value)
{
	char *i = (char *)(str + strlen(str));
	while (i != str) {
		if (*i == value)
			return i;
		i--;
	}
	return 0;
}

strspn

strspn用于计算指定字符集中字符出现的次数,这里采用了位图来标记,虽然使用了32个字节的空间,但是时间复杂度下降到了O(n+m)。

size_t strspn(const char *str1, const char *str2)
{
	size_t count;
	unsigned char map[32];

	for (count = 0; count < 32; count++)
		map[count] = 0;
	while (*str2) {
		map[*str2 >> 3] |= (1 << (*str2 & 7));
		str2++;
	}
	count = 0;
	while (*str1) {
		if (map[*str1 >> 3] & (1 << (*str1 & 7)))
			count++;
		str1++;
	}
	return count;
}

strcspn

类似于strspn,strcspn用于获得指定字符集中字符首次出现的偏移。

size_t strcspn(const char *str1, const char *str2)
{
	size_t count;
	unsigned char map[32];
	const char *pstr = str1;

	for (count = 0; count < 32; count++)
		map[count] = 0;
	while (*str2) {
		map[*str2 >> 3] |= (1 << (*str2 & 7));
		str2++;
	}
	while (*pstr) {
		if (map[*pstr >> 3] & (1 << (*pstr & 7))) {
			return pstr - str1;
		}
		pstr++;
	}
	return pstr - str1;
}

strpbrk

strpbrk用于获得指定字符集中字符首次出现的地址。

char *strpbrk(const char *str1, const char *str2)
{
	size_t count;
	unsigned char map[32];
	char *pstr = (char *)str1;

	for (count = 0; count < 32; count++)
		map[count] = 0;
	while (*str2) {
		map[*str2 >> 3] |= (1 << (*str2 & 7));
		str2++;
	}
	while (*pstr) {
		if (map[*pstr >> 3] & (1 << (*pstr & 7))) {
			return pstr;
		}
		pstr++;
	}
	return 0;
}

strstr

匹配一个模式串有很多中方法,包括朴素算法,RK,KMP,BM/BMG,Sunday,AC等方法。这里采用了比较简单的算法,首先查找模式串首字符出现的位置,然后比较模式串,最后通过一次或多次比较得到结果。

char *strstr(const char *str1, const char *str2)
{
	char * str = (char *)str1;
	size_t len = strlen(str2);
	while ((str = strchr(str, *str2))) {
		if (!strncmp(str, str2, len))
			return str;
		str++;
	}
	return 0;
}

strtok

strtok用于截断字符串,由于使用了一个静态变量,所以这个函数不是可重入的。

char *strtok(char *str, const char *delimiters)
{
	static char *last;
	if (!str) {
		if (!last)
			return 0;
		str = last;
	}
	last = strpbrk(str, delimiters);
	if (last)
		*last++ = '\0';
	return str;
}

memset

memset用于填充一块内存区域,将目标区域中的每一个字节填充为指定字符,一般用于清零。

void *memset(void *dst, int value, size_t n)
{
	char * p = (char *)dst;
	while (n--)
		*p++ = (char)value;
	return dst;
}

memcpy

memcpy用于拷贝一块内存区域,跟strcpy不同,memcpy不考虑源和目的重叠的问题。

void *memcpy(void *dst, const void *src, size_t n)
{
	char *p = (char *)dst, *q = (char *)src;
	while (n--) *p++ = *q++;
	return dst;
}

memmove

memmove类似于strcpy,它不保证不修改源地址空间的数据。

void *memmove(void *dst, const void *src, size_t n)
{
	char *p = (char *)dst, *q = (char *)src;
	if (p > q) {
		while (n--) *p++ = *q++;
	} else {
		p = p + n;
		q = q + n;
		while (n--) *(--p) = *(--q);
	}
	return dst;
}

memcmp

类似于strcmp,用于比较两块内存区域。

int memcmp(const void *str1, const void *str2, size_t n)
{
	const unsigned char *s1 = (const unsigned char *)str1;
	const unsigned char *s2 = (const unsigned char *)str2;
	int delta = 0;

	while ((*s1 || *s2) && n--) {
		delta = *s2 - *s1;
		if (delta)
			return delta;
		s1++;
		s2++;
	}
	return 0;
}

memchr

memchr类似于strchr。

void *memchr(const void *ptr, int value, size_t n)
{
	char *i = (char *)ptr;
	while ((*i != value) && n--) {
		i++;
	}
	return i;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值