しいたげられたしいたけ

弊ブログでいう「知的」云々は「体を動かさない」程の意味で「知能の優劣」のような含意は一切ない

GWだからWindowsのC&C++フリー開発環境MinGWをインストールしたら、またも世の中から取り残されていたことに気づいた(後編)

前回のエントリーの内容を1行で要約すれば、「MinGWgccbashgccと間違えてインストールしたが、結果オーライでどっちでも構わなかった」ということです。今回のエントリーの内容も、先に1行で要約してしまうと「名前空間(namespace)というのを知らなかったので、昔のコードをコンパイル通せなかった」ということに尽きます。

スポンサーリンク

 

なんでC++なのかというと、もう10年以上前に他人からもらった、こんなコードを持っていたからだ。もしこれがコンパイルできるなら、ちょっと改造すれば4月27日28日のエントリーに書いた例題のシミュレーションプログラムにできるんじゃないかと考えたわけである。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// サイコロの目のバラツキを調べるプログラム
//
// file name : saikoro1.cpp
//

#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <time.h>

main()
{
    const int Faces = 6 ; // サイコロの目の数

    int freq[Faces] = { 0 } ; // 各目の頻度
    int face ; // 出た目
    int trial; // 試行回数
    int i ;

    // 試行回数を入力する
    cout << "\nサイコロの各目の出る確率を調べる\n\n" ;
    cout << "何回、サイコロを振るか入力して下さい >> " ;
    cin >> trial ;

    // サイコロを試行回数分だけ振る
    srand( time(NULL) ) ; // 乱数の初期化
    for( i = 0 ; i < trial ; i++ ){
        face = rand() % 6 ;
        freq[face]++ ;
    }

    // 結果の出力
    cout << "\n\nサイコロを" ;
    cout << trial << "回振りました\n\n" ;
    for( i = 0 ; i < Faces ; i++ ){
        cout << setw( 5 ) << i + 1 << "の出た回数:" ;
        cout << setw( 8 ) << freq[i] << "回" << endl ;
    }
    cout << endl ;

    return 0 ;
}

以下、簡単なプログラムの内容の説明。29行目でCの組み込み関数randというのを6で割った剰余をサイコロの目としている。randは疑似乱数を発生する関数である。randの発生する疑似乱数は、システムによって違うが確か0~32767以上の範囲じゃなかったかな。

ただし疑似乱数というのは本物の乱数ではなくて、何度繰り返しても順番通りに同じ数字が出てくるのだ。そこで27行目で、疑似乱数の初期値を与える関数srandと、システム時間を秒単位(ノーフォーマット)で取り出す関数 time(NULL) を使って、乱数の初期化を行っている。秒単位で疑似乱数の発生する数列を変更するのだ。

次のような例えを使うとわかりやすいと思う。1を7で割ると、各桁に現れる数字は「1」「4」「2」「8」「5」「7」以下繰り返しとなり、「1を7で割った循環少数の各桁」ということを知らない者にとっては、次の数字を予測するのは難しい。この各桁に現れる数字と循環する周期を、うんと大きくしたのが疑似乱数なのだ。ただし順番に出力される数字は毎回同じになるので、最初に割る数字を1ではなく7の倍数以外の数字に変えると、プログラムを実行するたびに違った順番で数字を出力させることができる。

間違ったこと言ってたらコメントかブコメで突っ込んでください。

このコードをコンパイルしようとしたら、エラーメッセージが出てコンパイル通らなかったのだ。

こんなの。

f:id:watto:20160430123901p:plain

iostream.hがない? ヘッダファイルは別にダウンロードしなきゃならないのか? そう思って検索したら、C++ソースコードがいくつか出てきて、#include <iostream.h>ではなく#include <iostream> と書いてある。

「.h」を外せばいいのかと思ってやってみた。この時点では、何が悪いのか理解していない。

同様に8行目の#include <iomanip.h> も #include <iomanip> に変えてみた。9行目と10行目はそのままで、次のようなエラーメッセージが出るようになった。

f:id:watto:20160430123902p:plain

f:id:watto:20160430123903p:plain

1画面じゃ収まりきらなかったので、2画面に分割しています。

‘cin’ の代わりに ‘std::cin’ 、‘cout’ の代わりに ‘std::cout’ …の使用をお勧めします、だと?

これでようやくピンと来た。

繰り返すようだが、私のC++の知識は10年以上前で止まっている。Borland C++ compiler というフリーのコンパイラと、主に次の書籍で勉強したのだ。下のブルーバックスの方は本題に関係ないが、 付録のCD-ROMにBorland C++ compiler がついていて懐かしいので貼ってみた。

C++プログラミング〈Vol.1〉 (Computer Science Textbook)

C++プログラミング〈Vol.1〉 (Computer Science Textbook)

 
これならわかるC++―挫折しないプログラミング入門 CD-ROM付 (ブルーバックス)

