高分子計算科学A
UNIX
UNIXは, Windows のようなOSの仲間である. 昔のパソコンは,今のようにウィンドウの環境が発達 しておらず,マウスを用いる事なく,キーボードによって,ディスプレイに一行一行,コマンドを打ち込む 事により,色々な作業をしていた.UNIXを用いると,そうした時代の名残を見る事ができる.というより は,UNIXのベースは,そうしたコマンドラインにキーボードによって命令を打ち込む事なのである. スター ト→すべてのプログラム → ASTEC-X → 米沢Linux により,UNIX系列のOSであるLinuxのサーバーに ログインする事ができる. ASTEC-X のウィンドウシステムを右クリックして現れる 端末をクリックして端 末を立ち上げる.端末から,様々なUNIXの命令を実行する事ができる. UNIXシステムにログインした場合,通常,各人のHOMEと呼ばれるディレクトリ(フォルダ)が基準とな る.端末を立ち上げた状態でもそうである.ASTEC-X で端末を立ち上げ, $ pwd と入力すると,HOMEの場所が表示される.この時に /home1/t0/t0903 などというように, / で区切 られた文字列が出力される. この / の記号はディレクトリ(フォルダ)を示すための区切り記号である. ディレクトリとは,ハードディスクなどのファイルシステムの上に情報を記録して行く際に,幾つかの関連し たデータをまとめておく部屋あるいは箱のような概念である.Windows や Mac OSの上では,フォルダと呼 ばれている. /home1/t0/t0903 の一番左側の / は一番外側のディレクトリ(ルートディレクトリという)を 示している. • / はルートディレクトリで,一番外側の 箱 に相当する. • /home1/t0/t0903 などは絶対パス名.絶対パス名は / で始める. • /を付けないときは,相対パス名で,自分が今いるディレクトリが基準となる. • . は自分が今いるディレクトリを, .. は一つ上の階層のディレクトリを表す. • ~ はホームディレクトリを表す.
よく使うUNIXのコマンド
$ ls → ファイルの一覧を表示. $ ls -a → 隠しファイルも表示.Figure 1: ディレクトリの階層構造 $ ls -l → 詳しい情報も表示される. $ ls -l -a→ 隠しファイルと詳しい情報も表示される. $ ls -la→ 上に同じ. $ ls -F → ファイルの一覧を実行可能ファイルとディ レクトリの区別も含めて表示する. $ ls <dirname> → <dirname> というディレク トリ内のファイルの一覧を表示. $ cd <dirname> → カレントディレクトリ(今いるディ レクトリ)を <dirname> に変更. $ cd → ホームディレクトリ(端末を起動し た際のカレントディレクトリ)に戻る. $ pwd → カレントディレクトリ名の表示.
$ cat <filename> → ファイル <filename> の 内容を表示.
$ less <filename> → ファイル <filename> の内容を1ページずつ表示.less コマンド実行中は, Space で次のページ,bで前のページ, qで終了.
$ man <cmdname>→ コマンド <cmdname> の使 用法の表示.
$ mkdir <dirname>→ ディレクトリ <dirname> を作る. 絶対指定でないときは,カレントディレクト リが基準となる.
注意
ASTEC-X でログインした際のホームディレクトリは,Windows の Z ドライブと同一視されている.作業
Zドライブ上に, nrps という,この講義のためのディレクトリを作れ. Zドライブが, ASTEC-Xにログインした際のホームディレクトリ( ~)と関係づけられているため, このZ ドライブ上のディレクトリnrpsのことを ~/nrps と書く事にする. ASTEC-X の端末上で, $ cd ~/nrps とすることで, この nrps という名前のディレクトリに移動する事ができる.1 プログラミングの基礎知識
Cの位置付け (テキスト p.4‒) プログラムの作成から実行まで (テキスト p.14‒) ソースファイルを作るには,テキスト形式のファイルが編集できるエディタを用 いる.センターの端末に インストールされている, サクラエディタなどを使うことができる. スタート→ すべてのプログラム→ サクラエディタ にて, サクラエディタを起動する.そして,次の内容を打ち込むことにする. /* This is my first C program. */#include<stdio.h>
#define MYNAME "abcdefg" int main()
{
printf("Hello World!!\n");
printf("My name is %s.\n",MYNAME); return(0);
コンパイルの仕方
C言語のプログラムは, テキストエディタで作成した後, hello.c 等のように,.c の付いたファイル名 で保存する.
例えば,次の内容のファイルを, ~/nrps に, hello.c という名前で保存する. /* This is my first C program. */
#include<stdio.h>
#define MYNAME "abcdefg" int main()
{
printf("Hello World!!\n");
printf("My name is %s.\n",MYNAME); return(0); } この段階では,まだ,アスキー(テキスト)形式のファイルを作成しただけであ る.アスキー(テキスト) 形式と言うのは,簡単に言うと,人間が読む文字,文 章の情報を持っているファイルの形式である. これをコンピューターが実行できる形のファイル(実行ファイル)に変換する作 業が,コンパイルと呼ば れる操作である.コンパイルをして,実行ファイルを作 り,プログラムした作業を実行する方法は,何通り もあるが,ASTEC-Xでログイ ンしたUNIX上の端末では,次のようにする. まず,プログラム hello.c がカレントディレクトリ(今自分が居るディ レクトリ)にあることを確認する. ない場合は,hello.c が有るディレ クトリに移動する.この講義では, ~/nrps に全ての関係したファイル をおく事にするので, $ cd ~/nrps によって, nrps に移動する. $ ls�-a によって,カレントディレクトリにあるファイルが確認できる.コンパイルする には, $ gcc hello.c -o hello.out -lm
とする.hello.c というファイルをコンパイルして,hello.out という実行ファイルが作られる.ここ で,hello.out は,これである必 要は全くなく,この部分を aaa.out とすると, aaa.out という 実行ファ イルが作られる. $ ls で,hello.out ができたことが確認できる. $ ./hello.out で実行してくれる. このやり方だと,コンパイルの際に, いちいち, gcc .... とたくさん打ち込む必要があり,面倒であ る. c というファイルを講義のホームページからダウンロードし ~/nrps に置いて,一度, $ cd ~/nrps $ chmod +x c としておくと, $ cd ~/nrps $ ./c hello と,するだけで,ソースファイル hello.c をコンパイルして, 実行ファイル hello.out を作ってくれる. なお,hello.c のように,実行ファイルなどのファイルを作成するさい のもとになるファイルをソース ファイルと呼ぶこともある. ここまでの作業により,~/nrps にプログラムをおいて,コンパイルする準備が整ったことに なる.
注意
上の作業で,既に ~/nrps に移動している場合は,いちいち $ cd�~/nrpsとする必要はない.
ファイルの消し方
削除したいしたいファイルがあるディレクトリに, cd で移動し,消したいファイル, あるいは,ディレクト リを, $ ls -a にて確認し, $ rm -r <file name> で削除できる. 再び $ ls -a とすることで,削除したことを確認できる. 削除する際は,本当に削除しても良いかをもう一度考えてから にする. ここまでできた人は,テキストの第2章に書いてあるソースプログラムを作り, コンパイルして,実行し てみる.2 プログラミング入門
課題 まずは,テキストのリスト 2.3, リスト 2.4, リスト 2.5 を適当なファイル名にて作成し,コンパイ ル,および実行してみる. 次の内容のソースファイルを io_sample.c として作成し,コンパイルし て,実行せよ. #include <stdio.h> int main() { int a, b; int c; printf("Input:\n"); printf("a="); scanf("%d",&a); printf("b="); scanf("%d",&b); printf("a=%d,b=%d",a,b); c = a+b; printf("c = a + b = %d + %d = %d", a, b, c); if( c < 0 ){ printf("%d is negative.\n",c); }else if(c > 0){ printf("%d is positive.\n",c); }else{ printf("%d is zero.\n"); } return 0; } このプログラムは,整数 a, b の値を標準入力から入力し, c という整数変数にその和を入力し,結果を 出力する.さらに, 条件分岐により, c の正負を表示するものである.このプログラムは,テキスト第2章の理解を深くするためのものである. そのため,テキスト第2章を,良 く読むこと. 課題 このプログラムの出力は,整理されておらず,見づらいため, 改行 \n などを適当な場所に出力 して,出力される表示が見やすくな るように,改良せよ. 課題 整数を入力し,それが偶数か奇数かを判断し,その結果を表示 するプログラムを作成し,コンパ イル,および,実行せよ.
3 変数と式
• 変数とは,メモリ上に用意した,数値を格納するための箱のようなものである (テキスト p.60). • 使用できる変数名には制限がある(テキスト p. 62).
• 変数の型には,int, double, float, char などがある(テキスト p.64).
• long, short をつける事で,値の範囲を指定することがで きる(テキスト pp. 64,65) • 整数型は int, char, 浮動小数点型(実数)は double, float である(テキスト p.64). • 文字は小さな整数 char で表現をする. [やること] 次のプログラムは,テキストのリスト3.1(テキスト p.68)を参考にしたものであ る. char.c とし て作成し,コンパイルおよび実行せよ. #include <stdio.h> int main() { char a,b,c; a = 'x'; b = '1'; c = '%'; printf("%c %c %c\n",a,b,c); printf("%d %d %d\n",a,b,c); return(0); } [やること] また,表示をタブ(テキスト pp.37‒)で整えた次のプログラムを, char_tab.c として作成し,コ ンパイルおよび実行せよ. #include <stdio.h> int main() {
char a,b,c; a = 'x'; b = '1'; c = '%'; printf("%c\t%c\t%c\n",a,b,c); printf("%d\t%d\t%d\n",a,b,c); return(0); } • printf の ... の引数の中の %d は整数を, %c は char型の変数に格納された文字を表示する(テキ スト p.44,p.68). • printf の ... の引数の中の %f, %e, %g は,浮動小数点型の変数に格納された数値を表示する記 法で ある(テキスト pp. 75-76). [やること] テキスト p.74 の, リスト3.5 を適当なファイル名で作成し,コンパイルおよび 実行せよ. • 変数は定義されることにより,その入れ物がメモリ中にセットされるが, どんな初期値が入っている か不定である(テキスト pp.79-80). • 演算された式は値を持つ(テキスト p.86). • 代入式は,代入動作をする式である(テキスト pp.87-89). • 算術演算子には,整数型のものと,浮動小数点型のものがある.整数型の ものにあって,浮動小数 点型のものにないのは,余りを求める演算子 % である(テキスト pp.89-92). • 演算子には優先順位がある.自信がないときは,括弧を付けて, 間違いがないようにする(テキスト pp.94-94). • 整数型と浮動小数点型の混合演算に注意する(テキスト pp.95-97). • 代入における型変換に注意する(テキスト pp.97-98). • キャストの演算子を用いて型変換を行うことができる(テキスト pp.98-99). やること 次のプログラムを variable.c として作成し,コンパイルおよび実行せよ.また,動作を理解せ よ.
#include <stdio.h> int main(void) { int i,j; double f,g,h,k; i = 1.5; j = 2/3; f = 2/3; g = 2/(double)3; h = 2/3.0; k = 2.0/3; printf("int i = 1.5 = %d\n",i); printf("int j = 2/3 = %d\n",j); printf("double f = 2/3 = %f\n",f); printf("double g = 2/(double)3 = %f\n",g); printf("double h = 2/3.0 = %f\n", h); printf("double k = 2.0/3 = %f\n", k); return 0; }
C言語の約束事
• C言語のプログラムでは,main関数が必ず必要.(テキスト p.166‒参照) • プログラムを実行すると,main関数, すなわち,main()の後の, { と } で囲まれた部分が実行さ れる. • #include は,指定されたファイルをその位置に読み込む.(テキ スト p.166‒) • C言語のソースファイルは,拡張子を .c とする. • 数値は,小数点を付けると実数,付けないと整数として扱われる.(テキ スト p.73‒) • 一つの文の中で用いる数値について,小数点を付けたものと,付けないも のを混ぜるときは注意す る. • C言語では,文を ; によって区切る. • ここでは変数名や関数名の区切りとなる記号を区切り記号という. • 区切り記号には,空白文字,演算子などがある. • 空白文字とは,スペース,タブ,改行などのことである. 次の2つのプログラムはどちらも同じ内容である. ‒ その1 ‒ #include <stdio.h> int main() { printf("Hello World!\n"); return(0); } ‒ その2 ‒ #include <stdio.h>int main(){printf("Hello World!\n");return(0);}
#include <stdio.h>
int mai n(){printf("Hello World!\n");return(0);} 次に挙げるのも区切り記号である. 記号 意味 ; (セミコロン) 一つの実行文など : (コロン) ラベル {} (大カッコ) 複文 () (カッコ) 関数の引数など [] (中カッコ) 配列の要素数など <> (角カッコ) #include 文のファイル指定 '' (シングルクォーテーョン) 1文字 "" (ダブルクォーテーション) 文字列など / (バックスラッシュ) 制御文字 % (パーセント) 書式指定文字 • 字下げをしてプログラムを見やすくするようにする.(テキスト pp.32‒36) • 対応する { と } などの字下げの位置を揃えるとプロ グラムが見やすくなる. • 変数や関数の名前は,英文字,数字,アンダースコア _ の組み合 わせで作る.ただし,最初の文字 は,英文字か _ でなければなら ない.(テキスト pp.62‒63) • 変数や関数として,予め登録されている予約語や関数名は使わないように する. • 使うことのできない変数名を使ってしまった場合,コンパイルのときにエラーが出る.
演算子
(テキスト p.85‒) 算術演算子は,加減乗除など通常の演算に使われる演算子である. • 整数の除算の結果に余りが生じた場合は切り捨てになる. • 浮動小数点数での剰余算はエラーとなる. • 原則として演算結果のオーバーフローの処理は行わない.Table 1: ニ項演算子 演算子 意味 使用法 + 加算 a = b+c; b と c を加えた値を a に代入する. - 減算 a = b-c; b から c を引いた値を a に代入する. * 乗算 a = b*c; b と c を掛けた値を a に代入する. / 除算 a = b/c; b を c で割った値を a に代入する. % 剰余算 a = b%c; b を c で割った余りの値 を a に代入する. Table 2: 単項演算子 演算子 意味 使用法 他の 演算子による表記 ++ インクリメント a++; または ++a; a = a + 1; -- デクリメント a--; または --a; a = a - 1; - 符号の反転 b = -a; ̶ Table 3: 代入演算子 演算子 使用法 意味 = a = b 代入 += a += b; a = a + b; -= a -= b; a = a - b; *= a *= b; a = a*b; /= a /= b; a = a/b; %= a %= b; a = a%b; Table 4: 関係演算子 演算子 意味 > 大なり < 小なり >= 大なりイコール <= 小なりイコール == 等しい != 等しくない
Table 5: 論理演算子 演算子 意味 && AND || OR ! NOT a = (b > c); は,c よりも b が大きければ, a に1を,そうでなければ a に0を代入する. a = ( b != c); は,b と c が等しくないとき, a に1を,そうでなければ a に0を代入する. a = (b > c) && (c < d); は, b>c と c<d がともに真 (1)ならば, a に真(1)を,それ以外は偽(0)を代入す る. a = (b > c) || (c < d); は, b>c と c<d のどちらかが 真(1)であれば, a に1を代入し,両方とも偽(0) であれば偽(0)を代入す る. a = !(b > c); は,b>c が真(1)であれば a に偽(0)を, 偽(0)であれば真(1)を代入する. その他の演算子には,条件演算子,カンマ演算子,アドレス演算子,ポインタ演 算子がある.これらに ついては,必要のある時に説明する.
プログラムの流れを制御する
C言語では,プログラムの流れを制御するために, if, for, while などを用いる.(テキスト 4章) 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 int num,i; 7 8 for(i=0;i<4;i++){ 9 num = rand()%2000; 10 11 if(num < 100){ 12 printf("%d < 100\n", num); 13 }else if(num > 1000){ 14 printf("%d > 1000\n", num);
15 }else{
16 printf("100 <= %d <= 1000\n",num); 17 }
18
19 if(num < 100)printf("The num is smaller than 100\n"); 20 21 } 22 23 i=0; 24 while(i < 5){ 25 num = rand()%2000; 26 printf("num = %d\n",num); 27 i++; 28 } 29 30 do{ 31 num = rand()%2000;
32 printf("In do_while, num = %d\n",num); 33 i--; 34 }while(i>0); 35 36 return(0); 37 } 38
関数
関数は, 関数が返す値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2....); で宣言し, 関数が返す値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2....){
関数がする仕事,手順 }
[やること] 次のプログラムは,コンピュータとジャンケンをするための ものである. janken_1.c として 作成し,コンパイルおよび実行せよ. ただし,先頭の数字は行番号であり,テキストエディターでは入力し ないように する. 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #define GU "\n nnnn\nCUUUU\n" 5 #define TYOKI "\n n n\n ||//\n | N\n \\ /\n" 6 #define PA "\nnnnn\n|||||/\\\n| / /\n\\ | /\n" 7 8 int main() 9 { 10 char man, pc;
11 char *man_no_te, *pc_no_te; 12 time_t now;
13 char *time_char; 14 char ctmp[10];
15 int hour, min, sec, tmp; 16 unsigned seed;
17
18 printf("Let's do JAN-KEN with me !!!!!\n");
19 printf("JAN-KEN-PON \nGU: %s\n TYOKI: %s\n PAH: %s\n\n", GU, TYOKI, PA); 20 printf("Input your choice from G, T and P (for GU, TYOKI, PAH)\n"); 21 printf("-->"); 22 scanf("%c", &man); 23 24 if(man == 'g'){ 25 man='G'; 26 man_no_te = GU; 27 }else if(man == 'G'){ 28 man_no_te = GU; 29 }else if(man == 't'){ 30 man='T'; 31 man_no_te = TYOKI;
32 }else if(man == 'T'){ 33 man_no_te = TYOKI; 34 }else if(man == 'p'){ 35 man = 'P'; 36 man_no_te = PA; 37 }else if(man == 'P'){ 38 man_no_te = PA; 39 } 40
41 /* Initialization of random number */ 42 time(&now);
43 time_char = ctime(&now);
44 sscanf(time_char,"%s %s %d %d:%d:%d %d\n", ctmp, ctmp, &tmp, 45 &hour, &min, &sec, &tmp);
46 printf("%d:%d:%d\n", hour, min, sec); 47 seed = hour+min+sec; 48 srand(seed); 49 /****/ 50 51 pc = rand()%3; 52 if(pc == 0){ 53 pc = 'G'; 54 pc_no_te = GU; 55 } 56 if(pc == 1){ 57 pc = 'T'; 58 pc_no_te = TYOKI; 59 } 60 if(pc == 2){ 61 pc = 'P'; 62 pc_no_te = PA; 63 } 64
66 if((man == 'G' && pc == 'P') || (man == 'T' && pc == 'G') 67 || (man == 'P' && pc == 'T')){
68 printf("I CPU won.\n"); 69 }else if(man == pc){ 70 printf("Draw.\n"); 71 }else{ 72 printf("You won.\n"); 73 } 74 75 return(0); 76 }
4 制御の流れ
• 条件判断を行う文として,if 文, switch 文が用意され ている(テキスト pp.102,103).
• 関係演算子, 論理演算子,否定演算子(pp.103-111),を用いて記述する条件式が取り うる値は, 真 (true) か 偽(false)である(テキスト pp.103).関係演算子は0(偽)か1(真)の int 型の値を返す演算子 である(テキスト p.106).実際の真偽の 判定は,0ならば偽,非0ならば真という規則を用いる(テキス ト p.107).
• 関係演算子にはテキストp.104の表4.1のようなものがある.
• scanf変換文字について,int に対応する %d, char に対応する c, short に対応する %hd, long に対 応する %ld, float に対応する %f, double に対応する %lf などがある.
• 論理演算子 AND &&, OR ||(テキスト pp.107-111), 否定 演算子 ! (テキスト pp.111-112). • if文,switch文について(テキスト pp.112-131)
• ループとは,同じことをある条件が成立するまで何度も行う処理である (テキスト p.132).
• ループ処理を行うものとして,while文(テキスト pp.132-143), for文(テキスト pp.143-150), do-while文 (テキスト p.150) がある. [やること] 次のプログラムを for_if.c という名前で作成し,コンパイルおよび実行せよ.また,動作を理 解せよ. 1 #include <stdio.h> 2 3 int main() 4 { 5 int j; 6 7 for(j=0;j<6;j++){ /* j++ � j = j+1, j-- � j = j-1 */ 8 if(j%2 == 0){ 9 printf("j = %d\n", j); 10 }else{ 11 printf("---\n"); 12 } 13 }
14 15 printf("*****\n*****\n"); 16 17 for(j=0;j<6;j++){ 18 if(j/2 == 1){ 19 printf("j = %d\n", j); 20 }else{ 21 printf("---\n"); 22 } 23 } 24 25 return(0); 26 } [やること] つぎのプログラムは,1から10まで整数を足して, 表示するものである. #include <stdio.h> int main() { int i; int wa; wa = 0; for(i=0;i<=10;i++){ wa += i; } printf("��:%d\n",wa); return(0); } [やること]このプログラムと同じ動作をするものを, while文を用いて書いて下さい.
5 関数2
[やること] テキスト p.165 のリスト 5.3 の内容を適当なファイル名で 作成し、コンパイルおよび実行 し、動作を理解して下さい。 [やること] 次の2つのプ ログラムを作成し、実行を試みて下さい。 その1. #include <stdio.h> int even(int j) { if(j%2 == 0){ return(1); }else{ return(0); } } int main() { int j; printf("\n"); for(j=0;j<10;j++){ printf("%d is ",j); if(even(j)){ printf("even.\n"); }else{ printf("odd.\n"); } } return(0); } その2. #include <stdio.h>int even(int a); int main() { int j; printf("\n"); for(j=0;j<10;j++){ printf("%d is ",j); if(even(j)){ printf("even.\n"); }else{ printf("odd.\n"); } } return(0); } int even(int j) { if(j%2 == 0){ return(1); }else{ return(0); } } このようにmain()の位置は,プログラムの先頭でも後でも良いです. 関数を関数プログラムより前で呼ぶ ときには,プロトタイプ宣言をテキスト p.166 の図5.4のように入れるようにします. 次のプログラムは, コンパイルできませんが,どうすれば良いでしょうか. #include <stdio.h> int main() {
double a, b; a = 1.0; b = 2.0; printf("%f\n", waru(a,b)); return(0); }
double tasu(double x, double y) {
return(x+y); }
実は,関数のプロトタイプ宣言が抜けているからです. コンパイルできるようにするためには,関数のプ ロトタイプ宣言を追加する手段 とは,もう一つ別の手段があります.それは何でしょうか?
6 ポインタ
• int x; などと定義された x に対し,アドレス演算子 & を用いて,&x とすることで,この変数のアド レスを求 めることができます(テキスト p.273). • アドレスを格納できる変数,ポインタ変数は, int *p; などと定 義する(テキスト p.274). • ポインタ変数 p に対し,*p は,そのポインタが示すアド レスに格納されている値を示す(テキスト p. 275). [やること] 次のプログラムを作成し,コンパイルおよび実行せよ. #include <stdio.h> int main() { int x; int *p; p = &x; x = 1; printf("x = %d\n", x); printf("Address of x = %p\n", &x); printf("p = %p\n", p); *p = 2; printf("After *p = 2, \n"); printf("x = %d\n", x); printf("Address of x = %p\n", &x); printf("p = %p\n", p); printf("Input integer:"); scanf("%d",p); printf("\nx = %d\n", x); printf("Address of x = %p\n", &x); printf("p = %p\n", p); return(0); }
[やること] change.cを作成する。 ただし,change.c の内容は次の通りとする. 1 #include <stdio.h>
2
3 void change(int *a, int *b) 4 { 5 int c; 6 7 c = *a; 8 *a = *b; 9 *b = c; 10 11 } 12
13 void badchange(int a, int b) 14 { 15 int c; 16 17 c = a; 18 a = b; 19 b = c; 20 } 21 22 int main() 23 { 24 int x,y; 25 26 x=1; 27 y=2; 28 printf("x = %d, y = %d\n",x,y); 29 badchange(x,y); 30 printf("x = %d, y = %d\n",x,y); 31 change(&x,&y); 32 printf("x = %d, y = %d\n",x,y); 33
34 return(0); 35 }
36
badchange() の仮引数 a, b の通用範囲(スコープ)が, 関数 badchange() 内に限られているため,badchange() 内の, 変更は,badchange() を呼び出した,main() の変数に反映され ない.change() のように仮引数を ポインタにすることで,関数の呼び出 し側の変数を変更する関数を作成することができる(テキスト pp. 287-288).こ うした関数の良い例が, scanf() である.
7 ジャンケンプログラム 関数版
[やること] 以下のプログラムは,18ページで作ったジャンケン用プログラ ムを改良し,さらに,ジャンケ ンの過程を関数化しています. まだ,「あいこ」の場合は勝負を付けずに終わるようになっています.まず はこ のプログラムを, janken_2.c として作成し, 理解し,これをもとに,どちらかが勝つまでジャンケン を繰り返すプログラムに して下さい. 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #define GU "\n nnnn\nCUUUU\n" 5 #define TYOKI "\n n n\n ||//\n | N\n \\ /\n" 6 #define PA "\nnnnn\n|||||/\\\n| / /\n\\ | /\n" 7 8 int ran_123() 9 {10 static int first=1; 11 time_t now;
12 char *time_char; 13 char ctmp[10];
14 int hour, min, sec, tmp; 15 unsigned seed; 16 17 if(first){ 18 time(&now); 19 time_char = ctime(&now); 20 sscanf(time_char,"%s %s %d %d:%d:%d %d\n", ctmp, ctmp, &tmp, 21 &hour, &min, &sec, &tmp);
22 printf("%d:%d:%d\n", hour, min, sec); 23 seed = hour+min+sec; 24 srand(seed); 25 first = 0; 26 } 27 28 return(rand()%3);
29 } 30
31 int janken() 32 {
33 char man, pc, tmp;
34 char *man_no_te, *pc_no_te; 35
36 printf("Let's do JAN-KEN with me !!!!!\n");
37 printf("JAN-KEN-PON \nGU: %s\n TYOKI: %s\n PAH: %s\n\n", GU, TYOKI, PA); 38 printf("Input your choice from G, T and P (for GU, TYOKI, PAH)\n"); 39 printf("-->"); 40 scanf("%c", &man); 41 scanf("%*c",&tmp); 42 43 if(man == 'g'){ 44 man='G'; 45 man_no_te = GU; 46 }else if(man == 'G'){ 47 man_no_te = GU; 48 }else if(man == 't'){ 49 man='T'; 50 man_no_te = TYOKI; 51 }else if(man == 'T'){ 52 man_no_te = TYOKI; 53 }else if(man == 'p'){ 54 man = 'P'; 55 man_no_te = PA; 56 }else if(man == 'P'){ 57 man_no_te = PA; 58 } 59 60 pc = ran_123(); 61 if(pc == 0){ 62 pc = 'G';
63 pc_no_te = GU; 64 } 65 if(pc == 1){ 66 pc = 'T'; 67 pc_no_te = TYOKI; 68 } 69 if(pc == 2){ 70 pc = 'P'; 71 pc_no_te = PA; 72 } 73
74 printf("You:\n %s \nI CPU:\n %s \n", man_no_te, pc_no_te); 75 if((man == 'G' && pc == 'P') || (man == 'T' && pc == 'G') 76 || (man == 'P' && pc == 'T')){
77 printf("I CPU won.\n"); 78 return(1); 79 }else if(man == pc){ 80 printf("Draw.\n"); 81 return(0); 82 }else{ 83 printf("You won.\n"); 84 return(-1); 85 } 86 87 } 88 89 int main() 90 { 91 janken(); 92 return(0); 93 } 94 プログラムについて
1 ‒ 3 行目: C言語で準備されていて,このプログラムで用いる 関数に関する情報を含むファイルを取り 込む(テキスト p.167) 4 ‒ 6 行目: 文字の置換の指定.例えば,GU は,"\n nnnn\nCUUUU\n" に置き換えられる. 8 ‒ 29 行目: 整数 0, 1, 2 を,でたらめに返す(与える)関数 ran_123() の定義.ran_012() とすべき とろころを,まちがえて, ran_123() としてしまった.直す時間が無かったので,そのままにした. 10行目で,プログラムが起動されてから,この関数が呼ばれるのが,初めてかど うかという情報を持つ static変数 first を定義している(テキスト pp.195-196). first の初期値は 1 である.この関数が初めて 呼 び出されるときには,first の初期値は 1 である.このとき,17 ‒ 26 行目 の if 文が実行されます.こ の if 文では,time() 関数で time_t 型の変数 now に与えられた,時刻に関する情報を, ctime() 関数に よって,文字列に 変換して,time_char に文字列の先頭のポインタを代入し,その情報か ら, sscanf() 関数によって,時間,分,秒の情報をそれぞれ, hour, min, sec という変数に代入します.これらの変数 を足した値を,23行目で seed という符号なしの整数型の変数に代入し, 24行目における, srand() 関数 の種とします.srans() 関数は, 後に用いる乱数を発生させる関数, rand() を初期化するためのもので す. この seed に時刻の情報を繁栄させることで,PCがいつでも同じ手を出して くることを防ぎます. 25行目で,first を 0 にしておくことで,この if 文はそれ以降実行さ れることはありません.17 ‒ 26 行目は,乱数の初期化のプロセスと理解して もらえれば,それ以上,深入りする必要はありません.10 ‒ 15 行目の変数は,すべて,乱数の初期化のためだけに定義されたものです. 28行目で,rand() 関数で生成される乱数を3で割った余りをこの関数の戻り値として返すようにしてい る. なお,22行目で,確認のために,乱数の初期化に用いた時刻が示されます.ここ は,本質的ではない し,場合によっては,コンピューターの出す手を読まれてし まいますので,コメントアウトしておいて下さ い. 31 ‒ 87 行目: ジャンケンをする関数,janken() の定義.基本 的には,18 ページの janken_1.c の main() で行ったことと,ほとんど同じですが,乱数の初期化の仕方が異 なること, 60行目の乱数の与え方 が異なっています.この関数は,人間が勝てば, −1 を,あいこだと 0 を,PCが勝つと +1 を返すように なっています. 89 ‒ 93 行目: メイン関数です.ジャンケンのプロセスを関数化したこ とで,非常に短くなったのが分 かります.
ヒント: メイン関数で,while 文などを用いることで,あいこの 場合には, janken() を繰り返すように
8 ファイル操作
fileo.c として次の内容のファイルを作成し,コンパイル,実行し,保存せよ. 1 #include <stdio.h>
2 #include <stdlib.h>
3 #define FILENAME "myfile.txt" 4 #define DATAN 20 5 6 int main() 7 { 8 FILE *fp; 9 int c,rnum; 10
11 if((fp = fopen(FILENAME,"w")) == NULL){
12 printf("Can not open the file '%s'.\n",FILENAME); 13 return(1); 14 } 15 16 for(c=0;c<DATAN;c++){ 17 rnum = rand(); 18 fprintf(fp,"%d\n",rnum%40); 19 printf("%d\n",rnum%40); 20 } 21 22 fclose(fp); 23 return(0); 24 } 25
filei.c として次の内容のファイルを作成し,コンパイル,実行し,保存せよ. 1 #include <stdio.h>
2 #include <stdlib.h>
3 #define FILENAME "myfile.txt" 4 #define DATAN 20 5 6 int main() 7 { 8 FILE *fp; 9 int c,rnum; 10
11 if((fp = fopen(FILENAME,"r")) == NULL){
12 printf("Can not open the file '%s'.\n",FILENAME); 13 return(1); 14 } 15 16 for(c=0;c<DATAN;c++){ 17 fscanf(fp,"%d\n",&rnum); 18 printf("%d\n",rnum); 19 } 20 21 fclose(fp); 22 return(0); 23 } 24
9 配列
waki.c として次の内容のファイルを作成し,コンパイル,実行し,保存せよ. 1 #include <stdio.h>
2 #include <stdlib.h>
3 #define FILENAME "myfile.txt" 4 #define DATAN 20
5
6 void change(int *a, int *b); 7
8 void change(int *a, int *b) 9 { 10 int c; 11 12 c = *a; 13 *a = *b; 14 *b = c; 15 16 } 17 18 int main() 19 { 20 FILE *fp; 21 int c,rnum[DATAN],i,j; 22
23 if((fp = fopen(FILENAME,"r")) == NULL){
24 printf("Can not open the file '%s'.\n",FILENAME); 25 return(1); 26 } 27 28 for(c=0;c<DATAN;c++){ 29 fscanf(fp,"%d\n",rnum+c); 30 printf("%d\n",rnum[c]); 31 }
32 33 for(i=0;i<DATAN-1;i++){ 34 for(j=DATAN-1;j>i;j--){ 35 if(rnum[j] < rnum[j-1]){ 36 change(rnum+j, rnum+j-1); 37 } 38 } 39 } 40 41 printf("########\n"); 42 43 for(c=0;c<DATAN;c++){ 44 printf("%d\n",rnum[c]); 45 } 46 47 fclose(fp); 48 return(0); 49 } 50 課題:数字の順序が大きいもの順になるように上のプログラムを修正せよ。
10 応用例
微分方程式 d2x dt2 = a, (1) を考える.ただし,a は実数の定数である.この微分方程式の解は dx dt = at + A, (2) x = 1 2at 2+ At + B, (3) である.ただし,A, B は積分定数である.この場合は,こうして解析的に解くことができるが, すぐには 解けない微分方程式もある. そうしたときに,数値計算をすることを考えると,とりあえず,何かの解を得 ることができる. ここでは, 式(1)を数値的に解くことを考えてみる. x は t の関数であるので,x(t) と書くことができる.さて, p(t) = d dtx(t), (4) と定義すると,式(1)を解くということは, d dtp(t) = a, (5) d dtx(t) = p(t), (6) を解くことと同じである.微少な時間 ∆t で時間 t を分割して, tj = j∆t, (7) pj = p(j∆t), (8) xj = x(j∆t), (9) とすると,式(5), (6) は, pj+1 ∼ pj+ a∆t, (10) xj+1 ∼ xj+ pj∆t, (11) と近似できる.p(0) = 0, x(0) = 0 のとき, 解析的な解は,式(2), (3)で A = 0, B = 0,としたもので与えられる. 数値計算の上では,式(10), (11) を利用することで,次々と近似解を得ることが出来る. 例え ば,∆t = 0.1 とすると, p1 ∼ p0+ 0.1a, (12) x1 ∼ x0+ 0.1p0, (13) p2 ∼ p1+ 0.1a, (14) x2 ∼ x1+ 0.1p1, (15) というように求めるて行くことができる. 課 題: 上 記を 理 解し,p(0) = 0, x(0) = 0, a = 2.0, ∆t = 0.1 として, 0 ≤ t ≤ 10.0 の範囲で, tj, p(tj), x(tj), pj, xj をファイル deq.txt に出力するプログラムを書け. ただし, p(tj), x(tj) は,tj に おける解析的な解のことである. それができたら, deq.txt から得られた結果をグラフで表し,解析解と 近似解を比較することを試みよ. また, ∆t を変化させたときにどうなるか調べよ.
main2D.c
次の内容のプログラムを main2D.c として作成し,コンパイルおよび実 行せよ. ~/nrps に,main2D.c を作り,その後, ASTEC-X の端末上で, $ cd ~/nrps $ ./c main2D にてコンパイルし, $ ./main2D.out にて実行する. 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <GL/glut.h> 4 5 #define WIDTH 300 6 #define HIGHT 300 7 8 void display(void);9 void reshape(int w, int h);
10 void keyboard(unsigned char key, int x, int y); 11 void ginit(int* pargc, char** argv);
12 13 void display(void) 14 { 15 int i, j; 16 17 glClear(GL_COLOR_BUFFER_BIT); 18 glPointSize(1); 19 glBegin(GL_POINTS); 20 glColor3f(1.0,0.0,0.0); /* red */ 21 glVertex2f(1,1); 22 glColor3f(0.0,1.0,0.0); /* green */
23 glVertex2f(2,2); 24 glColor3f(0.0,0.0,1.0); /* blue */ 25 glVertex2f(3,3); 26 27 glEnd(); 28 glutSwapBuffers(); 29 } 30
31 void reshape(int w, int h) 32 {
33 glViewport(0, 0, (GLsizei)w, (GLsizei)h ); 34 glMatrixMode(GL_PROJECTION);
35 glLoadIdentity();
36 gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble) h); 37 }
38
39 void keyboard(unsigned char key, int x, int y) 40 { 41 switch(key) { 42 case 'q': 43 case 'Q': 44 exit(0); 45 break; 46 case ' ': 47 glutIdleFunc(0); 48 break; 49 default: 50 break; 51 } 52 } 53
54 void ginit(int* pargc, char** argv) 55 {
57 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 58 glutInitWindowSize(WIDTH,HIGHT);
59 glutInitWindowPosition(100,50);
60 glutCreateWindow("Numerical Recipes for Polymer Science"); 61 glClearColor(0.0,0.0,0.0,0.0); /* background color */ 62 glShadeModel(GL_FLAT); 63 glutDisplayFunc(display); 64 glutReshapeFunc(reshape); 65 glutKeyboardFunc(keyboard); 66 } 67
68 int main(int argc, char* argv[]) 69 { 70 ginit(&argc,argv); 71 glutMainLoop(); 72 return 0; 73 }
main2D.cについて
1‒3 行目: このプログラムで使用する関数,定数の情報を含むファイルを指定する. 5,6 行目:WIDTH と HIGHT のマクロ定義. 文中の WIDTH は, 300 に置き換えられてからコンパイルされる. 8‒11 行目:
プログラム中で定義されている関数の宣言.例えば, reshape という関数の戻り値が void で, 引数は int 型 (整数) 変数 2つであることが分かる.
13‒29行目:
関数 display の定義. 63行目の, glutDisplayFunc(display) との組み合わせで, この display にて OpenGL が画面に描画するものが決まる. OpenGL の詳細は講義しないが,知りたい場合はインターネッ トなどで調べるこ とが可能.
15 行目では, 整数型の変数 i, j を宣言しているが,実際には display() の中で用いていないので, この 部分は削除する. 17 行目: ウインドウを塗りつぶすOpenGLの関数. 18 行目: 点のサイズを 1 とする. 19 ‒ 27 行目: glBegin() ‒ glEnd() による描画. glBegin(GL_POINTS) の場合, 点が描画される. glBegin(GL_LINES) の場合,2点を対にして,その間が直線で結ばれる. glBegin(GL_LINE_STRIP) の場合,点間が直線で結ばれる. glBegin(GL_LINE_LOOP) の場合,点間が直線で結ばれ,さらに終点と始 点も結ばれる. glBegin(GL_TRIANGLES / GL_QUADS) の場合,3/4 点を組にして,三角形 /四角形が描画される. glBegin(GL_TRIANBLE_STRIP / GL_QUAD_STRIPE) の場合,一辺を共有し ながら,帯状に三角形/四角 形を描く. glBegin(GL_TRIANGLE_FAN) の場合,一辺を共有しながら扇状に三角形を 描く. glBegin(GL_POLYGON) の場合,凸多角形を描く. 22 ‒ 25 行目: glColor3f() で色を決め, glVertex2f(float x, float y) にて, 座標 (x, y) に点を打つ. 28 行目: glutSwapBuffers() にて,描いた画面を,表示する. 31 ‒ 37 行目: ウインドウの大きさが変化させられた時に,画面のサイズを調整するための関数 reshape(int w, int h) の定義.64 行目の glutReshapeFunc() との組み合わせで有効となる. 39 ‒ 52 行目: キーボードが押されたときに何をするかを決める関数.65行目の glutKeyboardFunc() との組み合わせ で有効となる. 54 ‒ 66 行目:
68 ‒ 73 行目: メイン関数. C言語のプログラムは main() から実行される. 70 行目: OpenGL の初期化手続き. 71 行目: OpenGL による描画プロセスを起動する. 72 行目: main() の戻り値として 0 を返す. 次のソースファイルを作成しコンパイルせよ.ソースファイルの名前 は各人で考えること. ただし,C言語のソースファイルの名前は末尾を .c としなければならないことに注 意. 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 double f2bai(double x);
5 double tasu(double x, double y); 6 7 double f2bai(double x) 8 { 9 printf("f2bai works.\n"); 10 11 return(2.0*x); 12 } 13
14 double tasu(double x, double y) 15 { 16 return(x+y); 17 } 18 19 int main() 20 { 21 double a,b; 22 23 a = (rand()%1000)/1000.0; 24 b = (rand()%1000)/1000.0;
25 26 printf("2*%lf = %lf\n", a, f2bai(a)); 27 printf("%lf + %lf = %lf\n", a, b, tasu(a,b)); 28 29 return(0); 30 }
hexagon.c
カレントディレクトリが,~/nrps であることを, $ pwd で 確 認し, 更に, main2D.c がカレントディレクトリに在ることを確認した上で, 次の操作によっ て,hexagon.c にコピーし,これを編集する. $ cp main2D.c hexagon.c ただし,次に変更箇所に * を付けて示し,関係無いところは省略する.. 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <GL/glut.h> * 4 #include <math.h> 5 6 #define WIDTH 300 7 #define HIGHT 300 * 8 #define RADIUS 100 * 9 #define X0 150 *10 #define Y0 150 11 17 void display(void) 18 {*19 int x, y, deg; 20 21 glClear(GL_COLOR_BUFFER_BIT); 22 glPointSize(1); *23 glBegin(GL_LINE_LOOP); *24 glColor3f(1.0, 1.0, 0.0); /* yellow */ *25 for(deg = 0; deg < 360; deg = deg+60){ *26 x = RADIUS*cos(deg*M_PI/180); *27 y = RADIUS*sin(deg*M_PI/180); *28 glVertex2f(X0+x,Y0+y); *29 } 30 glEnd(); 31 glutSwapBuffers(); 32 } できあがった,hexagon.c をコンパイルし,実行せよ. 1 ‒ 10 行目: 数学関数を使うので,math.h をインクルードした. また,RADIUS, X0, Y0 をマクロ定義する. 17 ‒ 34 行目: 黄色い六角形を描画するように,display() を書き換えた.
rothex.c
hexagon.c がカレントディレクトリに在ることを確認した上で, hexagon.c にコピーし,これを編集す る. ただし,次に変更箇所に * を付けて示す. 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <GL/glut.h> 4 #include <math.h> * 5 #include <time.h> 67 #define WIDTH 300 8 #define HIGHT 300 9 #define RADIUS 100 10 #define X0 150 11 #define Y0 150 *12 #define DEGSTEP 10 13 14 void display(void);
15 void reshape(int w, int h);
16 void keyboard(unsigned char key, int x, int y); 17 void ginit(int* pargc, char** argv);
*18 void idle(void); 19 *20 int Degshift=0; 21 *22 void idle(void) *23 {
*24 static int first = 1; *25 static time_t past; *26 time_t now; *27 *28 if(first == 1){ *29 time(&past); *30 first = 0; *31 return; *32 } *33 *34 time(&now); *35 *36 if(difftime(now,past) > 1.0){ *37 Degshift += DEGSTEP; *38 past = now; *39 } *40
*41 glutPostRedisplay(); *42 } 43 44 void display(void) 45 { 46 int x, y, deg; 47 48 glClear(GL_COLOR_BUFFER_BIT); 49 glPointSize(1); 50 glBegin(GL_POLYGON); 51 glColor3f(1.0, 1.0, 0.0); /* yellow */ 52 for(deg = 0; deg < 360; deg = deg+60){ *53 x = RADIUS*cos((deg+Degshift)*M_PI/180); *54 y = RADIUS*sin((deg+Degshift)*M_PI/180); 55 glVertex2f(X0+x,Y0+y); 56 } 57 glEnd(); 58 glutSwapBuffers(); 59 } 60
61 void reshape(int w, int h) 62 {
63 glViewport(0, 0, (GLsizei)w, (GLsizei)h ); 64 glMatrixMode(GL_PROJECTION);
65 glLoadIdentity();
66 gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble) h); 67 }
68
69 void keyboard(unsigned char key, int x, int y) 70 {
71 switch(key) { 72 case 'q': 73 case 'Q': 74 exit(0);
75 default: 76 break; 77 }
78 } 79
80 void ginit(int* pargc, char** argv) 81 {
82 glutInit(pargc,argv);
83 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 84 glutInitWindowSize(WIDTH,HIGHT);
85 glutInitWindowPosition(100,50);
86 glutCreateWindow("Numerical Recipes for Polymer Science"); 87 glClearColor(0.0,0.0,0.0,0.0); /* background color */ 88 glShadeModel(GL_FLAT); 89 glutDisplayFunc(display); 90 glutReshapeFunc(reshape); 91 glutKeyboardFunc(keyboard); *92 glutIdleFunc(idle); 93 } 94
95 int main(int argc, char* argv[]) 96 { 97 ginit(&argc,argv); 98 glutMainLoop(); 99 return 0; 100 } 101 できあがった,rothex.c をコンパイルし,実行せよ. 5行目: 時間を調べる関数 time() を使うため,その情報が記されているヘッダー ファイル time.h をインクルー ドする.ただし,拡張子が .h と なっているファイルをヘッダーファイルと言う. 12行目:
1秒あたりの角度のステップ DEGSTEP をマクロ定義する. 18行目: idle(void) 関数の宣言. 20行目: Degshift として,回転した角度を宣言.この位置で宣言すると,このプログ ラム内のどの関数内でも使 うことができる.通常,46行目のように,関数内で宣 言した場合は,その関数内でしか有効でない. 22 ‒ 42 行目: OpenGL がすることがなくて休んでいるときにする関数.92行目の glutIdleFunc() と組み合わせて使 う. この関数では,時刻を調べて,前回図 形を回転させた時刻より1秒以上経過していたら,決められた角 度だけ図形を回 転させることをする. 24行目: 初回のときに 1 という値をもち,それ以降は 0 とするような整数型の変数 first を宣言する.この変数 は,この関数を離れたときにも内容を保持したい ので, static として宣言する.こうしないときは,関数 を離れたときに 内容が忘れられてしまう. 25 ‒ 26 行目:
過去に調べた時刻を, これも static で宣言する.ただし, time.h に記載されている,time_t という時 間を示す変数として 宣言する.また,現在の時刻 now を宣言する.
28 ‒ 32 行目:
初めてのときは,まだ,過去にこの関数を訪れたことはない.この場合, past と now の時間差を求め ることはできないので, 過去の時刻 past に,time(&past) によって現在の時刻を代入す る.first を 0 にし, return によってこの関数を抜ける.
これ以降,この if 文の中が実行されることはない. 34 ‒ 39 行目:
time(&now) によって,now に現在の時刻を代入する. difftime(now,past) は,now と past の時間 差を返す関 数で,これが 1 以上であれば,回転角度 Degshift を DEGDTEP だけ増加させる.更に,今の 時刻 now を past に代入し,以降 ,次に回転されるまで,この時刻が過去の時刻として参照される. 42 行目: OpenGL にウィンドウの再描画をさせる. 53,54 行目: 回転が描画に反映されるよう変更.
92 行目: