字符串+内存函数的介绍(1)

发布于:2022-12-13 ⋅ 阅读:(404) ⋅ 点赞:(0)

函数介绍

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

重点介绍处理字符和字符串的库函数的使用和注意事项

  • 求字符串长度

strlen

  • 长度不受限制的字符串函数

strcpy

strcat

strcmp

  • 长度首限制的字符串函数介绍

strncpy

strncat

strncmp

  • 字符串查找

strstr

strok

  • 错误信息报告

strerror

字符操作

  • 内存操作函数

memcpy

memmove

memset

memcmp

1.1 strlen 

size_t strlen (const char* str);

  • 字符串以' \0 '作为结束标志,strlen函数返回的是再字符串中 ' \0 '前面出现的字符个数(不包含' \0 ')
  • 参数指向的字符串必须以' \0 '结束
  • 注意函数的返回值为size_t,是无符号的
  • 学会strlen函数的模拟实现

求字符串的长度,统计的是字符串中\0之前出现的字符的个数

strlen函数的头文件是#include<string.h>

但是像下面这种就是不正确的

 数组中没有\0,而strlen是计算\0之前的字符个数,所以打印的结果是随机值

 这里并非我们所想的那样,打印出<,是因为我们忽略了strlen的返回类型是size_t类型,3-6=-3,无符号数减去无符号数得到的仍然是无符号数,-3被当作无符号数相当于无穷大的数,所以是>

strlen函数的模拟实现:

#include<assert.h>
int my_strlen(const char* str)
{
	assert(str);
	const char* start = str;//记录起始位置
	const char* end = str;
	while (*end != '\0')
	{
		end++;
	}
	return end - start;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

1.2 strcpy

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

  • Copies the C string pointed by source into the array pointed by destination,including the terminating null character(and stopping at that point.)
  • 源字符串必须以' \0 '结束
  • 会将源字符串中的' \0 '拷贝到目标保存空间
  • 目标空间必须足够大,以确保能存放源字符串
  • 目标空间必须可变
  • 学会模拟实现

strcpy是将从源头空间中\0之前的字符拷到目标空间处,如果 源头空间没有\0将不会拷贝成功

看下面这个例子中将arr中的\0拷贝到目标空间中

 但是下面就是刚才所说的,源头空间中没有\0的情况,如果没有\0将会拷贝失败

 所以说strcpy是拷贝\0之前的字符

注意:不能将常量字符串修改,也就是说,不能将一个常量字符串的内容拷贝到另一个常量字符串,我们必须保证目标空间的内容必须是可修改的

strcpy函数的模拟实现:

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

int main()
{
	char arr[10] = { 0 };
	const char* p = "abcdef";
	my_strcpy(arr, p);
	printf("%s\n", arr);
}

1.3 strcat

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

  • Appends a copy of the source string to the destination string.The terminating null character in destination is overwritten by th first character of source,and a null-character is included at the end of the new string formed by the soncatenation of both destination.
  • 源字符串必须以' \0 '结束
  • 目标空间必须足够的大,能容纳源字符串的内容
  • 目标空间必须可修改

strcat是从一个字符串的末尾开始追加,,所以是遇到第一个\0之后就开始追加,比如从下面的例子中就能很好的看出来

strcat函数的模拟实现:

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	//1.找到目标空间中\0
	char* cur = dest;
	while (*cur != '\0')
	{
		cur++;
	}
	//2.拷贝源头数据到\0之后的空间
	while (*cur++ = *src++)
	{
		;
	}
	return dest;
}
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	my_strcat(arr1, arr2);
	printf("%s\n", arr1);

}

注意:这里有几个库函数是返回目标空间的起始地址,所以在最开始先保存一下目标空间的起始地址

这里while循环也可以写成while(*cur++=*src++);的形式,但是这样不太便于阅读,上面代码中while循环可读性更好一些

但是这个库函数不能自己给自己追加

如果自己给自己追加的话,就会将自己的\0覆盖掉,自己就没有\0了,而 strcat永远找不到\0就不能结束,也就成为死循环

1.4 strcmp

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

  • This function starts comparing the first character of each string.If they are equal to each other,it continues with the following pairs until the characters differ or until a terminating null-character is reached.
  • 标准规定:

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

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

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

strcmp比较的是两个字符串中相同位置中的ASCII码值的比较,在从前比较的过程中,哪个对应位置上的ASCII码值比较大,哪个就大,注意这里比较的不是长度

 strcmp模拟实现:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
		{
			return 0;//str1和str2相等
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abp";
	int ret = my_strcmp(arr1,arr2);
	if (ret < 0)
		printf("arr1<arr2\n");
	else if (ret > 0)
		printf("arr1>arr2\n");
	else
		printf("arr1==arr2\n");
	
	return 0;
}

小结:

strcpy、strcat、strcmp都是与\0有关,所以它们称为长度不受限制的字符串函数

1.5 strncpy

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

如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加0,直到num个

 

 1.6 strncat

 

 xy后面的\0是strncat函数主动追加的

上面的strcat函数不能自己给自己追加,但是我们可以利用strncat给自己给自己追加

 之前的strcat函数在追加的时候不会在后面添加\0,会导致死循环(前面已经写死循环的原因),但是strncat会再后面主动追加\0,这样就有机会停下来,构成一个完整的字符串

1.7strncmp

 

 1.8strstr

 在一个字符串中找另一个字符串是否存在(找str2在str1中是否存在),如果存在返回子串第一次出现的起始位置,如果不存在则返回空指针(NULL)

