高級 C 言語 | 文字関数と文字列関数 -- 関数のアナログ実装

C 言語では文字や文字列の処理が頻繁に行われますが、C 言語自体には文字列型がなく、文字列は定数文字列または文字配列に配置されるのが一般的です。文字列定数は、文字列定数を変更しない文字列関数に適用されます。

1. 文字列の長さを調べる

ストレン
size_t は、strlen 関数が符号なし整数を返し、str が文字列を指し、文字列のアドレスを受け取ることを意味します。

  • 文字列は '\0' で終わっており、strlen 関数は文字列内の '\0' より前に現れる文字数を返します ('\0' を除く)。
  • パラメータが指す文字列は「\0」で終わる必要があります
  • 関数の戻り値は size_t であり、これは符号なし (エラーが発生しやすい) であることに注意してください。

1.1 strlenの使用

#include <stdio.h>
#include <string.h>
int main()
{
    
    
	const char* str1 = "abcdef";
	const char* str2 = "bbb";
	if ((int)strlen(str2) - (int)strlen(str1) > 0)
	{
    
    
		printf("str2>str1\n");
	}
	else
	{
    
    
		printf("str1>str2\n");
	}
	return 0;
}

操作結果:
ここに画像の説明を挿入

注: strlen を使用して 2 つの文字列のサイズを比較する場合、strlen は符号なし整数を返すため、減算に直接使用することはできません。バイナリ ビットの場合、最上位ビットは符号ビットです。符号なしビットの場合、最上位ビットは符号ビットです。計算にも使用されますが、これは大きな数値になるため、計算中に int 型にキャストするか、直接比較 (strlen(str2)>strlen(str1)) して計算を行わないようにすることができます。

1.2 strlen関数のシミュレーション実装

#include <stdio.h>
int my_strlen(const char* str1)
{
    
    
	int count = 0;
	while ( * str1!='\0')
	{
    
    
		str1++;
		count++;
	}
	return count;
}
int main()
{
    
    
	char* str1 = "abcdef";
	int count = my_strlen(str1);
	printf("%d", count);
	return 0;
}

実行結果:
ここに画像の説明を挿入
もちろん、ポインタマイナスポインタの再帰的な方法でもシミュレーションできます。

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

2.1 strcpyの使用

ここに画像の説明を挿入
source が指すソース文字列を destination が指す宛先文字列にコピーし、char* 型のポインタを返します。

  • ソース文字列は「\0」で終わる必要があります。
  • ソース文字列の「\0」をターゲットスペースにコピーします
  • ターゲット空間は変更可能でなければなりません
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char* str1 = "abcdef";
	char str2[20] = {
    
     0 };
	strcpy(str2, str1);
	printf("str2=%s", str2);
	return 0;
}

操作結果:
ここに画像の説明を挿入

2.1.1 strcpy関数のシミュレーション実装

#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* str2, const char* str1)
{
    
    
	char* ret = str2;
	assert(str1 && str2);
	while (*str2++ = *str1++)
	{
    
    
		;
	}
	return ret;
}
int main()
{
    
    
	char* str1 = "abcdef";
	char str2[20] = {
    
     0 };
	char* ret = my_strcpy(str2, str1);
	printf("str2=%s", ret);
	return 0;
}

操作結果:
ここに画像の説明を挿入

アサートは、それがヌルポインタであるかどうかをアサートするために使用され、ヌルポインタである場合はエラーが報告され、そうでない場合は実行を継続します。

2.2 strcat の使用

ここに画像の説明を挿入
source が指すソース文字列を destination が指す文字列の末尾に接続し、ソース文字列の最初の文字が宛先文字列の終了 NULL 文字位置に書き込まれます。ここに画像の説明を挿入

  • ソース文字列とターゲット文字列の両方が「\0」で終わる必要があります
  • 宛先スペースは、ソース文字列の内容を保持するのに十分な大きさである必要があります。
  • ターゲット空間は変更可能である必要があります
#include <stdio.h>
#include <string.h>
int main()
{
    
    
    char str1[20] = "hello";
	char* str2 = "bit";
	strcat(str1, str2);
	printf("str1=%s", str1);
	return 0;
}

操作結果:
ここに画像の説明を挿入

2.2.1 strcat関数のシミュレーション実装

#include <stdio.h>
#include <assert.h>
char* my_strcat(char* str1, const char* str2)
{
    
    
	char* ret = str1;
	assert(str1 && str2);
	//寻找到'\0'的位置
	while (*str1)
	{
    
    
		str1++;
	}
	//开始拷贝
	while (*str1++ = *str2++)
	{
    
    
		;
	}
	return ret;
}
int main()
{
    
    
    char str1[20] = "hello";
	char* str2 = "bit";
	char* ret = my_strcat(str1, str2);
	printf("str1=%s", str1);
	return 0;
}

