C语言字符和字符串库函数的使用、注意事项及模拟实现

C语言字符和字符串库函数的使用、注意事项及模拟实现

C语言中没有字符串类型,字符串通常会被放到常量字符串或者字符数组中,在程序的开发过程中要经常对字符和字符串进行处理,这就需要引入一些库函数来简化字符、字符串的处理操作,提高程序的开发效率。

求字符串长度strlen

strlen()的函数原型为size_t strlen(const char *str),用于计算字符串长度(\0前),\0仅仅是字符串结束的标志,统计长度时不计算在内。

char *p = "abcdef";
int len = strlen(p);

模拟实现

int myStrlen(const char *pstr)
{
	assert(pstr != NULL);
	int len = 0;
	while(*pstr != '\0')
	{
		len++;
		pstr++;
	}
	return len;
}

int main()
{
	char *p = "abcdefg"; 
	int len = myStrlen(p);
	printf("len = %d\n", len);
	return 0;
}

模拟实现时使用的返回类型为int,而库函数中的返回类型为size_t无符号整型,将字符串的长度定义为无符号整型是很有道理的,因为字符串的长度是一个不小于零的数,但是要注意一个细节,看下面代码:

int main()
{
	char *str1 = "abc";
	char *str2 = "abcde";
	
	if(strlen(str1) - strlen(str2) > 0)
	{
		printf("a > b");
	}
	else
	{
		printf("a <= b");
	}
	
	return 0;
}

输出结果为a > b,这是因为当两个无符号整型数进行算数运算时,得到的结果仍然是无符号整数,所以strlen(str1) - strlen(str2)得到的是一个值特别大的整数。

长度不受限制的字符串函数strcpy、strcat、strcmp

strcpy

strcpy的函数原型为char *strcpy(char *dest, const char *src),用于把 src 所指向的字符串复制到 dest目标数组中,返回值类型为char *指向最终的目标字符串 dest 的指针。源字符串必须以'\0'结尾,在拷贝时会将'\0'也拷贝过去。还要保证目标空间的大小要满足源字符串的大小,避免溢出。

char str[] = "abcdefg";
strcpy(str, "hehe");
printf("%s", str);
// 打印结果为hehe

模拟实现

