C标准库 string.h (四) 其他函数

string.h 其他函数

  • memset 设置某个范围内每字节的值
  • strlen 获取字符串长度
  • strerror 输出对应的错误信息

memset

memory set

设置某个范围内每字节的值,通常用来清空结构体或者清空某块内存。


#include <stdio.h>
#include <string.h>
#include <malloc.h>	// for malloc

/*
	标准库
*/
void *memset(void *s, int c, size_t n)
{
	const unsigned char uc = c;
	unsigned char *su;

	for (su = s; 0 < n; ++su, --n)
		*su = uc;
	return (s);
}

int main(int argc, char const *argv[])
{
	char *p = (char *)malloc(sizeof(char)*10); // 获取10字节的内存
	memset(p, 0, sizeof(char)*10);	// 将该10字节内存全部初始化为0
	return 0;
}

strlen

string length

获取字符串长度


#include <stdio.h>
#include <string.h>

/*
	自实现
*/
int _strlen(const char *dest)
{
	const char *start = dest;
	while(*dest)
		dest++;
	return (dest - start);
}

/*
	标准库
*/
int strlen(const char *s)
{
	const char *sc;

	for (sc = s; *sc != '\0'; ++sc)
		;
	return (sc - s);
}

int main(int argc, char const *argv[])
{
	printf("length : %dn", _strlen("hello world"));
	return 0;
}

strerror

string error

根据错误号输出错误信息


#include <stdio.h>
#include <errno.h>
#include <string.h>

/*
	标准库

char *_Strerror(int errcode, char *buf)
{
	static char sbuf[] = {"error #xxx"};

	if (buf == NULL)
		buf = sbuf;

	switch(errcode)
	{
		case 0:
			return ("no error");
		case EDOM:
			return ("domain error");
		case ERANGE:
			return ("range error");
		case EFPOS:
			return ("file positioning error");
		default:
			if(errcode < 0 || _NERR <= errcode)
				return ("unknown error");
			else
			{
				strcpy(buf, "error #xxx");
				buf[9] = errcode % 10 + '0';
				buf[8] = (errcode /= 10) % 10 + '0';
				buf[7] = (errcode / 10) % 10 + '0';
				return (buf);
			}
	}
}
*/

