C言語 - 関数

1. 関数とは何ですか?

関数の Wikipedia 定義:サブルーチン

· コンピュータ サイエンスにおいて、サブルーチン (英語: Subroutine、プロシージャ、関数、ルーチン、メソッド、
サブプログラム、呼び出し可能ユニット) は、1 つまたは複数のステートメント ブロックで構成される、大きなプログラム内のコードの特定の部分です
これは特定のタスクを完了する責任を負い、他のコードからは比較的独立しています。
· 一般に、入力パラメータと戻り値があり、プロセスの詳細をカプセル化して非表示にします。これらのコードは通常、ソフトウェア
ライブラリに統合されます。

2. 機能の分類

関数はライブラリ関数カスタム関数の 2 種類に分類できます。

2.1 ライブラリ関数

プログラムを作成するとき、scanf、printf などの関数を頻繁に使用することがありますが、使用するたびにそれらを定義するのは非常に面倒です。

したがって、これらは基本的な機能を説明するものであり、ビジネス コードではありません。C 言語の基本ライブラリは、移植性をサポートし、プログラムの効率を向上させるために、プログラマがソフトウェアを開発しやすいように、一連の同様のライブラリ関数を提供しています。

ここで Web サイトを紹介します: Reference - C++ Reference (cplusplus.com)

ここでは、さまざまなライブラリ関数にアクセスできますが、これらの関数を使用するには、対応するヘッダー ファイル #include をインクルードする必要があります。

2.2 カスタム関数

カスタム関数とは、特定のシナリオで使用される特別な意味を持つ関数を指すと思います。

具体的なシナリオは次のとおりです。 pow がどのライブラリに含まれているか忘れてしまったので、それを使用する必要がある。pow 関数をカスタマイズできる。

2.2.1 カスタム関数の構文

3. 関数パラメータ

3.1 実パラメータ(実パラメータ)

実際に関数に渡されるパラメータを実パラメータと呼びます。
実際のパラメータは、定数、変数、式、関数などです。
実際のパラメータがどのような種類の量であるかに関係なく、関数呼び出しを行うときは、これらの値を仮パラメータに渡すことができるように、明確な値が必要です。

3.2 仮パラメータ(仮パラメータ)

仮パラメータは、関数名の後の括弧内の変数を指します。仮パラメータは、関数の呼び出し時にのみインスタンス化される (メモリ ユニットが割り当てられる) ため、仮パラメータと呼ばれます。仮パラメータは、関数呼び出しが完了すると自動的に破棄されます。したがって、仮パラメータは関数内でのみ有効です。

3.3 例

スワップ関数を書きたいので、2 つの異なる書き方を見てみましょう。

 プログラムを実行すると、Swap1 が無効で、Swap2 が有効であることがわかります。この部分を理解するために関数呼び出しを見てみましょう。

4. 関数呼び出し

4.1 値による呼び出し

関数の仮パラメータと実パラメータはそれぞれ異なるメモリ ブロックを占有し、仮パラメータを変更しても実パラメータには影響しません。

上記の Swap1 は値による呼び出しであり、渡されたパラメータを保存するためにシステムが別の領域を開くのと同等であるため、実際のパラメータの値は変更せずに仮パラメータの値のみが交換されます。

仮パラメータがインスタンス化されると、それは実際には実パラメータの一時コピーと同等になります。

4.2 アドレスによる呼び出し

1. アドレス指定呼び出しとは、関数の外で作成した変数のメモリアドレスを関数の引数に渡すことで関数を呼び出す方法です。
2. パラメーターを渡すこの方法では、関数と関数の外部の変数の間に実際の接続を確立できます。つまり、関数の外部の変数を関数の内部で直接操作できます

 仮パラメータで実パラメータの値を変更する必要がある場合、実パラメータのアドレスを関数に直接渡すことができるため、変更が必要な実パラメータをアドレスから直接見つけることができます。

5. 関数のネストされた呼び出しと連鎖アクセス

関数は実際のニーズに応じて組み合わせることができ、つまり相互に呼び出すことができます。

5.1 ネストされた呼び出し

main 関数では、three_line を呼び出しただけですが、three_line で「hehe\n」が表示されませんでした。これは、new_line がthree_line 関数内にネストされており、new_line 関数が 3 回実行されるためです。ただし、関数を個別に定義する必要があるため、three_line 内に new_line を定義すると、このような状況は許容されません。

関数はネストして呼び出すことができますが、定義することはできません。

5.2 チェーンアクセス 

ある関数の戻り値を別の関数のパラメータとして使用します。

