C语言中字符串函数以及内存函数的使用和注意事项

目录

0. 前言

1、求字符串长度函数

1.1、strlen

模拟实现

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

2.1 strcpy

 模拟实现

 2.2strcat

模拟实现

 2.3strcmp

模拟实现

3.长度受限制的字符串函数

3.1strncpy

3.2strncat

3.3strncmp

4、字符串查找函数

4.1strstr

模拟实现

3.2strtok 

 实现自动分割字符串

4、错误信息报告函数

4.1、strerror

 4.2、perror

5.内存操作函数

5.1、memcpy

模拟实现

5.2、memmove

模拟实现

 5.3、memset

 5.4、memcmp


0. 前言

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

1、求字符串长度函数

1.1、strlen

  • 1.strlen用于求字符串长度。
  • 2.包含头文件<string.h>。
  • 3.字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
  • 4.参数指向的字符串必须要以 '\0' 结束。

注意:

  • 函数的返回值为size_t是无符号的易错
  • 这里输出的应该是大于,为什么呢,就是这里的strlen函数的返回的是无符号类型的,所以-3会被当作无符号数来看待,

  • 由于strlen返回的是size_t类型,所以在输出时应该使用对应的格式说明符,如%zu

  • 因为strlen返回的是 '\0' 前面的字符个数,如果字符串中间本身就一个'\0',那么返回的值就会返回字符串中的'\0'之前的字符个数。

    例如:"abc\0def" 这个字符串,使用strlen函数会返回3

 易错提醒


请问ret的值是多少?
int ret = strlen("abc") - strlen("abcdef");

答案是3,因为函数的返回值为size_t,是无符号的整型。


strlen函数是C语言中的一个库函数,用于计算字符串的长度(不包括字符串末尾的空字符\0)。它位于<string.h>头文件中。

strlen函数的原型如下:

size_t strlen(const char *str);
  • const char *str:指向要计算长度的字符串的指针。

strlen函数会从指定的字符串地址开始遍历,直到遇到字符串结束标记\0为止,统计期间遇到的字符数量,然后返回计数结果

下面是一个示例代码,展示了如何使用strlen函数来计算字符串的长度:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World!";
    size_t len = strlen(str);

    printf("The length of the string is: %zu\n", len);

    return 0;
}

输出结果为:

The length of the string is: 13

在上面的例子中,strlen函数被用来计算字符串str的长度,并将结果存储在len变量中,然后通过printf函数进行输出。



模拟实现

int my_strlen(char* str)
{
	int count = 0;
	while (*arr != '\0')
	{
		count++;
		arr++;
	}
	return count;
}

 方法二、

size_t my_strlen(const char* str) {
    const char* end = str;
    while (*end) {
        ++end;
    }
    return end - str;
}

指针-指针的方式实现先把初始地址给end,然后end向后走直到遇到\0,最后返回初始地址和结束地址的差值就是中间的字符个数。

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

2.1 strcpy

`strcpy`函数是C语言中用于字符串复制的函数,其原型为:

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

`strcpy`函数将源字符串 `source` 复制到目标字符串 `destination` 中,包括字符串结束符 `'\0'`。复制的过程会覆盖原来 `destination` 中的内容。

`strcpy`函数的工作原理是从源字符串 `source` 的第一个字符开始,逐个将字符复制到目标字符串 `destination` 中,直到遇到字符串结束符 `'\0'` 为止。复制完成后,目标字符串 `destination` 会成为与源字符串 `source` 相同的副本。

需要注意的是,为了避免内存溢出,目标字符串 `destination` 的空间应该足够大,至少能够容纳源字符串 `source` 和字符串结束符 `'\0'`。

`strcpy`函数的返回值是目标字符串 `destination` 的指针,即函数执行完后,返回指向目标字符串的指针。

下面是一个简单的示例代码,演示了 strcpy 函数的使用:

#include <stdio.h>
#include <string.h>