/*
	自实现
*/
char *_Strerror(int errcode, char *buf)
{
	static char sbuf[] = {"Unknown error xxx"};

	if (buf == NULL)
		buf = sbuf;

	switch(errcode)
	{
		case 0 :
			return ("Success");
		break;
		case 1 :
			return ("Operation not permitted");
		break;
		case 2 :
			return ("No such file or directory");
		break;
		case 3 :
			return ("No such process");
		break;
		case 4 :
			return ("Interrupted system call");
		break;
		case 5 :
			return ("Input/output error");
		break;
		case 6 :
			return ("No such device or address");
		break;
		case 7 :
			return ("Argument list too long");
		break;
		case 8 :
			return ("Exec format error");
		break;
		case 9 :
			return ("Bad file descriptor");
		break;
		case 10 :
			return ("No child processes");
		break;
		case 11 :
			return ("Resource temporarily unavailable");
		break;
		case 12 :
			return ("Cannot allocate memory");
		break;
		case 13 :
			return ("Permission denied");
		break;
		case 14 :
			return ("Bad address");
		break;
		case 15 :
			return ("Block device required");
		break;
		case 16 :
			return ("Device or resource busy");
		break;
		case 17 :
			return ("File exists");
		break;
		case 18 :
			return ("Invalid cross-device link");
		break;
		case 19 :
			return ("No such device");
		break;
		case 20 :
			return ("Not a directory");
		break;
		case 21 :
			return ("Is a directory");
		break;
		case 22 :
			return ("Invalid argument");
		break;
		case 23 :
			return ("Too many open files in system");
		break;
		case 24 :
			return ("Too many open files");
		break;
		case 25 :
			return ("Inappropriate ioctl for device");
		break;
		case 26 :
			return ("Text file busy");
		break;
		case 27 :
			return ("File too large");
		break;
		case 28 :
			return ("No space left on device");
		break;
		case 29 :
			return ("Illegal seek");
		break;
		case 30 :
			return ("Read-only file system");
		break;
		case 31 :
			return ("Too many links");
		break;
		case 32 :
			return ("Broken pipe");
		break;
		case 33 :
			return ("Numerical argument out of domain");
		break;
		case 34 :
			return ("Numerical result out of range");
		break;
		case 35 :
			return ("Resource deadlock avoided");
		break;
		case 36 :
			return ("File name too long");
		break;
		case 37 :
			return ("No locks available");
		break;
		case 38 :
			return ("Function not implemented");
		break;
		case 39 :
			return ("Directory not empty");
		break;
		case 40 :
			return ("Too many levels of symbolic links");
		break;
		case 41 :
			return ("Unknown error 41");
		break;
		case 42 :
			return ("No message of desired type");
		break;
		case 43 :
			return ("Identifier removed");
		break;
		case 44 :
			return ("Channel number out of range");
		break;
		case 45 :
			return ("Level 2 not synchronized");
		break;
		case 46 :
			return ("Level 3 halted");
		break;
		case 47 :
			return ("Level 3 reset");
		break;
		case 48 :
			return ("Link number out of range");
		break;
		case 49 :
			return ("Protocol driver not attached");
		break;
		case 50 :
			return ("No CSI structure available");
		break;
		case 51 :
			return ("Level 2 halted");
		break;
		case 52 :
			return ("Invalid exchange");
		break;
		case 53 :
			return ("Invalid request descriptor");
		break;
		case 54 :
			return ("Exchange full");
		break;
		case 55 :
			return ("No anode");
		break;
		case 56 :
			return ("Invalid request code");
		break;
		case 57 :
			return ("Invalid slot");
		break;
		case 58 :
			return ("Unknown error 58");
		break;
		case 59 :
			return ("Bad font file format");
		break;
		case 60 :
			return ("Device not a stream");
		break;
		case 61 :
			return ("No data available");
		break;
		case 62 :
			return ("Timer expired");
		break;
		case 63 :
			return ("Out of streams resources");
		break;
		case 64 :
			return ("Machine is not on the network");
		break;
		case 65 :
			return ("Package not installed");
		break;
		case 66 :
			return ("Object is remote");
		break;
		case 67 :
			return ("Link has been severed");
		break;
		case 68 :
			return ("Advertise error");
		break;
		case 69 :
			return ("Srmount error");
		break;
		case 70 :
			return ("Communication error on send");
		break;
		case 71 :
			return ("Protocol error");
		break;
		case 72 :
			return ("Multihop attempted");
		break;
		case 73 :
			return ("RFS specific error");
		break;
		case 74 :
			return ("Bad message");
		break;
		case 75 :
			return ("Value too large for defined data type");
		break;
		case 76 :
			return ("Name not unique on network");
		break;
		case 77 :
			return ("File descriptor in bad state");
		break;
		case 78 :
			return ("Remote address changed");
		break;
		case 79 :
			return ("Can not access a needed shared library");
		break;
		case 80 :
			return ("Accessing a corrupted shared library");
		break;
		case 81 :
			return (".lib section in a.out corrupted");
		break;
		case 82 :
			return ("Attempting to link in too many shared libraries");
		break;
		case 83 :
			return ("Cannot exec a shared library directly");
		break;
		case 84 :
			return ("Invalid or incomplete multibyte or wide character");
		break;
		case 85 :
			return ("Interrupted system call should be restarted");
		break;
		case 86 :
			return ("Streams pipe error");
		break;
		case 87 :
			return ("Too many users");
		break;
		case 88 :
			return ("Socket operation on non-socket");
		break;
		case 89 :
			return ("Destination address required");
		break;
		case 90 :
			return ("Message too long");
		break;
		case 91 :
			return ("Protocol wrong type for socket");
		break;
		case 92 :
			return ("Protocol not available");
		break;
		case 93 :
			return ("Protocol not supported");
		break;
		case 94 :
			return ("Socket type not supported");
		break;
		case 95 :
			return ("Operation not supported");
		break;
		case 96 :
			return ("Protocol family not supported");
		break;
		case 97 :
			return ("Address family not supported by protocol");
		break;
		case 98 :
			return ("Address already in use");
		break;
		case 99 :
			return ("Cannot assign requested address");
		break;
		case 100 :
			return ("Network is down");
		break;
		case 101 :
			return ("Network is unreachable");
		break;
		case 102 :
			return ("Network dropped connection on reset");
		break;
		case 103 :
			return ("Software caused connection abort");
		break;
		case 104 :
			return ("Connection reset by peer");
		break;
		case 105 :
			return ("No buffer space available");
		break;
		case 106 :
			return ("Transport endpoint is already connected");
		break;
		case 107 :
			return ("Transport endpoint is not connected");
		break;
		case 108 :
			return ("Cannot send after transport endpoint shutdown");
		break;
		case 109 :
			return ("Too many references: cannot splice");
		break;
		case 110 :
			return ("Connection timed out");
		break;
		case 111 :
			return ("Connection refused");
		break;
		case 112 :
			return ("Host is down");
		break;
		case 113 :
			return ("No route to host");
		break;
		case 114 :
			return ("Operation already in progress");
		break;
		case 115 :
			return ("Operation now in progress");
		break;
		case 116 :
			return ("Stale NFS file handle");
		break;
		case 117 :
			return ("Structure needs cleaning");
		break;
		case 118 :
			return ("Not a XENIX named type file");
		break;
		case 119 :
			return ("No XENIX semaphores available");
		break;
		case 120 :
			return ("Is a named type file");
		break;
		case 121 :
			return ("Remote I/O error");
		break;
		case 122 :
			return ("Disk quota exceeded");
		break;
		case 123 :
			return ("No medium found");
		break;
		case 124 :
			return ("Wrong medium type");
		break;
		case 125 :
			return ("Operation canceled");
		break;
		case 126 :
			return ("Required key not available");
		break;
		case 127 :
			return ("Key has expired");
		break;
		case 128 :
			return ("Key has been revoked");
		break;
		case 129 :
			return ("Key was rejected by service");
		break;
		case 130 :
			return ("Owner died");
		break;
		case 131 :
			return ("State not recoverable");
		break;
		default:
		{
			buf[16] = errcode % 10 + '0';
			buf[15] = (errcode /= 10) % 10 + '0';
			buf[14] = (errcode / 10) % 10 + '0';
			return (buf);
		}
	}
}

char *strerror(int errcode)
{
	return (_Strerror(errcode, NULL));
}

int main(int argc, char const *argv[])
{
	int i;

	for(i=0; i<141; i++)
		printf("%d : %sn",i,strerror(i));	

	return 0;
}

标准库的实现其实只是描述一个大概的形式,自实现这里举了个例子。

Advertisements

C标准库 string.h (三) 查找函数

string.h 查找函数

  • memchr 内存中查找
  • strchr 字符串中查找
  • strrchr 字符串中反向查找
  • strstr 字符中查找字符串
  • strtok 根据标识(token)查找字符串
  • strspn 范围之内查找,返回下标
  • strcspn 范围之外查找,返回下标
  • strpbrk 同strcspn,返回指针

