2023年03月26日くいなちゃん


6さいからのプログラミング」第4話では、最も重要な概念である関数を紹介します!

1関数

第3話では、型とリテラルについて解説しました。 今回は、プログラミングにおける重要な概念である「関数」を紹介し、実際にプログラムを書いていきます。
プログラミングでの「関数」とは、複数の命令を1つの便利な命令としてまとめたものです。 Hello, world! の解説のところで、C言語の「printf("Hello, world!\n");」とは「画面に『Hello, world!』と表示せよ」という命令であると説明しましたが、このprintfが実は関数です。 見た目は数学の関数に似てはいますが、プログラミングにおける関数はあくまで「命令をまとめたもの」だと考えましょう。
さて、プログラミングにおける関数は、数学の関数と同様、与えられた値に応じて値を返すことができます。 例えば、C言語に初めから用意されている「sin」や、Javaの「Math.sin」は、数学のsin関数と同じような振る舞いをします(表1-1)。
表1-1: プログラミング言語と数学の関数
関数
数学の「sin(1)」 0.8414709848...
C言語の「sin(1.0)」 0.8414709848078965
Javaの「Math.sin(1.0)」 0.8414709848078965
JavaScriptの「Math.sin(1.0)」 0.8414709848078965
C#の「Math.Sin(1.0)」 0.8414709848078965
数学のsin(1)は小数が延々と続きますが、C言語のsin(1.0)などは倍精度浮動小数点型で返るため普通は16桁程度です。
数学の関数に中身はありませんが、C言語のsinなどは命令をまとめたものに過ぎないため、sinの中には数学のsin関数と同じになるように命令が書き連ねられています。 C言語のsin関数の中身を見てみましょう(図1-1)。
  1. double sin(double x)
  2. {
  3.   double n = 1.0, y = 1.0, d = 1.0, y2;
  4.   double pi2 = 3.14159265358979323846 * 2.0;
  5.   x -= (double)(int)(x / pi2) * pi2;
  6.   do
  7.   {
  8.     d *= -x * x / (2.0 * n * (2.0 * n + 1.0));
  9.     y2 = y;
  10.     y += d;
  11.     n += 1.0;
  12.   } while (y != y2);
  13.   return x * y;
  14. }
図1-1: C言語のsin関数の実装例
このプログラムの意味はさておき、C言語ではこのようなsin関数が「math.h」というファイルに用意されており、「#include <math.h>」と書くことで好きなプログラムに埋め込むことができます。 「#include」とは、他の場所にあるソースコードを呼び出す機能です。 「#include <math.h>」と書く代わりにこのsin関数の中身を書いても、同じプログラムが出来上がります。

2引数と戻り値

sin関数は、与えられた値に応じて値を返す関数でしたが、この与えられる値のことを「引数ひきすう」といい、返す値を「もど」といいます。 引数を複数受け取ったり、引数を受け取らない関数を作ることもできます。 多くの言語では、関数は戻り値を最大1つまでしか返すことができませんが、戻り値を返さない関数も作成できます。 表2-1に、C言語の関数をいくつか紹介します。
表2-1: C言語の関数の一部
関数名 引数の型 戻り値の型 解説
sin double double 数学のsin関数と同様
rand void int 乱数を返す
exit int void プログラムを終了する
pow double, double double xのy乗を計算する
printf char*, ... int 画面に文字列を表示させる
他の言語でもこれらと似たような関数が用意されていますので、ひとまずC言語の関数を把握しましょう。
C言語の「voidヴォイド」とは、「値が無い」ことを示す型です。 つまり、この表のrand関数は引数が無いことを意味し、exit関数は戻り値が無いことを意味します。
pow関数は、「」を計算する関数で、引数を2つ(xとy)受け取り、両方ともdouble型です。 double型とは倍精度浮動小数点型で、小数を扱う型でした。
文字列を画面に表示させるprintf関数は、2つ目の引数が「...」となっていますが、これは引数をいくつでも受け取れるという意味です。 このように、受け取る数が決まっていない引数のことを「可変長引数かへんちょうひきすう」といいます。

