その他勉強

左下が直角の直角二等辺三角形を作成してください、の罠

めっちゃ簡単なプログラムなんだけど動かない理由を探すのに少し時間がかかったのでメモ。

問題と正解のコード

左下が直角の直角二等辺三角形を作成してください、という問題。

正解のコードは以下の通り。

#include <iostream>

void put_stars(int n) {
    int i;
    for (i = 1; i <=n; i++) {
        putchar('*');
    }
}

int main()
{
    int a;//入力整数
    int i;

    printf("左下直角二等辺三角形を作ります。\n");
    printf("短辺:"); scanf_s("%d",&a);

    for (i = 1; i <=a; i++) {
        put_stars(i);
        putchar('\n');
    }
    //put_stars(1);
}

間違っていたコード

間違って作成したコードは以下の通り

#include <iostream>

void put_stars(int n) {
    int i;
    for (i = 0; i <n; i++) {
        putchar('*');
    }
}

int main()
{
    int a;//入力整数
    int i;

    printf("左下直角二等辺三角形を作ります。\n");
    printf("短辺:"); scanf_s("%d",&a);

    for (i = 0; i <a; i++) {
        put_stars(i);
        putchar('\n');
    }
    put_stars(1);
}

//出力結果
左下直角二等辺三角形を作ります。
短辺:5

*
**
***
****

う~む、5を入力しているのに4段しか出力されぬ。

中身の関数をかくにんする。

void put_stars(int n) {
    int i;
    for (i = 0; i <n; i++) {
        putchar('*');
    }
}

たとえば、引数が5で渡されてきたとするならば、「iは0~5」で、動く。つまり4まで動くので実質5回「putchar」が表示されることになる。

つまり、この関数は間違っていない。

次にmain関数のループを確認してみる。

printf("短辺:"); scanf_s("%d",&a);

    for (i = 0; i <a; i++) {
        put_stars(i);
        putchar('\n');
    }

ここでも、入力したa未満まで動くとすると、0からカウントを始めるのでa=5ならば、0~4回の計5回、実行される。

ん!?、間違っていないのでは?

とおもったら罠だった。

確かに両方のループとも問題なく、「5回実行される」のだが、それは

それぞれの関数において、引数が5だった場合の話である。

ここでmain関数のなかで使用した「put_stars」関数の引数に改めて着目してみると、

iが0の時、すなわちループが一回目の時にput_stars関数に渡される値は「0である。」

おぉうう、、、なんということだ、、これでは1段出力するために1、と入力したとしてもmain関数のループが1回動くだけで、put_stars関数は動けないではないか、、、

まぁ、つまり、例えば1段出力させたいとして、aに1を代入するまではよいのだが、

その場合、main関数内のループは0から1以下、つまりi=0の間1回実行されるのだけど、中身で動かす「put_stars」関数の引数が1ではなく"0" になってしまうので、a=1において出力することができず、1段マイナスされた状態で動いてしまう、ということだ。

ちなみにmain関数のループは"1"を入力しても0から1までで一回動いていることは確認できる。
改行を確認すれば、わかる。

出力結果 a=0のとき
左下直角二等辺三角形を作ります。
短辺:0

E:\

出力結果 a=1のとき
左下直角二等辺三角形を作ります。
短辺:1


E:

関数を動かす回数としては間違っていないんだけど、関数の中に入った関数の引数が、動かない場合がある、というのは今回の学びであった。

あ、ちなみに1から始めただけでもだめね。

この問題はループ内で実行される回数を「0以上、n未満」という表記でやった場合に、iの値がn-1回までしか実行されないことが問題だから。

なので以下のようなやつでやっても駄目。

#include <iostream>

void put_stars(int n) {
    int i;
    for (i = 1; i <n; i++) {
        putchar('*');
    }
}

int main()
{
    int a;//入力整数
    int i;

    printf("左下直角二等辺三角形を作ります。\n");
    printf("短辺:"); scanf_s("%d",&a);

    for (i = 1; i <a; i++) {
        put_stars(i);
        putchar('\n');
    }
    //put_stars(1);
}

左下直角二等辺三角形を作ります。
短辺:5

*
**
***

まぁこっちはそもそも間違ってるんだけど、順に追って考えると、

aが5で入力されました。

main関数内では1から5未満、つまり4回実行されます。(この時点でアウトだが、、)

main関数内で使用されたput_stars関数により、*が表示されるが、この時渡される引数は最初は1

しかし引数として1を渡されても、1から1未満までなので、*を表示することはできない。

次にメイン関数のループが2になる。

この時ようやく引数が2となるので、*が一回表示される。
.
.
.

このあとaが4まで実行されるので、*が出力されるのは「2,3,4」の三回のみとなる。

ダメな理由はお分かりだろう。

まとめ

簡単なところだけど、配列を使うときは「i=0,i<n」みたいに、「0以上、n未満」を使うべき。

配列を使わないときは、なるべく、数えやすいように、「1以上、n以下」を使った方が、間違いが少なくて済むな、と感じた。

関数の中の実行回数は基本的にどちらの表記でも同じになるけれど、なかで引数を扱う関数を取り扱った場合、入れ子の段階が1、2、3と増えるにつれて、実行回数が-1,-2,-3となってしまうことは今後留意すべき点なのであった。

以上。

-その他勉強
-