C语言中的字符串函数和字符函数

C语言中对字符和字符串的处理很是频繁,但是,C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

常量字符串适用于那些对它不做修改的字符串函数。

1.字符串函数的介绍及其模拟实现

1.strlen

size_t strlen (const char* str);

1.字符串是用'\0'作为结束标志的,strlen函数返回是的从给定位置开始到'\0'之间的字符个数,不包括'\0';

2.参数指向的字符串必须要以'\0'结束,否则可能会出现越界访问等情况;

3.strln函数的返回值是size_t,是无符号整型;

使用:

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

int main()
{
	char* arr = "abcdef";
	char arr2[] = "abcdefgh";
	int len = strlen(arr);
	int len2 = strlen(arr2);
	printf("%d\n", len);
	printf("%d\n", len2);
	return 0;
}

记得要引头文件

下面这种情况也要注意,因为strlen的返回值类型是size_t的,所以最后得到的是一个很大的数,而不是-2

接下里来模拟实现一下strlen函数

模拟实现:

1.计数器的方法:

#include <assert.h>

size_t my_strlen(const char* str)
{
	assert(str);
	size_t count = 0;
	while (*str++)
	{
		count++;
	}
	return count;
}

2.递归的方法:

#include <assert.h>

size_t my_strlen(const char* str)
{
	assert(str);
	if (*str == '\0')
	{
		return 0;
	}
	return 1 + my_strlen(++str);
}

3.指针-指针的方法:

#include <assert.h>

size_t my_strlen(const char* str)
{
	assert(str);
	const char* p = str;
	while (*str)
	{
		str++;
	}
	return str - p;
}

2.strcpy

char* strcpy(char* destination, const char* source);

1.源字符串必须以'\0'结束;

2. strcpy函数会将源字符串中的'\0'拷贝到目标空间;

3.目标空间必须足够大,以确保放得下源字符串,否则可能会越界访问;

4.目标空间必须可以被改变,如果是常量字符串则不能被改变;

int main()
{
	char* str1 = "abcdef";
	char* str2 = "abcde";
	char* ret = strcpy(str1, str2);
	printf("%s\n", ret);
	return 0;
}

像这种情况,str1所指向的字符串是不能被改变的

使用:

strcpy返回值是个char*类型的,是为了能够实现链式访问,即一个函数的返回值作为另一个函数的参数。

如:

int main()
{
	char arr1[] = "qwertyuiop";
	char arr2[] = "abc";
	printf("%s\n", strcpy(arr1, arr2));
	return 0;
}

模拟实现:

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* cur = dest;
	while (*src)
	{
		*dest = *src;
		dest++;
		src++;
	}
	*dest = *src;
	return cur;
}

可以对代码进行简化:

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* cur = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return cur;
}

3.strcat

char* strcat(char* destination, const char* source);

 1.源字符串必须以'\0'结束;

2.目标空间必须足够大,可以容纳得下源字符串的内容‘

3.目标空间必须可以被改变;

使用:

模拟实现:

从目标字符串的'\0'位置上开始追加源字符串

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* cur = dest;
	while (*dest)
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return cur;
}

4.strcmp

int strcmp(const char* str1, const char* str2);

从两个字符串的给定字符位置开始进行比较,如果对应位置上的字符相同,则继续比较下一对字符,直到遇到'\0'或者两个字符不同为止;

标准规定:

1.第一个字符串大于第二个字符串,则返回大于0的数字;

2.第一个字符串等于第二个字符串,则返回0;

3.第一个字符串小于第二个字符串,则返回小于0的数字;

使用:

在VS编译器中,strcmp只会返回-1,0,1三个数字。

模拟实现:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2 && *str1 != '\0')
	{
		str1++;
		str2++;
	}
	if (*str1 > *str2)
	{
		return 1;
	}
	else if (*str1 < *str2)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

 5.strncpy

char* strncpy(char* destination, const char* source, size_t num);

1.从源字符串拷贝num个字符到目标空间;

2.如果源字符串的个数小于num,则在拷贝完源字符串后,在目标的后面追加'\0',直到字符总数达到num个为止;

使用:

模拟实现:

char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	size_t len = strlen(src);
	if (num <= len)
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(dest + i) = *(src + i);
		}
	}
	else
	{
		int i = 0;
		for (i = 0; i < len; i++)
		{
			*(dest + i) = *(src + i);
		}
		for (; i < num; i++)
		{
			*(dest + i) = '\0';
		}
	}
	return dest;
}

6.strncat

char* strncat(char* destination, const char* source, size_t num);

使用:

 

 模拟实现:

char* my_strncat(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* cur = dest;
	while (*dest)
	{
		dest++;
	}
	size_t len = strlen(src);
	if (num < len)
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(dest + i) = *(src + i);
		}
		*(dest + i) = '\0';
	}
	else
	{
		while (*dest++ = *src++)
		{
			;
		}
	}
	return cur;
}

7.strncmp

int strncmp(const char* str1, const char* str2, size_t num);

使用:

 模拟实现:

int my_strncmp(const char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	size_t i = 0;
	while (*str1 == *str2 && *str1 != '\0' && i < num - 1)
	{
		str1++;
		str2++;
		i++;
	}
	if (*str1 > *str2)
	{
		return 1;
	}
	else if (*str1 < *str2)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

函数内部实现方法是比较两个字符的大小,相同的话指针就向后走一个字符大小,如果num个字符中前面都相同的话,最后是要比较第num对字符的大小的,所以判断语句while (*str1 == *str2 && *str1 != '\0' && i < num - 1)中num要-1.

8.strstr

char* strstr(const char* str1, const char* str2);

 str1为被搜索的字符串,str2为要检索出来的子字符串,如果在str1中找到了str2,那么就返回str1中找到str2时的起始位置的地址,如果str1中找不到str2,那么返回一个空指针。

使用:

 模拟实现:

思路:用一个cur指针指向str1的第一个字符,然后再拿一个s1指针去跟随着cur指针,用s2指针去指向str2的第一个字符,当cur指向第一个字符的时候,拿s1指向的字符去和s2指针指向的字符进行一个比较,要是不同,那么就让cur指针+1,s1和cur一样,当*s1和*s2相同的时候,让s1和s2同时往后走,继续比较字符,要是一直相同,那么就会出现3种情况,第一种就是str1字符串已经走到'\0'的位置了,但是str2还没有走完,那么此时退出s1和s2不断增加的循环,cur+1,s1回退到此时cur+1的位置,s2也重新回退到str2其实字符的位置,重新开始匹配相同的字符,不过要是出现这种情况的话,大概率后面的也会不相同,第2种情况,就是s1和s2一直往后走,直到s2指向的字符是'\0',那么此时,str2已经遍历完了,说明str2是str1的子字符串,那么也会退出循环,下一步再判断*s2是否是'\0',是的话就把此时cur作为返回值返回。第3中情况是前面的字符串相同,中间的不同,那么也会退出循环,让cur+1,然后把s1的位置回退到cur的位置,s2的位置也回退到str2起始字符串的位置,重新开始匹配。

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	const char* s1 = str1;
	const char* s2 = str2;
	const char* cur = str1;
	while (*cur)
	{
		s1 = cur;
		s2 = str2;
		while (*s1 && *s2 && (* s1 == *s2))
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)cur;
		}
		cur++;
	}
	return NULL;
}

9.strtok

char* strtok(char* str, const char* sep);

1.sep参数是个字符串,定义了用作分割符的字符集合

2.第一个参数str是个字符串,它包含了0个或者多个在sep字符串中存在的作为分割符的字符作为标记

3.strtok函数会在str中找到下一个标记,并将其用'\0'结尾,返回一个指向这个标记的指针。strtok函数会改变被操作的字符串,所以在用strtok对字符串进行分割时一般用临时拷贝的字符串,并且字符串可以被改变。

4.strtok函数的第一个标志不为NULL,函数将找到str中的第一个标记,strtok函数将会保存这个标记在字符串中的位置。

5.strtok函数的第一个参数为NULL,函数将从同一个字符串中保存的位置开始,查找下一个标记。

6.如果字符串中不存在更多的标记,则返回空指针

使用:

strtok函数将会返回分割出来的字符串的首字符地址,我们借助一个循环,就能把想要分割的字符串依次打印出来了

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