3return文

関数の戻り値を返す命令として、C言語やJavaなどでは「return文」を使います。 この命令が実行されると、戻り値が返されると同時に、関数を抜けます(図3-1)。
return文の流れ
図3-1: return文の流れ
この図ではreturn文が呼ばれるたびに、呼び出された関数を抜けています。
return文の表記は「return 戻り値;」と書きます。 例えば、戻り値として「5」を返す場合は「return 5;」とします。 戻り値が無い(void)関数の場合は、「return;」とだけ書くことで関数を途中で抜けることができます。

4関数定義

C言語で関数を作るには、「戻り値の型 関数名(引数1, 引数2, ) { 関数の中身 }」と書きます。 例えば、引数を受け取らずに常にint型の「4」を返す関数を「foo」という名前で作ると、図4-1のようになります。
  1. int foo(void)
  2. {
  3.   return 4;
  4. }
図4-1: C言語の関数定義の例
関数を呼び出すときは、「関数名(引数1, 引数2, )」と書きます。 引数が無い場合は、括弧の中を空にします。 例えば、このfoo関数を呼び出すときは「foo()」と書きます。
なお、JavaやC#などでは関数はどこに作っても良いですが、C言語やC++では、関数は「その関数が呼び出される箇所よりも上に書かなければならない」という制約があります。 もしどうしても下に書きたい場合は、「{ 関数の中身 }」の部分を「;」に置き換えた「プロトタイプ宣言せんげん」と呼ばれるものを代わりに上に書きます(図4-2)。
  1. int foo(void);
  2.  
  3. このあたりでfoo関数を呼び出す。
  4.  
  5. int foo(void)
  6. {
  7.   return 4;
  8. }
図4-2: プロトタイプ宣言の例
やや面倒なルールですが、コンパイラがソースコードをコンパイルするときには上から順番に見ていきますので、呼び出し部分より上に関数のことが書かれていたほうがコンパイラにとって都合が良いのです。
ちなみに同じプログラムをC#で書いた例は図4-3の通りです。
  1. int foo()
  2. {
  3.   return 4;
  4. }
図4-3: C#の関数定義の例
基本的にC言語と同じですが、C#やJavaでは引数が無いときは「()」とだけ書きます。 また、JavaScriptPHPで書いた例は図4-4の通りです。
  1. function foo()
  2. {
  3.   return 4;
  4. }
図4-4: JavaScriptやPHPの関数定義の例
JavaScriptやPHPでは基本的に型を明記しません。 戻り値の型の代わりに「function」と書いて関数を定義します。

5関数呼び出し

それでは第2話の Hello, world! のところで出てきた「printf」を例に、C言語の関数の仕組みに踏み込んでいきましょう。 Hello, world! のところでもやりましたが、「printf("Hello, world!\n");」という命令を呼び出すと、まずメインメモリ上のどこかにある "Hello, world!\n" という値のアドレスが、スタック領域に積まれます(図5-1)。
スタック領域と引数
図5-1: スタック領域と引数
図では、A4番地から順に「H=48」「e=65」「l=6C」…と「Hello, world!\n」の文字列が書き込まれていて、先頭アドレスA4がスタック領域に積まれています。
そしてprintf関数の中では、スタック領域に積まれたアドレスを取り出し、アドレスにある文字列を画面に表示します。 また最後にprintfを呼び出した側がスタック領域を空にします。 このように、関数の引数とは一般にスタック領域を通じて値がやり取りされる仕組みになっています。 ちなみに、printf関数に引数を複数渡したときの挙動は図5-2のようになります。
スタック領域と複数の引数
図5-2: スタック領域と複数の引数
引数に「5」と「Hello, world!\n」を渡すと、スタック領域には「Hello, world!\n」「5」の順番で値が積まれています。
このように、引数を逆順に積んで、関数を呼び出した側がスタックを空にするという流れは、「cdeclシーデクル」と呼ばれます。 この他にも、呼ばれた関数内でスタックを空にする「stdcallスタンダードコール」や、引数を左から積む方法、さらにはCPUのレジスタを使って引数を渡す方法など、いろいろな関数の呼び出し方があります。 これらの方法を「規約きやく」といいます。 呼び出す側と呼び出される側が同じ呼び出し規約でやりとりする必要があります。

