【C言語】応用ポインタ1


ポインタとは、メモリ空間を一意に識別するアドレスを格納する変数です。ポインタのサイズは 4/8 バイト固定です (32 ビット プラットフォーム/64 ビット プラットフォーム)。ポインタには型があり、ポインタの型によって、ポインタの±整数ステップ サイズと、ポインタ逆参照操作中の権限が決まります。

1. 文字ポインタ

ポインタの型の中には、文字ポインタ char* と呼ばれるポインタ型があることがわかります。

int main()
{
    
    
	char ch = 'a';
	char *p = &ch;
	*pc = 'b';
	return 0;
}

次のコードを見てください

int main()
{
    
    
	const char* p = "hello world";
	printf("%s\n", p);
	printf("%c\n", *p);
	return 0;
}

ここで p ポインタ変数に文字列が入れられますか?
ここに画像の説明を挿入します

実際にはそうではありません。後続の定数文字列のアドレスは、実際には最初の文字のアドレスです。本質的に、最初の文字 h のアドレスは p に与えられます。実際、p はこの定数文字列を指します。

コード const char* p = “hello world”; は、文字列 hello world が文字ポインタ p に配置されると特に考えやすいですが、本質は、文字列 hello world の最初の文字のアドレスが配置されるということです。 p. 実際には、定数文字列の最初の文字 h のアドレスをポインタ変数 p に格納します。

以下の質問を見てください

int main()
{
    
    
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)
		printf("str1和str2相同\n");
	else
		printf("str1和str2不相同\n");
	if (str3 == str4)
		printf("str3和str4相同\n");
	else
		printf("str3和str4不相同\n");
	return 0;
}

最終的な出力は次のとおりです。
ここに画像の説明を挿入します

str1 と str2 に格納される内容は同じですが、str1 と str2 は異なるアドレスを持つ 2 つの異なる文字配列です。str3 と str4 は 2 つの異なる文字ポインタですが、どちらも同じアドレス str3 と str4 を指します。格納アドレスは次のとおりです。同じ。

C/C++ は定数文字列を別のメモリ領域に保存します。複数のポインタが
同じ文字列を指す場合、実際には同じメモリを指します。ただし、同じ定数文字列を使用して異なる配列を初期化すると、異なるメモリ ブロックが開かれます。したがって、str1 と str2 は異なり、str3 と str4 は同じです。

2. 配列ポインタ

2.1 配列ポインタの定義

ポインターが配列を指す場合、それを配列ポインターと呼びます。
たとえば、配列へのポインタを定義することもできます。

int arr[] = {
    
     9, 15, 10, 8, 2 };
int *p = arr;

arr 自体はポインターであり、ポインター変数 p に直接割り当てることができます。arr は配列の 0 番目の要素のアドレスであるため、int *p = arr; は int *p = &arr[0]; と書くこともできます。つまり、arr、p、および arr[0] を記述する 3 つの方法は同等であり、それらはすべて配列の 0 番目の要素、または配列の先頭を指します。
次のコードのうち、配列ポインタはどれですか?

int *p1[10];
int (*p2)[10];

答えは int (*p2)[10]; です。
なぜなら、p2 は最初に * と結合され、p2 がポインター変数であることを示し、次に 10 個の整数の配列を指すからです。したがって、p2 は配列を指すポインターであり、配列ポインターと呼ばれます。
注: [] は * よりも優先度が高いため、p が最初に * と結合されるように () を追加する必要があります。

2.2 配列名(&A) VS 配列名

int arr[10];

arr と &arr とは何ですか?
arr が配列名であり、配列名が配列の最初の要素のアドレスを表すことがわかります。&arr 配列名とは正確には何でしょうか? コードの一部を見てみましょう。

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

実行結果は次のとおりです。
ここに画像の説明を挿入します

配列名と&配列名によって出力されるアドレスが同じであることがわかります。3つとも同じですか?別のコードを見てみましょう。

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

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

上記のコードによると、実際には &arr と arr の値は同じですが、その意味は異なるはずであることがわかりました。
実際: &arr は、配列の最初の要素のアドレスではなく、配列のアドレスを表します。&arr の型は int(*)[10] です。これは配列ポインター型のアドレス array + 1 で、配列全体のサイズをスキップします。そのため、&arr+1 と &arr の差は 40 です。
配列名の場合、&arrayname は配列のアドレスを取得します。

2.3 配列ポインタの使用

配列ポインタはどのように使用されますか? 配列ポインタは配列を指すため、配列のアドレスを配列ポインタに格納する必要があります。

void print_arr(int(*arr)[5], int row, int col)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int j = 0;
		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_arr(arr, 3, 5);
	return 0;
}

配列名 arr は、最初の要素のアドレスを表します。ただし、2 次元配列の最初の要素は 2 次元配列の最初の行になります。したがって、ここで渡される arr は、実際には最初の行のアドレス、つまり 1 次元配列のアドレスと等価です。配列ポインタで受信可能

3. 配列パラメータの受け渡しとポインタパラメータの受け渡し

3.1 1次元配列パラメータの転送

void test1(int arr[])
{
    
    }
void test1(int arr[10])
{
    
    }
void test1(int *arr)
{
    
    }
void test2(int *arr[20])
{
    
    }
void test2(int **arr)
{
    
    }
int main()
{
    
    
	int arr[10] = {
    
    0};
	int *arr2[20] = {
    
    0};
	test1(arr);
	test2(arr2);
	return 0;
}

3.2 2次元配列パラメータの転送

void test(int arr[3][5])
{
    
    }
void test(int arr[][5])
{
    
    }
void test(int (*arr)[5])
{
    
    }
void test(int **arr)
{
    
    }
int main()
{
    
    
	int arr[3][5] = {
    
    0};
	test(arr);
}

概要: パラメータを 2 次元配列で渡す場合、関数パラメータの設計では最初の [] 番号のみを省略できます。2 次元配列の場合、行数を知る必要はありませんが、行内に要素が何個あるかは知っておく必要があるためです。これにより計算が容易になります。

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

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;
}

3.4 二次ポインタパラメータの受け渡し

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;
}

4. 関数ポインタ

まずコードの一部を見てください。

void test()
{
    
    
	printf("hello\n");
}
int main()
{
    
    
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

出力結果:
ここに画像の説明を挿入します
関数名と & 関数名は両方とも関数のアドレスです。
では、関数のアドレスをどのように保存すればよいでしょうか? 答えは関数ポインタです。

void (*p)() = &test;

p は最初に * と結合されており、p がポインタであることを示します。ポインタは関数を指します。ポイントされた関数にはパラメータがなく、戻り値の型は void です。
以下の関数を見てください。ポインターでどのようにポイントするのでしょうか?

int add(int a,int b)
{
    
    
	return a + b;
}

答え:

int (*p)(int, int) = &add;

呼び出し関数ポインタ
ここに画像の説明を挿入します

ご覧いただきありがとうございます。私の記事があなたのお役に立てれば幸いです。皆様のご支援に感謝します、一緒に頑張りましょう!

おすすめ

転載: blog.csdn.net/qq_58032742/article/details/132090313