C言語 - ポインタの完全版

目次

1. ポインタ操作

1.1 ポインタ +- 整数

1.2 ポインター – ポインター

1.3void* ポインタ

2. 配列のポインタの走査

2.1 配列のポインタの走査

1. 配列名の意味 (および配列名と配列名の違い) を理解します。

2. ポインタを使用して配列を走査する 

3. ポインタ配列、配列ポインタ、関数ポインタ

3.1 ポインタ配列

3.1.1 ポインタ配列の形式

3.1.2 ポインタ配列の使用例

3.2 配列ポインタ

3.3 関数ポインタ

3. 関数ポインタの配列

3.1 関数ポインタ配列の形式

3.2 関数ポインタ配列の用途:転送テーブル

4. 関数本体パラメータとしての配列

5. 関数の戻り値として配列を使用する

5.1 静的ローカル配列のアドレスを返す

5.2 リテラル定数領域の文字列のアドレスを返す

5.3 ヒープ内容のアドレスを返す

5.4 概要


1. ポインタ操作

1.1 ポインタ +- 整数

概要: ポインターの種類によって、ポインターが前後に移動する距離 (距離) が決まります。

1.2 ポインター – ポインター

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

2 つのポインターを使用して減算する場合、上の図から推測できるように、出力は 2 つのアドレス間の要素の数でしょうか、それとも 2 つのアドレスの差 (バイト) でしょうか?

実行すると、ポインタ - ポインタ: ポインタとポインタの間の要素の数の結果がわかります。

それでは、任意の 2 つのポインターを減算できるでしょうか? 上記の答えから次のことが得られます。

ポインタ - ポインタの前提: 2 つのポインタは同じ領域を指し、ポインタの型は同じです。

したがって、小さいポインターと大きいポインターを使用すると、結果は -9 になります。

したがって、より厳密な結論を得ることができます。

ポインター - ポインターの結果。その絶対値はポインター間の要素の数です。

1.3void* ポインタ

void* 型のポインターは逆参照演算子には使用できません。また、+ - 整数演算にも使用できません。

void* 型のポインターは、あらゆる種類のデータのアドレスを格納するために使用されます。

void* 具体的な型を持たないポインタ

void* ポインターを使用すると、任意のタイプのポインターを受け取ることができ、void* ポインターを逆参照する必要がある場合は、キャストできます。

2. 配列のポインタの走査

2.1 配列のポインタの走査

1. 配列名の意味 (および配列名と配列名の違い) を理解します。

配列名は、配列の最初の要素のアドレスです。例外が 2 つあります。
1. sizeof(配列名)、配列全体のサイズを計算します。sizeof は内部に別の配列名を置き、配列名は配列全体を表します。
2. & 配列名は配列のアドレスを取り出します。& 配列名。配列名は配列全体を表します。
1 と 2 の 2 つのケースを除き、すべての配列名は配列の最初の要素のアドレスを表します。

& 配列名と配列名の違いを理解するためにコードを見てみましょう。

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

上記のコードによると、実際には &arr arrの値 は同じですが、その意味は異なるはずであることがわかりました。
実際: &arr は、 配列の最初の要素のアドレスではなく、配列のアドレス を表します。
配列のアドレス は +1 で 、配列全体のサイズをスキップするため、 &arr+1と &arr の差は 40 になります

2. ポインタを使用して配列を走査する 

配列名をアドレスとしてポインタに格納できるため、ポインタを使用して配列名にアクセスすることが可能になります。

#include <stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    int *p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr)/sizeof(arr[0]);//数组元素个数
    for(int i = 0; i < sz; i++)
   {
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
   }
    return 0;
}

 ご覧のとおり、ポインターを介して配列の各要素をトラバースできます。

p+i は実際に、インデックス i を持つ配列 arr のアドレスを計算します
その後、*(p+1) は配列 arr の各要素にアクセスできます

3. ポインタ配列、配列ポインタ、関数ポインタ

3.1 ポインタ配列

ポインタ配列は、ポインタを格納する配列です。

3.1.1 ポインタ配列の形式

まず、ポインター配列がどのようなものかを見てみましょう (他の型にも同じことが当てはまります)。

int* arr[5];    //数组名称:arr  数组元素个数:5  数组元素的类型:int* 

では、ポインターの配列を知っていて何の役に立つのでしょうか?

3.1.2 ポインタ配列の使用例

ほとんどの場合、配列名が配列の最初の要素のアドレスを表すことはすでにわかっているため、ポインター配列を使用して 2 次元配列をシミュレートできます。