其中,部分手痒自己实现过,而函数标准库的写法已然足够,就没有画蛇添足。
关于strspn的BUG详细部分回头会补充。

memchr

memory character

内存中查找,参数void *适合各种数据类型,不过只能查找一个字节(按照标准库我们可以看出来,虽然c是int类型,不过比较却是一个字节一个字节的)。


#include <stdio.h>
#include <string.h>

/*
	自实现
*/
void *_memchr(const void *s, int c, size_t n)
{
	const unsigned char *pstr = s;
	const unsigned char search = (unsigned char)c;

	while(n-- > 0)
	{
		if (*pstr == search)
		{
			return (void *)pstr;
		}
		pstr++;
	}

	return NULL;
}

/*
	标准库
*/
void *memchr(const void *s, int c, size_t n)
{
	const unsigned char uc = c;
	const unsigned char *su;

	for (su = s; 0 < n; ++su, --n)
		if (*su == uc)
			return ((void *)su);
	return (NULL);
}

int main(int argc, char const *argv[])
{
	char *p;
	p = _memchr("hello world", 'w', 8);
	printf("%sn", p);
	return 0;
}

使用for循环可以将循环的变量纳入语言结构,写起来更加简洁。

strchr

string character

字符串中查找字符,并返回这个字符第一次出现的地址


#include <stdio.h>
#include <string.h>

/*
	自实现
*/
char *_strchr(const char *s, int c)
{
	const unsigned char *pstr = s;
	const unsigned char search = (unsigned char)c;

	while(*pstr != '\0')
	{
		if (*pstr == search)
		{
			return (char *)pstr;
		}
		pstr++;
	}
	return NULL;
}

/*
	标准库
*/
char *strchr(const char *s, int c)
{
	const char ch = c;

	for (; *s != '\0'; ++s)
		if (*s == '\0')
			return NULL;
	return ((char *)s);
}

int main(int argc, char const *argv[])
{
	char *p;
	p = _strchr("hello world", 'o');
	printf("%sn", p);
	return 0;
}

自实现显得有些不上档次,不过可以给新手当个参考就保留了。

strrchr

string reverse character

字符串反向查找字符,返回这个字符最后一次出现的位置


#include <stdio.h>
#include <string.h>

/*
	自实现
*/
char *_strrchr(const char *s, int c)
{
	const unsigned char *pstr = NULL, search = (unsigned char)c;

	do
	{
		if (*s == search)
		{
			pstr = s;
		}
	} while (*s++);

	return (char *)pstr;
}

/*
	标准库
*/
char *strrchr(const char *s, int c)
{
	const char ch = c;
	const char *sc;

	for (sc = NULL; ; ++s)
	{
		if (*s == ch)
			sc = s;
		if (*s == '\0')
			return ((char *)sc);
	}
}

int main(int argc, char const *argv[])
{
	char *p;
	p = strrchr("hello world", 'o');
	printf("%sn", p);
	return 0;
}

标准库循环完了居然不返回NULL 值得吐槽

strstr

string string

字符串中查找字符串,如果能找到则返回其地址找不到则返回NULL


#include <stdio.h>
#include <string.h>

char *_strstr(const char *s1, const char *s2)
{
	while ( (s1 = strchr(s1, *s2)) != NULL )
		if(strcmp(s1, s2) == 0)
			return (char *)s1;
		else
			s1++;
	return NULL;
}

char *strstr(const char *s1, const char *s2)
{
	if(*s2 == '\0')
		return ((char *)s1);
	for( ; (s1 = strchr(s1, *s2)) != NULL; ++s1)
	{
		const char *sc1, *sc2;

		for(sc1 = s1, sc2 = s2; ; )
			if(*++sc2 == '\0')
				return ((char *)s1);
			else if(*++sc1 != *sc2)
				break;
	}
	return NULL;
}

int main(int argc, char const *argv[])
{
	printf("%sn", _strstr("that this think", "think"));
	return 0;
}

博主直接偷懒用了strcmp, 不过标准库的写法貌似对编译器有要求,如果是老版本的,变量是需要定义在函数开头的不能定义在函数中间,比如这里的for循环中。

strtok

string token

通过标识来查找字符串。需要注意的是这个会修改原来字符串的值。并且要获得标识前后的值需要两次调用。如果标识多次出现的话那么也需要多次调用该函数。


#include <stdio.h>
#include <string.h>

// Finding Tokens in a String

char * strtok(char *s1, const char *s2)
{
	char *sbegin, *send;
	static char *ssave = "";

	sbegin = s1 ? s1 : ssave;
	// printf("1.sbegin: %sn", sbegin);
	// printf("2.strspn(sbegin, s2): %dn", strspn(sbegin, s2));
	sbegin += strspn(sbegin, s2);
	if (*sbegin == '\0')
	{
		ssave = "";
		return NULL;
	}

	// printf("3.sbegin: %sn", sbegin);

	send = sbegin + strcspn(sbegin, s2);
	// printf("4.sbegin: %sn", sbegin);
	// printf("5.send: %sn", send);
	
	if (*send != '\0')
		*send++ = '\0';

	ssave = send;
	// printf("6.send: %sn", send);
	return (sbegin);
}

int main(int argc, char const *argv[])
{
	char input[] = "program,hello,world";
	char *p;

	/* 截取到第一个标识符之前 */
	p = strtok(input, "e");
	if(p)
	{
		printf("%sn", p);
	}
	/* 截取第一个标识符之后一段 */
	p = strtok(NULL, "e");
	if(p)
	{
		printf("%sn", p);
	}

	// 源字符串被截断
	printf("最后input : %s", input);
	return 0;
}

