c语言的字符串操作函数和内存操作函数

c语言的字符串操作函数和内存操作函数

  • 字符串有关的函数
    • strlen
      • 使用
      • 模拟实现
    • strcpy
      • 使用
      • 模拟实现
    • strncpy
      • 使用
      • 模拟实现
    • strcat
      • 使用
      • 模拟实现
    • strncat
      • 使用
      • 模拟实现
    • strcmp
      • 使用
      • 模拟实现
    • strncmp
      • 使用
      • 模拟实现
    • strstr
      • 使用
      • 模拟实现
      • 类似的函数strchr和strrchr
    • strtok
      • 使用
    • strerror
      • 使用
  • 内存操作函数
    • memcpy
      • 使用
      • 模拟实现
    • memmove
      • 使用
      • 模拟实现
    • memset
      • 使用

字符串有关的函数

在之前已经介绍过字符串操作函数是什么,这里弄出一个篇章重点介绍字符串有关的函数,以及部分函数的模拟实现。

这里的函数都能在cplusplus.com - The C++ Resources Network找到。

这些字符串有的和scanf一样,vs系列会认为它们不安全,让用户使用它准备的加_s的版本。解决方法是在代码开头加上这句:#define _CRT_SECURE_NO_WARNINGS 1

如果是c++,更推荐使用string这个被封装好的字符串类。

strlen

调用strlen需展开头文件string.hcstring(c++)。

使用

原型:

size_t strlen ( const char * str );

返回类型size_t是无符号整型,在vs2019是4个字节(unsigned int),在Dev-cpp5.11是8个字节(unsigned long long)。

参数str指向的字符串必须要以 \0 结束。

str可接受的实参有:

  1. 字符串常量。
  2. char数组(成员中最好有\0)。
  3. 存有连续字符串的指针。

strlen函数返回的是在字符串中 \0前面出现的字符个数(不包含 \0 )。

案例:

#include 
int main() {
	const char* str1 = "abcdef";
	char str2[] = "bbb";
	printf("%u %u", strlen(str1), strlen(str2));
	return 0;
}

模拟实现

#include
#include

size_t mystrlen1(const char* str)//循环计数
{
	assert(str);//内部表达式布尔值判断为假时终止程序
	size_t count = 0;
	while (*str != '\0')
	{
		++count;
		++str;
	}
	return count;
}

size_t mystrlen2(const char* str)//递归实现
{
	assert(str);
	if (*str != '\0')
		return 1 + mystrlen2(str + 1);
	return 0;
}

size_t mystrlen3(const char* str)//指针之差
{
	char* First = (char*)str;
	while (*str != '\0')
		++str;
	return str - First;
}

strcpy

调用strcpy需展开头文件string.hcstring(c++)。

使用

原型:

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

strcpy会将源字符串source包括\0,拷贝到目标空间destination

要求:

  • 源字符串source必须以 ‘\0’ 结束。
  • destination能存储的字符数要大于等于source的有效字符串长度。
  • 目标空间destination必须可变。

使用示例:

#include
#include

int main() {
	char a[20]={'\0'},b[15]="momoi";
	strcpy(a,b);
	printf("%s",a);
	return 0;
}

模拟实现

#include
#include
#include
#include

char* mystrcpy(char* destination, const char* source)
{
	char* start = destination;
	assert(destination && source);
	while (*destination++ = *source++);
	return start;
}

int no;
void f1()
{
	/*char* strcpy(char * destination, const char * source );//函数原型*/
	/*sizeof(destination)>=sizeof(source)*/
	char a[] = "hachiman\0yukino";
	char b[] = "yui";
	char* c = "iroha";
	char d[] = { 'n','a','o','\0' };
	strcpy(a, b);
	//strcpy(c, b);//c不可变,而destination要求可变
	//strcpy(b,a);//目标空间b空间不足,无法支撑函数运行
	int i = 0;
	for (i = 0; i < 15; i++)
	{
		if (a[i] == '\0')
			printf("\\0");
		else
			printf("%c", a[i]);
	}
	printf("\n");
	//char* p = strcpy(b, d);//strcpy返回b的地址
	char* p = mystrcpy(b, d);//strcpy返回b的地址
	printf("%s", p);

}


