プログラミング入門 第6回講義 第5回講義 制御の流れ(1) ループ(その2) - for - for文(3) プログラミング入門 第6回講義 第5回講義 制御の流れ(1) ループ(その2) - for - for文(3) break/continue(9) 見やすいプログラム(15) 効率の良い変数利用(20) マークのあるサンプルプログラムは /home/course/prog0/public_html/2013/lec/source/ 下に置いてありますから、各自自分のディレクトリに コピーして、コンパイル・実行してみてください
再掲:ループの種類(p.132) 見張り方式 前回とりあげました カウンタ方式 今回とりあげます 見張り方式 前回とりあげました ある特定の条件を満たしている間は、処理を 繰り返す(例:aが0より大きい間) 一般に繰り返し回数はあらかじめ 決まっていない カウンタ方式 今回とりあげます ループの回数を数える変数(カウンタ変数などと呼ぶ)を用いて繰り返し処理を決定する(例:10回) 一般的に繰り返し回数は固定 1 2
for 文(カウンタ方式に良く使われる) (p.143) おもに カウンタ方式 のループに使用 whileに、初期化とカウンタ変数の更新を追加 3 for(初期化 ; 条件式 ; カウンタ変数更新){ 文; … }
サンプルプログラム #include <stdio.h> main() { int i,total; total = 0; サンプルプログラム #include <stdio.h> main() { int i,total; total = 0; for(i = 1 ; i <= 10 ; i++){ total += i; } printf("1から10までの和は %d です\n",total); 1から10までの和を求める i = 1; while(i <= 10){ total += i; i++; } (while版) 4 /home/course/prog0/public_html/2013/lec/source/lec06-1.c
両者使い道は異なるが、書き換えると、こうなる。 for と whileの比較(p.144) for(初期化;条件式;カウンタ更新){ 文 … } 例 for(i = 0; i < 10; i++){ sum += i; 初期化 while(条件式){ 文 … カウンタ更新 } 例 i = 0 while(i < 10){ sum += i; i++; 両者使い道は異なるが、書き換えると、こうなる。 5
カウンタ変数に関する注意 カウンタ変数には通常 整数型変数 を用いる カウンタ変数には通常 整数型変数 を用いる 浮動小数点数では、等号条件(==)が成立しないことがある(誤差を含むため) カウンタ変数には、整数型を用いる 6 例えば0.1は2進数では循環小数になり、正確に表現出来ない /home/course/prog0/public_html/2013/lec/source/lec06-2.c
いろいろなループ for(i = 0 ; i < 10 ; i++ ){ 基本形 i : 0,1,...9のループ ..... } 7 等比数列 i : 1,2,4,8,16のループ
ループの制御(p.142,151) ループ実行中に、break、continueを使用してループの動作を変更する事が出来る while/for以外のループとしてdo-whileが、 ループの制御としてその他にgotoがあるが、使用頻度も少ないのでプログラミング入門では割愛する(自習のこと)
break(p.146,152) ループ中にbreakがあると、 ループを脱出し、 ループの次の文に制御を移す。 while(..){ ループ中にbreakがあると、 ループを脱出し、 ループの次の文に制御を移す。 case文の場合、caseからの脱出に使用される。(第4回講義参照) 二重ループなど、入れ子に なっているループの場合は、 一番内側のループからのみ脱出する。 右例のように無限ループからの脱出に良く使用される。 (第5回講義参照) 8 break; } 次の文 while(1){ if(条件) break; }
continue(p.142) ループ中にcontinueがあると、 以降を実行せず、 条件判断の直前に制御を移す ループの外に脱出しない while(..){ ループ中にcontinueがあると、 以降を実行せず、 条件判断の直前に制御を移す ループの外に脱出しない forの場合はカウンタ更新 を行い、再度条件判断 を行う continue; 9 }
break/continueのプログラム例 #include <stdio.h> main() { int i, data; int num = 0, sum = 0; for(i = 0 ; i < 10 ; i++){ scanf("%d",&data); if(data == 0) break; if(data < 0) continue; sum += data; num++; } printf("有効データ数 : %d\n",num); if(num != 0) printf("平均 : %f\n",(double)sum/num); データを10個読み、正のデータのみの平均を計算するプログラム データの値が0ならループから抜け、それまで読んだデータと個数を用いて平均を計算する データが負なら読み飛ばし、平均の計算には使用しない(有効データ数にもカウントしない) 10 11 10個のデータのうち値が0より大きかった「有効データ数」を表示 有効データ数が0で なければ平均を表示 /home/course/prog0/public_html/2013/lec/source/lec06-3.c
break/continueのプログラム実行例 std1dc1{s1000000}1: gcc lec06-5.c std1dc1{s1000000}2: ./a.out 1 2 3 4 5 6 7 8 9 10 11 12 有効データ数 : 10 平均 : 5.500000 std1dc1{s1000000}3: ./a.out 1 -2 3 4 -5 0 有効データ数 : 3 平均 : 2.666667 std1dc1{s1000000}4: 1から10までの10個のデータが使用される 1、3、4の3個のデータが使用される
まとめ:while/forの使い分け whileとforは書き換え可 while文をつかう場面 for文をつかう場面 しかし、下記のように適材適所で使い分ける while文をつかう場面 見張り方式に良く使われる(Lec05-11) ループする回数が分からない、又は分かり辛い場合(「100以下の2のべき乗の数2nの総和」など) 無限ループにも良く使われる for文をつかう場面 カウンタ方式に良く使われる(Lec06-3) ループする回数が定数や決まっている場合(「1から10の合計」など) for文は初期化、条件、更新を1文で書けるので以下の長所がある コンパクトにまとめられる 初期化等の書き忘れがない 無限ループは while(1){..} と書く
コラム:「難読Cプログラム国際コンテスト」? プログラミング入門の講義・演習では、一貫して「わかりやすいプログラム」を書くことを重点にしています。しかし、世の中には、逆に「わかりにくいプログラム」を競うという、なんとも変わったコンテストがあります。それが、IOCCC (International Obfuscated C Code Contenst。日本語に訳せば「難読Cプログラム国際コンテスト」)です。このコンテストでは、私たちの講義・演習とは逆に「いかにわかりにくく」「いかに読みにくく」「でもコンパイルはちゃんと通って動く」というプログラムを作り、そのすごさを競うというものです。入賞作品、そして優勝作品は、http://www.ioccc.orgでみることができます。 プログラムをみただけで頭が痛くなるようなものがたくさんありますが、中にはまるで芸術作品のように「きれいな」(但し、見た目がです)ものもあったりします。例えば、こんなプログラム(1984年の「匿名希望」さんの作品)も、ちゃんとコンパイルでき、動作します。試してみてはいかがでしょう? 但し、あまり深入りして解析しようとは思わない方がいいですよ。 int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);}
見やすいソースプログラム 一見してプログラムの構造がわかるように、 などを利用して、ソースプログラムを整形する 空白 空行 インデント などを利用して、ソースプログラムを整形する 整形せず、乱雑に書いたプログラムは、誤りが混入しやすい 12
インデント(1) プログラム中で、if文や、for、while ループの中身を、行頭に空白を入れて一段下げて記述すること 下げる文字数は2~4文字ぐらいが適当 emacsのC-modeの場合 M-x indent-region(領域) か タブキー(1行) でインデントが出来る。 インデントが崩れているプログラムは「汚い!」と感じる美意識を持とう つまり{ } 13
インデント(2) インデントの方法には何通りかある。いろいろなプログラムを見て自分のスタイルを作ること 同じプログラムではインデントスタイルを 首尾一貫 させること。そうでないと余計見づらい。 以下はインデントの代表例である(他にもある) 14 for(i = 0 ; i < 10 ; i++) { do_something(); } for(i = 0 ; i < 10 ; i++){ do_something(); }
インデント・空行の例 #include <stdio.h> main(){ int i; while(1){ printf("input number"); scanf("%d",&i); if(i<=0) break; if(i%2 == 0) printf("%dは偶数\n", i); else{ printf("%dは奇数\n", i); }} } #include <stdio.h> main() { int i; while(1) printf("input number"); scanf("%d",&i); if(i<=0) break; if(i%2 == 0) printf("%dは偶数\n", i); } else printf("%dは奇数\n", i); 意味の切れ目の空行
実現方法の選択 5通りのループの計算結果は、どれも同じである なるべく理解しやすい表現を選ぶことが大事(特に、初心者のうちは) #include <stdio.h> main() { int i, sum; /* 方法1 */ sum = 0; for(i = 1 ; i <= 10 ; i++){ sum += i; } /* 方法2 */ i = 1; while(i <= 10){ i++; /* 方法3 */ for(i = 1 ; i <= 10 ; sum += i++); /* 方法4 */ for(i = 0 ; i < 10 ;){ sum += ++i; /* 方法5 */ i = 0; while(i < 10){ sum += ++i ; 実現方法の選択 1から10までの和を5通りの方法で求める それぞれ変数sumに1から10までの和が計算される 5通りのループの計算結果は、どれも同じである なるべく理解しやすい表現を選ぶことが大事(特に、初心者のうちは) 1から10までの和を求めるループの他の書き方を考えてみよう
コラム:計算量のオーダー 一般にn個のデータを処理するプログラムにおいて、計算する量が何に比例するかを表すものを「計算量のオーダー」と呼び、「O()」で表す。これは「ランダウのO-記法」と呼ばれるが、単に「オーダー」とも言われる。例えば計算量がn2に比例する場合はO(n2)と書く。 ここで1+2+3+…+nと言う計算を考える。 普通にループで計算するとすると、加算の回数はn-1回。これはO(n) となる。 等比数列の和の公式 n(n+1)/2 を使うとnに関わらず演算の回数は3回。 これは、nに関係しないので、O(1)と書く。 nをどんどん大きくすると両者の計算量はどんどん開いていく。 次にn個の要素の配列の大きさ順の並び替え(ソートと呼ぶ)を考える 一番簡単なのは、まず最大の要素を見つけ、次に2位の要素を見つける 作業を順に行うもので、比較の回数は(n-1)+(n-2)+...+1 でO(n2) 高速であるQuick sortだと、説明は省くが、O(n・log2n)となる。 グラフを見ると分るように、この場合もnが大きくなるとその差は大きくなる。 このように同じ作業(計算)を行う場合にも手順(アルゴリズム)によって 計算量は大きく変わる。 計算量が変わると言う事は、実行時間も変わって来ると言う事で、プログラミングコンテストなどでは実行時間に制限を設けたりする場合もある。