実行結果:
ここに画像の説明を挿入
そこで質問は、文字列をそれ自体に追加できるかということです。シミュレートされたコードを使用して分析すると、
ここに画像の説明を挿入
まずソースとターゲットが同じ場所を指し、ターゲットは終端の NULL 文字の位置を見つけてコピーし、最初に h をコピーし、'\ まで停止します。 0' がコピーされますが、最初のコピー このとき、'\0' は上書きされ、以降のコピーでは '\0' が見つからず、無限ループが続きます。したがって、答えは、自分自身に文字列を追加しないのが最善であるということです。

2.3 strcmpの使用

ここに画像の説明を挿入
同じ位置にある文字を比較し、等しい場合は次の文字のペアを比較し、等しくない場合はどちらが大きいかを判断し、対応する数値を返します。戻り値の型は int 型です。
ここに画像の説明を挿入

  • 最初の文字列が 2 番目の文字列より大きい場合は、0 より大きい数値を返します。
  • 最初の文字列が 2 番目の文字列と等しい場合は、0 を返します。
  • 最初の文字列が 2 番目の文字列より小さい場合、0 未満の数値を返します
#include <stdio.h>
#include <string.h>
int main()
{
    
    
    char* str1= "hello";
	char* str2 = "bit";
	int ret = strcmp(str1, str2);
	printf("ret=%d", ret);
	return 0;
}

操作結果:
ここに画像の説明を挿入

2.3.1 strcmp関数のシミュレーション実装

#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
    
    
	assert(str1 && str2);
	//当比较到'\0'或者不相等时就可以停下来
	while (str1 && str2 && *str1++ == *str2++)
	{
    
    
		;
	}
	int ret=str1 - str2;
	if (ret > 0)
	{
    
    
		return 1;
	}
	else if (ret < 0)
	{
    
    
		return -1;
	}
	else
	{
    
    
		return 0;
	}
}
int main()
{
    
    
    char* str1= "hello";
	char* str2 = "bit";
	int ret = my_strcmp(str1, str2);	
	printf("ret=%d", ret);
	return 0;
}

操作結果:
ここに画像の説明を挿入

3. 長さ制限のある文字列関数の概要

3.1strncpy

ここに画像の説明を挿入

  • ソース文字列からコピー先スペースに num 文字をコピーします。
  • ソース文字列の長さが num 未満の場合、ソース文字列がコピーされた後、ターゲットの後に num まで 0 が追加されます。
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char str1[20] = {
    
     0 };
	char* str2 = "bit";
	strncpy(str1, str2,5);	
	printf("str1=%s", str1);
	return 0;
}

操作結果:
ここに画像の説明を挿入

3.2strncat

ここに画像の説明を挿入

  • ソース文字列の最初の num 文字を宛先文字列の末尾に追加し、最後に終端 null 文字を付けます。
  • ソース文字列の長さが num 未満の場合は、終端の null 文字までのみコピーされます。
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char str1[20] = "hello";
	char* str2 = "bit";
	strncat(str1, str2,5);	
	printf("str1=%s", str1);
	return 0;
}

操作結果:
ここに画像の説明を挿入

3.3strncmp

ここに画像の説明を挿入

この関数は各文字列の最初の文字の比較を開始し、それらが等しい場合は、別の文字が見つかるか、終端の null 文字が見つかるか、または両方の文字列が num 文字に一致するまで、後続の文字のペアの比較を続けます。

#include <stdio.h>
#include <string.h>
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;
}

操作結果:
ここに画像の説明を挿入

4、文字列検索

4.1 strstr の使用

ここに画像の説明を挿入

str1 内で最初に出現した str2 へのポインタを返します。str2 が str1 にない場合は null ポインタを返します。よく言われるように、strstr() 関数は、文字列に別の文字列が含まれているかどうかを確認し、最初に一致した文字列の位置を返すために使用されます。

#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char str[] = "This is a simple string";
	char* pch;
	pch = strstr(str, "simple");
	strncpy(pch, "sample", 6);
	puts(str);
	return 0;
}

操作結果:
ここに画像の説明を挿入

4.1.1 strstr関数のシミュレーション

