ここまでの冒険を記録しますか?

長岡技大ソフトウェア開発サークルの開発ブログ

C++で覚えるプログラミングのイロハ【ハ】

書き手:肥田野

入門者向けプログラミング講座の第3回です。

前回は変数と演算記号について解説したので、今回は制御文と関数について説明します。

制御文とは、条件分岐や繰り返しなど、プログラムの動きを制御する文のことです。

今までのプログラムは基本的に上から下へと処理が進んで行きましたが、制御文を使うことで、様々な動きをするプログラムを組むことができます。

次のコードを見てみましょう。

#include<stdio.h>

int main() {
    
    int num = 10;

    if (num > 5) {
        printf("%dは5より大きい\n", num);
    }

    if (num == 15) {
        printf("%dは15である\n", num);
    }else {
        printf("%dは15ではない\n", num);
    }

    for (int i = 0; i < num; i++) {
        printf("羊が%d\n", i);
    }

    while (num > 0) {
        printf("%d,", num--);
    }

    return 0;
}

まずはif文です。

if文は、()内の条件を満たしている時に{}で囲んだ処理を実行します。

最初はnumが5より大きいか比べているので、numに10が入っていれば内部のprintfが実行されます。

ここでさらっと出てきた比較演算子ですが、「<」「>」「<=」「>=」「==」「!=」などがあります。「A<B」は「BがAより大きい」を意味します。「Aより大きい」「A以上」の違いは、数学をサボらず勉強した方なら大丈夫ですよね? 「以上」をあらわすのは「A<=B」です。

「AとBが等しい」は「A==B」、等しくない場合は「A!=B」で比較します。よく使うので覚えておきましょう。

次もif文ですが、後ろに「else」が付いています。これは()内の条件が満たされなかった時に実行される処理です。

【イ】で解説したとおり、プログラムにおける等号は「==」なので注意してくださいね。

for文は、主に決められた回数処理を繰り返したい時に使います。

for文の()はセミコロンで3つに区切られていて、最初が初期設定、真ん中が処理を続ける条件、最後が1回処理が終わる毎に行う操作です。

今回はiという変数を宣言し0を代入して、numの数を超えるまで{}内の処理を繰り返しては、iを1ずつ増やしていきます。ここではnumに10が入っているので、処理は10回繰り返されます。

しかし、条件が「i<10」なのに10回目も実行されるのは違和感を感じるかもしれません。これはiの初期値が0から始まっているからなんです。1から始めて条件を「i<=10」にすることもできるのですが、0から始めた方がいいメリットもあるのです。それは、配列と組み合わせる場合ですね。配列の要素は「0」から始まるので、

int nums[3] = {0,1,2};
for(int i=0;i<3;i++){
    printf("%d,"nums[i]);
}

といった書き方ができるのです。むしろ上達してくるとfor文では配列を処理することがほとんどになると思います。

そして最後のwhile文ですが、こちらは不定の回数繰り返す、もしくは無限ループに使うことが多い文です。

while文は()内の条件を満たす限り処理を繰り返し続けます。

今回は「num>0」である限りループなので、printf内でカウントダウンしながら数を表示しているのがわかります。

ここでnumを-1する処理を書き忘れると無限ループになってしまうので、注意してください。

逆に、無限ループはゲームソフトなど半永久に動き続けるプログラムで活用されています。ゲームはユーザーが終了を選ばない限り描画やBGMの再生といった処理を繰り返しますが、この処理はwhile文でループさせていることが多いです。

他にも制御文はいくつもありますが、ここでは代表的な3種類を確認したところで次の関数の説明に移ります。

関数とはいくつかの演算処理をまとめたもので、数学の関数「f(x) = 10x + 2」みたいなのと基本は同じです。

では次のコードを見ていきます。

#include<stdio.h>

int f(int x) {
    return x * 10 + 2;
}

int main() {
    printf("%d\n", f(2));
    printf("%d\n", f(5));
    printf("%d\n", f(-4));
    
    return 0;
}

今例に挙げたf(x)をプログラムで書くと、このようになります。

関数は基本的に、値を受け取って内部で処理し、その結果を吐き出す役割を持ちます。この受け取る値を「引数」、吐き出す値を「返り値(または戻り値)」と言います。

引数の「x」は、この関数fの処理が終わったら破棄されてしまいます。その証拠に、main内でxという変数を使おうとしてもエラーになるはずです。

関数が外部に対して送れる情報は、基本的に「return」の後に記述した値のみとなります。そして返り値の型が、そのまま関数の型となります。関数fでは整数の値を返すので、関数の型はintになります。

結果として、関数fに適当な数を突っ込むと、×10して+2された値が返ってくるプログラムを作ることができました。

