【C语言】字符串与内存函数

目录

针对字符串的函数

strlen

strcpy

strcat

strcmp

strncpy

strncat

strncmp

strstr

strtok

strerror

针对字符的函数

字符分类函数

字符转换函数

针对内存的函数

memcpy

memmove

memcmp

memset


针对字符串的函数

strlen

模拟实现 strlen 的方法:

方法一:

int my_strlen(const char* p)
{
	assert(p != NULL);
	int count = 0;
	while (*p != '\0')
	{
		count++;
		p++;
	}
	return count;
}

方法二:(递归法)

int my_strlen(const char* p)
{
	assert(p != NULL);
	if (*p == '\0')
		return 0;
	else
		return 1 + my_strlen(p + 1);
}

方法三:(指针减指针)

int my_strlen(const char* p)
{
	assert(p != NULL);
	const char* start = p;
	while (*p)
	{
		p++;
	}
	return p - start;
}

实际上,库函数的 strlen 函数的返回值类型是 size_t 类型,size_t 类型其实就是 unsigned int 类型(长度不可能为负数),typedef unsigned int = size_t。

unsigned int 类型的数据减去一个 unsigned int 类型的数据,结果为 unsigned int 类型的数据

strlen(“abc”)- strlen(“abcde”)的结果不是负数,而是一个很大的正数。

所以说返回值的类型到底是 int 还是 size_t 有利有弊。

strcpy

模拟实现: 

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

1、源字符串我们不希望改变,所以我们将 src 指针用 const 修饰。

2、while 循环部分被简化。

3、这个函数应该返回目标字符串的首字符地址

注意:

1、源字符串必须以“\0’结束。

while 循环部分的结束条件是 src == ‘\0'。

2、会将源字符串中的“\0'拷贝到目标空间。

while 循环部分当 src 加1 指向 ’\0' 后,会进行赋值再判断

3、目标空间必须足够大 , 以确保能存放源字符串。

如果目标空间小于源字符串的长度,则 strcpy 函数会对目标空间越界访问,比如将 str2[5] = "abcde" 复制到 str1[3] = { 0 } ; 则 str1[3] = 'd',str1[4] = 'e' , 这是错误的。

4、目标空间必须可变。

如果 dest 是一个字符常量指针,如:char *p = “abc”;p 不能作为 dest 的实参。

strcat

模拟实现: 

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	//找目标字符串的\0
	while (*dest)
	{
		dest++;
	}
	//从\0开始追加
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

1、被追加的字符串末尾要有 \0 ,strcat 才知道从哪里开始追加。

2、追加的字符串末尾也要有 \0,strcat 才知道什么时候停止追加。

3、这个函数也应该返回目标字符串的首字符地址。

字符串自己给自己追加,不能使用 strcat 函数。

strcmp

strcmp 函数有两个参数,分别是两个字符数组名,即 strcmp(arr1,arr2),strcmp 函数比较arr1 与 arr2 字符串从开始到 ‘\0’ 逐个比较相应位置上的字符,只要发现某个位置上的字符的ASCII码值大于(或小于)对应位置的另一个字符串的字符,就返回一个大于 0 的数(或小于 0 的数),如果每个字符串的字符的ASCII码值都相等,即两个字符串一模一样,就返回 0。(VS环境下,大于返回 1,小于返回 -1,等于返回 0)。可以认为在词典中先出现的单词比后出现的单词小。

模拟实现:

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

}

注意:

1、我们只是想要比较两个字符串,所以我们可以用 const 修饰 str1 和 str2 。

2、有些编译器当 *str1 != *str2 时,直接返回 *str1 - *str2。

3、根据 strcmp 返回值的结果判断两个字符串的大小最好的办法是 if(strcmp(str1,str2)> 或 < 或 = 0)。

strncpy

strcpy、strcat、strcmp 都是长度不受限制的函数,编译器会认为这些函数不安全。后来引进了一些函数如 strncpy、strncat、strncmp ,这些函数更加安全,因为它们多了一个参数 int n 。

模拟实现:

char* my_strncpy(char* dest, const char* src,int n)
{
	assert(dest && src);
	char* ret = dest;
	int i = 1;
	while ((*dest++ = *src++) && (i < n) && (*src))
	{
		i++;
	}
	i++;
	while (i <= n)
	{
		*dest++ = '\0';
		i++;
	}
	return ret;
}

注意:

strncpy 与 strcpy 的不同之处:
1、strncpy 可以指定从源字符串中复制几个字符到目标字符串。

2、当指定复制的字符数量大于源字符串的长度,strcpy 会在目标字符串复制后补 ‘\0’,当指定复制的字符数量小于或等于源字符串的长度,目标字符串末尾不会补 ’\0‘ (这是 strncpy 的不足之处)

strncat

模拟实现: 

char* my_strncat(char* dest, const char* src,int n)
{
	assert(dest && src);
	char* ret = dest;
	//找目标字符串的\0
	while (*dest)
	{
		dest++;
	}
	//从\0开始追加
	while ((*dest++ = *src++) && (n - 1))
	{
		n--;
	}
	*dest = '\0';
	return ret;
}

注意:

strncat 与 strcat 的不同之处:
1、strncat 可以指定从源字符串中追加几个字符到目标字符串。

2、当指定追加的字符数量小于或等于源字符串的长度,目标字符串末尾仍然会补 ’\0‘ 。

strncmp

模拟实现: 

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

}

strstr

函数原型:

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

函数功能:在 str1 字符串中查找是否存在完整连续的 str2 字符串,如果有就返回 str1 中存在的与 str2 字符串一模一样的最先出现的字符串的起始地址,如果没有就返回 NULL。

函数模拟实现:

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

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

strtok

函数原型:

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