char* myStrcpy(char* dest, const char* src)
{
	assert(dest != NULL);
	assert(src != NULL);
	char *ret = dest;
	while(*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char str[] = "abcdefg";	
	char *p = "hehe";
	myStrcpy(str, p);
	printf("%s", str);
	
	return 0;
}

在使用strcpy时一定要注意,目标空间不能是一个常量字符串,同时目标空间有足够大的内存大小。以下用法是错误的:

char *dest = "abcdefg";
strcpy(dest, "hehe"); // 错误

char arr[2] = {0};
strcpy(arr, "hehe"); // 错误

strcat

strcat()函数原型为char *strcat(char *dest, const char *src),用于把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,返回值为指向最终的目标字符串 dest 的指针。使用该函数要保证以下几点:
1、源目字符串都要以\0结尾;
2、目标空间足够大;
3、目标空间可修改。

char arr[20] = "hello";
strcat(arr, "world");

模拟实现

char* myStrcat(char* dest, const char* src)
{
	assert(dest != NULL);
	assert(src != NULL);
	char* ret = dest;
	while(*dest != '\0')
	{
		dest++;
	}
	while(*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char str[20] = "hello";	
	myStrcat(str, "world");
	printf("%s", str);
	return 0;
}

strcmp

strcmp()函数原型为int strcmp(const char *str1, const char *str2),用于把 str1 所指向的字符串和 str2 所指向的字符串进行比较,返回值类型为int,当返回值小于 0,则表示 str1 小于 str2,返回值大于 0,则表示 str1 大于 str2,返回值等于 0,则表示 str1 等于 str2

char *str1 = "abcdef";
char *str2 = "ABCDEF";
int ret = strcmp(str1, str2);
printf("%d", ret);

模拟实现

int myStrcmp(const char* str1, const char* str2)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	while(*str1 == *str2)
	{
		if(*str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1-*str2;
}

int main()
{
	char *str1 = "aba";
	char *str2 = "abcde";
	int ret = myStrcmp(str1, str2);
	printf("%d", ret);
	return 0;
}

长度受限制的字符串函数strncpy、strncat、strncmp

strncpy

strncpy()的函数原型为char *strncpy(char *dest, const char *src, size_t n),用于把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。该函数返回最终复制的字符串。

	char arr[20] = {0};
	strncpy(arr, "hello", 5);
	printf("%s", arr);

模拟实现

char* myStrncpy(char* dest, const char* src, int sz)
{
	assert(dest != NULL);
	assert(src != NULL);
	char *ret = dest;
	
	while(sz && (*dest++ = *src++))
	{
		sz--;
	}
	if(sz)
	{
		while(--sz)
		{
			*dest++ = 0;
		}
	}
	return ret;
}

int main()
{
	char arr[20] = "aaaaaaaaaaaaaaa";
	myStrncpy(arr, "hello", 6);
	printf("%s", arr);
	
	return 0;
}

strncat

strncat()的函数原型为char *strncat(char *dest, const char *src, size_t n),用于把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。该函数返回一个指向最终的目标字符串 dest 的指针。

char arr[20] = "aaa";
strncat(arr, "hello world", 7);
printf("%s", arr);

注意细节问题,向目标字符串追加时,从目标字符串的\0开始进行覆盖,当指定的内容写入后,最后如果没有\0,还要加上\0以保证完成追加后,仍然是一个字符串。但是如果要写入的字符串本身包含\0,此时程序会默认追加到\0位置就停下。举例:

char arr[20] = "a\0aaaaaaaa";
strncat(arr, "he\0\0\0", 5);
printf("%s", arr);
// 只是为了测试,才这样写
// 此时通过监视,可以看到内存存储的数据为 ahe\0aaaaaa\0\0\0\0\0\0\0\0\0\0

可见strncat()不同于strncpy()strncpy()会在后面补\0直到达到n,而strncat()遇到\0就会停止。
模拟实现

char* myStrncat(char* dest, const char* src, int sz)
{
	assert(dest != NULL);
	assert(src != NULL);
	
	char* ret = dest;
	while(*dest)
	{
		++dest;
	}
	while(sz && (*dest++ = *src++))
	{
		--sz;
	}
	if(!sz)
	{
		*dest = '\0';
	}
	
	return ret;
}
		
int main()
{
	char arr[20] = "aaaa";
	myStrncat(arr, "hehe", 4);
	printf("%s", arr);
	return 0;
}

strncmp

strncmp()的函数原型为int strncmp(const char *str1, const char *str2, size_t n),用于把 str1str2 进行比较,最多比较前 n 个字节。返回值类型为int,当返回值小于 0,则表示 str1 小于 str2,返回值大于 0,则表示 str1 大于 str2,返回值等于 0,则表示 str1 等于 str2

strncmp("abcdef", "abcdfff", 4);

模拟实现

int myStrncmp(const char* str1, const char* str2, int sz)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	
	while(sz && (*str1++ == *str2++))
	{
		--sz;
	}
	return *--str1-*--str2;
}
		
int main()
{
	int ret = myStrncmp("abcdf", "abcd", 5);
	printf("%d", ret);
	return 0;
}

字符串查找strstr、strtok

strstr

strstr()的函数原型为char *strstr(const char *haystack, const char *needle),用于在字符串 haystack 中查找第一次出现字符串 needle 的位置,注意不包含终止符 ‘\0’。该函数的返回值为在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null

	char *str1 = "abcdabcdefghi";
	char *str2 = "abcdef";
	char *ret = strstr(str1, str2);
	printf("%s", ret);
	// 打印结果 abcdefghi

模拟实现

char* myStrstr(char* str1,char* str2)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	const char *s1 = str1;
	const char *s2 = str2;
	const char *cp = str1;
	
	if(!*str2)
		return (char*)str1;
	
	while(*cp)
	{
		s1 = cp;
		s2 = str2;
		while(*s1 && *s2 && *s1==*s2)
		{
			s1++;
			s2++;
		}
		if(!*s2)
			return (char*)cp;
		
		cp++;
	}
	return NULL;
}

int main()
{
	char *str1 = "abcc2ccccde";
	char *str2 = "c2c";
	char *ret = myStrstr(str1, str2);
	printf("%s", ret);
	return 0;
}

strtok

strtok()函数原型为char *strtok(char *str, const char *delim),用于将字符串 str 分解为一组子字符串,delim 为分隔符。该函数返回值为被分解的第一个子字符串地址,如果没有可检索的字符串,则返回一个空指针。

使用该函数时的注意事项:
1、delim参数是一个字符串,定义了用作分隔符的字符集合;
2、str指定一个字符串,包含了0个或者多个被delim字符串中一个或者多个分隔符分割的子串;
3、strtok()函数找到str中的下一个分割符,并将其用'\0'替换,返回一个指向这个子串的指针;
4、strtok()函数需要改变被操作的字符串,所以在使用strtok()函数切分的字符串一般都是临时拷贝的内容并且可修改;
5、如果strtok()函数的str不为NULL,函数将找到str中的第一个分隔符,并且保存它在字符串中的位置;
6、如果strtok()函数strNULL,函数将在同一个字符串中被保存的位置开始,查找下一个分隔符。
7、如果字符串中不存在更多的标记,则返回NULL指针。

char arr[] = "[email protected]";
char *p = "@.";
char *ret = NULL;
for(ret = strtok(arr,p); ret != NULL; ret = strtok(NULL, p))
{
	printf("%s\n", ret);
}

错误信息报告strerror

strerror包含在errno.h头文件中,函数原型为char *strerror(int errnum),用于从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。

int main ()
{
   FILE *fp;

   fp = fopen("file.txt","r");
   if( fp == NULL ) 
   {
      printf("Error: %s\n", strerror(errno));
   }
   
  return(0);
}

全局变量errno错误码,当使用库函数发生错误时,会将errno置成对应的错误码,用于输出对应的错误信息。

字符分类函数

函数 如果参数符合下列条件就返回为真
iscntrl 任何控制字符
isspace 空白字符,空格’ ‘,换页’\f’ ,换行’\n’,回车’\r’,制表符’\t’,垂直制表符’\v’
isdigit 十进制数字0-9
isxdigit 十六进制数字
islower 小写字母
isupper 大写字母
isalpha 字母
isalnum 字母数字
ispunct 任何不属于数字字母的可打印图形字符
isgraph 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

以上函数包含在ctype.h头文件中

字符转换函数

tolower函数原型 int tolower(int c)
toupper函数原型 int toupper(int c)

内存操作memcpy、memmove、memset、memcmp

memcpy

memcpy包含在string.h头文件中,其函数原型为void *memcpy(void *str1, const void *str2, size_t n),用于从存储区 str2 复制 n 个字节到存储区 str1,返回值为一个指向目标存储区 str1 的指针。注意此函数在处理过程中的强制类型转换,类型强制转换为 void* 指针。

int arr1[] = {1,2,3,4,5};
int arr2[20] = {0};

当数组中存放的不再是字符串时,如果使用strcpy函数进行拷贝会导致拷贝的内容出错。

strcpy(arr2, arr1);
// 此时只拷贝了一个字节
// 原因:arr1[]在内存中存放顺序为 01 00 00 00 02 00 00 00```
// 而strcpy在遇到 \0 时会停止拷贝

所以此时就要使用memcpy进行内存拷贝

int arr1[] = {1,2,3,4,5};
int arr2[20] = {0};
memcpy(arr2, arr1, 20);

模拟实现

void* myMemcpy(void* str1, const void* str2, int sz)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	void *ret = str1;
	char* s1 = (char*)str1;
	char* s2 = (char*)str2;
	
	while(sz--)
	{
		*s1++ = *s2++;
	}
	
	return ret;
}