int main()
{
	f1();
	return 0;
}

strncpy

调用strncpy需展开头文件string.hcstring(c++)。

使用

原型:

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

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

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

使用示例:

#include
#include

int main() {
	char a[20] = { "abcoria"}, b[15] = "momoi";
	strncpy(a, b,3);
	printf("%s", a);
	return 0;
}

输出:

momoria

模拟实现

#include
#include
#include
//using namespace std;

char* mystrncpy(char* destination, const char* source, size_t num) {
	char* start = destination;
	assert(destination && source);
	while (*destination && *source && num) {
		--num;
		*destination++=*source++;
	}
	return start;
}

char* mystrcpy(char* destination, const char* source)
{
	char* start = destination;
	assert(destination && source);
	while (*destination++ = *source++);
	return start;
}

char a[51] = "Donald ", b[51] = "Trump ", c[51] = { '\0' };

int main() {
	strcpy(c, a);//备份

	printf("%s\n", a);
	strncpy(a, b, 5);//演示效果
	printf("%s\n", a);

	mystrcpy(a, c);

	printf("%s\n", a);
	mystrncpy(a, b, 5);
	printf("%s\n", a);

	strcpy(a, c);

	return 0;
}

strcat

调用strcat需展开头文件string.hcstring(c++)。

使用

原型:

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

strcat能将source字符串加在destination的末尾。

source的第1个字符会覆盖destination\0,新的\0会在source拷贝完成之后加上。

因此使用这个函数:

  • 源字符串source必须以 \0 结束。

  • 目标空间destination必须有足够的大,能容纳下源字符串source的内容。

  • 目标空间必须可修改。

使用案例:

#include
#include

int main() {
	char a[20]={"midori"},b[15]="momoi";
	strcat(a,b);
	printf("%s",b);
	return 0;
}

输出:

midorimomoi

模拟实现

在模拟实现时需要注意这种情况:

destination!=source,否则会越界访问。

例如:

c语言的字符串操作函数和内存操作函数_第1张图片

实现这个,只需要添加记录用的变量size_t len=strlen(source);即可。也有别的办法。

模拟实现:

#include
#include
#include
#include

//不做处理的mystrcat
char* mystrcat(char* destination, const char* source)
{
	char* init = destination;
	assert(destination && source);
	while (*destination != '\0')
		++destination;
	while (*destination++ = *source++);
	return init;
}

//做处理的mystrcat
char* mystrcat2(char* destination, const char* source)
{
	assert(destination && source);

	char* init = (char*)source;
	size_t len = 0;
	while (*init) {//求source的长度
		++init; ++len;
	}
	init = destination;
	while (*destination != '\0')
		++destination;
	while (*source && len) {
		*destination++ = *source++;
		len--;
	}
	*destination = '\0';
	return init;
}

int main()
{
	char a[30] = "abc\0xyz";
	char a2[30] = "abc\0xyz";
	char b[30] = { 'd','e','\0' };
	char* res = strcat(a, b);
	printf("%s\n", res);

	int i = 0;
	for (i = 0; i < 8; i++)
	{
		if (a[i] == '\0')
			printf("\\0");
		else
			printf("%c", a[i]);
	}
	res = mystrcat(a2, b);
	printf("\n%s\n", res);
	for (i = 0; i < 8; i++)
	{
		if (a2[i] == '\0')
			printf("\\0");
		else
			printf("%c", a2[i]);
	}
	char a3[12] = "abcdefgh";
	mystrcat2(a3, a3 + 5);
	printf("\n%s", a3);
	return 0;
}

strncat

调用strncat需展开头文件string.hcstring(c++)。

使用

原型:

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

strcat能将source字符串的前num个字符加在destination的末尾,source的长度不够会补\0

source的第1个字符会覆盖destination\0,新的\0会在source拷贝完成之后加上。

使用示例

#include
#include

int main() {
	char a[20] = { "Aim" }, b[15] = { "momoi" };
	strncat(a, b, 3);
	printf("%s\n", a);
	strncat(a, b, 9);
	printf("%s", a);
	return 0;
}

模拟实现

#include
#include
#include
//using namespace std;

