この記事では、文字ポインター、配列ポインター、ポインター配列、配列パラメーターの受け渡しとポインター パラメーターの受け渡し、関数ポインターについて理解できます。

ここに画像の説明を挿入

レビュー

まずはポインタの基礎知識を簡単におさらいしましょう。
メモリはメモリ単位に分割されます。各メモリ単位は独立した番号を持っています。番号はアドレスとも呼ばれ、アドレスはC言語ではポインタとも呼ばれます。ポインタ(アドレス)を格納する必要があります。変数に格納されます。この変数はポインタ変数と呼ばれます。
次に例を示します。

int a = 10;
int* pa = &a;

ポインタのサイズは 4/8 バイトに固定されています (32 ビット/64 ビット プラットフォーム)
アドレスは物理ワイヤ上で生成され、
電気信号はデジタル信号に変換されます
32 ビット マシン - 32 アドレス ライン—32
0/1 で構成される 1/0 バイナリ シーケンス。このバイナリ シーケンスをアドレスとして受け取り、32 ビットでこのアドレスを格納できます。
つまり、格納するには 4 バイトが必要なので、
ポインタ変数のサイズは 4 バイトです
。同様に、64 ビット マシンでは、アドレスのサイズは 64 個の 0/1 で構成されるバイナリ シーケンスであり、格納するには 64 ビット、つまり 8 バイトが必要なので、ポインタ変数のサイズは 8 バイトになります。

1文字ポインタ


ポインタ型には、一般的に使用される文字ポインタ char* というポインタ型があることがわかります。

int main()
{
    
    
 char ch = 'w';
 //ch = 'a'//这里可以理解成办事的方法有很多种,这只是其中一种
 char *pc = &ch;//pc就是字符指针
 *pc = 'w';
 return 0;
}

次のコードができました

int main()
{
    
    
	char arr[] = "abcdef";
	const char* p = "abcdef";//本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改
	return 0;
}

これは基本的に、文字列の最初の文字 a のアドレスを p に入れることです。これは、p が文字列の最初の要素 a のアドレスを指すのと等価です。 //この文字列は定数文字列であり、定数文字列を指定することはできません。変更されているため、変更されないように const を追加する必要があります。また、const の使用法に注意してください。const を左側に配置すると p を制限し、const を右側に配置すると p を制限ます
。上記のコードを印刷して効果を確認してください

int main()
{
    
    
	char arr[] = "abcdef";
	const char* p = "abcdef";//本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改
	printf("%s\n", p);
	printf("%c\n", *p);
	return 0;
}

ここに画像の説明を挿入
ここに画像の説明を挿入
質問について考えてみましょう (文字ポインターに関する上記の知識を組み合わせることに注意してください)。次のコードを参照してください。

int main()
{
    
    
 const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
 printf("%s\n", pstr);
 return 0;
}

コード const char* pstr = "hello bit." は、本質的に、文字列 hello bit. の最初の文字のアドレスを pstr に入れることです。上記のコードは、定数文字列の最初の文字 h のアドレスを
ここに画像の説明を挿入
、ポインタ変数 pstr
文字ポインタの基本的な知識を習得したら、侵入テストの問題を見てみましょう。次のコードを参照してください。

#include <stdio.h>
int main()
{
    
    
 char str1[] = "hello bit.";
 char str2[] = "hello bit.";
 const char *str3 = "hello bit.";
 const char *str4 = "hello bit.";
 if(str1 ==str2)
 printf("str1 and str2 are same\n");
 else
 printf("str1 and str2 are not same\n");
 if(str3 ==str4)
 printf("str3 and str4 are same\n");
 else
 printf("str3 and str4 are not same\n");
 return 0;
}

ここに画像の説明を挿入
ここに画像の説明を挿入

2 ポインタの配列

整数配列 - 整数の配列 文字
配列 - 文字の配列 ポインタ
配列 - ポインタの配列
次のポインタ配列の意味を確認してみましょう。

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

ポインタの配列を使用して 2 次元配列をシミュレートしてみましょう

