配列格納とポインタ学習メモ (3) ポインタと配列

組み込み C 言語学習の上級シリーズの記事

GUN C コンパイラ拡張文法学習ノート (1) GNU C 特殊文法編 詳細
GUN C コンパイラ拡張文法学習ノート (2) 属性宣言
GUN C コンパイラ拡張文法学習ノート (3) インライン関数、組み込み関数、利用可能な関数 変数パラメータマクロ
配列のストレージとポインターの学習メモ (1) データ型とストレージ、データの配置、データ移行、typedef
配列のストレージとポインターの学習メモ (2) 列挙型、定数、および変数



1. ポインタ

1.1 ポインタの性質

  メモリは一般に静的メモリと動的メモリに分けられ、プログラムがメモリにロードされて実行されると、コードセグメントとデータセグメントは静的メモリに属し、スタックは動的メモリに属します。

  • スタティック メモリの特徴は、メモリ内の各変数のアドレスがコンパイル時に決定され、プログラムの実行中に変更されないことです。
  • 動的メモリ内の変数のアドレスは、関数のローカル変数など、プログラムの実行中に固定されません。関数が複数回呼び出されて実行される場合、スタック フレーム スペースがスタック上にランダムに割り当てられる必要があります。各実行; ポインタの本来の目的 実際の
      目的は、匿名の動的メモリにアクセスすることです。ポインタを介して、指定されたメモリを直接読み書きできます。共通変数は通常、左辺値または右辺値として使用できる直接アドレス指定を使用します。ポインター変数は通常、間接アドレス指定を使用します。ポインタ変数が間接指定によってアドレス指定される場合、ポインタ変数は通常の変数と同等になり (次のコードの *p は a に相当します)、左辺値または右辺値として使用できます。

1.2 複雑なポインタ宣言

  ポインターの宣言は、実際にはポインターの型を宣言することになります。ポインタ型は一般に 3 つのカテゴリに分類できます。

  • 関数ポインタ: void(*fp)(int, int)。
  • オブジェクト ポインタ: char*、int*、long*、struct xx*。
  • ボイドポインタ: 通常、関数のパラメータとして一般的なポインタとして使用されます。
      関数ポインタは、その名前が示すように、ポインタは関数を指し、ポインタ変数には関数のエントリアドレスが格納されます。ポインターがさまざまなタイプのデータを指す場合、そのようなポインターをオブジェクト ポインターと呼びます。void
    ポインターは、オブジェクト ポインターでも関数ポインターでもありません。

  ポインタに関する演算子には主に以下の種類があります。

  • ポインタ宣言: int*.
  • 演算子のアドレス: &
  • 間接アクセス演算子: *
  • インクリメント演算子とデクリメント演算子: ++、--
  • メンバー選択演算子: .、->
  • その他の演算子: []、().
      優先順位は降順で次のとおりです[]、()、.、->、++、--、*、&
    ここに画像の説明を挿入

  この種の複雑なポインタ宣言の場合、「左と右の法則」を利用して分析できます。つまり、最初に最も内側の括弧 (未定義の識別子) を調べ、最初に右を見て、次に左を見てください。括弧が見つかった場合は、読み取り方向を逆にする必要があります。括弧内のすべてが解析されると、括弧はエスケープされます。このプロセスは、ステートメント全体が解析されるまで繰り返されます。
  上記のステートメントの最後のポインター宣言を分析してみましょう。まず、最も内側の括弧から見て、 f はポインターなので、ポインター式全体も決定的です。このステートメントはポインターを宣言します。次に右側を見ると、それはパラメータ リストであり、ポインタの型が関数ポインタであることを示しています。左側を見ると、ポインタが指す関数の戻り値がポインタであることを示す記号です。この時点で、括弧内の内容の解析が完了し、括弧の外に飛び出し、プロセスを繰り返し続けます。次のように、右を見ると配列、左を見ると int* になります。ここに画像の説明を挿入
  最終的な分析結果は、上記の分析を組み合わせることで取得できます。この複雑なポインター宣言は、関数を指す関数ポインターを定義するのと同等であり、この関数の型パラメーターは (int) で、戻り値はポインター配列です。ポインター。ポインター配列の要素の型は int* です。

ここに画像の説明を挿入