char* mystrncat(char* destination, const char* source, size_t num) {
	assert(destination && source);
	char* start = destination, * space = source;
	while (*destination)
		++destination;
	while (*space && num) {
		--num;
		*destination++ = *space++;
	}
	return start;
}

char a[51] = "Donald_", b[51] = "Trump_", c[51] = { '\0' };

int main() {
	strcpy(c, a);//备份

	printf("%s\n", a);
	strncat(a, b, 5);//演示效果
	printf("%s\n", a);

	strcpy(a, c);

	printf("%s\n", a);
	mystrncat(a, b, 5);
	printf("%s\n", a);

	strcpy(a, c);



	return 0;
}

strcmp

调用strcmp需展开头文件string.hcstring(c++)。

使用

原型:

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

比较str1str2,从头开始枚举,遇到第1个不相同的字符时返回两个字符的ASCII码值的差(有的编译器的库函数是返回{-1,0,1}),或两个字符串完全相同时返回0。

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

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

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

使用示例:

#include
#include

int main() {
	char a[20] = { "momoi" }, b[8] = { "momoi" };

	printf("%d\n", strcmp(a, b));

	strcpy(a, "momo");
	printf("%d\n", strcmp(a, b));

	strcpy(a, "momoi0");
	strcpy(b, "momoi");
	printf("%d\n", strcmp(a, b));

	strcpy(a, "momoi");
	strcpy(b, "momp");
	printf("%d\n", strcmp(a, b));

	return 0;
}

输出结果之一:

0
-1
1
-1

模拟实现

#include
#include
#include
#include

int mystrcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2 && *s1 && *s2)
	{
		s1++;
		s2++;
	}
	return (*s1 - *s2 > 0 ? 1 : (*s1 == *s2 ? 0 : -1));
}

void f3()
{
	char a[] = "", b[] = "bbcde";
	int res = strcmp(a, b);//VS只有1,0,1 三种返回结果
	int res2 = mystrcmp(a, b);
	printf("%d %d", res, res2);
}

int main()
{
	f3();
	return 0;
}

strncmp

需展开头文件string.hcstring(c++)。

使用

原型:

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

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

使用示例:

#include
#include

int main() {
	char a[20] = { "momo" }, b[15] = { "momoi" };
	printf("%d\n", strncmp(a,b,4));
	strcpy(a, "momp");
	printf("%d\n", strncmp(a, b, 4));
	return 0;
}

模拟实现

strcmp的模拟实现的基础上,加无符号数num,设置num!=1的结束条件。这样即使前面的字符相同,也会比较最后一个字符。

#include
#include
#include

int mystrncmp(const char* s1, const char* s2, size_t num)
{
	//不用担心实参的num为负数,负数的无符号数的补码一般都很大
	assert(s1 && s2);
	while (num != 1 && *s1 == *s2 && *s1 && *s2)
	{
		s1++;
		s2++;
		--num;
	}
	return (*s1 - *s2 > 0 ? 1 : (*s1 == *s2 ? 0 : -1));
}

int main() {
	char a[20] = { "momo" }, b[15] = { "momoi" };
	printf("%d\n", mystrncmp(a,b,4));
	strcpy(a, "momp");
	printf("%d\n", mystrncmp(a, b, -1));
	return 0;
}

strstr

需展开头文件string.hcstring(c++)。

使用

原型有两个:

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

一个用于其中一个字符串是常量字符串的情况,另一个则是不包含常量字符串的情况。

strstr的功能是在str1中查找str2的位置并返回,找不到会返回NULL

要求两个指针都是字符串,否则会发生越界。

使用示例:

#include
#include
#include
#include

int main()
{
	char a[30] = { "abcdef" };
	char b[20] = { "bcd" };
	int i = 0;
	if (strstr(a, b) != NULL)
		printf("yes\n");
	else
		printf("no\n");
	return 0;
}

这个是暴力匹配,若用两个指针表示ab的枚举进程,则ab都会出现回退的情况,使得比较的次数等于两个字符串的长度相等。

模拟实现

char* mystrstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	char* p = (char*)str1;
	char* s1 = (char*)str1, * s2 = (char*)str2;
	while (*p != '\0' && p)
	{
		s1 = p;
		s2 = (char*)str2;
		while (*s1 == *s2 && (*s1 != '\0' && *s2 != '\0'))
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return p;
		p++;
	}
	if (*s1 == *s2 && *s2 == '\0')
		return s1;
	return NULL;
}

类似的函数strchr和strrchr

strchr用于在一个字符串里查找一个字符第一次出现的位置,strrchr则用于最后一个。

strtok

需展开头文件string.hcstring(c++)。

使用

原型:

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

sep参数是个字符串,定义了用作分隔符的字符集合。

str指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)

  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。

  • strtok函数的第一个参数为 NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

  • 如果字符串中不存在更多的标记,则返回 NULL 指针。

strtok常用于将通过指定字符分隔的字符串拆分成若干个部分。例如邮箱、网址等。

#include
#include
#include
#include

int main()
{
	char a[] = "@.", b[] = "[email protected]";
	char* c = strtok(b, a);
	while (c != NULL)
	{
		printf("%s\n", c);
		c = strtok(NULL, a);
	}
	return 0;
}

输出:

0721
qq
com

分析:

循环前:
b=="0712'\0'qq.com"
c==&b[0]//标记'0'的地址
第一次循环:
b=="0712'\0'qq'\0'com"
c==&b[5]//标记'q'的地址
第二次循环:
b=="0712'\0'qq'\0'com"//因为在a中找不到可清理的,便无变化
c==&b[8]//标记'c'的地址
第三次循环:
b=="0712'\0'qq'\0'com"//因为在a中找不到可清理的,便无变化
c==NULL//strtok标记的a的各字符串首地址已用完,返回NULL

这说明strtok内部存在静态指针,用于保存遍历进度。

strerror

c语言的库函数在调用失败时,会将错误码放在一个errno的变量中,想了解库函数发生的错误信息时可将errno中的错误码翻译。

strerror就是返回错误码的信息,错误信息用字符串表示。

调用errno变量,需要展开头文件errno.h

而调用strerror,需要展开头文件string.h

使用

原型:

char * strerror ( int errnum );

errno的值对应不同的错误信息:

0:No error

1:Operation not permittd

2:No such file or directory

3:No such process

使用示例:

/* strerror example : error list */
#include 
#include 
#include 

int main()
{
    FILE* pFile;
    pFile = fopen("unexist.ent", "r");
    if (pFile == NULL)
        printf("Error opening file unexist.ent: %s\n", strerror(errno));
    return 0;
}

FILE*是文件指针,fopen函数是打开指定文件用的函数,unexist.ent是文件名,r表示打开的方式是只读。

这个代码尝试打开一个不存咋的文件失败,于是pFile为空,errno的值是2。

内存操作函数

这里的内存操作函数同样需展开头文件string.hcstring(c++)。

memcpy

这个函数用的最多的场景是将一个数组的内容拷贝给另一个数组。

可以拆分成两个部分来理解:mem:memory,内存;cpy:copy,拷贝。所以这是一个内存操作函数,用于实现内存的拷贝。

使用

原型:

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

source开头的一篇连续的空间的数据拷贝给destination,其中numsource表示的连续的空间的大小。完成拷贝后,返回destination的地址。

strcpy不同,即使遇到\0也不会停下来。拷贝的方式是逐字节拷贝。

sourcedestination有任何的重叠,复制的结果都是未定义的。

例如这个样例:
{1,2,3,4,5,6},将{1,2,3,4}的部分拷贝给{3,4,5,6}的部分,可以发现{3,4}的部分重叠,这样会导致一个问题:拷贝的时候{1,2}将原本的{3,4}给覆盖,使得最终的结果变成了{1,2,1,2,1,2}。但最终目标是{1,2,1,2,3,4}

部分编译器的memcpy得到的结果是前者。

使用示例:

#include 
#include 

int main() {
    int a[10] = { 0,1,2,3,4 };
    int b[10] = { 1,1,4,5,1,4,0,7,2,1 };
    for (int i = 0; i < 10; i++)
        printf("%d ", a[i]);
    printf("\n");
    //数组b占用的内存是sizeof(int)*10,也就是40字节
    memcpy(a, b, 40);
    for (int i = 0; i < 10; i++)
        printf("%d ", a[i]);
    printf("\n");
    return 0;
}