int arr1[] = { 0,1,2,3,4,5 };
int arr2[] = { 1,2,3,4,5,6 };
int arr3[] = { 2,3,4,5,6,7 };

int* arr[] = { arr1,arr2,arr3 };

では、このシミュレートされた 2 次元配列をどのように使用すればよいのでしょうか?

添え字を使用して直接アクセスできます。

違い:

シミュレートされた配列と実際の 2 次元配列の違いは、実際の 2 次元配列の各要素の要素アドレスは連続しているのに対し、シミュレートされた 2 次元配列の各 1 次元配列の 1 次元配列アドレスは連続していることです。配列が連続していません。

3.2 配列ポインタ

配列ポインタは配列へのポインタです。

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

整数、全精度浮動小数点などはすべて int、double というデータ型を持っていることはわかっています。もちろん、ここでは配列ポインターにもデータ型があります。

3.3 関数ポインタ

まずはコードを見てみましょう

#include <stdio.h>
void test()
{
 printf("hello!\n");
}
int main()
{
 printf("%p\n", test);//打印test地址
 printf("%p\n", &test);//打印&test地址
 return 0;
}

実行結果から、関数名と配列名は実際には同じ効果を持っていることがわかりますが、観察の便宜上、将来関数アドレスが必要になった場合は、引き続き & 関数名を使用して実装します

ポインタを使用して上記のテスト関数を保存したい場合、どうすればよいでしょうか?

void(*pf)() = &test;
pf1 は最初に * と結合されており、pf1 がポインタであることを示します。ポインタは関数を指します。ポイントされた関数にはパラメータがなく、戻り値の型は void です。

上記から次のことが得られます。

int max(int a, int b)
{
	if (a > b)
		return a;
	else
		return b;
}
int main()
{
	int (*pf2)(int, int) = &max;
	return 0;
}

このようにして、関数ポインタの形式を明確に理解できます。

3. 関数ポインタの配列

3.1 関数ポインタ配列の形式

配列とは、同じ型のデータを格納する記憶領域であり、ポインタ配列についてはすでに学習しました。
例えば:
int * arr [ 10 ];
// 配列の各要素は int*
次に、関数のアドレスを配列に格納する必要があります。この配列は関数ポインタの配列と呼ばれます。関数ポインタの配列はどのように定義するのでしょうか?
(上記の int max(int a, int b) を例として、関数ポインター配列にそのような関数が格納されていると仮定します)
int ( * parr [ 10 ])( int, int );
Parr は最初に [ ] と結合され、parr は配列のように見えます。
配列の内容は何ですか?
これは、 int (*)(int, int) 型の関数ポインターです。

3.2 関数ポインタ配列の用途:転送テーブル

関数ポインターの配列を使用する便利さを確認するために、単純な計算機を例に挙げてみましょう。
準備するもの:
関数ポインター配列が使用されていない場合は、ユーザー入力に従って 1 つの関数を 1 つずつ呼び出す必要があります。
関数配列を使用した後は、準備段階で 4 つの関数すべてを関数ポインターの配列にカプセル化するだけです。

次回使用するときは、柔軟に使用できます。

かなり簡略化されているんじゃないでしょうか?

4. 関数本体パラメータとしての配列

関数本体を定義する際、引数が配列の場合、配列またはポインタのどちらでも受け取ることができますが、配列の要素の添字とは関係なく、配列で受け取ることができます。

5. 関数の戻り値として配列を使用する

配列を返すことは、実際にはポインタを返すことになります。

char* fun()
{
	char str[100] = "hello world!";
	return str;
}
int main()
{
	char* pf;
	pf = fun();

	printf("%s\n", pf);

	return 0;
}

5.1 静的ローカル配列のアドレスを返す

上記のコードには明らかな欠陥があります。

str は fun 関数本体で作成され、一時変数です。main 関数で pf を使用して受け取りたい場合、str は破棄されています。このとき、fun 関数内の static で str を変更して拡張できます。それ、そのライフサイクル。

動作は図のようになります。

5.2 リテラル定数領域の文字列のアドレスを返す

定数文字列は定数であり、常に存在します。

5.3 ヒープ内容のアドレスを返す

malloc ヘッダー ファイル:

#include<stdlib.h>

malloc は str のメモリに動的に適用され、サイズは 100、単位はバイトです。動的に割り当てられたメモリは、関数の終了後に解放されません。

ヒープ領域の内容は、解放されるまで常に存在します。

5.4 概要

返されたアドレスとそのアドレスが指すメモリの内容は、意味のあるものとして存在する必要があります。

おすすめ

転載: blog.csdn.net/m0_75186846/article/details/132081257