14回目 実用的なプログラムを求めて


グラフィックの利用

前回の課題は,コンソールに文字でグラフを示すという,古式ゆかしい方法を使ってみました.
コマンドラインだけで示すにはよい方法ですし,シミュレーション結果を簡単にチェックしておこうと
考える意味では利用価値もありますが,ソースコードを見れば分かる通り,結構面倒であったり
します.(90度曲げてしまえば配列を使うことなくグラフを作成することも可能ですが見辛いですね.)

以下を参考に,グラフィックを利用することが可能です.

参考ページ windows グラフィック 入門

なお,必ずmogra_2.fも併せてダウンロードしておいて下さい.

注意事項

プログラムを停止する場合,コマンドラインのコンソールをクリックして有効化した後,[Ctrll]+[C]を
押して終了して下さい.


main関数も,関数

これまで関数には引数があり,戻り値があり,それぞれ型があってやり取りをするという話は
何度か聞いたと思います.ではmain関数はどうなるのでしょうか?

関数であるから,引数も戻り値もあります.では,どこで引数を獲得するのでしょうか?

例えばコマンドライン上で以下のようにタイプしたとします.

c:\>sample.exe 1.52 6.44 9.81                                      

main関数に引数を設定すれば,1.52,6.44,9.81をそれぞれ値として取得,プログラム内で利用
することができます.

ソフトウェアを使う,と考えた場合,実行ファイルが起動されているのですが,実行される部分は
main関数ですので,上記のようにデータ(引数)をやり取りする手段が用意されているのです.

方法は,以下の記述法となります.

int main(int argc,char *argv[])

これまでのおなじみの書き方とは違っていることが分かるかと思います.


コラム: main関数の戻り値について(終了コード)

さて,main関数の表記について,いろいろなものを見たことがあると思います.例えば,

main()
void main()
void main(void)
int main()
int main(void)

などでしょうか.厳密に言えば,規格であるANSIに基いた記述をすべきであると言い放って
しまえばそれまでですが,実際にはどれでもコンパイルされてしまいます.(アラートが出る
こともあります.)

ここでソフトウェアがどのように実行されるかを説明します.



       ソフトウェア実行ファイルとランタイムライブラリの関係

作成したソフトウェアが直接PCを動かすわけではなく,ソフトウェアの実行により生成された
コマンドがすでに用意されているランタイムライブラリやOSを介して動作するような構造と
なっています.その際に,細かい命令の単位できちんと実行されたか確認するような動作が
含まれているのです.

プログラムの根幹となるmain関数も実行が終わったら,終了したこと自体,ならびに正常に
実行が完了したことをシステムであるOSに伝える必要があるのです.
従って,画面などに表示されることはありませんが,戻り値を返すことが求められるのです.
そのために終了コードと呼ばれるものがあり,正常であれば,0が渡されます.
何も処理しないmain関数であったとしても,以下のように記す必要があります.

C言語 int main()
{
  
  return 0;
}
C++言語 int main(void)
{
  
  return 0;
}

実はCとC++どちらの表記でも正常にコンパイルされますが,C++でint main()とした場合は,
暗黙の型としてvoid型として扱われるためなのです.但し,もう少し厳密に説明すると,

int main() int main(void)
C言語 引数をチェックしない 引数がない
C++言語 引数がない 引数がない

という意味であるため,int main(int argc,char *argv[])のように,コマンドラインにて引数を
与えるつもりがない場合,int main(void)と書いた方が分かりやすいと言えますが,int main()
でも文法(規格)上は何の問題もありません.

従って,厳密には, void main()やvoid main(void)では終了コードが返せないため,プログラム
終了したのか,それがOSには分からなくなってしまいます.これを厳密に回避するには,

C言語 void main()
{
  
  exit(0);
}
C++言語 void main(void)
{
  
  exit(0);
}

のように記述すれば,main関数の末尾で[強制終了]の上,正常終了である[終了コード0]を
システムに伝えるという処理を行うことが出来ますが,強制終了という意味では穏やかでは
ないのです.

main()の場合,厳密には文法ミスですが,コンパイラの方で足りない分を適当に補って何事も
ないコードにしておいてくれると考えた方が良さそうです.

結論としては,以下のいずれかで記述した方がよいと言えます.

コマンドラインでmain関数に引数を与えない int main()またはint main(void)
コマンドラインでmain関数に引数を与える int main(int argc,char *argv[])

話を戻しましょう.コマンドラインで,引数を受け取るには,以下のように記述します,

int main(int argc,char *argv[])

引数名がargcやargvである必要はありませんが,殆どの解説書でこれらの名前が充てられて
いるので,慣例に従って説明します.
先ほどの例のようにタイプしたとします.

c:\>sample.exe 1.52 6.44 9.81                                      

実行ファイル名の後,1.52 6.44 9.81という3つの変数が記述されています.上記のようにmain
関数に記述しておけば,以下のように認識されます.

argc 引数の数 → 配列数 3
argv[] 引数の配列 → 各引数の中身
但し,文字列として認識される
1.52
6.44
9.81

サンプルプログラムを見て下さい.

#include <stdio.h>

int main(int argc,char *argv[]){
 int i;
 
 for(i=0;i<argc;i++){
  printf("argv[%d] : %s\n",i, argv[i]);
 }
 return 0;
}

実行結果 