int main() {
    char source[] = "Hello, World!";
    char destination[20];

    strcpy(destination, source);

    printf("Copied string: %s\n", destination);

    return 0;
}

上述代码会输出:Copied string: Hello, World!

注意事项:

  • strcpy用于拷贝字符串,将字符串2拷贝到字符串1当中。
  • 包含头文件<string.h>。
  • 源字符串必须以 '\0' 结束。
  • 会将源字符串中的 '\0' 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。

验证确实是拷贝了\0
源字符串必须以\0结尾
目标空间必须可变化
这里就没法完成复制了,因为arr1是一个常量字符串没有办法被修改

 模拟实现


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

 2.2strcat

cat 是 concatenate(连接)的缩写。

strcat函数是C语言中用于字符串拼接的函数,其原型为:

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

效果strcat函数将源字符串 source 追加到目标字符串 destination 的末尾,拼接的结果会改变目标字符串 destination

工作原理:strcat函数的工作原理是首先定位目标字符串 destination 的结束符 '\0' 的位置,然后从源字符串 source 的第一个字符开始,逐个将字符复制到目标字符串的结束符之前,直到遇到源字符串的结束符 '\0' 为止。完成拼接后,目标字符串 destination 会包含原有内容和源字符串 source 的内容。

需要注意的是,为了避免内存溢出,目标字符串 destination 的空间应该足够大,以容纳源字符串 source 的内容及拼接后的结果。

 源字符串必须以 '\0' 结束(保证找得到目标空间的末尾),在拷贝时会把源字符串的 '\0 '也拷贝过去

不能字符串自己追加自己,因为当自己追加自己的时候,追加的过程中会将目标字符串的 '\0' 覆盖掉,(因为自己追加自己两个指针指向的是同一块空间)而有因为此时目标字符串就是源字符串,就会导致源字符没有 '\0' ,将会一直拼接下去导致死循环。

        虽然有些环境中该函数可以完成自己拼接自己,但是C语言的标准中并未规定strcat可以自己拼接自己,所以这个函数最好不要使用在自己拼接自己的情况下。如果真有自己追加自己的场景,建议使用strncat函数

返回值:strcat函数的返回值是目标字符串 destination 的指针,即函数执行完后,返回指向目标字符串的指针。

模拟实现

1.先找到目标空间的\0

  

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);

	char* ret = dest;
	//1. 找目标空间中的\0
	while (*dest)
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	my_strcat(arr1, arr2);

	printf("%s\n", arr1);
	return 0;
}

 问题:字符能不能自己给自己追加?

不能

  

源头和目标都是一块空间了,当src把h替代了\0,之后src就没有办法遇到\0了,因为追加第一个的时候就修改掉了\0

 2.3strcmp

strcmp 函数用于比较两个字符串,它返回一个整数值,表示两个字符串的大小关系。

函数原型如下:

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

参数 str1 和 str2 是需要比较的两个字符串。

  • 如果 str1 和 str2 相等,strcmp 返回值为 0。
  • 如果 str1 大于 str2strcmp 返回值大于 0。
  • 如果 str1 小于 str2strcmp 返回值小于 0。

下面是一个示例代码,演示了 strcmp 函数的用法:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "apple";
    char str2[] = "banana";

    int result = strcmp(str1, str2);

    if(result == 0) {
        printf("str1 和 str2 相等\n");
    } else if(result > 0) {
        printf("str1 大于 str2\n");
    } else {
        printf("str1 小于 str2\n");
    }

    return 0;
}

输出结果为:

str1 小于 str2

上述示例中,strcmp 函数比较了两个字符串 "apple" 和 "banana",并根据比较结果输出相应的信息。由于字母 "a" 的 ASCII 值小于字母 "b",因此 str1 小于 str2

易错:strcmp不是比较字符串长度的,而是比较对应位置上字符的大小(ASCII)

模拟实现

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);
}
//VS
//> 1
//= 0
//< -1
//