#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
    
    
	assert(str1 && str2);//断言str1和str2是否为空指针
	char* cp = (char*)str1;
	char* sstr1 ;
	char* sstr2 ;
	//*str2为'\0'返回str1的地址
	if (!*str2)
		return (char*)str1;
	while(*cp)
	{
    
    
		int count = 0;
		sstr1 = cp;
		sstr2 = (char*)str2;
		while (sstr1 && sstr2 && *sstr1++ == *sstr2++)
		{
    
    
			;
		}
		if (!*sstr2)
			return cp;
		cp++;
	}
	return NULL;
}
int main()
{
    
    
	char str[] = "This is a simple string";
	char* pch = my_strstr(str, "simple");	
	puts(pch);
	return 0;
}

操作結果:
ここに画像の説明を挿入

4.2 ストラトーク

ここに画像の説明を挿入

  • sep パラメータは、区切り文字の文字セットを定義する文字列です。
  • 最初のパラメータは、sep 文字列内で 1 つ以上の区切り文字で区切られた 0 個以上のトークンを含む文字列を指定します。
  • strtok 関数は、str 内の次のマークを検索し、'\0' で終了し、このマークへのポインタを返します。(注: strtok 関数は操作対象の文字列を変更するため、strtok 関数を使用して分割された文字列は通常、一時的にコピーされたコンテンツであり、変更できます。)
  • strtok 関数の最初のパラメータは NULL ではなく、関数は str 内の最初のマークを検索し、文字列内のその位置を保存します。
  • strtok 関数の 1 つのパラメーターが NULL の場合、関数は同じ文字列内の保存された位置から開始され、次のトークンを検索します。
  • 文字列内にそれ以上のトークンが存在しない場合は、NULL ポインターが返されます。
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char str[] = "[email protected]#566&520";
	char sep[] = "@.# & ";//分隔符,strtok找到str中下一个标记就是分隔符,找到之后会将分隔符置为'\0',所以最好拷贝一下内容
	//返回指向该标记的指针,同时返回分隔符前面字段的第一个字符的地址
	char arr[30];
	strcpy(arr, str);
	char* p = NULL;
	//当第一个参数不为NULL时,找到第一个标记,strtok会保存该标记的位置
	//当第一个参数为NULL时,函数将在同一个字符串中被保存的位置开始查找,直到下一个标记
	//如果字符串没有标记了,则返回空指针
	for (p = strtok(arr, sep); p != NULL; p = strtok(NULL, sep))
	{
    
    
		printf("%s\n", p);
	}
	return 0;
}

操作結果:
ここに画像の説明を挿入

5. エラーメッセージのレポート

5.1strエラー

ここに画像の説明を挿入

  • ライブラリ関数によって errno が設定されるのと同様に、エラー コードは errnum に格納され、errnum の値が解釈されて、エラー状態を説明する文字列が生成されます。
  • この関数は、プログラムによって変更されるべきではない静的に割り当てられた文字列へのポインタを返します。この関数をさらに呼び出すと、その内容が上書きされる可能性があります (データ競合を避けるために特定のライブラリ実装では必要ありません)。
  • strerror によって生成されるエラー文字列は、システム実装とライブラリ実装の間で異なる場合があります。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
    
    
	FILE* pFile;
	pFile = fopen("test.txt", "r");
	if (pFile == NULL)
	{
    
    
		printf("Error opening file test.txt:%s\n", strerror(errno));
		//也可以用perror
		perror(pFile);
	}
	return 0;
}

操作結果:
ここに画像の説明を挿入

6、キャラクター操作

ここに画像の説明を挿入
文字変換:

int to lower(int c);//大文字から小文字へ
int toupper(int c);//小文字から大文字へ

#include <stdio.h>
#include <ctype.h>
int main()
{
    
    
	int i = 0;
	char str[] = "Test String.\n";
	char c;
	while (str[i])
	{
    
    
		c = str[i];
		if (isupper(c))//是大写返回真
			c=tolower(c);//转小写
		putchar(c);
		i++;
	}
	return 0;
}

操作結果:
ここに画像の説明を挿入

7、メモリ操作機能

7.1 memcpyの使用

ここに画像の説明を挿入

  • 関数 memcpy は、コピー元の位置からコピー先のメモリ位置に num バイトのデータを逆方向にコピーします。ターゲットとソースの戻り値の型は void* であるため、あらゆる種類のデータのコピーを受け取ることができます。
  • この関数は、「\0」に遭遇しても停止しません。
  • コピー元とコピー先が何らかの形で重なっている場合、コピーの結果は不定になります。