int main()
{
	char* arr = "[email protected].";
	char buff[30];
	strcpy(buff, arr);
	char* sep = "@.0";
	char* ret = NULL;
	for (ret = strtok(buff, sep); ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}
	return 0;
}

10.strerror

char* strerror(int errnum);

strerror是用来返回错误码所对应的错误信息,平常上网有时会出现的404就是错误码。 

使用:

 

strerror会返回对应错误信息的首字符地址

11.memcpy

void* memcpy(void* destination, const void* source, size_t num);

1.memcpy会从source的起始位置开始向后复制num个字节的数据到memcpy的内存位置

2.这个函数在遇到'\0'的时候并不会停下来

3.如果source和destination由任何的重叠,复制的结果都是未定义的

使用:

 模拟实现:

void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

而如果在同一块空间进行拷贝可能会出现这种情况:

 这是因为数据是从前往后一个字节一个字节拷贝过去的,后面的将要拷贝的数据会被前面的数据给覆盖了,导致拷贝的数据发生了变化,而visual studio这款编译器将memcpy的功能已经实现到和memmove的功能差不多了,因此如果用库函数提供的memcpy去在同一块空间进行拷贝的话,不会发生这种情况。

 12.memmove

void* memmove(void* destination, const void* source, size_t num); 

1.和memcpy的差别是源内存块和目标内存块是可以重叠的,如果源空间和目标空间出现了重叠,就可以通过memcpy函数来进行处理

使用:

 模拟实现:

思路:

两块内存空间重叠分为两种情况:

 第一种,如果src在dest的前面的话,那么就将数据从后往前依次拷贝,先将5拷到8的位置,再将4拷到7的位置,依次类推。

 第二种,当src再dest的后面,那么就将数据从前往后依次拷贝,先将6拷到4的位置,再将7拷到5的位置,依次类推。

而如果两块内存空间没有重叠,则从前往后或者从后往前都可以拷贝。

代码:

void* my_memmove(void* dest, const void* src, size_t num)
{
	//stc在dest的前面
	if (src < dest)
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
		return dest;
	}
	//src在dest的后面
	else
	{
		void* ret = dest;
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
		return ret;
	}
}

13.memcmp

int memcmp(const void* ptr1, const void* ptr2, size_t num);

 1.比较从ptr1指针和ptr2指针开始的num个字节的数据,和strcmp有点相似,返回值也是分为<0、==0和 >0三种。

使用:

模拟实现:

int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
	num -= 1;
	while (num-- && (*(char*)ptr1 == *(char*)ptr2))
	{
		ptr1 = (char*)ptr1 + 1;
		ptr2 = (char*)ptr2 + 1;
	}
	if ((*(char*)ptr1 == *(char*)ptr2))
	{
		return 0;
	}
	else if (*(char*)ptr1 > *(char*)ptr2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}

因为指针+num-1就到要比较最后一个字节大小空间的位置了了,所以一开始num就要-1.

2.字符函数的介绍

 函数 如果它的参数符合下列条件就返回真(非0表示真)
iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
isdigit 十进制数字 0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母a~z或A~Z
isalnum 字母或者数字,a~z,A~Z,0~9
ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 任何图形符号
isprint 任何可打印字符,包括图形字符和空白字符

使用:

注意:参数会被转换成ASCII码

int main()
{
	printf("%d\n", iscntrl(1));
	printf("%d\n", isspace(3));
	printf("%d\n", isdigit(48));
	printf("%d\n", isxdigit(49));
	printf("%d\n", islower('A'));
	printf("%d\n", isupper('A'));
	printf("%d\n", isalpha('a'));
	printf("%d\n", isalnum('a'));
	printf("%d\n", ispunct('.'));
	printf("%d\n", isgraph('#'));
	printf("%d\n", isprint('3'));
	return 0;
}

字符转换:

int toupper(int c);

int tolower(int c);

使用:

int main()
{
	putchar(toupper('c'));
	printf("\n");
	putchar(tolower('C'));
	return 0;
}

关于字符串函数和字符函数的内容就到这里了,今后也会不定期更新

猜你喜欢

转载自blog.csdn.net/l_shadow_m/article/details/125236836