C言語の救世主(文字列関数とメモリ関数のシミュレートされた実装--11)

コンテンツ

C言語自体には文字列型がなく、文字列は通常、定数文字列または文字配列に配置されます。

1.無制限の長さの文字列関数

1.1 strlen

1.2strlenライブラリ関数をシミュレートする3つの方法

1.3strcpy文字列コピー

1.4strcpyをシミュレートする

strcpy関数は、ターゲットスペースの開始アドレスを返します

strcpy関数のreturnタイプは、連鎖アクセスを実現するように設定されています

1.5strcat文字列追加

1.6strcatをシミュレートする

1.7strcmpは文字列を比較します

1.8strcmpの実装をシミュレートする

2長さが制限された文字列関数

2.1 strncpy

2.2 strncat

2.3 strncmp

2.4 strstrは、別の文字列内で1つの文字列を検索します

2.5strstrの実現をシミュレートする

2.6 strtok

2.7 strerror perrorは、エラーコードと対応するエラーメッセージを返します

2.8文字分類機能

3.メモリ操作機能

3.1memcpyメモリスペースデータコピー

3.2memcpyの実装をシミュレートする

 同じメモリコピーで、ターゲットとソースのデータスペースが交差しているため、memmoveを使用する必要があります

3.3 memmoveは、重複するメモリコピーを実現できます

3.4memmoveを達成するためのシミュレーション

3.5memcpyメモリバイト対応の比較

3.6バイト単位のmemsetメモリ設定


C言語自体には文字列型がなく、文字列は通常、定数文字列または文字配列に配置されます。

1.無制限の長さの文字列関数

1.1 strlen

size_t strlen ( const char * str );//函数原型,注意函数的返回值为size_t

文字列の終了マーカーは「\0」であり、strlen関数は、文字列の「\ 0」の前に表示される文字数を返します(「\ 0」を除く)。

char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };//无法用strlen求字符串长度

char arr[10] = { 'a', 'b', 'c', 'd', 'e', 'f' };//限定长度,可以求

 以下の結果は何ですか?

 if ((int)strlen("abc") - (int)strlen("qwerty") > 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<=\n");
	}

回答:>、2つの符号なし数値を減算した結果は正の数です


1.2strlenライブラリ関数をシミュレートする3つの方法

通常書かれている

 #include <assert.h>
 #include <stdio.h>

