前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【字符串+内存函数的介绍】

【字符串+内存函数的介绍】

作者头像
每天都要进步呀
发布2023-03-28 11:25:18
7440
发布2023-03-28 11:25:18
举报
文章被收录于专栏:C++/Linux

字符串+内存函数的介绍

1.本章重点

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

1.求字符串长度 strlen 2.长度不受限制的字符串函数 strcpy strcat strcmp 3.长度受限制的字符串函数介绍 strncpy strncat strncmp 4.字符串查找 strstr strtok 5.错误信息报告 strerror 6.字符操作 7.内存操作函数 memcpy memmove memset memcmp

2.函数介绍

2.1 strlen

代码语言:javascript
复制
size_t strlen(const char* str);

字符串已经’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’) 注: 1)参数指向的字符串必须要以’\0’结束。 2)注意函数的返回值为size_t,是无符号的。(易错) 3)学会strlen函数的模拟实现。

代码语言:javascript
复制
#include <stdio.h>
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "bbb";
	if (strlen(str2) - strlen(str1) > 0)
	{
		printf("str2>str1\n");
	}
	else
	{
		printf("srt1>str2\n");//打印
	}
	return 0;
}

2.2 strcpy

代码语言:javascript
复制
char* strcpy(char * destination, const char * source );

将source的字符全部复制给destination,返回值为复制之后的destination的地址 注: 1)原字符串必须以’\0’结束。 2)会将source中的0拷贝到destination的空间。 3)目标空间必须足够大,以确保能存放原字符串。 4)目标空间必须可变。 5)学会模拟实现。

2.3 strcat

代码语言:javascript
复制
char* strcat(char* destination,const char* source);

将源字符串的副本追加到目标字符串。终止空字符在destination中被源的第一个字符覆盖,并且包含一个空字符在新字符串的末尾,将两者连接在destination中形成,返回值为连接之后的destination的首地址。 注: 1)源字符串必须以’\0’结束 2)目标空间必须足够大,即destination必须放得下二者之和 3)目标空间必须可修改。 4)字符串自己给自己追加,如何? 答 4):会陷入死循环,在模拟实现中解释

2.4 strcmp

代码语言:javascript
复制
int strcmp(const char* str1, const char* str2);

这个函数开始比较每个字符串的第一个字符。如果它们相等,则继续向后比较,直到字符不同或终止null字符。注:(比较的是ascii码值的大小) 标准规定: 1)第一个字符串大于第二个字符串,则返回大于0的数字 2)第一个字符串等于第二个字符串,则返回0 3)第一个字符串小于第二个字符串,则返回小于0的数字 4)那么如何判断两个字符串?一个字母一个字母比较ascii码值的大小,不相等就截止

2.5 strncpy

代码语言:javascript
复制
char* strncpy(char* destination, const char* source ,size_t num);

比strcpy多了一个长度上的限制,即将source的前num个字符拷贝到destination中。 注: 1)拷贝num个字符从源字符串到目标空间。 2)如果源字符串的长度小于num,则拷贝完字符串之后,在目标的后面追加0,直到num个。

2.6 strncat

代码语言:javascript
复制
char* strncat(char* destination,const char* source,size_t num);

比strcat多了一个长度上的限制,即将source的前num个字符连接到destination上。

代码语言:javascript
复制
int main()
{
	char str1[20];
	char str2[20];
	strcpy(str1, "To be ");
	strcpy(str2, "or not to be");
	strncat(str1, str2, 6);
	puts(str1);
	return 0;
}

结果如下:

在这里插入图片描述
在这里插入图片描述

2.7 strncmp

代码语言:javascript
复制
int strncmp(const char* str1,const char* str2,size_t num);

比较前num个字符二者的大小,比strcmp多了数量上的限制

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	char str[][5] = { "R2D2","C3Po","R2A6" };
	int n;
	puts("Looking for R2 astromech droids...");
	for (n = 0; n < 3; n++)
	{
		if (strncmp(str[n], "R2xx", 2) == 0)
		{
			printf("found %s\n", str[n]);
		}
	}
	return 0;
}
在这里插入图片描述
在这里插入图片描述