1.3 ポインタの種類と操作

  タイプは、値とそれらの値に対する関連操作のコレクションです。ポインタについても同様で、ポインタの型が異なれば演算も異なります。ポインタ変数 p は、通常、異なる型の変数を指すために使用されます。各変数は異なる型を持ち、メモリ内の異なる領域を占有するため、p++ は異なる数値演算に変換され、演算の結果も異なります。 。ただし、同じことが 1 つあります。p ++ は常に次の要素のアドレスを指します変数のアドレスを格納するには、ポインタ変数自体にも格納領域が必要です。
  加减运算: 2 つのポインタを直接減算することもできますが、ポインタの型が一貫している必要があり、減算のみが可能であり、加算はできません。減算の結果は、メモリ内の 2 つのポインタ間の距離を示します。2 つのポインターを減算した結果は、バイト単位ではなく、データ型の sizeof(type) 単位になります。
  关系运算: 2 つのポインターのサイズを比較できますが、比較の前提はポインターの型が同じである必要があり、ポインターのリレーショナル演算は通常同じ配列またはリンク リストで使用され、異なる比較結果は異なる意味を表します。

  • p < q: ポインタ p が指す番号は、q が指すデータの前にあります。
  • p > q: ポインタ p が指す番号は、q が指すデータよりも後ろにあります。
  • p == q:p と q は同じデータを指します。
  • p != q:p と q は異なるデータを指します。

1.4 ポインタと配列の関係

  使用の観点から:

  • 配列名が関数のパラメータとして使用される場合、それはポインタ アドレスと等価です。
  • 配列には、ポインターと同様に、間接演算子 * を使用してアクセスできます。
  • 配列には、ポインターと同様に、添字演算子 [] を使用してアクセスできます。

  C言語の文法から見ると、配列とポインタのアクセス方法や主な用途が異なります。
ここに画像の説明を挿入

  1. 添字演算子 []
      添字演算子 [] は配列要素にアクセスするために配列によって使用される演算子であり、間接アクセス演算子はメモリにアクセスするためにポインターによって使用される演算子です。論理的に言えば、配列とポインタは別のものですが、なぜ演算子を混在させることができるのでしょうか? その理由は次のとおりです。C语言对下标运算符的访问,是通过转化为指针来实现的。
    ここに画像の説明を挿入
      添字を介して配列 a[n] にアクセスすると、コンパイラはそれを
    (a+n) の形式に変換し、配列名 a は配列の最初の要素のアドレスを表します。これはポインタ定数と同等です。
    ここに画像の説明を挿入

  2. 配列名の本質
      配列を関数のパラメータとして使用する場合、アドレスが渡され、配列名は定数ポインタに相当します。実際、配列名には暗黙の変換があり、さまざまな場合に異なる意味を表します。配列名を使用して配列を宣言する場合、または配列名を sizeof、アドレス演算子 & と組み合わせて使用​​する場合、配列名は配列の型を表します。他の場合には、配列名は配列の最初の要素のアドレスを表す右辺値ですが、間接アクセス演算子 * と組み合わせて左辺値式を形成することができます。以下のプログラムに示すように。
    ここに画像の説明を挿入  プログラムの実行結果は以下の通りです。
    ここに画像の説明を挿入  さまざまな場合の配列名によって表される型とその暗黙的な変換を理解すると、値を配列に直接代入できないのは初期化時のみである理由も理解できます。

  3. ポインタ配列と配列ポインタ ポインタ
       配列は本質的に配列であり、配列内の各要素は通常のデータではなくアドレスを格納します。配列ポインタは、このポインタが指すデータ型が配列である点を除けば、本質的にはポインタです。配列要素へのアクセスは、添字演算子または間接アクセス演算子を使用して、配列またはポインターを通じて柔軟に実装できます。ポインタ配列の本質は依然として配列ですが、配列要素のデータ型は非常に特殊であり、ポインタであるため、配列の通常のアクセス方法に従ってアクセスできます。

2. ポインタと構造体

   構造体は、さまざまなタイプのデータのコレクションです。メンバー アクセス演算子 . を使用して各メンバーにアクセスするか、ポインタ間接アクセス演算子を使用して各メンバーにアクセスできます->ポインタや構造体に関する演算子は以下のとおりです。

  • メンバーアクセス演算子: .
  • メンバー間接演算子: ->.
  • 構造体メンバーのアドレス: &stu.num
  • 構造体のメンバーは次のように増加および減少します++stu.num、stu.num++
  • 間接アクセス演算子: *stu.p

   例えば。