size_t my_strlen(const char* str)
{
	int count = 0;//统计字符的个数
    assert(str);
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
 
 
int main()
{
	char arr[] = "abcd";
	//char* str = arr;
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

再帰

 #include <assert.h>
 #include <stdio.h>

//my_strlen("abcdef")
//1+my_strlen("bcdef")
//1+1+my_strlen("cdef")
//1+1+1+ my_strlen("def")
//1+1+1+1+ my_strlen("ef")
//1 + 1 + 1 + 1 +1+my_strlen("f")
//1 + 1 + 1 + 1 + 1 + 1+ my_strlen("")
//1 + 1 + 1 + 1 + 1 + 1 + 0 = 6
 
size_t my_strlen(const char* str)
{
    assert(str);
	if (*str != '\0')
		return 1 + my_strlen(str+1);
	else
		return 0;
}
 
int main()
{
	char arr[] = "abcd";
	//char* str = arr;
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

ポインタ-ポインタメソッド

 #include <assert.h>
 #include <stdio.h>


size_t my_strlen(const char* arr,int sz)//指针-指针
{
    assert(arr);
    char* right = arr + sz - 1;
    return right - arr;
}
 
int main()
{
    char arr[] = "hello";
    int sz = sizeof(arr) / sizeof(arr[0]);
    int len=my_strlen(arr,sz);
    printf("%d", len);
    return 0;
}

1.3strcpy文字列コピー

文字列のコピー、ソース文字列をターゲットスペース文字列にコピーします。注意が必要な事項

1.ソース文字列は「\0」で終わる必要があります。

char arr1[20] = {0};
char arr2[] = {'a','b','c'};//程序崩溃,没有\0

2.ソース文字列の「\0」がターゲットスペースにコピーされます。

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

int main()
{
	
	char arr1[] = "XXXXXXXXXXXX";//arr1指向的是常量字符串,常量是不可修改的
	char arr2[] = "abcdef";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

3.宛先スペースは、ソース文字列を保持するのに十分な大きさである必要があります。

strcpyは、十分なスペースがあるかどうかは関係ありません。あえて書く限り、プログラムがクラッシュした場合でも、strcpyを置くことができます。

char arr1[4] = "x";
char arr2[] = "abcdef";
strcpy(arr1, arr2);//程序崩溃

4.ターゲットスペースは可変である必要があります。

char* arr1 = "qwertyuiop";//arr1指向的是常量字符串,常量是不可修改的
char arr2[] = "abcdef";
strcpy(arr1, arr2);//程序崩溃

1.4strcpyをシミュレートする

strcpy関数は、ターゲットスペースの開始アドレスを返します

strcpy関数のreturnタイプは、連鎖アクセスを実現するように設定されています

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

int main()
{
	char arr1[20] = { 0 };
	char* arr2 = "hello bit";

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


1.5strcat文字列追加

int main()
{
	char arr1[10] =  "hello " ;
	char* arr2 = "bit";

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

予防:

1.ソース文字列は「\0」で終わる必要があります。\0から追加

int main()
{
	char arr1[20] =  "hello\0XXXXX" ;
	char arr2[] = "bit";

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

2.ターゲットスペースは、ソース文字列のコンテンツを収容するのに十分な大きさである必要があります。

3.ターゲットスペースは変更可能である必要があります。

1.6strcatをシミュレートする

char* my_strcat(char* dest, char* src)
{
	assert(dest && src);
	char* ret = dest;
	//找目标空间中的\0
	while (*dest)
	{
		dest++;
	}
	//拷贝
	while (*dest++ = *src++)
	{
		;
	}

	return ret;
}
int main()
{
	char arr1[20] = "hello";
	char arr2[] = " bit";

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

文字列をそれ自体に追加するのはどうですか?

作成したシミュレーション関数は、それ自体の追加を完了できないため、このように使用することはお勧めしません。


1.7strcmpは文字列を比較します

strcmp関数は、文字列の長さではなく、文字列

内の対応する位置にある文字のサイズ(ASCIIコード値)を比較します。同じ場合は、異なるか、両方が\のASCIIに遭遇するまで、次のペアを比較します。 0\0コード値は0です

 標準規制:

最初の文字列が2番目の文字列より大きい場合は、0より大きい数値を返します

最初の文字列が2番目の文字列と等しい場合は、0を返します

最初の文字列が2番目の文字列よりも小さい場合は、0未満の数値を返します


1.8strcmpの実装をシミュレートする

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[] = "abcd";
	char arr2[] = "abdc";
	int ret = my_strcmp(arr1, arr2);
	if (ret >0)
	{
		printf(">\n");
	}
	else if (ret == 0)
	{
		printf("== \n");
	}
	else
	{
		printf("<\n");
	}
	printf("%d\n", ret);
	return 0;
}

2長さが制限された文字列関数

2.1 strncpy

 ソース文字列から宛先スペースにnum文字をコピーします。

ソース文字列の長さがnum未満の場合は、ソース文字列をコピーした後、numまでターゲットの末尾に0を追加します。

char *strncpy( char *strDest, const char *strSource, size_t count );
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "qwewwwwww";

	strncpy(arr1, arr2, 5);

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

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "qwe";

	strncpy(arr1, arr2, 5);//不够默认补\0

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


2.2 strncat

 ソース文字列から宛先スペースにnum文字を追加します。

int main()
{
	char arr1[20] = "abcdef\0XXXXX";
	char arr2[] = "qwe";

	strncat(arr1, arr2, 3);//追加三个,还会再把\0放进去

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


2.3 strncmp

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdq";
	int ret = strncmp(arr1, arr2, 4);//相等
	
	printf("%d\n", ret);

	return 0;
}


2.4 strstrは、別の文字列内で1つの文字列を検索します

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

 str2がstr1のサブストリングであるかどうかを判別し、str2がstr1に出現する場合は、str1に最初に出現したアドレスを返します。

存在しない場合は、nullポインタを返します

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "de";
	char * p=strstr(arr1, arr2);
	if (p == NULL)//strstr找不到返回NULL指针,我们需要判断
	{
		printf("找不到");
	}
	else
	{
		printf("%s ", p);
	}
	return 0;
}

2.5strstrの実現をシミュレートする

 アイデア:検出される部分文字列が複雑な場合、支援するために3つのポインターが必要です

s1ポインターはstr1を指し、s2ポインターはstr2を指します。curポインターはstr1を指します。これは、一致が開始されるアドレスを記録するために使用されます。

2つの文字列の対応する位置が等しくない場合、str1は逆方向に移動します

等しい場合は、マッチングを開始します。str1の位置を覚えておく必要があります。これは、等しい場合もあれば、等しくない場合もあるためです。

\ 0で終わる場合、str2はstr1のサブストリングです。

それらが等しくない場合は、レコードの場所のアドレスを再検索し、+ 1に戻ります。戻って一致を再開します。ここで、str2ポインターは配列の開始アドレスを再ポイントします。

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);

	const char* s1 = str1;
	const char* s2 = str2;

	const char* cur = str1;
	while (*cur)//cur不等于\0进来 
	{
		s1 = cur;//判断失败返回cur指向的位置
		s2 = str2;//判断失败回到起始位置

		while (*s1 && *s2 && (*s1 == *s2))//两个字符串都被查找完,没有数据了
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//字串找到,返回记录地址
		{
			return (char*)cur;
		}
		cur++;//匹配不成功,指向下一步
	}

	return NULL;//找不到
}

int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbc";
	char* ret = my_strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("找不到子串\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

2.6 strtok

役割:区切り文字を指定し、配列をセグメント化します

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

 sepパラメーターは、区切り文字として使用される文字のセットを定義する文字列です。

最初のパラメーターは、sep文字列内の1つ以上の区切り文字で区切られた0個以上のトークンを含む文字列を指定します。

strtok関数は、str内の次のトークンを検索し、\ 0で終了して、このトークンへのポインターを返します。(注:strtok関数は操作される文字列を変更するため、strtok関数を使用してセグメント化された文字列は通常、一時コピーの内容であり、変更できます。)

 strtok関数の最初のパラメーターはNULLではなく、関数はstr内の最初のトークンを検索し、strtok関数は文字列内のその位置を保存します。

strtok関数の最初のパラメーターはNULLであり、関数は同じ文字列内の保存された位置から開始し、次のトークンを探します。

文字列にトークンがもうない場合は、NULLポインタが返されます。

int main()
{
	char arr[] = "[email protected]";
  //char arr[] = "lanyangyang\0landawang\0cunba";  strtok函数会把数组变成这样
	char buf[50] = { 0 };
    const char* sep = "@. ";
	strcpy(buf, arr);
    
   //printf("%s\n", strtok(buf, sep));//只找第一个标记
   //printf("%s\n", strtok(NULL, sep));//是从保存的好的位置开始继续往后找
   //printf("%s\n", strtok(NULL, sep));//是从保存的好的位置开始继续往后找
优化
    char* str = NULL;
	for (str=strtok(buf, sep); str!=NULL; str=strtok(NULL, sep))
	{
		printf("%s\n", str);
	}

    return 0;
}


2.7 strerror perrorは、エラーコードと対応するエラーメッセージを返します

char * strerror ( int errnum );

 ライブラリ関数が使用に失敗すると、Webサイト404エラーコードと同様のエラーコードerrno(グローバル変数)が残ります。

strerrorは翻訳エラーメッセージです

#include <string.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	int* p = (int*)malloc(INT_MAX);//想堆区申请内存的
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		perror("Malloc");//perror是打印错误信息,strerror是把错误码转换成错误信息
 //只需要写字符串,然后输出错误码对应得错误信息,更加方便
		return 1;
	}
	return 0;
}

エラーコードに対応するエラーメッセージ文字列の最初の文字のアドレスを返します

 


2.8文字分類機能

働き 引数が次の条件を満たす場合にtrueを返します
iscntrl 任意の制御文字
sspace 空白文字:スペース''、フォームフィード'\ f'、改行'\ n'、キャリッジリターン'\ r'、タブ'\t'または垂直タブ'\ v'
isdigit 10進数0〜9
isxdigit すべての10進数を含む16進数、小文字a〜f、大文字A〜F
islower 小文字a〜z
isupper 大文字A〜Z
isalpha 文字a〜zまたはA〜Z
isalnum 文字または数字、a〜z、A〜Z、0〜9
ispunct 句読点、数字または文字ではないグラフィック文字(印刷可能)
isgraph 任意のグラフィック文字
スプリント グラフィック文字や空白を含む、印刷可能な文字

 

 例:isdigitは、文字のASCIIコード値を受け取り、int型を返します(数値の場合は0以外を返し、配列文字でない場合は0を返します)

#include <ctype.h>

int main()
{
int ret = isdigit('5');//5
int ret = isdigit('Q');//0
printf("%d\n", ret);

return 0;
}
char ch = 'A';

	if (ch >= 'a' && ch <= 'z')
	{

	}

这样写很麻烦,我们一个函数搞定

int ret = islower(ch);//判断是否小写,是小写字母返回非0,否则返回0,快速判断

文字変換:

int tolower ( int c );
int toupper ( int c );
	int main()
{
	char ch = 'A';
	putchar(toupper(ch));
	putchar(tolower(ch));

	return 0;
}


3.メモリ操作機能

3.1memcpyメモリスペースデータコピー

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

予防:

1.関数memcpyは、カウントバイトのデータをソースの場所から宛先のメモリの場所に逆方向にコピーします。

2.この関数は、「\0」に遭遇しても停止しません。

3.ソースと宛先の間に重複がある場合、コピーの結果は未定義です。

4.宛先の開始アドレスを返します

int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[5] = { 0 };
	return 0;
    memcpy(arr2,arr1,20);//拷贝20个字节
}

3.2memcpyの実装をシミュレートする

アイデア:  

1.作成者がmemcpy関数を実装する場合、作成者はqsortと同様に、コピーするデータを知りません。 

2.コピーする場合、型変換はデータ型に従ってキャストする必要があり、バイトごとにコピーします

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

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

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

	return ret;
}


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



    int arr1[10] = { 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);

	return 0;
}