2.8 strstr

代码语言:javascript
复制
char* strstr(const char*str1,const char* str2);

在字符串str1中查找str2是否在str1内部,即查找子串,若找到,则返回str2在str1处的地址,若找不到,则返回NULL。

在这里插入图片描述
在这里插入图片描述

2.9 strtok

代码语言:javascript
复制
char* strtok(char* str, const char* sep);

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

代码语言:javascript
复制
//strtok example
int main()
{
	char str[] = "- This, a sample string.";
	char* pch;
	printf("Splitting string \"%s\" into tokens:\n", str);
	pch = strtok(str, ",.-");
	while (pch != NULL)
	{
		printf("%s\n", pch);
		pch = strtok(NULL, ", .-");//逗号,空格,点,减号
	}
	return 0;
	//下面的循环方法同样适用:
//int main()
//{
//	const char* sep = "@.";
//	char email[] = "zhangpengwei@bitejiuyeke.com.net";
//	char cp[40] = { 0 };//"zhangpengwei@bitejiuyeke.com"
//	strcpy(cp, email);
//
//	char* ret = NULL;
//	for (ret = strtok(cp, sep); 
//		 ret != NULL; 
//		 ret=strtok(NULL, sep))
//	{
//		printf("%s\n", ret);
//	}

}

结果为:

在这里插入图片描述
在这里插入图片描述

注:当出现两个及以上分隔符连在一起时,看成一个分隔符即可”

2.10 strerror

代码语言:javascript
复制
char* strerror(int errnum);

strerror的功能是返回错误码,所对应的错误信息。从而找到错误的原因。

代码语言:javascript
复制
//C语言的库函数,在执行失败的时候,都会设置错误码
//0 1 2 3 4 5 6 7 8 
#include<errno.h>
int main()
{
	/*printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	printf("%s\n", strerror(5));*/

	//errno - C语言设置的一个全局的错误码存放的变量
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
	{
		//一系列操作
	}
	
	return 0;
}

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

大小写字母转换函数: int tolower ( int c ); int toupper ( int c );

代码语言:javascript
复制
int main()
{
	//int a = isspace(' ');
	//printf("%d\n", a);

	//int a = isdigit('x');
	//printf("%d\n", a);
	
	printf("%c\n", tolower('@'));//@
	printf("%c\n", tolower('A'));//a
	printf("%c\n", toupper('b'));//B
	
	return 0;
}

2.11 memcpy

代码语言:javascript
复制
void* memcpy(void* destination,const void* source,size_t num);

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

代码语言:javascript
复制
struct {
 char name[40];
 int age;
} person, person_copy;
int main ()
{
 char myname[] = "Pierre de Fermat";
 
 memcpy ( person.name, myname, strlen(myname)+1 );
 person.age = 46;
 
 memcpy ( &person_copy, &person, sizeof(person) );
 printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );//Pierre de Fermat 46
 return 0;
}

memcpy 负责拷贝两块独立空间中的数据,那么重叠内存的拷贝,是怎么做的呢?–>利用memmove

2.12 memmove

代码语言:javascript
复制
void* memmove(void* destination, const void* source, size_t num);

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

代码语言:javascript
复制
int main()
{
	char str[] = "memmove can be very useful......";
	memmove(str + 20, str + 15, 11);
	puts(str);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

2.13 memcmp

代码语言:javascript
复制
int memcmp(const void* ptr1, const void* ptr2, size_t num);

比较从ptr1和ptr2指针开始的num个字节,返回值与strncpy相同,区别:strncpy比较的是指针指向的字符的大小,memcmp比较的是所有类型的大小。

3. 库函数的模拟实现

3.1 模拟实现strlen(三种方式)

方式1:计数器

代码语言:javascript
复制
int my_strlen(const char * str)
{
	int count = 0;
	while(*str)
	{
	   count++;
	   str++;
	}
	return count;
}

方式2:递归

代码语言:javascript
复制
int my_strlen(const char * str)
{
	if(*str == '\0')
	return 0;
	else
	return 1+my_strlen(str+1);
}

方式3:指针减指针(得到的为指针指向类型的个数)

代码语言:javascript
复制
int my_strlen(char *s)
{
   char *p = s;
   while(*p != ‘\0’ )
       p++;
   return p-s;
}

3.2 模拟实现strcpy

代码语言:javascript
复制
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);
	while(*dest++ = *src++)
	{
		;
	}
	return ret;

}