注意:这里返回的起始位置的地址,并没有改变字符串的内容

 strstr函数的模拟实现

char* my_strstr(const char* str1, const char* str2)
{
	const char* s1 = str1;
	const char* s2 = str2;
	const char* p = str1;
	while (*p)
	{
		s1 = p;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' &&( *s1 == *s2))
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//此时遍历了要找的字符串,也就是这个字符串的每个字符都找到了
		{
			return(char*) p;
        //p的类型是const char*,而这个函数的返回类型是char*,所以要强制类型转换
		}
		p++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abcabcdefdef";
	char arr2[] = "efd";
	char* p = my_strstr(arr1, arr2);
	printf("%s\n", p);
	return 0;
}

1.9 strtok函数

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

  • sep参数是个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
  • strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数且分的字符串一般都是临时拷贝的内容并且可修改)-->就比如下面的例子中,@作为分隔符,会将其改为\0,并且返回首元素的地址(  l  的地址),因为会改变原字符串的内容,所以将源字符串的内容拷贝到另外一个数组中,然后strtok函数调用这个数组,接着,在取找分隔符,将其改为\0然后返回q的地址
  • strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它的字符串中的位置
  • strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置(上一次被保存的位置)开始,查找下一个标记
  • strtok函数在一个字符串中第一次调用不为空指针,再下一次调用的时候为空指针
  • 如果字符串中不存在更多的标记,则返回NULL指针

 

 但是我们一般不这么使用,像下面这种才是strtok函数的正确使用方式

 1.10 strerror

char * strerror( int errnum) ;

返回错误码,所对应的错误信息(把错误码转换成错误信息)

 这里我们只关注strerror函数的使用

 strerror(errno)错误码记录到错误码的变量中,errno时C语言提供的全局的错误变量,并且在使用errno的同时要引头文件#include<errno.h>

由于我没有创建test.txt文件,所以会报错

 此时我在此路径下创建了test.txt文件,所以这时不会报错

还有另外一个库函数和strerror库函数类似:perror函数

 这个库函数perror=printf+strerror

字符分类函数:

使用要引用对应的头文件#include<ctype.h>

函数 如果它的参数符合下列条件就返回真(返回值为非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 任何图形字符

字符转换函数:(对应头文件也是#include<ctype.h>)

int tolower( int c );//转换成小写

int toupper( int c );//转换成大写

 如果是小写,我们转换成大写,但是它本身就是大写,我们是不会转换的

1.11memcpy

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

  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
  • 这个函数在遇到' \0 '的时候并不会停下来
  • 如果source和destination有任何的重叠,复制的结果都是未定义的

一组内存函数:

  • memcpy
  • memmove
  • memcmp
  • memset

memcpy是什么类型的数据都能拷贝,而strncpy只能拷贝char类型的

参数为void*的时候,可以传进来任何类型的地址,所以使用这函数的时候是不挑类型的

模拟实现memcpy函数:

注意void*类型指针不能直接解引用,因为它是void*类型,解引用的时候不知道访问几个字节,同理也不能直接进行++和--操作

但是我们发现这里强制类型转换也是错误的,因为这里的强制类型转换是临时,这里++是对于当前的dest和src的,对当前左边的有用,但是对右边没用

 有些人可能会这么写((char*)dest++);  ((char*)src)++;但是这样的写法,在有些编译器能跑过去,在有一些编译是不能跑过去的,这里并不是一个好的方法,所以还需要换另一种方法,

#include<assert.h>
#include<stdio.h>
void* my_memcpy(void* dest,const void* src, size_t num)
{
	assert(dest && src);
	char* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
//这里dest和src为void*类型能够接受任意类型的值
	}
	return (void*)dest;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
}

因为num的单位是字节,所以一个一个字节拷贝比较合适

重叠拷贝:

 当我们想要将2 3 4拷贝到4 5 6时,4和5拷贝成功,但是4已经被拷贝成2,此时6的位置会被拷贝成2,此时我们就需要从后向前拷贝,但是并不是任何时候都要从后向前拷贝

 此时是从后向前拷贝,但是这时3不能被拷贝成5,而是被拷贝成7,所以这次需要从前往后拷贝

因此重叠拷贝时,有时候需要从前向后拷贝,有时候需要从后前拷贝,但是我们memcpy函数并不能完成重叠时的拷贝,这时我们就引入了一个库函数:memmove函数

1.12 memmove

void * memove ( void* destination , const void*  source , size_t )

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

当dest在src的前面的时候,从前往后拷贝;当dest在src的后面的时候,从后向前拷贝

注意:这里是将src的内容从前到后还是从后到前拷贝到dest中,不要混淆了

3区域是不重叠的区域,如果不重叠从后向前和从前向后都无所谓,但是2区域重叠时从后向前拷贝,此时,这个3区域从后向前拷贝更加的方便

#include<stdio.h>
#include<assert.h>
void* my_mommove(void* dest, void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);
	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;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_mommove(arr + 2, arr, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
}

memcpy只需要实现不重叠的拷贝就可以了

memmove是需要实现重叠内存拷贝的

也可以说memmove的功能是包括memcpy的,在VS编译器上memcpy和memmove的实现是一样的,但是在有些编译器上,它们的实现是不同的

1.13 memcmp

int memcpy ( const void* ptr , void* ptr2 , size_t num ) ;

 

 比较从ptr1和ptr2指针开始的num个字节

 这个函数也是一对一对字节进行比较的

1.14 memset-->内存设置

将ptr前num个字节改为value

 

 将前8个字节改为0


网站公告

今日签到

点亮在社区的每一天
去签到