しかし、同じスペースにデータをコピーしたい場合、データは間違っています

 その理由は、データがコピーしたいスペースを上書きするためです

 同じメモリコピーで、ターゲットとソースのデータスペースが交差しているため、memmoveを使用する必要があります


3.3 memmoveは、重複するメモリコピーを実現できます

void *memmove( void *dest, const void *src, size_t count );

#include <string.h>

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

1

3.4memmoveを達成するためのシミュレーション

アイデア:

34567を12345にコピーしても、データは上書きされません(dest <srcの場合)

 34567を45678にコピーする場合は、データが上書きされます。最初に7を8に、6を7に、5を6に配置できます...データを後ろから前にコピーして、データがコピーされないようにします。カバーされる

 概要:コピーするアドレスがdest> srcアドレスの場合は、後ろから前にコピーします。dest<srcの場合は、前から後ろにコピーします。

destとsrcスペースの間に交差がない場合、前面と背面の関係は重要ではありません。ここではデフォルトで背面から前面にコピーします(便利)

{
	if (dest > src)
	{
		;  //从后向前拷贝
	}
	else
	{
		;  //从前向后拷贝
	}
}

別の方法で書くこともできます

if (dest > src && dest<((char*)src+count))
	{
		;//从后向前拷贝
	}
	else
	{
		;//从前向后拷贝
	}

