マクロ・関数

はじめに

前回は繰り返しや論理式、break文、switch文について勉強しました。よく確認しておいてください。

マクロ

C言語にはマクロという機能があります。予め定義しておくことでプログラム中の特定の文字列を置換することができる機能です。この説明だけではよくわからないとおもうので、ちょっとプログラムで説明してみましょう。

例えば、円の面積と長さを計算するプログラムを考えましょう。円周率は3.14とします。プログラムとしてはこんな風になるでしょうか。

#include <stdio.h>
int main(void){
    int radius;
    printf("半径を入力してください:");
    scanf("%d",&radius);
    printf("円の面積は%fです。\n",radius * radius * 3.14);
    printf("円の長さは%fです。\n",radius * 2 * 3.14);
    return 0;
}

さて、プログラム中に2回も3.14が書かれています。ここで、「円周率は3.141592で計算してください」という指示が出たらどうでしょうか。プログラム中の3.14の部分を、3.141592に書き換えれば良さそうですね。ですが、もっと簡単な方法があります。

#include <stdio.h>
int main(void){
    int radius;
    double pi = 3.14;
    printf("半径を入力してください:");
    scanf("%d",&radius);
    printf("円の面積は%fです。\n",radius * radius * pi);
    printf("円の長さは%fです。\n",radius * 2 * pi);
    return 0;
}

こうすれば、簡単に円周率の値を変えることが出来ますね。とっても便利。ですが、ちょっと考えてみてください。変数piは、円周率を保存することしかしていないわけです。変数が増えると、プログラムは複雑化します。もっと簡単な書き方はないのでしょうか。そんなときに使えるのが、マクロです。

マクロを使って円周率の計算のプログラムを書き直してみます。

#include <stdio.h>
#define PI 3.14

int main(void){
    int radius;
    printf("半径を入力してください:");
    scanf("%d",&radius);
    printf("円の面積は%fです。\n",radius * radius * PI);
    printf("円の長さは%fです。\n",radius * 2 * PI);
    return 0;
}

どうでしょうか?ちょっとスッキリしたんじゃないでしょうか?二行目の#define PI 3.14に注目してください。これがマクロの定義です。この定義によって、プログラム中のPIはプログラムがコンパイルされるとき、3.14に置き換わります。変数を宣言する必要がなくなるので、便利ですね。

#define 置換前の文字列 置換後の文字列

マクロを使いこなせるようになると様々なことができるようになります。ただ、それを学ぶのはこの講習の目的ではないので触れないでおきます。気になる人は自分で調べてみてください。 マクロは基本的に全て大文字で定義されますし、みなさんもそうすべきです。なぜならそれが変数なのか、マクロなのかわかりやすいからです。

関数

最初の講習の話を思い出してください。最初の講習のとき、「こことりあえず必要なのだ」という説明をしたことを覚えていますか?

#include <stdio.h>

int main(void){
  // ここに色々書いていく
  return 0;
}

これです。この節では、説明をしていなかったこの部分がなんなのかということを理解してもらいます。

さて、プログラミングをしていると、同じ処理をなんども書いてるなと感じるときがありませんか?例えば、次のプログラムを考えてみましょう。「ユーザーに数字を入力してもらう。その数字が2の倍数であるときは、その数字の二乗・三乗をそれぞれ表示する。2の倍数でないときは、もう一度ユーザーに数字を入力してもらう。その数字が3の倍数であれば、その数字の二乗・三乗をそれぞれ表示する。そうでなければ、プログラムを終了する。」ちょっと複雑ですね。フローチャートはこんなふうになります。

#include <stdio.h>

int main(void){
    int input1,input2;
    printf("整数を入力してください:");
    scanf("%d",&input1);
    if(input1 % 2 == 0){
        printf("二乗:%d\n",input1 * input1);
        printf("三乗:%d\n",input1 * input1 * input1);
    } else {
        printf("整数を入力してください:");
        scanf("%d",&input2);
        if(input2 % 3 == 0){
            printf("二乗:%d\n",input2 * input2);
            printf("三乗:%d\n",input2 * input2 * input2);
        }
    }
    return 0;
}
フローチャート