输出结果:

program,h
llo,world
最后input : program,h

strspn

string span

span 范围,比较范围内相同的字符串, 命名为早期VAX指令的风格。
字符串范围查找,按照下方函数定义,在s1中找s2,情况是从s1的开头依次与s2比较,返回第一个与s2不同的字符下标


#include <string.h>
#include <stdio.h>

// 函数说明 strspn 返回字符串中第一个不在指定字符串中出现的字符下标

size_t _strspn(const char *s1,const char *s2)
{
	const char *sc1 = s1, *sc2 = s2;

	for(sc1 = s1, sc2 = s2; *sc1 != '\0'; ++sc1, ++sc2)
		if (*sc2 == '\0')
			return (sc1 - s1);
		else if (*sc1 == *sc2)
			continue;
		else
			break;
	return sc1 - s1;
}

size_t strspn(const char *s1,const char *s2)
{
	const char *sc1, *sc2;

	printf("sc2: [%s] ", s2);

	for(sc1 = s1; *sc1 != '\0'; ++sc1)
		for (sc2 = s2; ; ++sc2)
			if (*sc2 == '\0')
				return (sc1 - s1);
			else if (*sc1 == *sc2)
				break;
	return (sc1 - s1);
}

int main(int argc, char const *argv[])
{
	char *str = "what do you think about this this program? 1";
	printf("%dn", _strspn(str,"w"));
	printf("%dn", _strspn(str,"what"));
	printf("%dn", _strspn(str,"what "));
	printf("%dn", _strspn(str,"what d"));
	printf("%dn", _strspn(str,"what do"));
	printf("%dn", _strspn(str,"what do "));
	printf("%dn", _strspn(str,"what do y"));
	printf("%dn", _strspn(str,"what do yo"));
	printf("%dn", _strspn(str,"what do you"));
	printf("%dn", _strspn(str,"you"));
	printf("%dn", _strspn(str,"1234567890"));
	return 0;
}

关于该函数的BUG,详细回头补上

strcspn

string complement span

complement 补集,可以理解成 strspn 的补函数。
在s1中依次比较每个字符是否s2在中出现过,如果该字符在s2中出现过,则返回该字符在s1中的下标。


#include <stdio.h>
#include <string.h>

// 功能:顺序在字符串s1中搜寻与s2中字符的第一个相同字符,包括结束符NULL,返回这个字符在S1中第一次出现的位置。

/*
	标准库
*/
size_t strcspn(const char *s1,const char *s2)
{
	const char *sc1, *sc2;

	for (sc1 = s1; *sc1 != '\0'; ++sc1)
		for(sc2 = s2; *sc2 != '\0'; ++sc2)
			if (*sc1 == *sc2)
				return (sc1 - s1);
	// 返回NULL,即字符串结束
	return (sc1 - s1);
}


int main(int argc, char const *argv[])
{
    char *str = "Linux was first developed for 386/486-based pcs. ";
    printf("字符串长度为 %dn", strlen(str));
    printf("%dn", strcspn(str, " "));
    printf("%dn", strcspn(str, "/-"));
    printf("%dn", strcspn(str, "1234567890"));
    printf("%dn", strcspn(str, ""));
	return 0;
}

执行结果:

字符串长度为 49
5	// 只计算到" "的出现, 所以返回"Linux"的长度
33	// 计算到出现"/"或"-", 所以返回到"6/486"之前的长度
30	// 计算到出现数字字符为止, 所以返回"386"出现前的长度

strpbrk

string pointer break

早期的命名风格,这里可以理解为找到目标(string)中的字符后中断(break)并返回其地址(pointer),其功能与strcspn相同,区别只是strpbrk返回的是地址。

可查阅资料:
MSDN: strPBrk
GNU:(推荐) strpbrk


#include <stdio.h>
#include <string.h>

/*
	依次检验字符串s1中的字符,当被检验字符在字符串s2中也包含时
	则停止检验,并返回该字符地址,空字符NULL不包括在内。
*/

/*
	标准库
*/
char * strpbrk(const char * s1,const char * s2)
{
	const char *sc1, *sc2;

	for(sc1 = s1; *sc1 !='\0'; ++sc1)
		for(sc2 = s2; *sc2 !='\0'; ++sc2)
			if(*sc1 == *sc2)
				return ((char *)sc1);
	return NULL;
}

int main(int argc, char const *argv[])
{
	char *s1="Welcome To Beijing";
	char *s2="BIT";
	char *p;

	p = strpbrk(s1,s2);
	if(p)
		printf("%sn",p);
	else
		printf("Not Found!n");

	p = strpbrk(s1, "Daj");

	if(p)
		printf("%s",p);
	else
		printf("Not Found!n");

	return 0;
}

C标准库 string.h (二)比较函数

string.h 比较函数

  • strcmp 两个字符串相比较
  • strncmp 两个字符串的前N个字节相比较
  • memcmp 两块内存的前N个字节相比较
  • strcoll 与 strxfrm 引用的函数
  • strcoll 确定两个以空字符结尾的字符串再指定区域下的词典顺序
  • strxfrm 将空字符结尾的字符串s2转换为s1处的一个(不重叠的)版本

strcmp

字符串比较函数,用于比较两个字符串的区别

#include <stdio.h>
#include <string.h>

/*
	自实现
*/
int _strcmp(const char *dest, const char *src)
{
	int res = 0;
	while( res == 0 && *src != '\0')
		res = *dest++ - *src++;
	return res;
}

/*
	标准库
*/
int strcmp(const char *s1, const char *s2)
{
	for ( ; *s1 == *s2; ++s1, ++s2 )
		if(*s1 == '\0')
			return (0);

	return ((*(unsigned char *) s1 < *(unsigned char *)s2) ? -1 : +1);
}