これならわかるC++―挫折しないプログラミング入門 CD-ROM付 (ブルーバックス)

 

 問題は上の方だ。原著第2版の訳本だが、原著の方は少なくとも第4版までは改訂が進んでいる。最新が第何版なのかは知らない。リアルの知人に新しい原書を持っている人がいて、最初の方のページをパラパラとめくったら、訳本にはないページが何ページかあった。そこに ‘std::cin’ 、‘std::cout’ みたいなことも書かれていたような記憶が、かすかに残っていたのだ。

そんなことはブルーバックスの方にも書いてなかったし、C++関連書籍は上記2冊以外にも何冊か持っているが、どれにも書いてなかった。繰り返すが10年以上前のことだ。

かすかな記憶を頼りに検索するというのは、案外よくやる。「名前空間(namespace)」というらしい。そういうものが導入された意義については、あとからじっくり調べよう。とりあえずエラーが出た予約語の頭に、片っ端から ‘std::’ をペーストしてみた。片っ端からと言っても ‘cin’、 ‘cout’、 ‘setw’、 ‘endl’ だけでよかったんじゃないかな。要するに入出力関係だ。

こんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// サイコロの目のバラツキを調べるプログラム
//
// file name : saikoro3.cpp
//

#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <time.h>

main()
{
    const int Faces = 6 ; // サイコロの目の数

    int freq[Faces] = { 0 } ; // 各目の頻度
    int face ; // 出た目
    int trial; // 試行回数
    int i ;

    // 試行回数を入力する
    std::cout << "\nサイコロの各目の出る確率を調べる\n\n" ;
    std::cout << "何回、サイコロを振るか入力して下さい >> " ;
    std::cin >> trial ;

    // サイコロを試行回数分だけ振る
    srand( time(NULL) ) ; // 乱数の初期化
    for( i = 0 ; i < trial ; i++ ){
        face = rand() % 6 ;
        freq[face]++ ;
    }

    // 結果の出力
    std::cout << "\n\nサイコロを" ;
    std::cout << trial << "回振りました\n\n" ;
    for( i = 0 ; i < Faces ; i++ ){
        std::cout << std::setw( 5 ) << i + 1 << "の出た回数:" ;
        std::cout << std::setw( 8 ) << freq[i] << "回" << std::endl ;
}
    std::cout << std::endl ;

    return 0 ;
}

 これでコンパイル通った! サイコロを振る回数を12000回として実行したスクショを下に示す。

f:id:watto:20160430184912p:plain

‘std::’ は省略する方法があったはずと検索したところ、あっさり見つかった。宣言部に ‘using namespace std;’ の一行を追加すればいいのだ! こんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//
// サイコロの目のバラツキを調べるプログラム
//
// file name : saikoro4.cpp
//

#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <time.h>
using namespace std;

main()
{
    const int Faces = 6 ; // サイコロの目の数
 
    int freq[Faces] = { 0 } ; // 各目の頻度
    int face ; // 出た目
    int trial; // 試行回数
    int i ;

    // 試行回数を入力する
    cout << "\nサイコロの各目の出る確率を調べる\n\n" ;
    cout << "何回、サイコロを振るか入力して下さい >> " ;
    cin >> trial ;

    // サイコロを試行回数分だけ振る
    srand( time(NULL) ) ; // 乱数の初期化
    for( i = 0 ; i < trial ; i++ ){
        face = rand() % 6 ;
        freq[face]++ ;
    }

    // 結果の出力
    cout << "\n\nサイコロを" ;
    cout << trial << "回振りました\n\n" ;
    for( i = 0 ; i < Faces ; i++ ){
        cout << setw( 5 ) << i + 1 << "の出た回数:" ;
        cout << setw( 8 ) << freq[i] << "回" << endl ;
    }
    cout << endl ;

    return 0 ;
}

最初に示したコード(saikoro1.cpp)との違いは、7行目と8行目の ‘.h’ がないことと、11行目が追加されていることだけのはず。

念のためこれもコンパイルして実行、動作を確認した。

f:id:watto:20160430193716p:plain

いずれも、いったん動作を確認したコードをブログに貼り付けて、それを画面からコピーして新たなファイルに保存して再度コンパイル&実行できるところまで確認しています(当然ながら行番号はコピーしないでください。表を作成していますので、コードだけコピーできるはずです)。

名前空間」やC++のバージョンについては、これから少しずつ勉強しよう。

それから、突っ込みどころのあるエントリーを書くとブコメ 炎上 盛況になるという法則が、今度も発動しました。コメントくださった方々に感謝します。いっぺんにいろんなことはできないので、取捨選択して少しずつ新しいことも試してみます。

追記:

そうそう、肝心の問題のシミュレーションはどうなったかというと、まずは4月28日の問題。実はこっちの方が簡単だった。

打率3割ちょうどの打者が5打席で3本のヒットを打つ確率を求めよ。

コード。行数は必要ないので表示する手間を惜しみました。

//
// file name : sananda.cpp
//

#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <time.h>
using namespace std;