フローチャート

なんだか長くなってしまいましたね。もしここで、「二乗・三乗をそれぞれ表示する」というのをもっとシンプルにまとめて書くことが出来たら良さそうですね。そんなときに使うのが関数です。関数の構文はこのような感じです。

返り値の型 関数名(引数) 処理文

"返り値"、そして"引数"。わからない言葉が2つも出てきてしまいました。まずは簡単な例を考えてみましょう。数学の関数はどんなものか覚えていますか?y = x*2みたいなやつです。ああいうのって、xにある値を与えると、yが1つに定まりますよね。ここでのxが引数、yが返り値となります。ちょっと例を見ましょう。

int add2(int x){
    return x + 2;
}

上のadd2という関数は、引数xに2を足した値を返す関数です。数学の式でいえばy = x + 2というわけです。

この関数をプログラム上で呼び出すときは、例えばこうします。

int i = 0;
int b = 0;
i = add2(b); // iは2になる。

こんなふうに書いたこと、ありますよね。だってずっと、printfscanfを使ってきたんですから。add2という関数を実行する場合には、add2に渡したい値をカッコ内に書いてあげればいいわけです。

return文についての説明をします。return文があると、関数は呼び出された時点に戻ります。そしてそれが右辺値として評価されます。少し難しい言い方ですが、上の例を見てください。3行目でadd2関数が呼ばれ、return文によってx + 2つまりb + 2が評価されて、iに代入されるわけです。

引数を省略することも出来ます。数学の関数は必ずしもxが入ってくるわけではないですよね。たとえば、y = 100というのも立派な関数です。このような関数はいかにして表現するのでしょうか?

int get100(void){
    return 100;
}

こんな風にすれば、y = 100という関数がC言語上でも表せます。実際にはこんな関数は使いませんが。引数のところにあるvoidは、その関数は引数を指定しないということを明示的にしめすものです。voidは書かなくても結構です。

関数は数学の関数にようなものだけではなく、処理をまとめたいときにも使えます。たとえばこんな風に。

void greeting(){
    printf("こんにちは!\n");
    printf("これからこのプログラムの説明をします。\n");
    // 以下適当になんかせつめいが入る
}

greeting関数はプログラムの説明を表示してくれるものです。この関数には返り値がありません。なので、返り値の型を書く部分には返り値がないことをしめすvoidが書かれています。

返り値がvoidな関数の中では、return文はちょっと特殊です。次の関数を見てみましょう。

// 15以下の数字が与えられたときに注意喚起を表示するプログラム
void print_warn(int age){
    if(age > 15){
        return;
    }
    printf("注意!15歳以下には適切でない内容が含まれる可能性があります。\n");
}

上から四行目のreturn文では、右辺に値を持っていません。返り値なしの関数では、return文は呼び出し元に戻るという意味を持ちます。上の例のように、関数を途中で終了したい場合にreturn文を使うといいでしょう。

関数は宣言することで、プログラム上で何度でも使うことができるようになります。関数を使うことは、プログラムをわかりやすく、そして書きやすくするためにとても大切です。実際に上のプログラムを関数を使って書き直してみましょう。

#include <stdio.h>
// ある整数値を受け取って、その2乗と3乗を表示する関数。
// 関数名はなにをしてくれるのか適切に書くこと。
void print_square_cube(int num){
    printf("二乗:%d\n",num * num);
    printf("三乗:%d\n",num * num * num);
}

int main(void){
    int input1,input2;
    printf("整数を入力してください:");
    scanf("%d",&input1);
    if(input1 % 2 == 0){
        print_square_cube(input1); 
    } else {
        printf("整数を入力してください:");
        scanf("%d",&input2);
        if(input2 % 3 == 0){
            print_square_cube(input1);
        }
    }
    return 0;
}