int main()
{
	int ret = my_strcmp("bbq", "bcq");
	if (ret>0)
		printf(">\n");

	printf("%d\n", ret);
	return 0;
}

 先排除相等的部分,剩下不相等的部分才进行比较,第一个while循环中*str1==‘\0’判读是不是读完了

3.长度受限制的字符串函数

3.1strncpy

 函数介绍:

strncpy 函数是一个用于复制字符串的函数。它的原型如下:

char *strncpy(char *dest, const char *src, size_t n);

strncpy 函数将从源字符串 src 复制最多 n 个字符到目标字符串 dest 中。

如果 src 的长度小于 n,则复制完 src 中的所有字符后,将剩余部分用空字符 \0 填充直到n

如果 src 的长度大于等于 n,则仅复制前 n 个字符,不会添加额外的空字符。

需要注意的是,strncpy 函数没有在目标字符串 dest 的末尾自动添加终止符 \0 的能力。因此,在使用 strncpy 复制字符串后,需要手动在 dest 的最后一个元素添加 \0,以确保目标字符串正确终止。

 相比较于strcpy函数多了一个参数 n。

下面是一个示例代码,展示了如何使用 strncpy 函数:

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello, World!";
    char dest[20];

    strncpy(dest, src, sizeof(dest));
    dest[sizeof(dest) - 1] = '\0';

    printf("Source: %s\n", src);
    printf("Destination: %s\n", dest);

    return 0;
}

输出结果:

Source: Hello, World!
Destination: Hello, World!

在这个示例中,我们将源字符串 "Hello, World!" 复制到了目标字符串 dest 中,并保证了目标字符串的正确终止。由于 dest 的大小为 20,所以在复制后,目标字符串中的剩余部分将被空字符 \0 填充。

3.2strncat

  • 区别也仅与strcat差一个参数,记录要操作的个数。
  • 使用strncat追加,当结束追加时,就算没到\0,也会在末尾追加一个\0。
  • 如果源字符串的长度小于num,则追加完源字符串之后,会自动停止追加。注意此处与strncpy的区别。
  • 包含头文件<string.h>。

下面是示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char s1[20] = "Hello ";
    char s2[] = "world!";
    
    strcat(s1, s2);
    printf("Result of strcat: %s\n", s1);
    
    char s3[20] = "Hello ";
    char s4[] = "world!";

    strncat(s3, s4, 5);
    printf("Result of strncat: %s\n", s3);
    
    return 0;
}

输出结果为:

Result of strcat: Hello world!
Result of strncat: Hello world

可以看到,在使用strncat函数时,只拼接了源字符串的前5个字符。

3.3strncmp

  • 区别也仅与strcmp差一个参数,记录要操作的个数。
  • 包含头文件<string.h>。

下面是一个使用strncmp函数比较两个字符串的示例代码:

#include <stdio.h>
#include <string.h>

int main()
{
    char str1[] = "Hello";
    char str2[] = "World";
    int result = strncmp(str1, str2, 3); // 只比较前3个字符

    if (result == 0)
    {
        printf("str1和str2的前3个字符完全相同\n");
    }
    else if (result < 0)
    {
        printf("str1小于str2\n");
    }
    else
    {
        printf("str1大于str2\n");
    }

    return 0;
}

以上代码输出结果为"str1小于str2",因为在比较前3个字符时,'H'的ASCII值为72,'W'的ASCII值为87,72小于87。

4、字符串查找函数

4.1strstr

strstr函数是一个C语言字符串处理函数,用于在一个字符串中查找另一个字符串的第一次出现位置。

函数原型如下:

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

函数接受两个参数:str1和str2。str1是源字符串,在该字符串中进行查找操作。str2是要查找的目标字符串。

函数的返回值是一个指向第一次在str1中出现str2的位置的指针。如果未找到str2,函数返回NULL。

strstr函数的工作原理是基于字符串匹配算法,通常使用的是KMP算法或Boyer-Moore算法等。它会从str1的第一个字符开始比较,如果找到了一个匹配的字符,就会继续比较下一个字符。如果完全匹配,就返回匹配位置的指针。如果未能找到匹配,就继续在剩余的str2中继续查找。