memmove

memmove()函数同样包含在string.h头文件中,其原型为void *memmove(void *str1, const void *str2, size_t n),其作用为从 str2 复制 n 个字符到 str1,但是在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。

模拟实现

void* myMemmove(void* str1, const void* str2, int sz)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	void *ret = str1;
	char* s1 = (char*)str1;
	char* s2 = (char*)str2;
	
	if(s1 > s2)
	{
		while(sz--)
		{
			*(s1+sz) = *(s2+sz);
		}
	}
	else if(s1 < s2)
	{
		while(sz--)
		{
			*s1++ = *s2++;
		}
	}
	return ret;
}

memset

memset()函数的函数原型为void *memset(void *str, int c, size_t n),用于复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。参数为str指向要填充的内存块;c 是要被设置的值,该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式;n 为要被设置为该值的字节数。返回值是一个指向存储区 str 的指针。

char str[50];
memset(str,'$',7);

memcmp

memcmp()函数的原型为int memcmp(const void *str1, const void *str2, size_t n)),用于把存储区 str1 和存储区 str2 的前 n 个字节进行比较,如果返回值 < 0,则表示 str1小于 str2,如果返回值 > 0,则表示 str2 小于 str1,如果返回值 = 0,则表示 str1 等于 str2

int arr1[] = {1,2,3,4,5};
int arr2[] = {1,2,3};
memcmp(arr1, arr2, 12);

你可能感兴趣的:(C/C++,c语言)