print_square_cube関数により、もっとシンプルに表示をすることができるようになりましたね。

main関数

さて、関数の意味を説明する前に、今まで説明していなかった部分の説明をしておきましょう。

聡明な読者諸君はお気づきでしょうが(気付いていなくても大丈夫です)、int main(void){ ~ }というのは、関数です。これはmain関数と呼ばれる特別な関数になります。

main関数は、プログラムが実行される際、一番最初に呼び出される関数です。main関数の返り値はintです。この返り値はどこに渡されるのかというと、基本はプログラムを実行しているOSに渡されます。OSはこの返り値によって、プログラムが正常に終了したのかを判定したり、異常に終了した場合はなにが原因で終了したのかを判断したりします。0を返しておけば、「プログラムは問題なく終了した」という意味になりますので、main関数の最後にはreturn 0;を書くようにしましょう。

もしあなたが自分でソフトウェアを作りたいのであれば、システムに返す値を調べると便利です。ユーザーからの入力が想定外だった場合はこの値、単純に内部のバグだったらこの値、といったふうにわけられるようになるといいですね。

printf,scanfとinclude

さて、先程"関数は宣言することで使えるようになる"という話をしました。ここで少し疑問を持った人もいるでしょう。 「printf関数はどこにも宣言していないのにどうして使えるの?」

これを解決してくれるのは、今まで一行目になんとなく書いていた#include <stdio.h>です。

C言語にはヘッダファイルというものがあります。これは、たくさんの関数を定義したものです。そのヘッダファイルをincludeを使って読み込むと、そのヘッダファイル内で定義された関数をプログラム中で使うことができます。

printfscanfstdio.hの中に定義された関数です。stdioは"standard input output"の略で、標準入出力、つまり文字を画面に出力したりユーザーからの入力を受け付けたりするための関数が揃っているものとなります。

ほかにも以下のようなヘッダファイルが存在します。

マイコンの機能をいじるためには、マイコンの機能を使えるようにした関数を利用する必要があります。マイコンを作っている会社が作ってくれたヘッダーファイルがありますので、それを使ってマイコンの機能を触っていくことになります。

関数の意味

さて、関数の意味とはなんでしょうか?先程のプログラムをもう一度見てみましょう。

#include <stdio.h>
// ある整数値を受け取って、その2乗と3乗を表示する関数。
// 関数名はなにをしてくれるのか適切に書くこと。
void print_square_cube(int num){
    printf("二乗:%d\n",num * num);
    printf("三乗:%d\n",num * num * num);
}

int main(void){
    int input1,input2;
    printf("整数を入力してください:");
    scanf("%d",&input1);
    if(input1 % 2 == 0){
        print_square_cube(input1); 
    } else {
        printf("整数を入力してください:");
        scanf("%d",&input2);
        if(input2 % 3 == 0){
            print_square_cube(input1);
        }
    }
    return 0;
}

よく関数の利点について、「同じ処理を何度も書かなくて済むから、結果としてプログラムが短くて済む」といった説明をされることがあります。しかし、ちょっと考えてみてください。そんなに行数減っているでしょうか?コメントの行を除いても、最初に書いたプログラムとそこまで行数は変わっていないはずです。プログラム上で同じ処理をなんども書く、ということはそこまでありません。そういう場合は大抵繰り返しをすれば済むわけですから。

関数の本当の利点は、ある一連の処理に名前をつける事ができることです。

printf("二乗:%d\n",num * num);
printf("三乗:%d\n",num * num * num);

例えば上のプログラムでは、という処理に、print_square_cubeという名前をつけているわけです。これをプログラム上で見たあなたは、「ああ、二乗と三乗を表示する関数なんだな」ということがわかるはずです。

処理に名前をつけることの、何が重用なのでしょうか。ここからは大切な話をしていきます。