#include <stdio.h>
#include <string.h>
struct {
    
    
	char name[40];
	int age;
}person;
int main()
{
    
    
	char myname[] = "Pierre de Fermat";
	memcpy(person.name, myname, strlen(myname) + 1);
	printf("%s\n", person.name);
	return 0;
}

操作結果:
ここに画像の説明を挿入

7.1.1 memcpy関数のシミュレーション

#include <stdio.h>
#include <assert.h>
void* my_memcopy(void* str2, const void* str1, size_t num)
{
    
    
	void* ret = str2;
	assert(str2 && str1);
	while (num--)
	{
    
    
		//完成每一对字节的交换
		*(char*)str2 = *(char*)str1;
		str2 = (char*)str2 + 1;
		str1 = (char*)str1 + 1;
	}
	return ret;
}
int main()
{
    
    
	int str1[20] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int str2[20] = {
    
     0 };
	int* pc = (int*)my_memcopy(str2, str1, 40);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", pc[i]);
	}
	return 0;
}

7.1.2 memcpyのソースとデスティネーションの重複

#include <stdio.h>
#include <assert.h>
void* my_memcopy(void* str2, const void* str1, size_t num)
{
    
    
	void* ret = str1;
	assert(str2 && str1);
	while (num--)
	{
    
    
		//完成每一对字节的交换
		*(char*)str2 = *(char*)str1;
		str2 = (char*)str2 + 1;
		str1 = (char*)str1 + 1;
	}
	return ret;
}
int main()
{
    
    
	int str1[20] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	//int str2[20] = { 0 };
	int* pc = (int*)my_memcopy(str1+2, str1, 20);//将12345复制到34567这个位置
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", pc[i]);
	}
	return 0;
}

期待される結果は 12123458910 ですが、これは次のとおりです。これは
ここに画像の説明を挿入

ここに画像の説明を挿入
3 が 1 でカバーされており、3 が 5 に割り当てられると、それも 1 になり、4 についても同様です。したがって、memcpy 関数は重複を持たないことが最善です。つまり、memcpy は重複しないコピーを実装するだけで済みますが、コンパイラの違いにより、VS では重複するコピーを実装できます。

7.2 memmoveの使用

ここに画像の説明を挿入

  • memcpy との違いは、memmove 関数によって処理されるソース メモリ ブロックとターゲット メモリ ブロックが重複できることです。
  • ソース空間とターゲット空間が重なっている場合は、memmove 関数を使用して対処する必要があります。
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	int arr[20] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	memmove(arr + 2, arr, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
	return 0;
}

実行結果:
ここに画像の説明を挿入
memmove でオーバーラップが実現できることが結果からわかりますが、この機能をどのようにシミュレーションして実現すればよいでしょうか?

7.2.1 memmove関数のシミュレーション

分析します:
ここに画像の説明を挿入

#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
    
    
	assert(dest && src);
	if (src < dest)
	{
    
    
		void* ret = src;
		//从后向前开始拷贝
		src = (char*)src + num-1;
		dest = (char*)dest + num-1;
		while (num--)
		{
    
    
			*(char*)dest = *(char*)src;
			src = (char*)src - 1;
			dest = (char*)dest - 1;
		}
		return ret;
	}
	else
	{
    
    
		void* ret = dest;
		while (num--)
		{
    
    
		//从前向后拷贝
			*(char*)dest = *(char*)src;
			src = (char*)src + 1;
			dest = (char*)dest + 1;
		}
		return ret;
	}
}
int main()
{
    
    
	int arr1[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int arr2[20] = {
    
     0 };
	int* pc=(int*)my_memmove(arr1+2 , arr1, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", pc[i]);
	}
	return 0;
}

操作結果:
ここに画像の説明を挿入

7.3memcmp

ここに画像の説明を挿入

  • ポインタ ptr1 が指すメモリ ブロックの最初の num バイトと、ポインタ ptr2 が指すメモリ ブロックの最初の num バイトを比較し、両方が一致する場合は 0 を返し、一致しない場合はどちらが大きいかを示す値を返します。
  • strcmp とは異なり、この関数は null 文字を見つけた後も比較を停止しないことに注意してください。
#include <stdio.h>
int main()
{
    
    
	char* str1 = "alfjlkfafj";
	char* str2 = "alffljaoiogi";
	int n =memcmp(str1, str2, 8);
	if (n > 0)
		printf("str1>str2\n");
	else if (n < 0)
		printf("str2<str1\n");
	else
		printf("str1=str2\n");
	return 0;
}

実行結果:
ここに画像の説明を挿入
終了~

おすすめ

転載: blog.csdn.net/weixin_68201503/article/details/131720009