int main()
{
	char arr1[] = "abcdefghidef";   //def出现了两次
	char arr2[] = "def";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
		printf("找不到\n");
	else
		printf("%s\n", ret);
	return 0;
}

 结果是defghidef

模拟实现

char* my_strstr(char *str1, char* str2)
{
	char* cp = str1;
	char* s1 = cp;
	char* s2 = str2;

	if (*str2 == '\0')
		return str1;

	while (*cp)
	{
		//开始匹配
		s1 = cp;
		s2 = str2;
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return cp;

		cp++;
	}

	return NULL;
}


int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbc";

	char* ret = my_strstr(arr1, arr2);
	if (ret != NULL)
		printf("%s\n", ret);
	else
		printf("找不到\n");

	return 0;
}

Cp最开始指向起始位置,cp赋给s1

*cp不等于\0,因为等于\0说明检查完了,就返回NULL

S1和s2来遍历字符串

没有匹配完全的话,跳出第二个while

循环,然后cp++,改变开始匹配的位置,然后把cp赋给s1,重新赋值给s1是因为要找到开始匹配的正确位置,每次cp都像后面移动一位

应该返回cp因为只有*s1==*s2才会进入循环,这个时候cp里面是第一次出现位置的指针。

只有*s2为\0才是才是在str1中匹配到了str2         

最前面是特殊情况是str2是空字符串       

                            

3.2strtok 

strtok函数是C语言中的一个字符串分割函数,其原型为:

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

该函数的作用是将字符串str按照指定的分割符delim进行分割,并返回分割后的第一个子字符串。同时,该函数会静态地记录当前的分割位置,便于下一次调用时继续分割剩余的字符串。

函数的使用步骤如下:

  1. 在第一次调用时,需要将待分割的字符串str传入,之后的调用中将传入NULL。strtok函数会将其修改,将分割出的子字符串以'\0'结尾并返回该子字符串的指针。
  2. 在第一次调用时,需要将分割符delim传入。之后的调用中,delim可以传入NULL。如果传入NULL,则将沿用上一次调用时的分割符。
  3. 使用返回的子字符串指针进行处理操作,之后再次调用strtok函数可以获取下一个分割出的子字符串指针。直到返回NULL,表示没有更多的子字符串可供分割。
  4. delimiters参数是个字符串,定义了用作分隔符的字符集合。
    第一个参数指定一个字符串,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。
    strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
    strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
    strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
    如果字符串中不存在更多的标记,则返回 NULL 指针

下面是一个简单的示例代码,展示了如何使用strtok函数分割一个字符串:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello,World!How,are,you?";
    char *token;

    // 第一次调用strtok函数
    token = strtok(str, ",!");
    printf("%s\n", token);  // 输出"Hello"

    // 之后的调用
    while ((token = strtok(NULL, ",!")) != NULL) {
        printf("%s\n", token);
    }

    return 0;
}

该代码首先将字符串"Hello,World!How,are,you?"传给strtok函数进行第一次调用。由于传入的分割符为",!",所以它会将字符串分割成"Hello"、"World"、"How"、"are"和"you?"五个子字符串。然后,通过循环调用strtok函数,我们可以依次输出这五个子字符串。

第一次调用把,改为\0然后返回H的地址,第二次函数已经记住了第一次标记的位置,然后向后访问直到找到下一个标记然后改为\0,再传回来W的地址。

 实现自动分割字符串

	char* ret = NULL;

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

初始部分就是第一次调用strtok函数,判断部分是判断

调整部分是传空指针直到切割完了。

循环中的判断部分 ret != NULL 是用来判断 strtok 函数返回的切割结果是否为 NULL,如果是 NULL,则意味着没有更多的切割结果了,循环就会结束。换句话说,当 strtok 函数切割完所有的子字符串之后,它会返回 NULL,这时循环就会终止。

4、错误信息报告函数