main()
{
    const int Dasu = 5 ; // 打席 = 5

    long int siai ; // 試合数
    long int sankai = 0; // 3回ヒットを打った試合数
    int kekka ; // 結果(1:ヒット、0:アウト)
    double wariai ; // 3回ヒットを打った割合
    int i ,j ;

    // 試行回数を入力する
    cout << "\n打率3割の打者が5打席で3安打する確率\n\n" ;
    cout << "統計を取る回数(試合数)を入力してください >> " ;
    cin >> siai ;

    // 試合数だけ繰り返す
    srand( time(NULL) ) ; // 乱数の初期化
    for( i = 0 ; i < siai ; i++ ){
        kekka = 0 ;
        for( j = 0 ; j < Dasu ; j++ ){
        // 0から9までの乱数を3で割った余りが1のときヒットとする(3/10)
            if( (rand() % 10 )%3 == 1 ) kekka++ ;
        }
        // cout << "\nkekka = " << kekka << endl; // デバッグ用出力
        if ( kekka == 3 ) sankai++ ;
    }
    wariai = static_cast< double >( sankai ) / siai;

    // 結果の出力
    cout << "\n\n試合数は" << endl ;
    cout << siai << "回です\n";
    cout << "\n3回ヒットを打った試合の数は" << endl ;
    cout << sankai << "回です"<< endl ;
    cout << "\n3回ヒットを打った試合の割合は" << endl ;
    cout << wariai << "です"<< endl ;

    cout << endl ;

    return 0 ;
}

実行結果のスクショ。

f:id:watto:20160501002011p:plain

4月27日の問題。

10本のくじの中に2本の当たりくじがあり、A、B、Cが順に1本ずつ引く。A、B、Cがそれぞれ当たる確率を求めよ。 

 コード。「きったねぇコード」などと言わない。素人がやってます

//
// A、B、Cの三人が10本中2本の当たりくじを引いて当たる確率
//
// file name : kujibiki.cpp
//

#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <time.h>
using namespace std;

main()
{
    const int Kuji = 10 , Atari = 2 ; // くじ = 10本、 当り = 2本

    long int sikosu ; // 試合数
    long int Aatari, Batari, Catari ; // A、B、Cが当たった回数
    int Akuji, Bkuji, Ckuji ; // A、B、Cが引いたくじ
    double Awari, Bwari, Cwari ; // A、B、Cが当たった割合
    int i ;

    // 試行回数を入力する
    cout << "\nA、B、Cの三人が10本中2本の当たりくじを引いて当たる確率\n\n" ;
    cout << "統計を取る回数(くじを引く回数)を入力してください >> " ;
    cin >> sikosu ;

    // 試行数だけ繰り返す
    srand( time(NULL) ) ; // 乱数の初期化
    Aatari = Batari = Catari = 0 ;

    for( i = 0 ; i < sikosu ; i++ ){
        // A がくじを引き、当りであれば当たった数を一つ増やす
        Akuji = rand() % Kuji;
        if( Akuji < Atari )Aatari++; // Aの引いた値が2未満であれば当り

        Bkuji = rand() % 10;// B がくじを引く
        // BがAと同じくじを引くことはないので、同じであればやり直し
        while( Bkuji == Akuji )Bkuji = rand() % Kuji;
        // B が当りであれば当たった数を一つ増やす
        if( Bkuji < Atari )Batari++; // Bの引いた値が2未満であれば当り

        Ckuji = rand() % 10;// C がくじを引く
        // CがAまたはBと同じくじを引くことはないので、同じであればやり直し
        while( Ckuji == Akuji || Ckuji == Bkuji)Ckuji = rand() % Kuji;
        // C が当りであれば当たった数を一つ増やす
        if( Ckuji < Atari )Catari++; // Cの引いた値が2未満であれば当り

        //cout << "\nAkuji = " << Akuji << endl; // デバッグ用出力
        //cout << "\nBkuji = " << Bkuji << endl; // デバッグ用出力
        //cout << "\nCkuji = " << Ckuji << endl; // デバッグ用出力
    }
    Awari = static_cast< double >( Aatari ) / sikosu;
    Bwari = static_cast< double >( Batari ) / sikosu;
    Cwari = static_cast< double >( Catari ) / sikosu;

    // 結果の出力
    cout << "\n\n試行数は" << endl ;
    cout << sikosu << "回です\n";
    cout << "\nAが当たった回数は " << Aatari << "回です"<< endl ;
    cout << "\nBが当たった回数は " << Batari << "回です"<< endl ;
    cout << "\nCが当たった回数は " << Catari << "回です"<< endl ;
    cout << "\nAが当たった割合は" << Awari << "です"<< endl ;
    cout << "\nBが当たった割合は" << Bwari << "です"<< endl ;
    cout << "\nCが当たった割合は" << Cwari << "です"<< endl ;

    cout << endl ;

    return 0 ;
}

スクショ。 

f:id:watto:20160501002010p:plain

いずれもスクリーンからコピーしたコードも再コンパイルして動作確認しています。

例によって、間違いとか何かお気づきの点があれば、ぜひ教えてくださいm(_ _)m