コードのアイデア:前から後ろへのコードはmemcpyシミュレーションであり、後ろから前へ、destとsrcの最後まで+20バイトが必要です

void* my_memmove(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* ret = dest;
	if (dest > src)
	{
		while (count--)
		{
	*((char*)dest + count) =  *((char*)src + count);  //count=19,正好指向最后一个字节
		}
		//从后向前拷贝
	}
	else
	{
			void* ret = dest;
			while (count--)
			{
				*(char*)dest = *(char*)src;
				dest = (char*)dest + 1;
				src = (char*)src + 1;
			}
		 //从前向后拷贝
	}
	return ret;
}


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

3.5memcpyメモリバイト対応の比較

int memcmp ( const void * ptr1, 
 const void * ptr2, 
 size_t num );//比较的字节个数

 

 int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,0x11223305 };
	int ret = memcmp(arr1, arr2, 18);
	printf("%d\n", ret);

	return 0;
}


3.6バイト単位のmemsetメモリ設定

void *memset( void *dest, int c, size_t count );//目的空间,设置的字符,字符个数

 int main()
{
	int arr[] = { 0x11111111,0x22222222,3,4,5 };
	memset(arr, 6, 20);//memset是以字节为单位来初始化内存单元的
	return 0;
}

おすすめ

転載: blog.csdn.net/weixin_63543274/article/details/123986777