int main()
{
	char text[10] = "hello";

	if (_strcmp(text, "hello") == 0)
	{
		printf("it's samen");
	}	
}

标准库实现中,先转成无符号再比较,是为了避免特殊字符出现负数,然后负数不对称的情况,所以先转成无符号再比较。博主是觉得返回1或者-1都没什么意义,所以直接返回相减的结果了。

strncmp

比较两个字符串的前n个字符

#include <stdio.h>
#include <string.h>

/*
	自实现
*/
int _strncmp(const char *s1, const char *s2, size_t n)
{
	const unsigned char *dest = s1, *src = s2;
	while( n-- > 0 && *dest != '\0')
		if (*dest++ - *src++)
			return *dest - *src;
	return 0;
}

/*
	标准库
*/
int strncmp(const char *s1, const char *s2, size_t n)
{
	for( ; 0 < n; ++s1, ++s2, --n)
		if(*s1 != *s2)
			return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1 : +1);
		else if(*s1 == '\0')
			return 0;
	return 0;
}

int main()
{
	char hi[] = "hello world";

	if ( _strncmp( hi, "hello", 5 ) == 0) {
		printf("相等n");
	} else {
		printf("不相等n");
	}
}

标准库很是推崇for循环,其实也是有道理的,因为作为计数用的变量 n 以及每次都需要改变的 s1、s2 就可以很明确的放在后面了,或许这个地方for循环也有优化,不过博主还是用while循环自实现一下吧。

memcmp

#include <stdio.h>
#include <string.h>

/*
	自实现
*/
int _memcmp(const void *s1, const void *s2, size_t n)
{
	const unsigned char *dest = s1, *src = s2;
	while( n-- > 0 )
		if (*dest++ - *src++)
			return *dest - *src;
	return 0;
}

/*
	标准库
*/
int memcmp(const void *s1, const void *s2, size_t n)
{
	const unsigned char *su1, *su2;

	for(su1 = s1, su2 = su2; 0 < n; ++su1, ++su2, --n)
		if(*su1 != *su2)
			return ((*su1 < *su2) ? -1 : +1);
	return 0;
}

int main()
{
	char hi[] = "hello world";

	if ( _memcmp( hi, "hello", 5 ) == 0) {
		printf("相等n");
	} else {
		printf("不相等n");
	}
}

/*
	在处理一些特殊字符的时候,他们的值有可能是负数
	C语言中补码形式下,负数与正数不对称,所以使用 unsigned char是很推荐的

	memcmp 与 strncmp 的区别是 memcmp 不会检查字符串是否到结束
*/

strcoll 与 strxfrm

与 locale.h 本地库有关的字符串比较函数,在开始比较之前会按照特定的方式转换字符串然后在进行比较。

xstate.h

#ifndef _SXTATE_H_
#define _SXTATE_H_

// page 100 为了方便区域设置改变时获得操作状态表所需的信息。
/* xstate.h internal header */
	/* macros for finite state machines */
	// 有限状态机的宏

#define ST_CH		0x00ff
#define ST_STATE	0x0f00
#define ST_STOFF	8
#define ST_FOLD		0x8000
#define ST_INPUT	0x4000
#define ST_OUTPUT	0x2000
#define ST_ROTATE	0x1000
#define _NSTATE		16
	/* 类型定义 */

typedef struct{
	const unsigned short *_Tab[_NSTATE];
} _Statab;
	/* 声明*/
extern _Statab _Costate, _Mbstate, _Wcstate;

#endif

xstrxfrm.h

/*
	所有的整理函数都包含内部头文件"xstrxfrm.h"
	该头文件又包含标准头文件<string.h>和内部头文件"xstate.h"
	除此之外,"xstrxfrm.h"定义了类型 _Cosave 并且声明了函数 _Strxfrm
	一个 _Cosave 类型的数据对象在调用 _Strxfrm 之间存储状态信息
*/
#include <string.h>
#include "xstate.h"
	/* type definnitions 类型定义 */
typedef struct{
	unsigned char _State;
	unsigned short _Wchar;
} _Cosave;
	/* declarations 声明 */
size_t _Strxfrm(char *, const unsigned char **, size_t, _Cosave *);

xstrxfrm.c

#include <limits.h>
#include "xstrxfrm.h"

/* 设置默认为 0 的本地配置项 */
_Statab _Costate, _Mbstate, _Wcstate;

size_t _Strxfrm(char *sout, const unsigned char **psin,
					size_t size, _Cosave *ps) // _Cosave 存储状态信息
{
	// translate string to collatable form
	// 翻译字符串到可校对的格式
	char state = ps->_State;
	int leave = 0;
	int limit = 0;
	int nout = 0;
	const unsigned char *sin = *psin;
	unsigned short wc = ps->_Wchar; 	// 宽字节字符累加器

	for( ; ; )
	{   // perform a state transformation
		// 执行状态转换
		unsigned short code;
		const unsigned short *stab;

		if(_NSTATE <= state
			|| (stab = _Costate._Tab[state]) == NULL
			|| (_NSTATE * UCHAR_MAX) <= ++limit
			|| (code = stab[*sin] == 0) )
			break;

		state = (code & ST_STATE) >> ST_STOFF;

		if(code & ST_FOLD)
			wc = wc & ~UCHAR_MAX | code & ST_CH;
		if(code & ST_ROTATE)
			wc = wc >> CHAR_BIT & UCHAR_MAX | wc << CHAR_BIT;
		if(code & ST_OUTPUT && ((sout[nout++]
			= code & ST_CH ? code : wc) == '\0'
			|| size <= nout))
			leave = 1;
		if(code & ST_INPUT)
			if(*sin != '\0')
				++sin, limit = 0;
			else
				leave = 1;
		if(leave)
		{   // return for now
			// 现在返回
			*psin = sin;
			ps->_State = state;
			ps->_Wchar = wc;
			return nout;
		}
	}
	sout [nout++] = '\0';	// error return 错误返回
	*psin = sin;
	ps->_State = _NSTATE;
	return nout;
}

