第42章 私はαでありωである2

 前回はプログラムのω(最後)についての話でした。今回はプログラムのα(最初)についての話をしたいと思います。


 今回の要点は以下の通りです。


 では、いってみましょう。


 前回、main 関数はプログラムの終わりでもあるといいました。とはいえ、やはり main 関数はプログラムの初めでもあります。プログラムの終わりにプログラムを呼んだ「何か」に値を返すことができるのなら、また、プログラムの初めにその「何か」からプログラムに値を渡すこともできていいと思います。

 実は、実際にプログラムに値を渡すことができます。その値は常に文字列で渡されます。この文字列のことをコマンドラインと呼びます。

 UNIXを使っている人には説明するまでのことではないと思いますが、Windows を使っている人にはちょっと例を挙げておきましょう。

 Windows を使っている人はDOSプロンプトを開いてみてください。すると次のような文字が出てきて、入力待ちになると思います。

C:\WINDOWS>

これは、現在いるディレクトリです。これを、カレントディレクトリと呼びます。大抵 WINDOWS ディレクトリになっていると思います。そこで、"cd .." と入力して、Enter を押してみて下さい。すると...

C:\WINDOWS>cd ..

C:\>

このようになりますね。この "cd" というのは "Change Directory" の略で、カレントディレクトリを変更する命令です。UNIXにも同名の命令があります(だから "cd" を例に使いました)。そして、".." は親ディレクトリを表します。ということで、C:\WINDOWS の親ディレクトリである C:\ に移動したわけです。

 "cd" という命令に ".." というパラメータを渡しましたが、この ".." がコマンドラインなわけです。"cd" ではなく、ここに普通のプログラムを持ってくることもできるわけです。例えば、前回のプログラム kuku.exe にコマンドラインを渡すには、

kuku kuku.txt

のようにします(".exe" は省略可能です)。


 では、今度はプログラムでコマンドラインを受け取ることを考えようと思います。

 コマンドラインを受け取る窓口はやはり main 関数であって欲しいところですが、いままで見てきたとおり main 関数に引数はありません。では、どのようにすればいいのでしょうか?

 では、試しに問答無用で main 関数に引数をつけてみましょう

プログラム
// Main3.cpp
#include <iostream.h>

int main(char* cmdline)
{
    cout << cmdline << endl;
    return 0;
}

 一応ビルドはできるものの、実行してみるとエラーになります。はずれではありましたが、どうやら main は引数をとることができるようです。

 答を言ってしまうと、コマンドラインを受け取る際の main の引数は次のようになります。

int main(int argc, char** argv)

 これがどうなっているかは...実際に使って確かめてみましょう。

プログラム
// Main4.cpp
#include <iostream.h>

int main(int argc, char** argv)
{
    cout << "argc : " << argc
         << ", argv : " << argv[0] << endl;

    return 0;
}

 argv はポインタのポインタです。これは恐らく char* の配列でしょう。しかし、まだ argc, argv が何者か分かってないので、その要素数がさっぱりわかりません。そこで、argv[0] だけ表示するようにしました。

 では、このプログラムを実行してみましょう。UNIXの人は何も問題はないと思いますが、Windows の人にはちょっと解説がいると思います。

 先ず、DOSプロンプトから実行することもできます。また、VC++では設定をいじることによってコマンドラインを指定することができます。DOSプロンプトから実行するのは面倒なので、こっちでやりましょう。VC++以外でも似たような設定があると思いますが、すみませんが全てをフォローすることはできません。各自探してみて下さい。

 先ず、プロジェクトの設定を開きます。メニューの「プロジェクト」の中から開いても、Alt+F7 で開いても構いません。そして、「デバッグ」タブを選択します。すると、次のような画面になります。

コマンドラインの指定

 上の赤い丸で囲んだ「プログラムの引数」がコマンドラインにあたります。ここにコマンドラインを指定してやればいいわけです。

 では、上のプログラムでそれをやったときの結果を表にしてみましょう。

コマンドライン 結果
竹藪焼けた
argc : 2, argv : C:\...省略...\Main4.exe
隣の客は よく柿食う客だ
argc : 3, argv : C:\...省略...\Main4.exe
私 負けましたわ
argc : 3, argv : C:\...省略...\Main4.exe
青巻紙 赤巻紙 黄巻紙
argc : 4, argv : C:\...省略...\Main4.exe

 見れば分かるとおり、どうやら argv[0] には実行したプログラムのファイル名が入っているようです。

 では、argc はどうでしょうか? よく見ると、パラメータの個数+1であることがわかります。ここでパラメータと呼んだのは、空白で区切られたそれぞれのことです。

 なぜ1が足されているかは、argv[0] にプログラム名が入っているからだと推測できます。ということは、argc は argv の要素数であり、コマンドラインは argv[1] 以降に入っているという推測が成り立ちます。

 そこで、今度は次のプログラムで確かめてみましょう。

プログラム
// Main4b.cpp
#include <iostream.h>

int main(int argc, char** argv)
{
    for(int i = 1; i < argc; i++)
        cout << i << " : " << argv[i] << endl;

    return 0;
}

 コマンドラインを表示するだけのプログラムです。各パラメータは改行で区切られて出力されます。コマンドラインのみを表示するように、i は0ではなく1から始めています。

 では、上と同じように実行してみましょう。