printfの文字列には変数を埋め込めるという話を前にしましたが、ここに関数を埋め込むことも可能です。変数の代わりに、関数の返り値を表示することができるようになります。

このように、引数を変えながら何度も同じ処理を行い、別々の結果を受け取ることができるのが、関数を使う大きな目的のひとつです。

しかし、上の関数は1行しか処理がないので、ちょっとありがたみに欠けますね。では次のコードはどうでしょう。

#include<stdio.h>
#include<math.h>

double S(int ax,int ay,int bx,int by,int cx,int cy) {
    double disAB = sqrt(pow(ax - bx, 2) + pow(ay - by, 2));
    double disBC = sqrt(pow(bx - cx, 2) + pow(by - cy, 2));
    double disCA = sqrt(pow(cx - ax, 2) + pow(cy - ay, 2));
    /*
   BC^2 = AB^2 + CA^2 - 2*AB*CA*cosA
   2*AB*CA*cosA = AB^2 + CA^2 - BC^2
   cosA = (AB^2 + CA^2 - BC^2)/(2*AB*CA)
   */
    double cosA = (pow(disAB, 2) + pow(disCA, 2) - pow(disBC, 2)) / (2 * disAB * disCA);
    //(sinA)^2 + (cosA)^2 = 1
    //(sinA)^2 = 1 - (cosA)^2
    double sinA = sqrt(1 - pow(cosA,2));
    //S = (1/2)*AB*AC*sinA
    return 0.5*disAB*disCA*sinA;
}

int main() {
    int a[2] = { 0,0 };
    int b[2] = { 5,0 };
    int c[2] = { 5,5 };
    int d[2] = { 0,5 };
    double sumABC = S(a[0], a[1], b[0], b[1], c[0], c[1]);
    printf("三角形ABCの面積は%fです\n", sumABC);
    double sumCDA = S(a[0], a[1], d[0], d[1], c[0], c[1]);
    printf("四角形ABCDの面積は%fです\n", sumABC + sumCDA);
    return 0;
}

三点の座標から三角形の面積を求める関数Sを使い、三点ABCの面積と、そこに点Dを加えた四角形の面積を出しています。

冒頭で「#include <math.h>」と宣言することで、数学公式に関する様々な関数(累乗根sqrt、べき乗powなど)を使えるようにしてあります。

ここでは答えが分かりやすいように5×5の正方形を使っていますが、もっと複雑な形でも正しい答えが出る(はず)です。

関数Sでは三点のお互いの距離を計算し、余弦定理でcosの値をだし、三角関数の基本公式からsinの値を導いて、最後にsinの公式で面積を出しています。高校数学ですね。一応コメント文に元の公式を書いておきました。

ちなみにもっとスマートに面積を出す数学公式もありますが、関数のありがたみを知ってもらうためにちょっと手間のかかる計算をさせています。

コメントの重要性

プログラム中の「/*」と「*/」で囲った部分や「//」に続く行をコメント文といいます。コメント文はプログラムの実行に影響しないので、そのプログラムがどんな挙動をするのかメモをしたり、一時的に処理させたくない行を隠す(コメントアウト)為に用いられます。

大掛かりなプログラムは複数人で開発することが多いので、ソースコードを共有したとき、的確なコメント文が書いてあるかどうかで開発のスピードが段違いになります。

また、プログラミングに慣れない内は少し前に書いた処理を思い出せなくなることが多いので、こまめにコメントを書く癖をつけておくとよいでしょう。

main内だけに注目すると、4点の座標を指定したあとは、それを関数Sに放り投げて三角形の面積を受け取っています。この間わずか1行。面積を求めるたびに関数S内の処理をmainに書き込んでいたら、どんどん長くなって読みづらいコードになってしまいます。

これを別のところに書き溜めておいて、使いたい時に名前と引数を書くだけでその処理を実行してくれるというのも、関数を使う大きなメリットです。

最初から使える関数

もう気づいていると思いますが、printfも関数です。引数には文字列と、そこに埋め込む変数を入れられます。

printfも自作の関数Sと同様にプログラム中で宣言しないと呼び出せないのですが、その宣言が書かれているのが冒頭で呼び出している「stdio.h」です。

stdio.hやmath.hの他にも、C++で使える様々な関数を種類ごとにまとめた「.h(ヘッダ)ファイル」が標準で用意されています。

もちろん、ヘッダファイルを自分で作ることも可能です。今回作った関数Sを別のヘッダファイルに書き写し、そのファイルを「#include"○○.h"」とすることで、ソースコードを読みやすくすることができます。

それとmain()も関数なのですが、これはプログラムの開始地点と終了地点(return 0;のこと)を司る唯一の関数です。printfやsqrtの仲間は山ほど存在しますが、mainだけは特例中の特例なので、そこは区別しておくようにしましょう。