深入C语言3——字符串函数

今天我们将再深入研究一个老朋友了,字符串,没想到吧,原来字符串也在C语言中有如此的地位,今天呢我将带领各位来研究一些C语言为我们提供的字符串函数,专门为了字符串的一些操作,相信学过今天的内容后就不用自己再费劲去为了两个字符串的简单操作发愁了。

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中 或者 字符数组 中。 字符串常量适用于那些对它不做修改的字符串函数。

1.strlen:size_t strlen ( const char * str ); 求字符串长度

这个函数想必大家已经见过很多次了,我们经常拿它计算数组的长度,也经常和sizeof区别来记忆,我们印象最深的就是sizeof是操作符strlen是函数,没错,strlen就是我们今天要学习的第一个字符串函数,它用来计算字符串的长度,那具体如何使用呢?

int main()
{
	char arr1[] = { "abcdef" };
	char arr2[] = { 'a','b','c' };
	//size_t len = strlen("abcdef");
	size_t len = strlen(arr1);//可能为随机值(error)
	printf("%u", len);
	return 0;
}

当我们定义一个数组,我们知道在数组的结尾会自动补上'\0',当字符串函数遇到'\0'时就自动停止,所以'\0'也可以比作一个结束符号,所以我们创建两个数组arr1和arr2时,arr1就会自动补齐'\0',但是arr2的初始化方式导致不会补上'\0',因为你是一个一个初始化的,C语言认为你并不想让其帮你补上结束符,所以当我们用strlen计算arr1和arr2的长度时,arr1由于有'\0'所以返回的值就为6,而arr2由于没有'\0'所以它的返回值是一个随机数,这个随机数可能是说不准在哪里碰到'\0'停止了,记录下来了长度,所以我们要分清这两种字符串的初始化造成的区别。

而我们看到strlen的返回值好像不是我们常见的int或者char类型,而是size_t,在C语言当中size_t代表无符号整形,也就等于unsigned int,所以strlen返回的是一个>=的整数

int main()
{
	if (strlen("abc") - strlen("abcdef") > 0)//strlen返回的是size_t类型——无符号整型(sizeof返回值也是size_t)
	{
		printf("hehe");
	}
	else
	{
		printf("haha");
	}
	return 0;
}

strlen模拟实现:

见识了strlen的强大功能后,我们知道当把一个数组扔给它时它可以帮你自动算出数组的长度,那我们可不可以自己实现一个strlen呢?我们来模拟实现strlen内部的算法,答案是可以的,那如何实现呢?我们只需要遍历一遍数组然后定义一个计数器不停的++就可以了。

int my_strlen(char* ptr)
{
	assert(ptr);//断言,代表ptr不可为空
	char* start = ptr;
	while (*ptr != '\0')
	{
		ptr++;
	}
	return ptr-start;//指针相减得到的是两个指针间隔的元素个数,即数组的长度
}
int main()
{
	char arr[] = "abcdefg";
	printf("%d", my_strlen(arr));
	return 0;
}

2.strcpy:char* strcpy(char * destination, const char * source );字符串拷贝

第二个函数时字符串拷贝,其作用时专门用来拷贝字符串,我们可以看一下它的参数,我们需要一个char*类型的源头和一个char*类型的目的地,前为目的地后为源头,也就是我们需要将后面的字符串利用strcpy拷贝到前面的目的地中,那么它具体如何实现呢?

int main()
{
	//char arr1[] = "xxxxxxxxx";
	//char arr2[] = "hello";
	//char arr2[] = { 'a','b','c' };//error

	//char arr1[] = "xxx";//目标空间必须足够大
	//char arr2[] = "hello";

	//const char* p = "xxxxxxxxxxxx";//目标字符串必须可变
	//char arr2[] = "hello";

	//strcpy(arr1, arr2);//拷贝时\0也会拷贝过去
	return 0;
}

我们注意当我们使用strcpy时要注意源头字符串的长度要小于目的地字符串的长度,这样才能保证字符串能正常拷贝,而还要注意的是strcpy在拷贝时候也是遇到'\0'时会停止,而且也会把'\0'也拷贝进去,所以我们之前说的一个字符一个字符的初始化方式就不可行了。

接下来我们还是照例完成对strcpy的模拟实现:

char* my_strcpy(char* des, const char* src)
{
	assert(des && src);
	char* ret = des;
	while (*des++ = *src++)
	{
		;//代表什么都不做,因为在判断表达式当中++操作符已经进行了操作
	}
	return ret;
}
int main()
{
	char arr1[] = "xxxxxxxxxxxxx";
	char arr2[] = "abcdef";
	printf("%s\n", my_strcpy(arr1, arr2));
	return 0;
}

3.strcat:char * strcat ( char * destination, const char * source );字符串链接