コマンドライン 結果
竹藪焼けた
竹藪焼けた
隣の客は よく柿食う客だ
隣の客は
よく柿食う客だ
私 負けましたわ

負けましたわ
青巻紙 赤巻紙 黄巻紙
青巻紙
赤巻紙
黄巻紙

 予想通りの結果になりましたね。このように、コマンドラインは main 関数に引数を持たせることで使うことができるのです。


 では、前回のプログラムをコマンドラインを使って書き直してみましょう。kuku.exe で入力したファイル名をもう1度入力するのは無駄なので、kuku.exe にファイル名を渡すように書き換えたいと思います。

プログラム1
// kuku.cpp
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv)
{
    // パラメータ数は1(argc は2)のみ
    if(argc != 2)
    {
        cout << "パラメータ数が異常です。" << endl;
        return EXTI_FAILURE;
    }

    char  buffer[1024];
    char* pszFile = argv[1];  // 名前を付けておきます

    // ファイルの存在確認です
    FILE* file = fopen(pszFile, "r");
    if(file != NULL)
    {
        fclose(file);
        return EXTI_FAILURE;
    }

    // ファイルを開きます
    file = fopen(pszFile, "w");
    if(file == NULL)
        return EXTI_FAILURE;

    // 九九表を書き込みます
    for(int i = 1; i <= 9; i++)
    {
        for(int j = 1; j <= 9; j++)
            if(fprintf(file, " %2d", i * j) < 0)
                return EXTI_FAILURE;
        if(fputc('\n', file) == EOF)
            return EXTI_FAILURE;
    }
    fclose(file);

    return EXIT_SUCCESS;
}
プログラム2
// Main5.cpp
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>

typedef int kuku_t[9][9];

// 九九表の読み出し
bool KuKu(FILE* file, kuku_t& kuku);
// 表示
bool Disp(kuku_t& kuku);

int main()
{
    char bufFile[1024];
    char bufExec[1024];

    cout << "九九表のファイル名を入力して下さい > " << flush;
    cin >> buffer;

    // $ を入力すると終了します
    if(strcmp(buffer, "$") == 0)
        return EXIT_SUCCESS;

    sprintf(bufExec, "kuku %s", buffer);
    if(system(bufExec) != 0)
        return EXIT_FAILURE;

    FILE* file = fopen(bufFile, "r");
    if(file == NULL)
    {
        cout << "ファイルが開けません。" << endl;
        return EXIT_FAILURE;
    }

    kuku_t kuku;
    if(!KuKu(file, kuku))
    {
        cout << "ファイルエラー。" << endl;
        return EXIT_FAILURE;
    }

    while(Disp(kuku));

    return EXIT_SUCCESS;
}

// 以下は前と同じ

 ファイル名の入力が1回になったことを除けば、動作は前と同じです。

 kuku.cpp では、先ずパラメータの個数を確かめています。コマンドラインを扱うときはおそらくいつもすることになると思います。

 で、分かりやすくするために、argv[1] を一旦 pszFile という変数に入れます。で、あとは九九表を作るだけです。

 そして、問題はプログラム内でどうやってコマンドラインを指定して kuku を実行するかです。前回散々環境依存だ、環境依存だといいましたが、プログラムを実行する機種依存しない関数も存在します。それが system です。

 ただこの関数、戻り値は環境依存します。system は「コマンドプロセッサ」と称されるものを使ってプログラムを実行します。DOSでは command.com 、UNIXでは /bin/sh などです。system の戻り値はこのコマンドプロセッサの終了コードになります。このコマンドプロセッサが環境によって異なるので、終了コードも必然的に環境依存するというわけです。ということで、前回は使いませんでした。

 この system 関数の使い方は簡単です。"cd .." や "kuku kuku.txt" のような文字列を渡せば、その通り実行してくれます。その system に渡す文字列を作るために、sprintf を使っているわけです。可変個引数(第16章参照)をとる system を作ってみると便利かもしれません。その際、va_list を引数にとる sprintf である vsprintf を使うといいでしょう

 で、あとは前回と同じです。コマンドラインの使い方はこんなもんですね。


 最後に補足です。

 先ず、argv についてです。argv の中には argv[0] にプログラム名が、argv[1] 〜 argv[argc - 1] にパラメータが入っているわけですが、実は argv[argc] にもある値が入っています。それは NULL です。つまり、

for(int i = 1; argv[i] != NULL; i++)

というループが使えます。

 そして、次に空白を持つパラメータを指定する方法についてです。パラメータは空白で分けるようになっているので、これでは空白の入ったパラメータが指定できません。その時は " " で囲んでやればOKです。

kuku "kuku kuku.txt"

 UNIXでは ' ' も使えます。ただし、" " と ' ' には若干違いがあります。そこら辺はUNIXの話になってしまうので割愛します。


 では、今回の要点です。


 それでは、また。


第41章 私はαでありωである | 第43章 切り捨て御免

Last update was done on 2001.4.22

この講座の著作権はロベールが保有しています