strcoll

#include <stdio.h>
#include "xstrxfrm.h"

typedef struct
{
	char buf[32];
	const unsigned char *s1, *s2, *sout;
	_Cosave state;
} Sctl;

static size_t getxfrm(Sctl *p)
{
	size_t i;

	do {
		p->sout = (const unsigned char *)p->buf;
		i = _Strxfrm(p->buf, &p->s1, sizeof (p->buf), &p->state);
		if (0 < i && p->buf[i-1] == '\0')
			return (i-1);
		else if (*p->s1 == '\0')
			p->s1 = p->s2;
	} while( i == 0 );
	return i;
}

int _strcoll(const char *s1, const char *s2)
{
	size_t n1, n2;
	Sctl st1, st2;
	static const _Cosave initial = {0};

	st1.s1 = (const unsigned char *)s1;
	st2.s2 = (const unsigned char *)s1;
	st1.state = initial;

	st2.s1 = (const unsigned char *)s2;
	st2.s2 = (const unsigned char *)s2;
	st2.state = initial;

	for (n1 = n2 = 0 ; ; )
	{
		int ans;
		size_t n;

		if (n1 == 0)
			n1 = getxfrm(&st1);
		if (n2 == 0)
			n2 = getxfrm(&st2);
		n = n1 < n2 ? n1 : n2;
		if (n == 0)
			return (n1 == n2 ? 0 : 0 < n2 ? -1 : +1);
		else if((ans = memcmp(st1.sout, st2.sout, n)) != 0)
			return ans;

		st1.sout += n, n1 -= n;
		st2.sout += n, n2 -= n;
	}
}

int main()
{
	char hi[] = "中文";

	if ( _strcoll( hi, "中文") == 0) {
		printf("相等n");
	} else {
		printf("不相等n");
	}
}

strxfrm

#include <stdio.h>
#include <stdlib.h>
#include "xstrxfrm.h"
#include "xstate.h"

size_t _strxfrm(char *s1, const char *s2, size_t n)
{
	/* transform s2[] to s1[] by locale-dependent rule */
	size_t nx = 0;
	const unsigned char *s = (const unsigned char *)s2;
	_Cosave state = {0};

	while(nx < n)
	{   // 转化 并 传递
		size_t i = _Strxfrm(s1, &s, n - nx, &state);

		s1 += i, nx += i;
		if(0 < i && s1[-1] =='\0')
			return nx-1;
		else if (*s == '\0')
			s = (const unsigned char *)s2;
	}
	for( ; ; )
	{
		char buf[32];
		size_t i = _Strxfrm(buf, &s, sizeof(buf), &state);

		nx += i;
		if(0<i && buf[i-1] == '\0')
			return nx-1;
		else if(*s == '\0')
			s = (const unsigned char *)s2;
	}
}

int main()
{
	char hi[] = "中文";

	if ( _strxfrm( hi, "中文", 4) == 0) {
		printf("相等n");
	} else {
		printf("不相等n");
	}
}

小结

其实可以发现三个cmp函数最后都是用的无符号字符型来相减的,
原因是为了避免有符号的负数运算。

接着就是 strcmp、strncmp和memcmp的区别了。。

str开头的是专门处理字符串的,如果去处理别的数据需要显示的强转成 char * 。
然后strcmp是一直比较到 s1 结束为止,strncmp 则是比较传入的参数 n 个,不过依旧保留了strcmp的特点,就是 s1 结束的话也会停止。
memcmp 与 strncmp 是非常类似的,区别可以说 memcmp 只比较 n 个不管s1有没有结尾。

strcmp 适合与比较两个可能完全相同的字符串。
strncmp 适合与比较一个字符串与另一个字符串开头的 n 个。
memcmp 适合与比较两个数据(不一定是char类型)的前 n 个字节是否相同。

strxfrm 与 strcoll 会根据 locale.h 中的 LC_COLLATE 来把字符串转换成另外一种形式,然后再比较。目前是一件比较坑的事情,以后研究 locale.h 的时候,我们还会回来的。

C标准库 string.h (一)复制函数与剪切函数

string.h 复制函数与剪切函数

一些问题提前汇总

size_t

size_t 的作用,龙崎发现了cstdde.h中定义者 typedef _W64 unsigned int size_t; 也即 size_t 就是无符号的int类型。实际上 size_t 是为了兼容平台而设计的,比如到某些平台会变成普通的 int 之类的。

*s1++ = *s2++;

这样的代码其实是符合C的逻辑的,++运算就是在运算结束后在进行自增运算,也就是说自增在生效之前这个代码的情况其实就是*s1 = *s2; 而到了“;”分号这里运算结束然后s1 与 s2 自增。

const 修饰

	int a;

	int * const p = &a; 
	// 指针常量,*p可以修改 *p = 8;(允许)
	// p不可以修改 p++(错误)

	const int *p = &a;
	// 常量指针 *p不可修改 *p = 8;(错误)
	// p 可以修改 p = &b  (允许)

	const int * const p = &a;
	// *p 和 p均不可改变了
	// 注 int const与const int的写法是一样的

数组重叠

情况如图:

memcpy

memcpy的函数原型为:

void *memcpy( void *s1, const void *s2, size_t n );

功能:函数memcpy从s2指向的对象中复制n个字符到s1指定的对象中。
如果发生在两个重叠的对象中,则这种行为未定义。

如果确定目标字符串 s1 和 源串 s2 不重叠, memcpy(s1,s2,n)将快速安全地执行复制。如果它们两个可能重叠,可以树勇memmove(s1,s2,n)来代替它。不要认为这两个函数以任何一种特殊的顺序访问存储空间。特别地,如果想在一个字符数组的相邻元素构成的序列中存储相同的值,可以使用memset。

#include <stdio.h>
#include <string.h>

void *memcpy(void *s1, const void *s2, size_t n);



/*
	自实现
*/
void *_memcpy(void *s1, const void *s2, size_t n)
{
	char *start, *src;
	start = s1;
	src = s2; // 警告:赋值丢弃了指针目标类型的限定 ?

	while(n > 0  && (*start++ = *src++) != '\0')
		n--;

	while(n-- > 0)
		*start++ = '\0';

	*start = '\0';

	return s1;
}

/*
	不知道是不是编译器的问题 while循环中 等式复杂时自增自减可能出现莫名其妙的段错

	如18行:
	while(n-- > 0  && (*start++ = *src++) != '\0')
		;
	这样写时,会出现段错
*/

/*
	标准库
*/
void *memcpy(void *s1, const void *s2, size_t n)
{
	char *su1;
	const char *su2;

	for(su1 = s1, su2 = s2; 0 < n; ++su1, ++su2, --n)
	{
		*su1 = *su2;
	}

	return s1;
}

int main(int argc, char const *argv[])
{
	char text[20];
	printf("%sn", memcpy(text, "hello world", 7));
	return 0;
}

首先是 void *

string.h 字符串处理,按理说直接处理 char * 就可以了,为什么要设置成 void * ?
其实也很好理解,这是为什么方便处理其他数据的时候也可以用 memcpy 这样的函数来 copy

把 const void * 原本就意味着可以修改指针,倒是不能通过指针修改所指向的内存数据,这也是一种对源数据的保护行为,而在我的实现中将其赋值到 char * 再去处理就没有原来保护原本空间的意思了。弹出“赋值丢弃了指针目标类型的限定”这样的警告也是可以理解的

根据标准库的实现

可以发现memcpy只是单纯的从 s2 出拷贝n个字节到 s1 处
与字符串是否结尾无关

所以在使用这个函数的时候要注意越界的问题

memmove

memmove的函数原型为:

void *memmove( void *s1, const void *s2, size_t n );

功能:函数memmove从s2指向的对象中复制n个字符串到s1指向的对象中,
复制看上去就像先把n个字符从s2指向的对象复制到一个n个字符的临时数组中,
这个临时数组和s1、s2指向的对象都不重叠,然后再把这n个字符从临时数组中复

#include <stdio.h>
#include <string.h>

void *memmove(void *s1, const void *s2, size_t n);

/*
	自实现
*/
void *_memmove(void *s1, const void *s2, size_t n)
{
	char *su1;
	const char *su2;
	int i;
	if ( s1 < s2 && s2 - s1 < n )
	{
		for ( su1=s1+n, su2=s2+n ; n > 0; --n, --su1, --su2) // 加了 n 忘记减一了
		{
			*su1 = *su2;
		}
	}else
	{
		for( i=0, su1=s1, su2=s2; i < n; ++i, ++su1, ++su2 ) // i 多余了
		{
			*su1 = *su2;
		}
	}
	return s1;
}

/*
	标准库
*/
void *memmove(void *s1, const void *s2, size_t n)
{
	char *sc1;
	const char *sc2;

	sc1 = s1;
	sc2 = s2;

	// 如果 sc1 的地址比 sc2 要低,并且两者相处不足 n 字节
	if (sc2 < sc1 && sc1 < sc2 + n)
	{
		for(sc1 += n, sc2 += n; 0 < n; --n) // 逆向复制
		{
			*--sc1 = *--sc2;
		}
	}else
	{
		for( ; 0 < n; --n) 				// 正向复制
		{
			*sc1++ = *sr2++;
		}
	}
	return s1;
}

int main()
{
	char text[20];
	printf("%sn", memmove(text, "hello world", 5));
	return 0;
}

/*
	1. 没用while用for循环之后,第一问题发现,都要初始化的话,
	   可以把初始化放在外面
	2. 逆向赋值的时候,加了 n 忘记减一了
	3. for循环定义的那个 i 多余了


	关于 memmove 的使用
	这个也只是简单的复制 n 个字节,只不过有区分特殊情况
	在相同的情况下使用 memmove 比 memcpy 要安全
*/

memmove有判断内存重叠的情况,这确实是以前从未想到过的问题。正如标准库中所提醒的“不要认为这两个函数以任何一种特殊的顺序访问存储空间”,我们可以从标准库的实现看出来这一点。

strcpy

strcpy的函数原型为:

char *strcpy( char *s1, const char *s2 )

功能:把s2指向的串(包括终止的空字符)复制到s1指向的数组中,如果
复制发生在两个重叠的对象中,这行为为定义。

#include <stdio.h>
#include <string.h>

char *strcpy(char *s1, const char *s2);

/*
	自实现
*/
char *_strcpy(char *s1, const char *s2)
{	
	char *start = s1;
	while(*s1++ = *s2++)
		;
	return start;
}

/*
	标准库
*/
char *strcpy(char *s1, const char *s2)
{
	char *s = s1;

	for(s = s1; (*s++ = *s2++) != '\0'; )
		;

	return s1;
}