接下来来到第三个字符串函数,strcat,字符串链接函数,其实可以看成string catch,也就可以理解为什么strcat叫链接函数了,我们还是老规矩看传参,我们发现它的参数和strcpy一样,也是一个源头一个目的地,这样我们就知道给两个数组该怎么传参了。

int main()
{
	char arr1[20] = "abc";//目的数组的空间必须足够大,否则会产生越接
	char arr2[] = "def";
	strcat(arr1, arr2);
	return 0;
}

我们要注意的是目的数组的空间一定要足够大,如果都是两个空间只有5的数组那就没法进行链接,而strcat函数也是以'\0'为结束符,所以我们的源头字符串必须以'\0'作为结尾。

strcat的模拟实现相信肯定比较简单了,我们只需要照着前面的模拟实现函数照葫芦画瓢

char* my_strcat(char* des, const char* src)
{
	assert(des && src);
	char* ret = des;
	while (*des)
	{
		*des++;
	}
	while (*des++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[20] = "abc";//目的数组的空间必须足够大,否则会产生越接
	char arr2[] = { 'd','e','f','\0' };
	printf("%s\n",my_strlen(arr1, arr2));
	return 0;
}

4.strcmp:int strcmp (const char * str1, const char * str2 );字符串比较

接下来来到第四个函数,字符串比较函数,这个函数的功能也就是来对两个数组的内容进行比较,注意这里比较的并不是字符串的长度。我们也发现它的返回值变成了int,也就是说第一个字符串大于第二个字符串,则返回大于0的数字 第一个字符串等于第二个字符串,则返回0 第一个字符串小于第二个字符串,则返回小于0的数字,有点像我们java里的boolean类型。

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abq";
	int ret = strcmp(arr1, arr2);
	printf("%d\n", ret);
	return 0;
}

strcmp在作比较的时候比较的是两个字符串中字符的ascci码,这也就是为什么strcmp比较的是字符串的内容而不是字符串的长度,所以这里arr1和arr2比较的就可以理解为:ab都一样,当q和cdef进行相比时q的ascci码值都比它们大,所以arr2要比arr1大,所以返回一个小于0的数字,通常来说大于0的返回值就是1,小于0的返回值就是-1,但是可能有其他情况,但是也比较少。

strcmp的模拟实现:

int my_strcmp(const char* p1, const char* p2)
{
	assert(p1 && p2);
	while (*p1 == *p2)
	{
		if (*p1 == '\0')
		{
			return 0;
		}
		p1++;
		p2++;

	}
	return *p1 - *p2;
}
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abcf";
	int ret = my_strcmp(arr1, arr2);
	if (ret < 0)
	{
		printf("<");
	}
	else if (ret == 0)
	{
		printf("=");
	}
	else
	{
		printf(">");
	}
	return 0;
}

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

这个函数想必大家猜也能猜出来它是干什么的,strcpy是复制函数,那strncpy就是比较指定长度的字符串,n在这里就代表指定的长度,所以我们可以看到它的参数要比strcpy多一个,一个size_t类型的数,也就是说用这个函数来完成指定长度字符串的拷贝。

int main()
{
	char arr1[] = "abcde";
	char arr2[] = "xxx";
	strncpy(arr1, arr2, 2);//超过字符串个数时会补够'\0'
	printf("%s\n", arr1);
	return 0;
}

strncpy的模拟实现:

char* my_strlen(char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	for (int i = num; i > 0; i--)
	{
		*str1++ = *str2++;
	}
	return str1;
}
int main()
{
	char arr1[] = "abcde";
	char arr2[] = "xxx";
	char* ret = my_strncpy(arr1,arr2,2);
	printf("%s\n", ret);
	return 0;
}

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

int main()
{
	char arr1[] = "abcd";
	char arr2[] = "efg";
	strncat(arr1, arr2, 6);//如果count的值合理的话,就会进行合理追加,如果count大于目标字符串时,
							 //会无视count,只追加arr2字符串的全部内容在补上一个'\0'
	return 0;
}

strncat的模拟实现:

char* my_strncat(char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	while (*str1++)
	{
		;
	}
	while (num)
	{
		*str1++ = *str2++;
		num--;
	}
	return str1;
}
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "efg";
	char* ret = my_strncat(arr1,arr2,6);
	return 0;
}

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

int main()
{
	char arr1[] = "abcd";
 	char arr2[] = "abcq";
	int ret = strncmp(arr1, arr2, 3);
	printf("%d\n", ret);
	return 0;
}

模拟实现strncmp:

int my_strncmp(char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	while (num)
	{
		if (*str1 = *str2)
		{
			*str1++;
			*str2++;
		}
		if (*str1 > *str2)
		{
			return *str1 - *str2;
		}
		else
		{
			return *str2 - *str1;
		}
		num--;
	}
}
int main()
{
	char arr1[] = "abcd";
 	char arr2[] = "abcq";
	int ret = my_strncmp(arr1, arr2, 3);
	printf("%d\n", ret);
	return 0;
}