输出:

0 1 2 3 4 0 0 0 0 0
1 1 4 5 1 4 0 7 2 1

模拟实现

#include
#include
#include

void* mymemcpy(void* dest, const void* src,size_t count)
{
	assert(dest && src);
	void* init = dest;
	while (count--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return init;
}

memmove

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

使用

原型:

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

使用示例:

#include 
#include 

int main() {
    int a[10] = { 0,1,2,3,4 };
    int b[10] = { 1,1,4,5,1,4,0,7,2,1 };
    for (int i = 0; i < 10; i++)
        printf("%d ", a[i]);
    printf("\n");
    //数组b占用的内存是sizeof(int)*10,也就是40字节
    memmove(a, b, 40);
    for (int i = 0; i < 10; i++)
        printf("%d ", a[i]);
    printf("\n");
    return 0;
}

模拟实现

这个样例:
{1,2,3,4,5,6},将{1,2,3,4}的部分拷贝给{3,4,5,6}的部分,可以发现{3,4}的部分重叠,这样会导致一个问题:拷贝的时候{1,2}将原本的{3,4}给覆盖,使得最终的结果变成了{1,2,1,2,1,2}。但最终目标是{1,2,1,2,3,4}

memmove要杜绝这种情况的发生,因此模拟实现时可采用根据情况进行拷贝策略。

  1. {1,2,3,4,5,6}{1,2,3,4}拷贝给{3,4,5,6},使最终结果为{1,2,1,2,3,4}
    此时可以发现source指针小于destination指针,因此可以从后向前拷贝。

c语言的字符串操作函数和内存操作函数_第2张图片

  1. {1,2,3,4,5,6}{3,4,5,6}拷贝给{1,2,3,4},使最终结果为{3,4,5,6,5,6}
    此时可以发现source指针大于destination指针,因此可以从前向后拷贝。

c语言的字符串操作函数和内存操作函数_第3张图片

source指针和destination指针分别指向不同内存的数组时,两种顺序用哪种都可以。

#include
#include
#include
#include
int no;

void* mymemmove(void* destination, const void* source, size_t num)
{
	void* init = destination;
	//The value of source is smaller than destination.
	if ((int)source - (int)destination < 0)
	{
		while (num != 0)//源地址比目标地址小的时候从后向前拷贝 
		{
			*((char*)destination + num - 1) = *((char*)source + num - 1);
			--num;
		}
	}
	The value of source is bigger than destination.
	else//否则就按memcpy的方式从前向后拷贝 
	{
		while (num != 0)
		{
			*((char*)destination) = *((char*)source);
			destination = (void*)((char*)destination + 1);
			source = (const void*)((char*)source + 1);
			--num;
		}
	}
	return init;
}

void f3()
{
	//void* memmove(void* destination, const void* source, size_t num)
	int a[] = { 1,2,3,4,5,6 }, i = 0, b[6] = { 0 };
	memcpy(b, a, sizeof(a));

	memmove(a + 2, a, 12);
	for (i = 0; i < 6; i++)
		printf("%d ", a[i]);
	putchar(10);

	memcpy(a, b, sizeof(b));
	mymemmove(a + 2, a, 12);
	for (i = 0; i < 6; i++)
		printf("%d ", a[i]);
}

int main()
{
	f3();//memmove模拟
	return 0;
}

memset

memory + set,内存设置函数。

用于将内存变成指定的值。

使用

原型:

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

ptr是要设置的内存块的首地址。

value是要统一设置成的值。

num是要设置的内存块的字节数。

设置方式是获取value的最后1个字节的内容,然后将ptr指向的内存块的每个字节的内容都用value覆盖。

比如若value的值是 -1 ,则 -1的补码是0xffffffff,于是取value的最后1个字节的内容0xff给所有ptr表示的内存块赋值。

使用示例:

#include 
#include 

int main() {
    int a[10] = { 1,1,4,5,1,4,0,7,2,1 };
    memset(a, 0, 40);
    for (int i = 0; i < 10; i++)
        printf("%d ", a[i]);
    return 0;
}

你可能感兴趣的:(c语言学习,c语言,开发语言)