int main()
{
	char text[20];
	printf("%sn", _strcpy(text, "hello world"));
	return 0;
}

/*
	这个貌似没什么好说的,个人还是偏好while
*/

如果可以保证目标串 s1 和源串 s2 不重叠,那么 strcpy(s1,s2)将安全并快速地执行复制操作。如果它们可能重叠,使用 memmove(s1, s2, strlen(s2)+1) 来代替它。依旧还是老样子没有判断重叠,貌似如果越界也不会检查,所以还是memmove最好了,考虑以后都用memmove了。

strncpy

strncpy的函数原型为:

char *strcpy( char *s1, const char *s2 )

功能:把s2指向的串(包括终止的空字符)复制到s1指向的数组中,如果
复制发生在两个重叠的对象中,这行为未定义。

#include <stdio.h>
#include <string.h>

char *_strncpy(char *s1, const char *s2, size_t n);

/*
	自实现
*/
char *_strncpy(char *s1, const char *s2, size_t n)
{	
	char *start = s1, count=0;

	while((count < n) && (*s1 = *s2)) {
		count++, s1++, s2++;
		// printf("第%d个 已拷贝n", count);
	}

	while (count++ < n) {
		*s1 ++ = '\0';
		// printf("第%d个 已补零n", count);
	}

	return start;
}

/*
	标准库
*/
char *(strncpy) (char *s1, const char *s2, size_t n)
{
	char *s;
	// 当n不为0 且s2为拷贝完时,复制字符过去
	for(s = s1; 0 < n && *s2 != '\0'; ++s)
		*s++ = *s2++;
	// 若n有多出,则补零
	for( ; 0 < n; --n)
		*s++ = '\0';
	return s1;
}

int main()
{
	char text[20]="";
	printf("%sn", _strncpy(text, "hello world", 20));
	return 0;
}

/*
	size_t 是一种兼容平台的大小类型
	比较标准库发现,在计数上可以让计数的n反过来从n开始递减直到0为止
	这样可以少定义一个变量,节省了1字节的内存,看起来也干净些
*/

如果可以保证目标串 s1 和源串 s2 不重叠,strncpy(s1,s2,n2)将安全的执行复制操作。然而,函数在s1处精确地存储n2个字符,它可能丢弃尾部的字符,包括终止的空字符。函数会更具需要存储一些额外的空字符来弥补一个短的技术。如果两个区域可能重叠,使用 memmove(s1,s2,n2) 来代替它。(必须在结尾存储适当数量的空字符,如果这很重要的话。)不要认为这两个函数以任意特定的顺序访问存储空间。

strcat

strcat的函数原型为:

char *strcat( char *s1, const char *s2 );

功能:函数strcat把s2指向的串(包括终止的空字符)的副本添加到s1指向
的串的末尾,s2得第一个字符覆盖s1末尾的空支付。如果复制发生在两个重
叠的对象中,则行为未定义。

#include <stdio.h>
#include <string.h>

char *_strcat(char *s1, const char *s2);

/*
	自实现
*/
char *_strcat(char *s1, const char *s2)
{
	char *start = s1;
	while( *s1 )
		s1++;
	while(*s1++ = *s2++)
		;
	return start;
}

/*
	标准库
*/
char *strcat(char *s1, const char *s2)
{
	char *s;
	// 指针移动到s1的结尾
	for(s = s1; *s != '\0'; ++s)
		;
	// 如果s2未结尾则拷贝
	for( ; (*s = *s2) != '\0'; ++s, ++s2)
		;
	return (s1);
}


int main()
{
	char text[20]="hello";
	printf("%sn", strcat(text, " world"));
	return 0;
}

/*
	这个还比较简单,但是感觉for循环确实挺万能的
*/

strncat

strncat的函数原型为:

char *strcat( char *s1, const char *s2, size_t n );

功能:函数strncat匆匆s2指向的数组中将最多n个字符(空字符及其后面的
字符不添加)添加到s1指向的串的末尾,s2的第一个字符覆盖s1末尾的空字
符。通常在最后的结果后面加上一个空字符。如果复制发生在两个重叠的对
象中,则行为未定义。

#include <stdio.h>
#include <string.h>

char *_strncat(char *s1, const char *s2, size_t n);

/*
	自实现
*/
char *_strncat(char *s1, const char *s2, size_t n)
{
	char *start = s1;
	while( *s1 )
		s1++;
	while((0 < n--) && (*s1++ = *s2++)) {
		;
		// printf("第%d个 已赋值n", n);
	}

	while (0 < n--) {
		*s1 ++ = '\0';
		// printf("第%d个 已补零n", n);
	}
	return start;
}

/*
	标准库
*/
char *strncat(char *s1, const char *s2, size_t n)
{
	char *s;
	// 指针移动到s1的结尾
	for(s = s1; *s != '\0'; ++s)
		;
	// 如果s2未结尾则拷贝
	for( ; 0 < n && *s2 != '\0'; --n)
		*s++ = *s2++;
	// 字符串s1结尾补'\0'
	*s = '\0';
	return (s1);
}


int main()
{
	char text[20]="hello";
	printf("%sn", _strncat(text, " world", 10));
	return 0;
}

/*
	这次换n自减,中间好像很奇怪的溢出了,没发现什么情况,或者是我单词写错了?
	不过确实还挺神奇的发现 这个strncat 并没有像 strncpy 那样不足的补零
*/

感慨:

我们以后还是多用 memmove 来编写程序把,反正这些库函数都没有检查数组越界,大家自己定好范围然后传个n进去让他move吧。感觉平常很有爱的strcpy和strncpy都其实不太完善。回头工作室还是自己重写一个string库吧。