ここに画像の説明を挿入   プログラムの実行結果は以下の通りです。
ここに画像の説明を挿入ここに画像の説明を挿入
   構造体のメンバーにアクセスするには、直接メンバー アクセスと間接メンバー アクセスの 2 つの方法があり、対応する演算子はそれぞれ stu.num と p->num です。構造体はスカラーです。構造体をパラメータまたは関数の戻り値として使用する場合、構造体全体のすべてのメンバーの値が渡されます。これは配列とは異なります。配列名をパラメータとして使用する場合、アドレスのみが渡されます。実際のプログラミングでは、パラメータを渡すために構造体が必要な場合、通常は構造体ポインタを使用してそれを実現しますが、アドレスを直接渡すだけなので簡単で効率的です。

3、セカンダリポインタ

   ポインタ変数は主に、メモリのアドレスを格納し、間接アクセス演算*子を介してこのメ​​モリにアクセスし、このメモリ上で読み取りおよび書き込み操作を実行するために使用されます。ポインタ変数は、配列、構造体、関数、さらには別のポインタ変数のアドレスなど、あらゆる種類の変数のアドレスを保持できます。ポインター変数が別のポインター変数のアドレスを保持している場合、そのポインターをポインターへのポインター、またはセカンダリ ポインターと呼びます。たとえば、
ここに画像の説明を挿入
   プログラムを実行した結果は次のようになります。変数によってバインドされたメモリ空間に
ここに画像の説明を挿入
   アクセスするには3 つの方法があります。a

  • 変数名によるa直接アクセス。
  • 第 1 レベルのポインターpと間接演算子を介し*た間接アクセス。
  • 2 次ポインターppと間接演算子を介し**た間接アクセス。
       セカンダリ ポインタはシーンを解決します。
  • ポインタ変数の値を変更します。
  • ポインター配列の渡しパラメーター。
  • 2 次元配列を操作します。

3.1 ポインタ変数の値を変更する

   関数のパラメータの受け渡しは値の受け渡しであり、渡されるのは変数のコピーであり、関数の仮パラメータを変更しても実パラメータの値は変わりません。第 1 レベルのポインターを介して、通常の変数の値を変更できます。ポインター変数の値を変更したい場合は、セカンダリ ポインターを使用して変更できます。
ここに画像の説明を挿入
  プログラムの実行結果は以下の通りです。上記のプログラムでは、関数を通じてポインタ変数 p の値を変更し
ここに画像の説明を挿入
  たい場合、関数のパラメータを二次ポインタとして設計し、ポインタ変数のアドレスを実際のパラメータとして関数に渡すだけです。 、および関数は、値を変更するメモリ アドレスを指定できます。change3()change3()&pchange3()p

3.2 2 次元ポインタとポインタの配列

  ポインタの配列は、各配列要素がポインタである点を除けば、本質的には配列です。1 次元配列の場合、配列が関数のパラメーターとして使用される場合、配列名は暗黙的に配列の最初の要素のアドレス、つまり第 1 レベルのポインターに変換されます。ポインターの配列が関数パラメーターとして使用される場合、配列名も最初の要素のアドレス、つまりポインター (2 次ポインター) のアドレスに暗黙的に変換されます。配列が関数パラメータとして使用される場合、一致する仮パラメータ形式は次のとおりです。
ここに画像の説明を挿入

3.3 二次ポインタと 2 次元配列

  1 次元配列の配列要素の型は でありint、この 1 次元配列を整数配列と呼びます。1 次元配列の配列要素の型は構造体であり、この配列を構造体配列と呼びます。1 次元配列の配列要素がまだ配列である場合、それを配列配列と呼ぶことはできませんが、一般にそれを 2 次元配列と呼びます。C 言語は、2 次元配列を特別な 1 次元配列として扱います。つまり、各要素は 1 次元配列です。1 次元配列は第 1 レベルのポインタを介して操作できますが、2 次元配列は第 2 レベルのポインタを介して操作するにはどうすればよいでしょうか?