int main(int argc, char* argv[], char* envy[])
{
    
    
	int arr1[5] = {
    
     1,2,3,4,5 };
	int arr2[5] = {
    
     2,3,4,5,6 };
	int arr3[5] = {
    
     3,4,5,6,7 };
	int* arr[3] = {
    
     arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < 5; j++)
		{
    
    
			printf("%d ", *(*(arr + i) + j));
			Sleep(1000);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

*(*(arr + i) + j) ここで、最初の要素のアドレスがポインター変数 arr に格納されること、つまり arr1 配列名の最初の要素のアドレスが格納され、 + が 2 つの要素を走査することを意味します。ここで追加した Sleep(1000) は、配列を出力するために 1 秒間滞在することを意味します。
これは実際の 2 次元配列ではありません。実際の 2 次元配列はメモリに継続的に格納されます。

3 配列ポインタ

3.1 配列ポインタの定義

配列ポインタ
類似:
整数ポインタ - 整数変数へのポインタ、整数変数のアドレスを格納するポインタ変数 文字
ポインタ - 文字変数へのポインタ、文字変数のアドレスを格納する文字変数へのポインタ
配列ポインタ - 格納されている配列へのポインタ配列のアドレスの

ポインタ変数とは? 配列ポインタ次のコードのうちどれが配列ポインタですか?


int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

説明

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指
向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 & 配列名 VS 配列名

ここで配列名の理解について説明します
配列名とは、配列の最初の要素のアドレスです。

int main()
{
    
    
	int arr[10] = {
    
     0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

ただし、配列名は最初の要素のアドレスを示します。例外が 2 つあります
。1. sizeof (配列名)。ここでの配列名は配列の最初の要素のアドレスを示しません。配列名は配列全体を示します。sizeof (配列名) は配列全体のサイズを計算します。単位はバイト
2 です。&配列名。配列名は配列全体を表します。&配列名は配列全体のアドレスを取り出します。
また、他のすべての場所の配列名は配列の最初の要素のアドレス
ここで現象が発生しています。次のコードを参照してください。

int main()
{
    
    
	int arr[10] = {
    
     0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}

ここに印刷されている住所はどれも同じですが、本当に本質は同じなのでしょうか?
コードをデバッグして見てみましょう。
ここに画像の説明を挿入
表示されるアドレスは同じですが、型が異なります。ここでは、型が異なるため
、アドレス arr の型は int (*) [10] である必要があります。以下のコードを見てください

int main()
{
    
    
	int arr[10] = {
    
     0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	return 0;
}

このコード文字列をコピーしてコンパイルして実行すると、それぞれに 1 を加算する効果が異なることがわかります。その理由は、
ここに画像の説明を挿入
ポインタの型が異なると効果も異なるからです。
配列名 の場合
、 & 配列名はアドレスを取得します配列の。

3.3 配列ポインタの使用

int main()
{
    
    
	int arr[10] = {
    
     0 };
	//数组的地址存放到数组指针变量
	//int[10]*p=&arr;//error
	int(*p)[10] = &arr;
	//int*p1=&arr;
	return 0;
}

ここでの int*p1=&arr は、ポインタの型が一致しないため、警告を報告する可能性があります。上で、&arr のポインタの型が int(*p1)[10] であることを紹介しました。では、配列ポインタを使用するにはどうすればよいでしょうか
? 一般的には二次元配列に置くと便利です。

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    
    
 int i = 0;
 for(i=0; i<row; i++)
 {
    
    
 for(j=0; j<col; j++)
 {
    
    
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
void print_arr2(int (*arr)[5], int row, int col)
{
    
    
 int i = 0;
 for(i=0; i<row; i++)
 {
    
    
 for(j=0; j<col; j++)
 {
    
    
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
int main()
{
    
    
 int arr[3][5] = {
    
    1,2,3,4,5,6,7,8,9,10};
 print_arr1(arr, 3, 5);
 //数组名arr,表示首元素的地址
 //但是二维数组的首元素是二维数组的第一行
 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
 //可以数组指针来接收
 print_arr2(arr, 3, 5);
 return 0;
}

以下のコードを見てください

//二维数组传参,形参是指针的形式
void Print(int (*p)[5], int r, int c)
{
    
    
	int i = 0;
	for (i = 0; i < r; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < c; j++)
		{
    
    
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
    
    
	int arr[3][5] = {
    
     1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	//arr 是数组名,数组名表示数组首元素的地址
	Print(arr, 3, 5);
	return 0;
}

ここで *(*p+i)+j) について説明します。i を追加することは特定の行のアドレスを取得することと同じであり、j を追加して 2 次元配列の各行を横断することは要素として理解できます
。 2 次元配列の各 A 行は 1 次元配列でもあるため、2 次元配列は実際には 1 次元配列の配列です。2 次元配列の配列名は配列名でもあります
。配列名は最初の要素のアドレス 1 次元配列のアドレス - 配列のアドレス
ここに画像の説明を挿入
1 次元配列はパラメータを渡し、仮パラメータの一部は配列またはポインタになる可能性があります
。コード

//void test1(int arr[5], int sz)
//{}
//void test2(int* p, int sz)
//{}
//
//int main()
//{
    
    
//	int arr[5] = { 0 };
//	test1(arr, 5);
//	test2(arr, 5);
//	return 0;
//}


仮パラメータ部分は配列として書くこともできますが、本質的にはポインタなので、 2次元配列のパラメータを受け取るにはポインタを使用することを推奨します。仮パラメータ部分は配列またはポインタにすることができます

//void test3(char arr[3][5], int r, int c)
//{}
//
//void test4(char (*p)[5], int r, int c)
//{}
//int main()
//{
    
    
//	char arr[3][5] = {0};
//	test3(arr, 3, 5);
//	test4(arr, 3, 5);
//
//	return 0;
//}
//

配列ポインタ - ポインタ - 配列へのポインタ
ポインタ配列 - 配列 - ポインタを格納する
配列

4配列パラメータの受け渡し ポインタパラメータの受け渡し

コードを書く際に関数に「配列」や「ポインタ」を渡すことは避けられませんが、関数のパラメータはどのように設計すればよいのでしょうか?

4.1 1次元配列パラメータの受け渡し

#include <stdio.h>
void test(int arr[])//ok?
{
    
    }
void test(int arr[10])//ok?
{
    
    }
void test(int *arr)//ok?
{
    
    }
void test2(int *arr[20])//ok?
{
    
    }
void test2(int **arr)//ok?
{
    
    }
int main()
{
    
    
 int arr[10] = {
    
    0};
 int *arr2[20] = {
    
    0};
 test(arr);
 test2(arr2);
}

1 つ目は OK、パラメータを渡すために配列を受け取ることに問題はありません。2
つ目は OK、ただし要素数が書き出されます。3
つ目は OK、
4 つ目はポインタを使用して受け取ります。配列には 20 個の要素があり、各要素は int* です。受信するには int*arr[20] を使用しても問題ありません。5
番目の型も問題ありません。第 1 レベルのポインタのアドレスは第 2 レベルのポインタによって受信されるため、何も問題ありません。

4.2 2次元配列パラメータの受け渡し

void test(int arr[3][5])//ok?
{
    
    }
void test(int arr[][])//ok?
{
    
    }
void test(int arr[][5])//ok?
{
    
    }
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{
    
    }
void test(int* arr[5])//ok?
{
    
    }
void test(int (*arr)[5])//ok?
{
    
    }
void test(int **arr)//ok?
{
    
    }
int main()
{
    
    
 int arr[3][5] = {
    
    0};
 test(arr);
}

OK パラメーター部分の最初のタイプでは
、行は省略できますが、列は省略できません。要約
: パラメーターを 2 次元配列で渡す場合、関数パラメーターの設計では、最初の [] 番号のみを省略できます。
2 次元配列の場合、行数を知る必要はありませんが、計算しやすいように
1
行に要素が何個あるかを知っておく必要があります。 type は OK で
、4 番目の型では、配列名は最初の要素のアドレスです。2 次元配列は行のアドレスであり、特定の要素のアドレスではないため、エラーが発生します。5 番目の方法は機能しません
。 、配列の名前を渡したため、ここでの仮パラメータは単なるポインタの配列であるため、配列ポインタまたは 2 次元配列として記述されても機能しません。6 番目のメソッドは OK です。これは配列ポインタです。 、これは
1 行を
指すことができます。 7 番目のタイプのエラーです。配列名は最初の要素のアドレスを示しています。最初の行のアドレスが第 2 レベルのポインターによってどのように受け取られるのでしょうか。それは間違っているに違いありません。

4.3 第 1 レベルのポインタパラメータの受け渡し

#include <stdio.h>
void print(int *p, int sz)
{
    
    
	 int i = 0;
	 for(i=0; i<sz; i++)
 	 {
    
    
 		printf("%d\n", *(p+i));
	 }
}
int main()
{
    
    
 	int arr[10] = {
    
    1,2,3,4,5,6,7,8,9};
 	int *p = arr;
 	int sz = sizeof(arr)/sizeof(arr[0]);
	 //一级指针p,传给函数
	 print(p, sz);
	 return 0;
}

比較的簡単なので自分で理解してみましょう
ここで質問を考えてみましょ
う 関数のパラメータ部分が第 1 レベルのポインタの場合、関数はどのようなパラメータを受け取ることができますか?
例えば

void test1(int *p)
{
    
    }
//test1函数能接收什么参数?
void test2(char* p)
{
    
    }
//test2函数能接收什么参数?

ここに画像の説明を挿入

4.4 第 2 レベルのポインタパラメータの受け渡し

#include <stdio.h>
void test(int** ptr)
{
    
    
 printf("num = %d\n", **ptr); 
}
int main()
{
    
    
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

考察:
関数のパラメータが 2 次ポインタである場合、関数はどのようなパラメータを受け取ることができるでしょうか?

void test(char **p)
{
    
    
 
}
int main()
{
    
    
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);//Ok?
 return 0;
}

ここに画像の説明を挿入

5 関数ポインタ

関数ポインタ - 関数への
ポインタ 配列ポインタ - 配列へのポインタ
例については、コードを参照してください。

//int Add(int x, int y)
//{
    
    
//	return x + y;
//}
//
//int main()
//{
    
    
//	int arr[10] = {0};
//	int (*pa)[10] = &arr;
//	//printf("%p\n", &Add);
//	//printf("%p\n", Add);

結果はまったく同じです。独自のコンパイラで試してください。
関数名は関数のアドレスであり
、関数名は関数のアドレスでもあります。

//int Add(int x, int y)
//{
    
    
//	return x + y;
//}
//	int (*pf)(int, int) = &Add;//pf是函数指针变量
//	//int (*)(int, int) 是函数指针类型
//	return 0;
//}

コードの一部を見てみましょう

#include <stdio.h>
void test()
{
    
    
 printf("hehe\n");
}
int main()
{
    
    
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

出力結果は
ここに画像の説明を挿入
以下のコードのようになります。

//int Add(int x, int y)
//{
    
    
//	return x + y;
//}
//
//int main()
//{
    
    
//	//int (*pf)(int, int) = &Add;
//	int (*pf)(int, int) = Add;
//
//	int r = Add(3, 5);
//	printf("%d\n", r);
//
//	int m = (*pf)(4, 5);
//
//	printf("%d\n", m);
//
//	return 0;
//}

ポインターと呼ばれるには * が最初に pf と結合され、次に関数ポインターと呼ばれるにはパラメーターの型と結合される必要があることに注意してください。次の 2 つの興味深いコードを見てみましょう。まず最初のコードを見てみましょ

//代码1
(* (void (*)())0 )();

「この 0 は何ですか? 関数ポインタに関する関連知識は非常に複雑です。実際、このコード行には特定の意味があります。0 は次のことができることを説明しましょう」
これは整数です。0 はint
型と考えることができます。これは x0012ff40 と変わりません。
また、0x0012ff40 (これは 16 進数で表現された整数です) とすることもできます。
また、これをアドレス (アドレス) と考えることもできます。 address は数値です — 整数のアドレスにすることができます — Int

例:
//void(*p)() — p は関数ポインターです

//void(*)() は関数ポインタ型です。0
の前に括弧を追加しました。
これは 0 を次のように強制的に型変換したものです。

void(*)() はポインタ型である
ため、このコード行はアドレス 0 で関数を呼び出し、0 を次のように変換することを意味します。

void(*)() type の関数ポインタを
呼び出し、アドレス 0 で関数を呼び出します
。これは関数ポインタの知識を使用します。このコード行が C のトラップと欠陥から来ていることを理解してください。2 番目のコードを見てみましょ

//代码2
void (*signal(int , void(*)(int)))(int);

signal は関数宣言です。signal
関数には 2 つのパラメータがあります。最初のパラメータの型は Int で、2 番目のパラメータの型は void(*)(int) 関数ポインタ型です。関数ポインタが指す関数は次のとおりです
。 Int 型のパラメータ。戻り値の型は void です。

シグナル関数の戻り値の型も void(*)(int) 関数ポインタ型です。関数ポインタが指す関数には int 型のパラメータがあり、戻り値の型は void です。このコードは少し複雑です。少し
変更するには、typedef 関数を理解する必要があります

//	typedef void(*pf_t)(int);
//	pf_t signal(int, pf_t);
//	void (* signal(int, void(*)(int) ) )(int);
//

おすすめ

転載: blog.csdn.net/fjj2397194209/article/details/131581908