C语言字符串函数,内存函数讲解及其模拟

在这篇博客会讲解以下函数:strlen,strcpy,strcat,strcmp,strncpy,strncat,strncmp,strstr,strtok,memcpy,memmove,memset,memcmp以及字符函数。



strlen

函数讲解:计算字符串的长度

size_t strlen(const char * str );

strlen这个函数的参数是一个字符指针,返回的是一个无符号的整数;

需要注意以下几点 :

1.字符串已经 '\0' 作为结束标志, strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数。
2. 参数指向的字符串必须要以 '\0' 结束。
3.函数的返回值是一个无符号整数。

 其中第三点要注意一下:下面这段代码是错误的

if(strlen(str1) - strlen(str2) > 0)
{
    printf("str1长一些\n");
}

因为strlen的返回值是无符号整数,两个无符号整数相减还是无符号整数,永远会输出str1长一些。


模拟实现

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

 除了上述计数器方法,还有递归和指针减指针的方法。



strcpy

函数讲解:把sourse字符串复制给destination字符串

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

strlcpy这个函数的参数是两个字符指针,返回的是destination这个字符指针;

需要注意以下几点 :
1.源字符串必须以 '\0' 结束。
2.会将源字符串中的 '\0' 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可以被改变。

其中第四点要注意一下:下面这段代码是错误的

int main()
{
	char* p = "abcdefghi";
	char arr2[20] = "hehe";
	strcpy(p, arr2);

	return 0;
}

因为p指针指向的是一个字符串常量的首元素,目标空间不能被修改。


模拟实现

char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest != NULL);
	assert(src != NULL);

	while ((*dest++ = *src++))
	{
		;
	}
	return ret;
}

asset()函数是断言函数当assert里面的判断为假时程序就会报错,提高了程序的安全性。



strcat

函数讲解:把sourse字符串追加到destination字符串的末尾

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

strcat这个函数的参数是两个字符指针,返回的是destination这个字符指针;

需要注意以下几点 :

1.源字符串必须以 '\0' 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。
4.字符串自己给自己追加,这个行为是未被定义的。

其中第四点要注意一下:下面这段代码的行为是不被定义的

int main()
{
	char str[20] = { "abd" };
	strcat(str, str);
	printf("%s\n", str);
	return 0;
}

在C语言标准中使用strcat函数追加自己的的行为是未定义的,所以在某些编译器上这段代码可能会报错。


模拟实现

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

这段代码,先让dest指针指向字符串的末尾,在进行和字符串拷贝类似的操作。



strcmp

函数讲解:比较两个字符串的大小

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

strcmp这个函数的参数是两个字符指针,返回的是一个整形;

函数返回值的具体信息:

1.第一个字符串大于第二个字符串,则返回大于 0 的数字。
2.第一个字符串等于第二个字符串,则返回 0。
3.第一个字符串小于第二个字符串,则返回小于 0 的数字。

程杰《大话数据结构》中有讲到串的比较

        串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。(在C语言中字符串用的是ASCII编码)
        1.相等的情况:给定两个串: S="a1a2…an″, T="b1b2…bm", 当且仅当n=m时,且a1=b1,a2=b2…an=bm时,我们认为S与T这两个串相等。
        2.不等的情况:给定两个串: S="a1a2…an″, T="b1b2…bm", 当满足以下条件之一时,我们认为S <T。
        (1) n < m,且ai = bi(i = 1,2,3...n)
        (2) 存在某个k <= min(n, m),使得 ai = bi(i = 1,2,3...k-1),ak < bk。

书本中的定义其实有点太公式化而不太好理解,以下是我的总结:

字符串的比较可以看错一对循环的字符比较 (若字符相等则比较下一对字符)

我们从字符串比较的终止条件来判断字符串的大小:

终止条件1:字符串比较未达到两个字符串的末尾 ('\0')

        这个情况时,当有一个字符串对应的字符大于另一个字符串的时,大的那一个对应的字符串大。

终止条件2:字符串比较到两个字符串的末尾 ('\0')

        这个情况时,两个字符串相等。


模拟实现 

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);

	while (*str1 == *str2)
	{
		if (*str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}

	return *str1 - *str2;
}


strncpy

函数讲解:与strcpy类似,不过这个函数可以选择复制的长度。

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

strncpy这个函数的参数是两个字符指针和一个无符号整形(复制的长度),返回的是destination这个字符指针;

需要注意以下几点 :
1.拷贝 num 个字符从源字符串到目标空间。
通过观察vs编译器中strncpy的使用(读者可以自己观察试试):
1.如果源字符串的长度小于 num ,则拷贝完源字符串之后,在目标的后边追加 0 ,直到 num 个。
2.不管怎么样只会复制num个数,不会主动加\0

 模拟实现