ここに画像の説明を挿入  上記のコードには 2 つの代入ステートメントがあります。最初の p 代入ステートメントは問題ありません。ポインタ p は、1 次元配列の最初の要素のアドレスを指します。配列要素の型は で、ポインタの型は です。 2 つは一致し、プログラムはint正常にpコンパイルされますint*2 番目の文の第 2 レベル ポインタ pp の割り当てに関しては、コンパイラはコンパイル時に警告を発行します: 型に互換性がありません。
ここに画像の説明を挿入
  2 番目の文の代入ステートメントは pp=&b[0] と同等ですが、b[0] は何を表しますか? C 言語では、2 次元配列を 1 次元配列として扱います。2 次元配列は、b[3][5]実際には 1 次元配列ですb[3]。1 次元配列の各配列要素は、長さ 5 の 1 次元配列ですint c[5]b配列名をポインタ変数に直接割り当てる場合はpp、ポインタ変数の型をint(*p)[5]この型にする必要があります。この問題を解決するには、次の 2 つの解決策があります。

  • ポインタ配列アクセス

ここに画像の説明を挿入

  • セカンダリ ポインタ アクセス
    ここに画像の説明を挿入ここに画像の説明を挿入
  • 第 1 レベルのポインタ アクセス
    ここに画像の説明を挿入
      : 異なるポインタ タイプが自動インクリメント演算を実行する場合、実際のオフセット アドレスは異なります。ポインタを使用して配列を操作する場合、1 次元配列を操作するか 2 次元配列を操作するかに関係なく、プログラマは、定義するポインタの型が異なり、配列の操作方法も異なることを常に念頭に置く必要があります。このことを念頭に置いて、ポインター関連の宣言と演算子の優先順位をマスターして、ポインターを便利に使用できるようにします。

4. 関数ポインタ

  関数ポインタは関数を指すために使用され、一般的には関数ポインタ変数を定義して関数の入口アドレスを保存します。
ここに画像の説明を挿入
  呼び出し例は次のとおりです。
ここに画像の説明を挿入ここに画像の説明を挿入
  関数名 + 関数呼び出し演算子 () を使用して関数を呼び出すことができます。関数名の本質は、実際には関数を指すポインタ定数、つまり関数のエントリ アドレスです。fp=func; ステートメントでは、関数名は暗黙の変換によって fp=&func; の形式に変換されます。ポインターを介して関数を呼び出す場合、(*fp)() 間接アクセスは実際には fp() 式と同等です。以下に示すように、間接アクセスであっても、複数の間接アクセスであっても、その効果は実質的に同じであり、fp() と同等です。
ここに画像の説明を挿入
  違い: ポインタ関数は関数のタイプを指します。つまり、関数の戻り値がポインタであることを指します。それ以外は通常の関数と同じですので、ここでは繰り返しません。ポインタ関数は次のように宣言されます。
ここに画像の説明を挿入

5. 虚無との再会

  void* ポインタ、void キーワードは C 言語で広く使用されており、誰もが知っているものもあれば、馴染みのないものもあります。void は実際には型ですが、特別であり、値も操作もありません。

  • voidこれは、関数の戻り値の型を変更するためによく使用され、関数に戻り値がないことを示します。
  • void関数のパラメータとして使用される場合、関数にパラメータがないことを示します。
  • void*ポインターは任意のデータ型を指すことができ、void*必須の型変換を行わずに、任意の型のポインターをポインターに直接割り当てることができます。
  • void*ポインタを別の型のポインタに割り当てる場合は、必須の型変換が必要です。任意の型のポインターを に変換しvoid*、その後元の型に変換しても、データの損失は発生せず、値も変わりません。
    ここに画像の説明を挿入
      void*ポインタは主に関数のパラメータとして使用され、関数のパラメータが任意のポインタ型である可能性があることを示しています。関数の戻り値の型void*が の、返されるポインターは任意のデータ型を指すことができます。C 標準ライブラリの多くの関数プロトタイプはvoid*ポインターを使用します。
    ここに画像の説明を挿入
      malloc() 関数によって返されるポインタの型は void* であるため、malloc() 関数によって返されるアドレスをポインタ変数に割り当てる場合は、通常、強制的な型変換が必要です。ポインター型として、 void は通常、関数プロトタイプの変更を除いて、特定のポインター操作には関与しません。void* にアクセスするために間接アクセス演算子を使用することはできず、void* に対して添字演算を実行することもできませんが、GNU C では自己インクリメント演算と自己デクリメント演算を実行できます。

おすすめ

転載: blog.csdn.net/qq_41866091/article/details/130600804