こんにちは!フリーランスの長野です。
関数って使ってますか?関数は同じ処理を何度も繰り返して使う必要がある場合に、一度定義しておいて後はそれを使いまわすことができるので便利です。
C言語では処理に使用するのも変数とは限らず、配列(ポインタ)、構造体なども使うことができます。
また処理した結果も戻り値で取得でき、変数とは限らず、ポインタ、構造体などを返すことが可能です。
また、ポインタを使って操作すると複数の処理結果を返すこともできます。この記事では、関数について
- 関数の作り方、呼び出し方
- 宣言(プロトタイプ宣言)について
という基本的な内容から、
- 値渡しと参照渡しについて
- 引数や戻り値に配列を指定する方法
- 引数や戻り値に構造体を指定する方法
- 関数形式マクロについて
- 関数ポインタについて
- 関数の一覧を作成する方法
など応用的な使い方の内容についても解説していきます。
今回は関数について、使い方をわかりやすく解説します!
関数の作り方、呼び出し方
関数とは処理のひとかたまりのことです。C言語では変数やポインタ、構造体などを使って処理を行います。
処理に使う変数やポインタ、構造体などのオブジェクトことを引数といいます。関数内では引数もしくは関数内で定義したオブジェクトのみ使用することができます。
また、返す処理結果のことを戻り値といいます。この戻り値を処理結果として取得することもできます。
戻り値の前には「return」句を記述します。戻り値には変数、ポインタ、構造体の実体などのオブジェクトを1つ指定することができます。
関数の作り方
関数名の前には戻り値の型を指定します。返す値がなければ型の指定の代わりに「void」句を記述します。
関数は下記のような形式になります。戻り値ありの場合:
型名 関数名(型名 引数1, 型名 引数2, ・・・) { 処理 return オブジェクト }
戻り値なしの場合:
void 関数名(型名 引数1, 型名 引数2, ・・・) { 処理 }
引数には変数やポインタ、構造体の実体のように処理で使用するオブジェクトの型と引数名を記述します。関数内では、引数名を使って処理を記述します。
なお「return」句を使用した以降の処理は実行されませんので、ご注意ください。
関数の呼び出し方
作った関数は以下のようにして呼び出して、処理を実行します。
戻り値ありの関数を呼び出す場合:
戻り値の型名 オブジェクト名 = 関数名(変数1, 変数2, ・・・)
戻り値なしの関数を呼び出す場合:
関数名(変数1, 変数2, ・・・)
関数の引数には、宣言時に指定した型と同じ型の変数を指定します。
サンプルコードで確認しましょう。
#include <stdio.h> const float pi = 3.14; // 関数の宣言と定義 float calc_a(float r) { float area = pi * r * r; return area; } float calc_l(float r) { float len = pi * 2 * r; return len; } void prt(float r, float f1, float f2) { printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, f1, f2); } // 関数の呼び出し、実行 int main(void) { float r = 10.0f, ans = 0, len = 0; ans = calc_a(r); len = calc_l(r); prt(r, ans, len); return 0; }
実行結果:
半径10.0の円の面積は314.00、円周の長さは62.80
このサンプルコードでは、実行関数mainの前に「calc_a」、「calc_l」、「void prt(float r, float f1, float f2)」関数のプ宣言、定義を行っています。
main関数内ではそれぞれの処理を実行しています。
宣言(プロトタイプ宣言)について
C言語では記述した順に上から処理が行われます。先に宣言、定義していない変数や関数を使うとコンパイルできません。
でも処理内容によっては記述量が多くなり、処理の順番を簡単に確認できない状況も発生してきます。そんなときには関数をプロトタイプ宣言すると便利です。
プロトタイプ宣言とは関数の戻り値の型や関数名、引数を使ってあらかじめソースコードの序盤に宣言だけしておくことをいいます。処理の定義、内容の記述については後で別に行います。
サンプルコードで確認しましょう。
#include <stdio.h> // グローバル変数(定数)の宣言と定義 const float pi = 3.14; // 関数のプロトタイプ宣言(関数の宣言のみ。関数の定義は後述。) float calc_a(float r); // 戻り値:float型。引数:float型。 float calc_l(float r); // 戻り値:float型。引数:float型。 void prt(float r, float f1, float f2); // 戻り値:なし。引数1:float型。引数3:float型。引数3:float型。 // 関数の呼び出し、実行 int main(void) { float r = 10.0f, ans = 0, len = 0; ans = calc_a(r); len = calc_l(r); prt(r, ans, len); return 0; } // プロトタイプ宣言した関数の定義 float calc_a(float r) { float area = pi * r * r; return area; } float calc_l(float r) { float len = pi * 2 * r; return len; } void prt(float r, float f1, float f2) { printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, f1, f2); }
実行結果:
半径10.0の円の面積は314.00、円周の長さは62.80
このサンプルコードでは、まず実行関数mainの前に「calc_a」、「calc_l」、「void prt(float r, float f1, float f2)」関数のプロトタイブ宣言を行っています。
実行関数mainの前では関数の定義、処理内容は記述していません。
しかし、main関数内ではそれぞれの処理を実行することができています。main関数の記述より後にそれぞれの関数の定義、処理を記述しています。
値渡しと参照渡しについて
これまでは主に関数での値渡しについて説明してきました。値渡しとは変数などのオブジェクトの値を引数や戻り値とすることです。
これに対して、オブジェクトのアドレスを引数や戻り値とすることを参照渡しといいます。オブジェクトのアドレスを使用するので、ポインタを使用します。
参照渡しはアドレスを関数の引数や戻り値としますので、関数内の処理では引数や戻り値のアドレスを操作することになります。
したがって、引数を参照渡しし関数内でその引数のアドレスを操作すると処理後に引数の値が変更されます。
これを利用して処理で変更したいオブジェクトのアドレスを引数に指定しておけば、関数内の処理でその引数の値を変更することができます。関数ではひとつのオブジェクトのみ戻り値とすることができますが、引数の数には制限がありません。
複数のオブジェクトの値を変更したい場合は引数で参照渡しにすれば、その複数のオブジェクトの値を処理変更することができます。
それではサンプルコードで確認しましょう。
#include <stdio.h> const float pi = 3.14; // 関数のプロトタイプ宣言 void calc(float r, float *area, float *len); // 値渡し(r)&参照渡し(area, len) void prt(float r, float f1, float f2); // 戻り値:なし。引数1:float型。引数3:float型。引数3:float型。 // 関数の呼び出し、実行 int main(void) { float r = 10.0f, area = 0, len = 0; // 変数の初期化 calc(r, &area, &len); prt(r, area, len); return 0; } // プロトタイプ宣言した関数の定義 void calc(float r, float *area, float *len) { *area = pi * r * r; *len = pi * 2 * r; } void prt(float r, float f1, float f2) { printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, f1, f2); }
実行結果:
半径10.0の円の面積は314.00、円周の長さは62.80
このサンプルコードではfloat型のポインタを2つ引数にもつ関数「calc」を宣言しています。この関数はmain関数以降に定義されています。
「calc」関数では「area」と「len」の2つのアドレス先の値を処理しています。ちなみにこの関数の戻り値はありません。
これをmain関数内で呼び出して使用しています。「calc」関数の引数には「&area」、「&len」と2つの変数のアドレスを指定しています。
「calc」関数の処理の結果を「prt」関数で出力表示するとfloat型の変数「area」と「len」の2つの値が処理され変わっています。
このように、関数の引数を参照渡しする場合には、ポインタを使用します。ポインタの使い方については、こちらで詳しく解説していますので、ぜひ参考にして下さい。
引数や戻り値に配列を指定する方法
引数や戻り値に配列を指定したい場合もあります。
ただC言語では配列そのものを指定することはできず、ポインタを使って配列のアドレスを指定します。ポインタを使って参照渡しで配列を指定することになります。
サンプルコードで確認しましょう。
#include <stdio.h> const float pi = 3.14; // 関数のプロトタイプ宣言 float *data(float *nums); // 戻り値:floatポインタ型。引数:ポインタ型(配列)。 void prt(float r, float f1, float f2); // 関数の呼び出し、実行 int main(void) { // 引数:ポインタ型(配列) float nums[3] = {10.0f, 0, 0,}; float *new_nums = data(nums); prt(new_nums[0], new_nums[1], new_nums[2]); return 0; } // プロトタイプ宣言した関数の定義 float *data(float *nums) { static float data[3]; data[0] = nums[0]; data[1] = pi * nums[0] * nums[0]; data[2] = pi * 2 * nums[0]; return data; } void prt(float r, float f1, float f2) { printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, f1, f2); }
実行結果:
半径10.0の円の面積は314.00、円周の長さは62.80
このサンプルコードでは、まず関数のプロトタイプ宣言をしています。そのなかで、float型のポインタを戻り値、引数とする関数「data」を宣言しています。
配列を戻り値とする関数は関数名にアスタリスクを付けます。関数は配列自体を返すことはできず、配列のポインタを返します。
また、関数は配列を引数とすることはできません。配列を引数とする場合は配列のポインタを引数にします。
配列の使い方についてはこちらで詳しく解説していますので、ぜひ参考にしてください。
引数や戻り値に構造体を指定する方法
引数や戻り値に構造体の実体を指定することができます。
サンプルコードで確認しましょう。
#include <stdio.h> const float pi = 3.14; typedef struct str { float r; float area; float len; } circle; // 関数のプロトタイプ宣言 circle calc(circle c); // 引数:circle型(構造体) void prt(float r, float f1, float f2); // 関数の呼び出し、実行 int main(void) { // 引数:circle型(構造体) circle c = {10.0f, 0, 0,}; c = calc(c); prt(c.r, c.area, c.len); return 0; } // プロトタイプ宣言した関数の定義 circle calc(circle c) { circle new_c = {c.r, pi * c.r * c.r, pi * 2 * c.r}; return new_c; } void prt(float r, float f1, float f2) { printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, f1, f2); }
実行結果:
半径10.0の円の面積は314.00、円周の長さは62.80
このサンプルコードでは、まず構造体の宣言が行われています。circle型を戻り値とする関数「calc」を宣言しています。これは構造体(struct)型を「circle」型に呼び変えた構造体の実体を戻り値とする関数です。
また構造体のcircle型の実体を引数としています。このように構造体型の実体を引数とすることも可能です。
構造体の使い方についてはこちらで詳しく解説していますので、ぜひ参考にしてください。
関数形式マクロについて
関数はマクロでも定義することができます。
「#define」句の記述の後に関数名と引数を記述し、その後に処理内容を記述します。
#define 関数名(変数) (処理内容)
関数形式マクロでは引数は型を指定する必要はありません。
また戻り値を「return」句で返す必要もありません。それではサンプルコードで確認していきましょう。
#include <stdio.h> #define PI 3.14 #define area(r) (PI*r*r) #define prt(f) printf("%f\n",f) int main(void) { prt(area(10.0f)); return 0; }
実行結果:
314.000000
このサンプルコードでは2つの関数をdefineマクロで定義しています。処理内容はカッコで囲っておいた方が意図しない処理が起こらず安心です。
関数ポインタについて
関数ポインタは関数が格納されたメモリアドレスを取得します。関数を呼び出す際に関数ポインタの内容を別の関数のアドレスに変更することで、呼び出す関数を変更することができます。
つまり呼び出す関数を動的に変更することができます。
ただし、引数の型と引数の数はそろえる必要があります。以下のように記述して宣言します。
typedef 戻り値の型 (*関数ポインタ型名)(引数1, 引数2);
使用するには以下のようにして、関数ポインタ型名のオブジェクトを生成する必要があります。
関数ポインタ型名 オブジェクト名;
詳しい使い方については、こちらのサイトで解説していますので、ぜひ参考にしてください。
関数の一覧を作成する方法
業務としてC言語でプログラミングを行う場合に、ファイルごとに関数の一覧を作成してドキュメントを作る必要がある場合もあります。
C言語では関数の一覧を取得するような関数は用意されていません。
別のプログラミング言語、例えばVBAなどで自作する方法もありますが、フリーソフトを利用する方法をオススメします。
開発環境のOSがWindowsの場合は、「EPTREE」がオススメです。「EPTREE」のダウンロード、使い方についてはこちらを参照してください。
https://freesoft-100.com/review/eptree.html
フリーソフトは「EPTREE」の他にもありますので、下記のサイトを参考に選択して試してみて下さい。
http://search.vector.co.jp/vsearch/vsearch.php?key=c+関数一覧
まとめ
ここでは、関数の使い方について説明しました。関数はプロトタイプ宣言を使うことで読みやすく宣言することができます。
また、変数やポインタ、構造体の実体など様々なオブジェクトを引数や戻り値に指定できます。引数に複数のポインタを指定し参照渡しを行うと、複数のオブジェクトの処理を1つの関数で行うこともできます。
関数を使いこなすことができるように、この記事を何度も参考にして下さいね!