関数を書いてみましょう:

最も内側の printf は 43 を出力し、戻り値は 2 でした。中央の printf は 2 を出力し、戻り値は 1 でした。最後の printf は 1 を出力しました。 

連鎖アクセスって面白いですよね?

6. 関数の宣言と定義

6.1 関数宣言

1. 関数の名前、パラメータ、戻り値の型をコンパイラに伝えます。ただし、関数宣言では存在するかどうかを判断できません。
2. 関数の宣言は通常、関数を使用する前に行われます。最初に宣言してから使用する必要があります
3. 関数宣言は通常、ヘッダー ファイル内に配置されます。

 上記のように、バブルソート関数を作成した場合、メイン関数の下に配置した場合、メイン関数はそれを呼び出すことができますか?

答えは「はい」ですが、コンパイラはステートメントを上から下に実行するため、関数宣言を使用する必要があります。

使用する必要がある関数の最初の行に ; を追加し、main 関数の上に配置すると宣言が完了し、コンパイラはその関数を通常どおり使用できるようになります。

6.2 関数の定義

関数の定義は、関数の特定の実装を指し、関数の機能実装について説明します。
関数を定義するときの構文は次のとおりです。
同時に、関数は配列やポインターと組み合わせることができます。これについては後で説明します。

 

6.3 ヘッダファイルとソースファイルに関数を格納する方法

将来の作業シナリオでは、便宜上、コンパイラで関数の .h ファイルと .c ファイルを作成し、それぞれ関数の宣言と定義を保存できます。
.h ファイルでは、関数を宣言できます。 
もちろん、他の関数を使用できる場合は、stdio.h や他の関数など、他の関数をその中に入れることもできます。

.c ファイルでは、関数を定義できます。定義する前に、 #include ".h ファイル" を置く必要があります。.h ファイルは stdio.h とは異なり、<> の代わりに "" である必要があります。

このような .h および .c ファイルがある場合、メイン ファイルで #include ".h ファイル" を直接使用して関数呼び出し操作を実装できます。これはライブラリ関数を自分で構築するのと同じです。

 7. 関数の再帰(難易度)

7.1 再帰とは何ですか?

プログラムが自分自身を呼び出すプログラミング手法は再帰と呼ばれます。
アルゴリズムとしての再帰は、プログラミング言語で広く使用されています。プロセスまたは関数には、その定義または説明でそれ自体を直接または間接的に呼び出すメソッドがあります。通常、大規模で複雑な問題を、元の問題と同様の小さな問題に変換して解決します。再帰的戦略のみが必要です。必要なプログラムの数は少ないです。問題解決プロセスで必要となる複数の繰り返し計算を記述するため、プログラム コードの量が大幅に削減されます。
再帰についての主な考え方は、大きなものを小さくすることです
再帰は再帰 + 回帰です

7.2再帰に必要な 2 つの条件

制限条件があり 、この制限条件が満たされると再帰は続行されません。
· 各再帰呼び出しは、この制限にどんどん近づいていきます。

7.3 例

再帰には制限条件が必要であり、呼び出されるたびにその値はある程度変化し、この変化は要件に応じて変化することがわかります。

7.4 再帰と反復

7.4.1 スタックのオーバーフロー

フィボナッチ数列を書いてみましょう: 1、1、2、3、5、8、13、21、34...

次に、結果を出力してみましょう。5 と 10 を入力すると、プログラムはまだ正常に実行できることがわかりますが、50 を入力すると、カーソルは点滅しますが、プログラムは応答しないことがわかります。再帰値が大きすぎるため、プログラムがクラッシュします。

関数を再帰的に呼び出すたびに、実際にはコンピュータのスタック領域にその関数に対応するスペースが空きますが、再帰の回数が多すぎるとスタック オーバーフローが発生します。

システムがプログラムに割り当てるスタック領域には限りがありますが、無限ループやデッドリカージョンが発生するとスタック領域が割り当てられ続け、最終的にはスタック領域が枯渇してしまう現象をスタックといいます。オーバーフローです。

 7.4.2 反復

 反復は実際にはループです

1. 多くの問題が再帰的形式で説明されているのは、単に非再帰的形式よりも明確であるためです。
2. ただし、これらの問題の反復実装は、コードが若干読みにくくなりますが、多くの場合、再帰実装よりも効率的です。
3. 問題が複雑すぎて反復的に実装できない場合、再帰的実装の単純さによって、それがもたらす実行時のオーバーヘッドを補うことができます。

 

おすすめ

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