argv[0] : c:\sample.exe
argv[1] : 1.52
argv[2] : 6.44
argv[3] : 9.8
0番目の文字列で実行ファイル(フルパス)
1番目の数値の文字列
2番目の数値の文字列
3番目の数値の文字列

上記の通りですが,ちょっと扱いにくい点がありますので考えてみましょう.
コマンドラインにタイプされたものはすべて文字列として扱われ,スペースを区切りとして,それ
ぞれ長さの異なる複数の文字列として扱われます.しかも,それぞれの文字列は配列のポインタ
として扱われるため,アクセスの仕方を間違えると,本当に必要な引数が獲得できない可能性が
あるのです.

そこで,以下のように用意されている関数を用いて文字列配列の各要素を浮動小数点に変換
します.
文字列はそれ自体が配列であるため,(double)といったキャストを行うことができないのです.

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv[]){
 int i;
 char *ep=NULL;

 for(i=1;i<argc;i++){
  printf("argv[%d] : %lf\n",i, strtod(argv[i],ep));
 }
 return 0;
}

strtod関数を利用するために必要なヘッダファイル



strtod関数を利用するために必要な変数ポインタ


文字列を数値に変換する関数


なお,文字列配列の0番目は,実行ファイルのフルパスが格納されますので,無視して数値の配列に
格納された値は以下のものとなります.

argv[1] : 1.52
argv[2] : 6.44
argv[3] : 9.81

従って,これまでプログラム本文中で記述したり,scanfで取得していた数値は,プログラム実行時に
まとめて指定することが可能となります.

コマンドライン引数を利用するメリット

ソースファイル内の数値を変更してコンパイルすれば,もちろん必要な演算を行うことはできるのは確か
です.しかし,コンパイルしなおすのは時間が掛かり過ぎであるだけでなく,ソースファイルの他の部分を
うっかり変更してしまう危険性があるので,一度生成させた実行ファイルを変えることなく,必要な変数の
値のみを与える方法は有効なのです.

注意すべき点

実行ファイル名 変数 変数・・・のように記述するため,必要な値がプログラム内の必要な変数に格納
されたかどうか分からない危険性があります.従って,獲得されたコマンドライン引数を表示するような
工夫が必要となります.

※ 慣例的ですが,実行ファイル名 /?や実行ファイル名 /hと入力すれば,何番目にどのような値を入力
する必要があるか説明が表示できるようにプログラムを作成する方法もあります.

(参考) コマンドライン引数を用いない方法

コマンドライン引数が便利な場合もあれば,毎度引数を入力する必要があるため煩わしい場合もあります.
コンパイルを繰り返さず,複数の引数を扱うために,別途決められた名前のテキストファイルを作成しておき
これを読み込ませる方法もあります.例えば,sim.txtとかsim.iniのような名前をつけたりすればよいのです.

※ コマンドライン引数にファイル名を指定するという方法もあります.


複数の変数をプログラムで扱う工夫についてまとめ

方法 メリット デメリット
対話的に変数を入力 1変数ごとに確認しながら
入力できる
変数の数が多い場合面倒であり,
入力ミスを招く可能性がある.
コマンドライン引数 一括して変数を入力でき,
軽快な操作となる.
条件を変えたときは,全変数を
入力しなおさなければならない.
テキストファイルの読み込み 計算条件を予め検討して
用意しておくことができる.
テキストエディタで編集の必要があり,
スペース,タブ,改行コードでエラーを起こす.

課題1 比例制御およびPID制御についてそれぞれ以下のファイルをダウンロードし,シミュレーションを
行いましょう.速度型・位置型のどちらを用いても構いません.

速度型アルゴリズム p_p_gra.c
位置型アルゴリズム p_v_gra.c

速度型アルゴリズム pid_p_gra.c
位置型アルゴリズム pid_v_gra.c

前回同様,時定数T=2.0秒,シミュレーション時間SimTは5〜10.0秒にし,目標値はSV=5000[rpm],Kpを
自由に調整して良いと思われる応答になるよう調整し.今回のソースではグラフが表示されるはずなので
何回か試行しながら値を決めましょう.

※ プログラムを停止するには [Ctrl] + [C] という操作をして下さい.
※ ソースファイルの拡張子はcppではなくcにしておいて下さい.

参考ページ windows グラフィック 入門
なお,必ずmogra_2.fも併せてダウンロードしておいて下さい.

提出するもの: 自ら書き換えたソースファイル,グラフの入ったファイル※2

※2 グラフの提出の方法
画面上のウィンドウに出力されたグラフを簡単に出力するには,[Print Screen]キーを押し,パワーポイントや
ペイントなどに貼り付け,それを保存すればよいのです.ppt形式でもjpg, png, gifのどれでも構いません.

課題2 上記プログラムのどれかひとつを選択し,コマンドライン上で引数を受け取ることができるように
変更したものを作成して下さい.

提出するもの: 自ら書き換えたソースファイル

課題3 
前回は,シミュレーションした数値をグラフ化して結果を比較し,与えたいパラメータを検討し,今回は,
簡易的にグラフをその場で表示して,応答を見ながら与えたいパラメータを検討しました.

数値シミュレーションを行うにあたって,即時グラフを表示することがどのように有効であるか論じて下さい.
また,前回,今回のように与えたいパラメータを決めるにあたって,さらに検討すべきことはどのようなことが
あるか(何が不足しているか)について説明して下さい.
ヒント: 理論の扱い

提出するもの: 本課題について論じたWordファイル