函数功能:将 elimiters 所指向的常量字符串的每个字符作为分隔符,以这些分隔符来分割 str( 将  str 中的分隔符变为 ’\0‘,所以strtok 函数会改变原字符数组,要求原字符数组为可变的,如果不想改变原字符数组,一般将原字符数组使用 strcpy 函数拷贝到另一个字符数组去),并返回分割后的字符串首字符的地址,第一次调用 strtok 函数时,str 的实参为要分割的字符数组名,一次函数调用只分割一次,如果要多次分割,第二次及以后调用 strtok 函数时,str 的实参为 NULL,strtok 会记住上一次分割时分割符下一个字符的地址,并从这个地址开始寻找下一个分割符,将下一个分隔符变为 '\0' 。

使用技巧:

如果原字符数组有多个分隔符,可以使用 for 循环来快速分割:

for (ret = strtok(buf, p); ret != NULL; ret=strtok(NULL, p))
{
    printf("%s\n", ret);
}

strerror

C语言的库函数在运行的时候,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno(这是一个全局变量,使用它需要包含头文件 include <errno.h>)

错误码是一些数字:1 2 3 4 5

我们需要讲错误码翻译成错误信息,就要使用 strerror 函数

函数原型:

char * strerror ( int errnum );

errnum 就是错误码,strerror 函数将返回一个指向错误信息的首字符的字符指针。

 应用举例:

int main()
{
	FILE* pf = fopen("test.exe", "r");
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 1;
	}
	fclose(pf);
	return 0;
}

 假设 test.exe 这个文件不存在,那么程序将输出 “打开文件失败”,这会让我们迷惑:到底是不存在这个文件,还是访问权限的问题,还是其他的原因。如果我们使用 strerror 这个函数

#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
	FILE* pf = fopen("test.exe", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	fclose(pf);
	return 0;
}

程序会输出 “ No such file or directory ”,这样我们就知道问题出在哪里了。

perror

相对于 strerror 函数,perror 函数更加方便,它可以直接打印错误信息。使用 perror 需要包含头文件 include <stdio.h>

函数原型:

void perror ( const char * str );

str 是指向你自定义的信息(在打印错误信息之前,会打印你自定义的信息)

应用举例:

int main()
{
	FILE* pf = fopen("test.exe", "r");
	if (pf == NULL)
	{
		perror("error");
		return 1;
	}
	fclose(pf);
	return 0;
}

程序会输出:

error: No such file or directory

针对字符的函数

字符分类函数

需要包含头文件 #include <ctype.h>

函数名参数满足下列条件就反回非零,否则返回零
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任何可打印字符,包括图形字符和空白字符

字符转换函数

大小写转换函数(需要包含头文件 #include <ctype.h>):toupper 函数(小写转大写)和 tolower 函数(大写转小写),它们的参数当然是一个大写或小写字符(或 ASCII 码值),返回值是转换后的字符的 ASCII 码值。 

应用举例:

int main()
{
	char arr[] = "I Have An Apple."; 
	int i = 0; 
	while (arr[i])
	{
		if (isupper(arr[i]))
			arr[i] = tolower(arr[i]);
		printf("%c", arr[i]); 
		i++;
	}
		 
	return 0;

}

针对内存的函数

以下函数都要包含头文件 string.h 

memcpy

strcpy 是拷贝字符串的函数,如果我们想拷贝一个整形数组,就要使用 memcpy 函数。

函数原型:

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

destination:指向目标空间的指针

source:指向源空间的指针

size_t num:要拷贝的字节数

void*:函数返回目标空间的首地址

应用举例:

int main()
{
	int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int arr2[10] = { 3,4,5,6,7 };
	memcpy(arr1, arr2 + 2, 12);
	return 0;
}

模拟实现:

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;
}

不足之处:如果源空间与目标空间有重叠的地方,用这种方式拷贝有 BUG。比如:

int main()
{
	int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };
	my_memcpy(arr1 + 2, arr1, 20);
	return 0;
}

我想把 0,1,2,3,4 拷贝到 2,3,4,5,6 的位置上去,预期的结果应该是:

0,1,0,1,2,3,4,7,8,9

实际的结果是:

0,1,0,1,0,1,0,7,8,9

解决方法:

如果我们想从后往前拷贝,可以解决这个问题,但是,如果是将 2,3,4,5,6 拷贝到 0,1,2,3,4 的位置上去呢?这时候又只能从前往后拷贝了,所以我们得分情况讨论:

1、当 dest 在 src 的左边时,从前往后拷贝

2、当 dest 在 src 的右边时,从后往前拷贝

这就是 mommove 函数的思路。

memmove

mommove 函数可以实现有重叠的内存拷贝。

运用以上思路模拟实现 mommove 函数:e

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

注意:

1、拷贝时要防止数组越界,这不是函数该保证的事情,而是程序员该保证的。

2、库函数的 memcpy 与 mommove 的其实都能处理有重叠的情况。

3、最保险的方式是统一用 mommove

memcmp

函数原型:

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

ptr1 和 ptr2 :指向要比较的字节

num:要比较的字节数量

返回值:只要 *ptr1 > *ptr2 ,返回一个大于 0 的数,只要 *ptr1 < *ptr2 ,返回一个小于 0 的数,否则返回 0。

memset

函数原型:

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

ptr:要设置的内存的起始地址。

value:要设置的数据,可以是整形,可以是字符的 ASCII 码值,也可以是字符如 ‘x’。

num:要设置的字节数。

返回值:设置的内存的起始地址。

注意:

momset 函数是一个一个字节设置的,将数组中每一个元素都初始化为 1 ,如果用:

int arr[10] = { 0 };

momset(arr , 1 , 40);

这种方式初始化数组是不对的,数组中每一个元素都被初始化为 0x 01 01 01 01

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值