3.3 模拟实现strcat

代码语言:javascript
复制
char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);
	while (*dest)//不能在里面直接++,这样会跳过一个'\0'的位置
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	
	return ret;
}

int main()
{
	char arr1[20] = "abcdef";
	//char arr1[20];
	char arr2[5] = "ABCD";
	char* p = my_strcat(arr1, arr2);
	printf("%s\n", p);
	printf("%s\n", arr1);
	return 0;
}

strcat不能自己给自己追加,会无限循环下去:

在这里插入图片描述
在这里插入图片描述

3.4 模拟实现strstr(下一篇扩展KMP算法)

三指针迭代,一指针记录位置,两个指针向后++查找

代码语言:javascript
复制
char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	const char* s1 = str1;
	const char* s2 = str2;
	const char* p = str1;

	while (*p)
	{
		s1 = p;
		s2 = str2;
		while ( *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)p;
		}
		p++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "def";
	char* ret = my_strstr(arr1, arr2);

	if (ret == NULL)
	{
		printf("子串不存在\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

3.5 模拟实现strcmp

代码语言:javascript
复制
int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
		{
			return 0;//相等
		}
		s1++;
		s2++;
	}
	return (*s1 - *s2);
}
int main()
{
	char arr1[20] = "zhangsan";
	char arr2[] =   "zhangsanfeng";
	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;
}

3.6 模拟实现memcpy

代码语言:javascript
复制
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;//强转的目的是让其+1迈过的长度为char,即一个字节
	}
	return ret;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	
    int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 20);//20个字节大小,即5个整形


	return 0;
}
在这里插入图片描述
在这里插入图片描述

为什么memcpy不能拷贝重叠的内存呢?通过模拟实现我们知道:当我们拷贝到重叠的部分时,那里已经被新的值所覆盖,故再次拷贝将会把新值拷贝过去。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当然,这是模拟实现的memcpy,当我们真正运用memcpy时,会发现出来的结果跟memmove一样:

在这里插入图片描述
在这里插入图片描述

这是因为由于VS本身的功能会将这个错误避免,在不同编译器下,函数处理的结果可能会不一样,而模拟实现的my_memcpy才是memcpy真正的逻辑原理,因此,memmove还是非常必要的

3.7 模拟实现memmove

还以上面arr1数组为例,由于dest>src,故我们可以从右往左拷贝,即从高地址拷贝到低地址:

在这里插入图片描述
在这里插入图片描述

即对模拟实现的my_memcpy稍作拷贝上的修改即可:

代码语言:javascript
复制
void* my_memmove(void* dest, void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;

	if (dest < src)
	{
	  //与my_memcpy拷贝方向一致,即从前到后
		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;
}
void test3()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr1 + 2, arr1, 20);
	//1 2 1 2 3 4 5 8 9 10
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
}
int main()
{
	test3();
	return 0;
}

在这里插入图片描述
在这里插入图片描述

4. 实现一道典型题目

在这里插入图片描述
在这里插入图片描述

这个分支是我后续加上的(2022.7.25),因为我突然想到了一个新的方法来实现这道题目。在此之前,可以用两种方法实现,今天之后,就变成三种了:

    1. 指针数组
    1. 两步翻转
    1. strtok辅助实现
  • 1.指针数组

那先来介绍第一种,这也是我刚接触到这道题第一反应想到的方法: 思路是通过记住每一个单词的首地址,通过数组(数组的每一个元素为指针变量,即指针数组)封装起来,再将其地址逆序按%s输出:

代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
int main()
{
    char* at[500];
    char arr[100];
    gets(arr);
    int len = strlen(arr);
    int i = 0;
    int j = 0;
    at[j++] = &arr[0];
    for(i=0;i<len;i++)
    {
        if(arr[i]==' ')
        {
            at[j++] = &arr[i+1];
            arr[i] = '\0';
        }
    }
    //arr[i] = '\0';
    for(i=j-1;i>=0;i--)
    {
        printf("%s ",at[i]);
    }
    return 0;
}
在这里插入图片描述
在这里插入图片描述

当然结果是对的,但实际上,我们只是记录其位置将其打印而已,字符串本身并没有发生变化。因此,我们参考一下第二种方法:

  • 2.两部翻转

两步翻转,即先翻整体,再翻局部

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
#include<assert.h>
void reverse(char* left, char* right)
{
    assert(left);
    assert(right);

    while (left < right)
    {
        char tmp = *left;
        *left = *right;
        *right = tmp;
        left++;
        right--;
    }
}

int main()
{
    char arr[101] = { 0 };
    //输入
    gets(arr);//I like beijing.
    //逆置
    int len = strlen(arr);
    //1. 逆序整个字符串
    reverse(arr, arr + len - 1);
    //2. 逆序每个单词
    char* start = arr;
    while (*start)
    {
        char* end = start;
        while (*end != ' ' && *end != '\0')
        {
            end++;
        }
        reverse(start, end - 1);
        if (*end != '\0')
            end++;
        start = end;
    }

    //输出
    printf("%s\n", arr);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

即这种方法在原数组的基础之上逆序了单词。

接下来,也就是增加这个分支的目的,即第三个方法,因为上文已经提到过strtok的用处,这里就不具体描述了。

    1. strtok 辅助实现
代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
	char str[] = "I like beijing.";
	char* pch;
	char flag = *(str + strlen(str) - 1);
	pch = strtok(str, " ");
	char** arr = (char**)malloc(sizeof(char*) * 5);
	
	int j = 0;
	while (pch != NULL)
	{
		arr[j++] = pch;
		printf("%s ", pch);
		pch = strtok(NULL, " ");
	}
	printf("\n");
	for (int i = j-1; i>=0; i--)
	{
		printf("%s ", arr[i]);
	}
	free(arr);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

这种方式其实也是类似于第一种的思想,存储位置,逆序打印,但很明显,这种方法在strtok的辅助之下使其更为简洁和灵活,值得关注的是,这里的malloc大小为20个字节,如果单词数比较多的话,可以多开辟空间,是没有影响的,因为j记录了实际有效单词的数量。 当然即便面对单词之间有多个空格,strtok也会自动将其忽略,这也是strtok的强大之处。

5.总结:

通过对以上函数的了解,对于字符数组的操作以及内存类的函数会变得得心应手,要用其功能必先了解其原理。那么,这篇文章就到这里,码字不易,你们的支持将是我前进的不竭动力!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字符串+内存函数的介绍
  • 1.本章重点
  • 2.函数介绍
    • 2.1 strlen
      • 2.2 strcpy
        • 2.3 strcat
          • 2.4 strcmp
            • 2.5 strncpy
              • 2.6 strncat
                • 2.7 strncmp
                  • 2.8 strstr
                    • 2.9 strtok
                      • 2.10 strerror
                        • 2.11 memcpy
                          • 2.12 memmove
                            • 2.13 memcmp
                            • 3. 库函数的模拟实现
                              • 3.1 模拟实现strlen(三种方式)
                                • 3.2 模拟实现strcpy
                                  • 3.3 模拟实现strcat
                                    • 3.4 模拟实现strstr(下一篇扩展KMP算法)
                                      • 3.5 模拟实现strcmp
                                        • 3.6 模拟实现memcpy
                                          • 3.7 模拟实现memmove
                                          • 4. 实现一道典型题目
                                          • 5.总结:
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档