4.1、strerror

strerror 函数是一个 C 标准库中的函数,它可以根据给定的错误码返回对应的错误消息字符串。

strerror 函数的原型如下:

#include <string.h>

char *strerror(int errnum);

其中,errnum 是一个整数类型的参数,表示错误码。函数会根据 errnum 找到对应的错误消息字符串,并返回该字符串的指针。

使用 strerror 函数,我们可以根据系统函数返回的错误码获取相应的错误信息,从而更好地了解问题所在,方便进行调试和错误处理。

  • strerror函数是将错误码翻译成错误信息,返回错误信息的字符串起始地址。
  • 包含头文件<string.h>。
  • C语言中使用库函数的时候,如果发生错误,就会将错误码放在errno的变量中,errno是一个 全局变量,可以直接使用。

int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d: %s\n", i, strerror(i));//
	}
	return 0;
}

 每一个错误码都对应一个错误信息。

 

【使用方式】

以打开文件为例子,fopen以读的形式打开文件,当文件存在时打开成功,文件不存在时打开失败,并返回空指针。可以利用这个来设置一个打开失败时的错误信息告知。

 【使用方法】

int main()
{
	FILE* pf = fopen("add.txt", "r");  //当前文件路径中并没有add.txt文件,打开失败
	if (pf == NULL)
	{
		printf("打开文件失败,原因是:%s\n", strerror(errno));
		return 1;
	}
	else
	{
		printf("打开文件成功\n");
	}
	return 0;
}

fopen 是 C 语言标准库中的一个函数,用于打开一个文件。函数声明如下:

FILE *fopen(const char *filename, const char *mode);

fopen 函数接受两个参数,第一个参数 filename 是一个字符串,表示文件的路径或名称;第二个参数 mode 是一个字符串,表示文件的打开模式,可以是以下几种之一:

  • "r":以只读方式打开文件。
  • "w":以写入方式创建文件,如果文件已存在则清空文件内容。
  • "a":以写入方式打开文件,如果文件不存在则创建文件,新增的内容会被追加到文件末尾。
  • "rb":以二进制只读方式打开文件。
  • "wb":以二进制写入方式创建文件,如果文件已存在则清空文件内容。
  • "ab":以二进制写入方式打开文件,如果文件不存在则创建文件,新增的内容会被追加到文件末尾。

fopen 函数返回一个 FILE* 类型的指针,如果打开文件成功,则返回指向该文件的指针;如果打开文件失败,则返回 NULL

 4.2、perror

perror 是 C 语言标准库中的一个函数,用于打印与当前错误代码相对应的错误信息到标准错误文件 stderr 中。函数声明如下:

void perror(const char *s);

perror 函数接受一个参数 s,表示要输出的错误信息前缀。它会根据全局变量 errno 的值,在标准错误文件上输出一个相应的错误信息,形如 s: 错误信息

perror也是用于翻译错误信息 ,但与strerror不同的是,perror会直接打印错误码所对应的错误信息。而perror中传递的字符串参数就是自定义显示信息的部分,打印的结果就是 自定义显示信息:错误信息


包含头文件<stdlib.h>


可以简单理解为:perror = printf + strerror 即翻译又打印

5.内存操作函数

为了满足各种数据都可以使用的情况而不只是字符串,我们要从内存入手。下面我们来学习一下内存操作函数。

5.1、memcpy

memcpy() 函数是 C 语言中的一个标准库函数,它用于将内存块的内容从源地址复制到目标地址。它的函数原型如下:

void *memcpy(void *dest, const void *src, size_t n);
  • dest:目标地址,即复制后的数据将存储的位置。
  • src:源地址,即要被复制的数据的起始位置。
  • n:要复制的字节数。
  • 拷贝结束之后返回目标空间的起始地址

memcpy() 函数会将 src 地址开始的 n 个字节的内容复制到 dest 地址开始的内存区域。需要注意的是,复制过程中不会考虑字符的含义,只是简单地复制二进制数据