8.strstr:char * strstr ( const char *str, const char *set );字符串查找

 我们刚刚介绍了字符串拷贝,比较,链接等函数,接下来我们介绍一个新函数,也就是第八个函数,字符串查找函数,也就是在一个给定的字符串内找我们想要的字符。

int main()
{
	char arr1[] = "i am a good student";
	char arr2[] = "good";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("no find");
	}
	else
	{
		printf("find")
	}
	return 0;
}

模拟实现strstr:

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	char* s1;
	char* s2;
	char* cp = str1;
	if (*str2 == '\0')
	{
		return str1;
	}
	while (*cp != '\0')
	{
		s1 = cp;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abbbbcdef";
	char arr2[] = "bbc";
	char* ret = my_strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("no find");
	}
	else
	{
		printf("find");
	}
	return 0;
}

9.strtok:char * strtok( char * str, const char * sep );返回字符串的分隔符

 sep参数是个字符串,定义了用作分隔符的字符集合,第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。 strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改 变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。) strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。 strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。 如果字符串中不存在更多的标记,则返回 NULL 指针。

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

由于这个函数没什么模拟的必要,所以就不在这里模拟实现了。

10.strerror:char* strerror(int errnum);返回字符串的错误

我们知道当我们再编程时编译器会为我们提出错误,而C语言也提供了在使用字符串时候专门报错的函数,当我们把数字传入strerror时,每个数字就对应一个错误(一般用errno),这是就可以将错误返回给我们。

int main()
{
	printf("%s", strerror(0));
	printf("%s", strerror(1));
	printf("%s", strerror(2));
	printf("%s", strerror(3));
	return 0;
}
#include  
#include  
#include //必须包含的头文件
int main () 
{ 
 FILE * pFile = fopen ("unexist.ent","r"); 
 if (pFile == NULL) 
 printf ("Error opening file unexist.ent: %s\n",strerror(errno)); 
 //errno: Last error number 
 return 0; 
} 

我们刚才讲了字符串函数的用法,那C语言也有针对字符的函数,我就列在下方大家如果有需要可以参考,在这里就不展开说了。

函数 如果符合条件就返回真

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 任何可打印字符,包括图形字符和空白字符

11.memcpy:void * memcpy ( void * destination, const void * source, size_t num );内存拷贝

 对于字符串函数呢,我们就先告一段落,接下来我给大家介绍几个内存函数,大家知道变量在被定义后都是有内存有地址的,所以C语言也为我们创建了很多内存的函数,意思就是可以直接通过内存的改变来改变存在内存中的值,那究竟是怎么实现的呢?我们来看:

int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
	int arr2[20] = { 0 };
	memcpy(arr2, arr1, 10 * sizeof(int));
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", arr2[i]);
	}
	return 0;
}

模拟实现memcpy:

void* my_memcpy(void* des, const void* src, size_t count)
{
	assert(des && src);
	void* ret = des;
	while (count--)
	{
		*(char*)des = *(char*)src;//由于是内存函数,所以void*要根据传入参数的类型改变
		des = (char*)des + 1;;
		src = (char*)src + 1;
	}
	return ret;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memcpy(arr + 2, arr, 16);//memcpy只要完成了不重叠的内存拷贝就完成任务了
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

12.memmove:void * memmove ( void * destination, const void * source, size_t num );内存移动

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就得使用memmove函数处理。 

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr + 2, arr, 16);//内存拷贝时,出现内存重叠的现象,应该使用memmove
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

模拟实现memmove:

void* my_memmove(void* des, const void* src, size_t count)
{
	assert(des && src);
	void* ret = des;
	if (des < src)
	{
		while (count--)
		{
			*(char*)des = *(char*)src;
			des = (char*)des + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (count--)
		{
			*((char*)des + count) = *((char*)src + count);
			des = (char*)des + 1;
			src = (char*)src + 1;
		}
	}
	return ret;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr + 2, arr, 16);//内存拷贝时,出现内存重叠的现象,应该使用memmove
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

13.memcmp:int memcmp( const void *buf1, const void *buf2, size_t count );内存比较

 刚刚也有一个cmp函数,就是strcmp字符比较,那么这个是内存比较函数,也就是对内存直接比较。

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,6,6 };
	int ret = memcmp(arr1, arr2, 12);
	printf("%d", ret);
	return 0;
}

14.memset:void *memset( void *dest, int c, size_t count );内存设置

如果说内存拷贝是无论内存块重叠不重叠的情况下都将它们进行直接覆盖,那么内存设置和内存拷贝的功能类似,它是将一块指定的内存里的内容设置成指定的内容。

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	memset(arr, 0, 24);
	return 0;
}

好了,针对字符串函数我们今天就讲到这里,我们下次再见。

你可能感兴趣的:(笔记,程序小白,学生,c语言,javascript,r语言)