6エスケープシーケンス

「printf("Hello, world!\n");」の「\n」が気になっていると思いますが、これは「改行」を表します。 つまり、画面に「Hello, world!」を表示したあとに改行しています。 このようにC言語やJavaなどでは「\」を使って特殊な文字を表すことができます。 このような表記を「エスケープシーケンス」といいます。 C言語の主なエスケープシーケンスは表6-1の通りです。
表6-1: C言語の主なエスケープシーケンス
表記 意味
\n 改行
\t タブ文字
\\ 「\」の文字
\" 「"」の文字
\' 「'」の文字
\x5A 文字コード「5A」の文字
C言語では文字列は「"」で囲むので、文字列の中で「"」という文字を使いたい場合は文字列の終わりと区別するために「\"」と書く必要があります。 また「\」という文字を普通に表現したいときは「\\」と書きます。
Windowsなどでは、改行が「\n」ではなく「\r\n」と決められていることがあり、その場合「\r\n」と書かないと改行されないことがあります。 「\r」は「復帰」という意味の特殊文字で、単体で使われることは滅多にありません。
この他にもいくつかのエスケープシーケンスがあり、「\a」と書くと音が鳴ったりしますが、実際のプログラムではほとんど使われません。

7書式指定文字列

C言語のprintf関数には他にも、引数の値を文字列に埋め込む機能が存在します。 こちらは「書式指定文字列しょしきしていもじれつ」と呼ばれ、「%d」のように「%」の記号を用います。
書式指定文字列は、引数を1つ取ってきて、その値を埋め込みます。 例えば、「printf("AB%dCD", 4);」と書くと、引数から「4」という値を取ってきて「%d」の部分に埋め込み、画面には「AB4CD」が表示されます。 書式指定文字列1つにつき、引数が1つずつ順番に埋め込まれます。 「printf("%d%d%d", 1, 2, 3);」と書くと、画面には「123」が表示されます。
「%」という文字そのものを画面に表示したい場合は「%%」と書きます(「\%」ではありません)。 printf関数の書式指定文字列はとても多機能です。 表7-1に一部を紹介します。
表7-1: printfの書式指定文字列の一部
表記 解説 備考
%d 整数型の値を取ってきて10進数の整数として表示 Decimalの略
%X 整数型の値を取ってきて16進数の整数として表示 heXadecimalの略
%f 浮動小数点型の値を取ってきて小数として表示 Floating-pointの略
%c 文字型の値を取ってきて文字として表示 Characterの略
%s 文字列型の値を取ってきて文字列として表示 Stringの略
この他にも、「%d」の代わりに「%04d」と書くと「1」の代わりに「0001」と表示されたり、「%.10f」と書くと小数点以下10桁が表示されたりと多機能です。

8Hello, world!

それでは、改めてC言語の Hello, world! のプログラムを見てみましょう(図8-1)。
  1. #include <stdio.h>
  2.  
  3. int main(void)
  4. {
  5.   printf("Hello, world!\n");
  6.   return 0;
  7. }
図8-1: hello_world.c
1行目の「#include <stdio.h>」では、printf関数の定義をここに埋め込んでいます。
3行目からの「int main(void) { }」で「main」という名前の関数を作っていますが、これは「メイン関数」と呼ばれ、C言語では、プログラムはメイン関数の呼び出しによって開始し、メイン関数を抜けたときに終了することになっています。 メイン関数の戻り値は、プログラムが正常終了したかどうかの値を返すことになっていて正常終了時には0を返します(6行目)。
残る「printf("Hello, world!\n");」は既に解説していますので、あなたはこの Hello, world! プログラムがすべて理解できるはずです。 いかがでしょうか。 次回は、メインメモリ上の値を書き換えたりして、いよいよコンピュータに様々な計算をさせる方法を解説します!
1679822453jaf