函数memcpy从source的位置开始向后拷贝num个字节的数据到destination的内存位置。


包含头文件<string.h>


这个函数在遇到 '\0' 的时候并不会停下来。


如果source和destination有任何的重叠,复制的结果都是未定义的。


因为C语言标准中并未规定memcpy能适用于重叠内存的拷贝,因此不重叠内存的拷贝才使用memcpy,而重叠内存的拷贝使用接下来讲解的memmove函数。

下面是一个使用 memcpy() 函数的示例:

#include <stdio.h>
#include <string.h>

int main() {
    const char src[] = "Hello, World!";
    char dest[100];

    memcpy(dest, src, strlen(src) + 1);
    printf("Copied string: %s\n", dest);

    return 0;
}

在这个示例中,我们首先定义了一个源字符串 src,然后创建了一个足够大的目标字符串 dest 来存储复制后的内容。然后,memcpy() 函数将 src 字符串的内容复制到 dest 字符串,并通过 printf 函数打印出复制结果。

模拟实现

函数拷贝结束后,返回目标空间的起始地址
void* my_memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(src && dest);

	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00

	int arr2[20] = { 0 };
	my_memcpy(arr2, arr1, 21);
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		printf("%d ", arr2[i]);
	}
	return 0;
}

写成(char*)dest++是不能实现指针先后走一个bit的,因为这里强制转换是临时的,++的时候已经没用了。

如果拷贝有重叠的空间会怎么样呢

比如传参为arr+2和arr1

void* my_memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(src && dest);

	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

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

结果不是我们想要的那样1 21 2 3 4 5 8 9 10

因为空间上有重叠的地方

把34拷贝为12之后想要把56拷贝为34就没有办法实现了因为34已经被改成12了。

memcpy函数是用来处理不重叠的内存的拷贝的,处理有重叠的内存的拷贝的使用memmove函数

5.2、memmove

原来出现的错误的原因是因为拷贝给后面的空间的时候前

想办法不被覆盖。到着来拷贝可以解决问题先把5拷贝到7的位置,4拷贝到6的位置,

但是不能一劳永逸。比如如果我把src和des调换就出错了

dest在src前面就要倒着拷贝,dest在src后面就要

一次性解决:

如果dest落在src前面那就是从前向后拷贝就是说先把src的第一个位置拷贝到dest依次往后

如果dest落在src到末尾之间的位置,那就是从后向前面拷贝

指针可以比较大小

模拟实现

void* my_memmove(void* dest, const 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--)//20
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

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

后向前拷贝要先找到src+num的位置,这个是第一个要拷贝的。

 5.3、memset

memset()函数用于将一块内存区域的每个字节都设置为特定的值。其函数原型如下:

void *memset(void *ptr, int value, size_t num);
  • ptr:指向要设置值的内存区域的指针。
  • value:要设置的值,它的类型是int,但会被转换成unsigned char
  • num:要被设置的字节数。
  • 将ptr所指向空间的前num个字节设置为指定值value。
  • 包含头文件<string.h>

memset()函数在初始化内存块、清零内存块、以及将内存块中的特定字节设置为某个值时非常有用。例如:

#include <stdio.h>
#include <string.h>

int main() {
    char str[50];
    
    strcpy(str, "Hello");
    printf("Before memset: %s\n", str);
    
    memset(str, '$', 3);
    printf("After memset: %s\n", str);
    
    return 0;
}

输出结果为:

Before memset: Hello
After memset: $$$lo

在上述示例中,memset()函数将字符串str的前3个字节设置为'$',修改了字符串的一部分内容。

 5.4、memcmp

memcmp()函数用于比较两个内存区域的内容是否相同。其函数原型如下:

int memcmp(const void *ptr1, const void *ptr2, size_t num);
  • ptr1:指向要比较的第一个内存区域的指针。
  • ptr2:指向要比较的第二个内存区域的指针。
  • num:要比较的字节数。