具体と抽象

さて、重用な話ですから、メモを取ってください。

……と言われた時。みなさんはどのようにしてメモをとりますか?紙のメモを取る人なら「バックからメモ帳を取り出して机に置き、自分のペンケースからシャーペンを取り出し右手に持ち、紙に書く準備をする」といった流れでメモを取る用意をするでしょう。PCで取る人は「メニューを開きメモアプリのアイコンをクリックして、メモアプリが起動したらフォーカスを当てる」といった流れで準備をしたのではないのでしょうか。

「メモを取ってください」という指示だけで、みなさんはそれぞれメモを取るための準備が出来ます。ここで「メモ帳を取り出してペンケースからペンを出して利き手ペンを持ってメモ帳の空いているページを開いてこれから私が言うことを書き留めてください」なんて言われたらどうでしょう?わけがわからないですね。

これは、わかりやすい指示が抽象であるのに対して、わかりづらい方の指示が具体であるからです。具体抽象という概念が大切になってきます。

人は抽象の世界にいます。物事の理解や、コミュニケーションなどにおいて、多くのものを抽象的に捉えることによって生活しています。

一方で、コンピューターは具体の世界にいます。「a+bの値をcに代入する」「"Hello,World"という文字列を表示する」これらはすべて具体です。プログラムを書くときは、具体的な操作手順を1つ1つかいていくことでプログラムを実現しています。

つまり、プログラムが人間にとって理解しがたいのは当たり前なのです。処理がそのまま書かれたプログラムは具体が並んでいるだけなのですから、その具体の処理を読み解いていく必要があります。自分で抽象的に「これらの処理は一体なにをしているのか」ということを掴んでいかなければいけません。

関数に処理をまとめるということは、そういった具体の羅列を抽象的にわかりやすくするということに繋がります。並べられた具体からその意味を理解するのと、与えられた抽象から具体の処理を想像するのでは圧倒的に後者の方が楽なのです。

printf("二乗:%d\n",num * num);
printf("三乗:%d\n",num * num * num);

という処理をみて、これが抽象的になにをするものなのかを理解することは難しいです1print_square_cubeという関数名をみて、それがどんな処理をするのか抽象的につかみ、中身を予想しつつ確認するほうが簡単なわけです。

大きなプログラムになればなるほど、それは大切な事になります。適切に処理を関数にわけて、「この処理はこの関数を呼べばいい」と考えられるようになれば、その処理の具体的な内容は実際にその関数を記述するときだけ考えればいいわけです。また、プログラムを読み直すときにも、いちいち具体の処理を追う必要がなくなるわけです。まだプログラミングを学び始めたばかりであるみなさんにはあまり実感のないことですが、実はプログラミングというのは書くことと同じぐらい読むことが大切なのです。

プログラムを作るときに考えなければいけないことはとてもたくさんあります。自分の中で問題を個別にわけて考える事が重用になるのです。関数を使うということは、そういった処理を自分の中でまとめる。

わかっていただけたでしょうか?

今回の関数の章では、JavaScriptの関数で何ができるのか、もう一度考えるという記事を多く参考にしています。わかりやすく関数の重要性が書いてありますので、参考にしてみてください。扱っている言語がCではなくJavaScriptなので、すこし難しいですが。

この部分への理解がちゃんとできるようになると、あなたのプログラミング能力は上達します。最初のうちは理解できなくても、頑張ってプログラミングを勉強していくうちに感覚的に理解できるようになるはずです。

最後の回では練習問題を多く用意していますので、関数を使って抽象化するということを学んでみてください。

練習問題

  1. 与えられた数字が15の倍数ならFizzBuzz、5の倍数ならBuzz、3の倍数ならFizz、と出力する関数fizzbuzz(int num);を実装してください。その関数を1から100までの数字をあたえて呼び出して、出力がどうなるのかみてみましょう。

  1. 今回の場合はそこまでではないですが