char* my_strncpy(char* dest, const char* src, size_t num)
{
	int is_end = 0;
	for (int i = 0; i < num; i++)
	{
		if (is_end == 1)
		{
			*(dest + i) = '\0';
		}
		else
		{
			if (*(src + i) != '\0')
			{
				*(dest + i) = *(src + i);
			}
			else
			{
				*(dest + i) = '\0';
				is_end = 1;
			}
		}
	}
	return dest;
}


strncat

函数讲解与strcat类似,不过这个函数可以选择追加的长度。

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

strncat这个函数的参数是两个字符指针和一个无符号整形(追加的长度),返回的是destination这个字符指针;

通过观察vs编译器中strncat的使用(读者可以自己观察试试):
1.strncat 遇到\0就会停止追加,要是没遇上就会自动加一个\0。

模拟实现:

char* my_strncat(char* dest, const char* src, size_t num)
{
	char* ret = dest;

	while (*dest != '\0')
	{
		dest++;
	}

	int is_end = 0;
	for (size_t i = 0; i < num; i++)
	{
		if (*(src + i) != '\0')
		{
			*(dest + i) = *(src + i);
		}
		else
		{
			*(dest + i) = '\0';
			is_end = 1;
			break;
		}
	}

	if (is_end == 0)
	{
		*(dest + num) = '\0';
	}

	return ret;
}


strncmp

函数讲解: 与strcmp类似,不过这个函数有比较长度的限制。

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

strncmp这个函数的参数是两个字符指针和一个无符号整形(比较的长度),返回的是一个整形,整形的意义和strcmp的返回值一样。

通过观察vs编译器中strncmp的使用(读者可以自己观察试试):

1.比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。


模拟实现:

//比较简单,和strcmp类似。读者可以自己尝试


strstr

函数讲解:在一个字符串中寻找另一个字符串

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

这个函数的参数是两个字符指针;返回的是一个字符指针,如果在str1中找到了str2,则会返回str1中的那段串的第一个字符;如果没找到,则会返回空指针。


模拟实现

char* my_strstr(const char* arr1, const char* arr2)
{
	assert(arr1 && arr2);

	char* cp = (char*)arr1;
	char* s1 = NULL;
	char* s2 = NULL;
	while (*cp)
	{
		s1 = cp;
		s2 = (char*)arr2;
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}

	return NULL;
}

 关键:用cp指针记录每次寻找,没找到则指针加1;



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. 如果字符串中不存在更多的标记,则返回 NULL 指针。

模拟实现:无



memcpy

函数讲解:这是可以看作一个拷贝函数,与strcpy类似,但不仅限于拷贝字符串。

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

函数的参数是两个void*类型的指针,和一个代表复制的字节长度的无符号整形。返回值也是一个void*的指针,是目标的起始空间;

需要注意以下几点:

1. 函数 memcpy source 的位置开始向后复制 num 个字节的数据到 destination 的内存位置。
2. 复制的长度只与num相关,在遇到 '\0' 的时候并不会停下来,
3. 如果 source destination 有任何的重叠,复制的结果都是未定义的(可能成功,也可能失败)。

模拟实现:

void* my_memcpy(void* dest, 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;
}

这个模拟实现的关键 是把void*类型 强制类型转换 成char* 进行操作;



memmove

函数讲解:看名字可以知道这个函数可以理解为内存移动函数。这个函数与memcpy类似,只不过sourcedestination有重叠,也可以进行复制。

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

需要注意以下几点:

1. memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

2. 在复制时,如果源空间和目标空间出现重叠,就得使用memmove函数处理。


模拟实现:

void* my_memmove(void* dest, void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

关键:根据在src中要复制的元素,要在修改前就要复制,来决定时从后往前复制还是从后往前复制(画图即可弄明白)。



memset

函数讲解:把内存中的每一个字节赋值一个相同的值。

void* memset(void* ptr, int value, size_t num);

ptr指针:指向的内存块地址; value:赋的值(可以是字符,原因:字符以ASCII码的形式存储);num:需要修改的字节数。 

需要注意以下几点:

1. 是修改内存中的每一个字节为value,单位为一个字节。

2. int arr[10] = { 0 };memset(arr, 1, sizeof(arr));

        这段代码并不能把arr数组中每个元素赋值为1, 而赋值是一个很大的数。

3. 如果value是0,则无这方面的问题。


模拟实现:无



memcpy

函数讲解:与strcpy类似,但又不仅限于比较字符串。

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

无符号的num:比较的字节数; int返回值:与strcpy类似;

需要注意以下几点:

1. 比较从ptr1ptr2指针开始的num个字节


模拟实现:无



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值