memcmp()函数将比较两个内存区域中的字节内容,并返回一个整数结果,表示比较结果的大小关系:

  • 如果ptr1ptr2指向的内存区域完全相同,则返回值为0。
  • 如果ptr1指向的内存区域小于ptr2指向的内存区域,则返回值小于0。
  • 如果ptr1指向的内存区域大于ptr2指向的内存区域,则返回值大于0。

下面是一个使用memcmp()函数的示例:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello";
    char str2[] = "World";
    
    int result = memcmp(str1, str2, 5);
    
    if (result == 0) {
        printf("str1 and str2 are equal.\n");
    } else if (result < 0) {
        printf("str1 is less than str2.\n");
    } else {
        printf("str1 is greater than str2.\n");
    }
    
    return 0;
}

输出结果为:

str1 is less than str2.

在上述示例中,memcmp()函数比较了两个字符串str1str2的前5个字节内容,发现str1小于str2,因此打印出相应的结果。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/568181.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C/C++笔试练习】线程作用、磁盘的固定块、多进程、进行调度、cache、内存抖动、非抢占CPU调度、inode描述、文件操作、进制、最难的问题、因子个数

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;线程作用&#xff08;2&#xff09;磁盘的固定块&#xff08;3&#xff09;多进程&#xff08;4&#xff09;进行调度&#xff08;5&#xff09;cache&#xff08;6&#xff09;内存抖动&#xff08;7&#xff09;非抢占…

一台服务器同时启动两个版本jdk

之前Java项目都是1.8的jdk&#xff0c;在服务器部署正常使用&#xff0c;服务器配置环境变量jdk1.8版本。最近一次我用了jdk17版本&#xff0c;部署服务器后&#xff0c;遇见了jdk版本不一致报错 报错内容&#xff1a; 52指向jdk1.8,61指向jdk17&#xff0c;大概就是jdk版本不…

第十六届“华中杯”B 题使用行车轨迹估计交通信号灯周期问题

某电子地图服务商希望获取城市路网中所有交通信号灯的红绿周期,以便为司机提供更好的导航服务。由于许多信号灯未接入网络,无法直接从交通管理部门获取所有信号灯的数据,也不可能在所有路口安排人工读取信号灯周期信息。所以,该公司计划使用大量客户的行车轨迹数据估计交通…

条件编译 #和##运算符

目录 1. #运算符2. ##运算符3. 条件编译4. 题目分享总结 正文开始 前言: 本章为C语言语法完结撒花, 下文将进行C语言中#和##操作符以及条件编译的讲解, 来进一步让我们了解C语言. 作者主页: 酷酷学!!! 1. #运算符 #运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带…

牛客社区所有的表和SQL语句

文章目录 1 帖子表 discuss_post1.1 字段描述1.2 相关功能描述1.2.1 分页查询帖子1.2.2 查询帖子总数量1.2.3 插入一条帖子记录1.2.4 根据帖子ID查询某条帖子1.2.5 更新帖子评论数量1.2.6 更新帖子类型1.2.6 更新帖子状态1.2.7 更新帖子分数 2 用户表 user2.1 字段描述2.2 相关…

cesium primitive 移动 缩放 旋转 矩阵

旋转参考&#xff1a;cesium 指定点旋转rectangle Primitive方式 矩阵篇-CSDN博客 平移参考&#xff1a;cesium 调整3dtiles的位置 世界坐标下 相对坐标下 平移矩阵-CSDN博客 一、primitive方式添加polygon let polygonInstance new Cesium.GeometryInstance({geometry: Ce…

陆金所控股一季报到底是利好还是利空?

3月底&#xff0c;陆金所控股&#xff08;LU.N;06623.HK&#xff09;因其特别分红方案受到市场高度关注。但在4月23日发布的2024年一季度财报中&#xff0c;陆金所控股营收同比下降30.9%&#xff0c;净亏损8.3亿元。 两者对比&#xff0c;外界不由得对公司的经营状况产生疑惑。…

ROS 话题订阅模型之自定义消息类型 C++实现

ROS 话题订阅模型之自定义消息类型 1.自定义消息类型好处 ROS提供了许多标准的消息类型&#xff0c;如 std_msgs/String、sensor_msgs/Image 等&#xff0c;涵盖了很多常见的数据类型和传感器数据。但是&#xff0c;在实际的开发中&#xff0c;我们经常会遇到需要传输的数据类…

【Image captioning】论文阅读九—Self-Distillation for Few-Shot Image Captioning_2022

摘要 大规模图像字幕数据集的开发成本高昂,而大量未配对的图像和文本语料库可能有助于减少手动注释的工作。在本文中,我们研究了只需要少量带注释的图像标题对的少样本图像标题问题。我们提出了一种基于集成的自蒸馏方法,允许使用不成对的图像和字幕来训练图像字幕模型。该…

springcloud alibaba 整合seata的TCC

一、seata服务端搭建同上篇。 Seata的AT模式客户端两阶段提交流程源码分析 二、seata客户端的结构 1.示例DEMO工程 下单&#xff0c;扣余额&#xff0c; 减库存。 2. MAVEN配置。 父工程&#xff1a;由于spring-cloud-starter-alibaba-seata依赖的seata-spring-boot-starter…

C语言(static和extern)

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

Python写个二维码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、进入官网下载二、下载一下三.输入代码 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、进入官网下载 官网 pip insta…

FR-E840-0120-4-60 三菱变频器5.5KW型

FR-E840-0120-4-60 三菱变频器替换FR-E740-5.5K FR-E840用户手册,FR-E840-0120-4-60价格,FR-E840-5.5K价格,FR-E840-0120-4-60外部连接图,FR-E740-5.5K替换产品。 FR-E740-5.5K-CHT逐渐开始停产&#xff0c;现在用新型号FR-E840-0120-4-60替换。 FR-E840-0120-4-60参数说明&…

2024年前端技术发展趋势

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

anaconda安装python 3.8环境

打开anaconda命令行窗口 在命令行窗口中&#xff0c;输入命令&#xff1a;conda create -n py38 python3.8 执行命令后&#xff0c;显示conda版本、安装路径和安装的包 然后提醒是否安装&#xff0c;输入y 等待安装完成。然后进入python3.8&#xff0c;执行命令&#xff1a;con…

全网最全的平行坐标图(parallel coordinates plot)的绘制攻略

早上起来拥抱太阳&#xff0c;写小论文&#xff0c;看到人家的图怎么那么好看&#xff01;&#xff01;&#xff1f;&#xff1f; 这不得赶紧抄下来&#xff0c;我也发一个顶刊&#xff1f;于是开始思考如何解决绘制这个问题&#xff0c;目前现有的大部分解决方案都是直接调库…

【计算机毕业设计】理发店管理系统产品功能说明——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

Python Selenium无法打开Chrome浏览器处理自定义浏览器路径

问题 在使用Python Selenium控制Chrome浏览器操作的过程中&#xff0c;由于安装的Chrome浏览器的版本找不到对应版本的驱动chromedriver.exe文件&#xff0c;下载了小几个版本号的驱动软件。发现运行下面的代码是无法正常使用的&#xff1a; from selenium import webdriver …

HWOD:合并整型数组

一、知识点 合并整型数组目前有两种方法 合并数组并不一定需要真正的合并 1、下意识的方法 对两个整型数组分别排序&#xff0c;然后合并 2、不排序的方法 遍历两个数组&#xff0c;找出最小值&#xff0c;输出最小值。将两个数组中与最小值相等的位置置为超大值 重复以…

二叉树oj题(2)

1.二叉树的最近公共祖先 解题思路&#xff1a;方法一&#xff1a; 1.先判断p或者q 是不是 root当中的一个 2.左子树当中递归査找p或者q 3.右子树当中递归查找p或者q 如何查找: root 的 left 和 right 都不为空 ->root root的 left 为空 right 不为空->right这一侧找…