#author("2016-11-12T09:24:57+09:00","default:tutimura","tutimura") #author("2016-11-12T09:45:52+09:00","default:tutimura","tutimura") [[Cygwinでデバッグ]] #contents ** 例題 [#y07d8f9b] 例題を思いつくまま書き連ねてみます。 ** 問 [#q22b5668] + #include <stdio.h> の "stdio" の読み方を記せ。 + 以下のプログラムを読んで、次の問に答えよ。 #include <stdio.h> int five_times(int x) { /* (1) */ x *= 5; /* (2) */ return x; /* (3) */ } int main(void) { int x = 5; /* (4) */ int y = 10; /* (5) */ printf("%d\n", five_times(x)); /* (6) */ printf("%d\n", five_times(x)); /* (7) */ printf("%d\n", five_times(y)); /* (8) */ printf("%d\n", five_times(y)); /* (9) */ return 0; } ++ (3) で返される x の変数と同じものは (1)〜(2), (4), (6), (7) のうちどれであるか。 ++ (2) で x の値は5倍になるが、(6) と (7) の表示は同じになるか。 ++ (2) で x の値は5倍になるが、(8) と (9) の表示は同じになるか、それともコンパイルエラーになるか。 ** 問題分割の方法(1) [#k039b403] 底辺 b, 高さ h の三角形を考える。ただし b, h ともに整数で、10≦b≦20, 10≦h≦20 の範囲をとるものとする。この中で、面積が 70 以上 100 以下になるものの数を数えたい。次の指示に従って、プログラムを完成させなさい。 + 底辺 b, 高さ h の三角形の面積を返す関数 double triangle(int b, int h) を作りなさい。 + main() 関数で、底辺5, 高さ5 の三角形の面積を、関数 triangle() を用いて計算し、printf() で表示しなさい。正当な値が表示されることも合わせて確認しなさい。 + main() 関数を作り直して、底辺 10〜20, 高さ 10〜20 の範囲の三角形の面積をすべて表示するようにしなさい。for ループが2重の入れ子になることを期待している。 + (内側の)ループの中に if 文を追加して、三角形の面積が 70〜100 の場合のみ、表示するように変更しなさい。 + 数を数えるための変数 int count を main() 関数の中で宣言し、これを利用して三角形の面積が 70〜100 となるものの数を数えなさい。 + main() 関数の最後で「条件を満たす三角形は x 個あります。」と表示しなさい。 + ループ内の printf() をコメントにしなさい。消さずにコメントで残しておくのがよい。 > ねらい - 複雑な動作をするプログラムを、一気に完成させるのは難しい。 - 効率よくプログラムを作るには、小さなパーツに分解して、計算結果を表示させるなどして、少しずつ動作を確認しながら完成させるとよい。 - しかし、どのように問題を分解して、小さなパーツに分ければよいのか、一口に説明するのは難しい。上記のような誘導がひとつの参考になる。 - 三角形の面積計算を関数に独立させる有難みがわからない人は、a と b の最大公約数が 10〜20 になるような (a, b) の組みの数を求めるプログラムを作ってみるとよい。 ** 問題分割の方法(2) [#y19cac82] sin(x)の小数第1位と、tan(x)の小数第1位が、同じ値になるような角度xを見つけたい。ただしxは度数法で表した整数値であり、0≦x≦360 の範囲で探すものとする。以下の誘導に従ってプログラムを作成せよ。 + 角度をラジアンに変換する関数 double deg2rad(double deg) を作れ。なお、円周率は <math.h> に定義されている M_PI というマクロを用いるとよい。 + 角度を引数にとって正弦を返す関数 double sin_deg(double deg) を作れ。同様に、余弦を返す関数 double cos_deg(double deg) と正接を返す関数 double tan_deg(double deg) も作れ。ただし、ラジアンへの変換は 1.で作った deg2rad() 関数を利用すること。 + 小数点第1位を返す関数 int first_decimal_place(double x) を作れ。 + 以下の main() 関数で、これまでに作った関数の動作を確認せよ。 + 角度をラジアンに変換する関数 double deg2rad(double deg) を作れ。なお、円周率は <math.h> に定義されている M_PI というマクロを用いるとよい。以下の main() を用いて動作を確認せよ。 #include <stdio.h> #include <math.h> double deg2rad(double deg) // 1.で作成 { _____________; } /* double sin_deg(double deg) // 2.で作成 { _____________; } double cos_deg(double deg) // 2.で作成 { _____________; } double tan_deg(double deg) // 2.で作成 { _____________; } int first_decimal_place(double x) // 3.で作成 { _____________; } */ int main(void) { int i; printf("角度をラジアンに変換する関数 deg2rad() の動作を確認します。\n"); for (i=0; i<=180; i+=30) { printf("%3d %f\n", i, deg2rad(i)); } printf("\n"); printf("sin_deg() 関数等の動作を確認します。\n"); printf("x\tsin(x)\t\tcos(x)\t\ttan(x)\n"); for (i=0; i<=180; i+=30) { printf("%3d\t%f\t%f\t%f\n", i, sin_deg(i), cos_deg(i), tan_deg(i)); } printf("\n"); printf("小数第1位を取り出す関数 first_decimal_place() の動作を確認します。\n"); for (i=0; i<20; i++) { double a = (i-10)*0.31; printf("%f %d\n", a, first_decimal_place(a)); } printf("\n"); /* printf("sin(x) と tan(x) の小数第1位が等しくなる角度は以下の通りです。\n"); printf("x\tsin(x)\t\ttan(x)\n"); for (i=0; i<=360; i++) { _____________; if (_____________) { printf("%d\t%f\t%f\n", _____________); } } */ return 0; } 出力は以下のようになる。 上記プロラムでは以下のように出力される。 角度をラジアンに変換する関数 deg2rad() の動作を確認します。 0 0.000000 30 0.523599 60 1.047198 90 1.570796 120 2.094395 150 2.617994 180 3.141593 + 角度を引数にとって正弦を返す関数 double sin_deg(double deg) を作れ。同様に、余弦を返す関数 double cos_deg(double deg) と正接を返す関数 double tan_deg(double deg) も作れ。ただし、ラジアンへの変換は 1.で作った deg2rad() 関数を利用すること。以下を main() 関数に書き加えて、動作を確認せよ。 printf("sin_deg() 関数等の動作を確認します。\n"); printf("x\tsin(x)\t\tcos(x)\t\ttan(x)\n"); for (i=0; i<=180; i+=30) { printf("%3d\t%f\t%f\t%f\n", i, sin_deg(i), cos_deg(i), tan_deg(i)); } printf("\n"); 出力は以下のようになる。 sin_deg() 関数等の動作を確認します。 x sin(x) cos(x) tan(x) 0 0.000000 1.000000 0.000000 30 0.500000 0.866025 0.577350 60 0.866025 0.500000 1.732051 90 1.000000 0.000000 16331239353195370.000000 120 0.866025 -0.500000 -1.732051 150 0.500000 -0.866025 -0.577350 180 0.000000 -1.000000 -0.000000 + 小数点第1位を返す関数 int first_decimal_place(double x) を作れ。以下を main() 関数に書き加えて、動作を確認せよ。 printf("小数第1位を取り出す関数 first_decimal_place() の動作を確認します。\n"); for (i=0; i<20; i++) { double a = (i-10)*0.31; printf("%f %d\n", a, first_decimal_place(a)); } printf("\n"); 出力は以下のようになる。 小数第1位を取り出す関数 first_decimal_place() の動作を確認します。 -3.100000 1 -2.790000 7 -2.480000 4 -2.170000 1 -1.860000 8 -1.550000 5 -1.240000 2 -0.930000 9 -0.620000 6 -0.310000 3 0.000000 0 0.310000 3 0.620000 6 0.930000 9 1.240000 2 1.550000 5 1.860000 8 2.170000 1 2.480000 4 2.790000 7 + 角度x(ただしxは0以上360以下の整数)に関して、sin(x)の小数第1位と、tan(x)の小数第1位が、同じ値になるものをすべて表示しなさい。出力は以下のようになる。 + 角度x(ただしxは0以上360以下の整数)に関して、sin(x)の小数第1位と、tan(x)の小数第1位が、同じ値になるものをすべて表示しなさい。たとえば main() 関数に以下を書き加えるとよい。 printf("sin(x) と tan(x) の小数第1位が等しくなる角度は以下の通りです。\n"); printf("x\tsin(x)\t\ttan(x)\n"); for (i=0; i<=360; i++) { _____________; if (_____________) { printf("%d\t%f\t%f\n", _____________); } } 出力は以下のようになる。 sin(x) と tan(x) の小数第1位が等しくなる角度は以下の通りです。 x sin(x) tan(x) 0 0.000000 0.000000 1 0.017452 0.017455 2 0.034899 0.034921 ...(略) 109 0.945519 -2.904211 ...(略) 200 -0.342020 0.363970 ...(略) 299 -0.874620 -1.804048 330 -0.500000 -0.577350 ...(略) 360 -0.000000 -0.000000 > ねらい - first_decimal_place() 関数を単独で動作確認しておいたおかげで、後の作業が効率化されている。このことを実感してほしい。sin_deg() などと組み合わせた状態では、first_decimal_place() 関数の動作を網羅的に確認することは難しい。 - sin_deg() と tan_deg() でラジアン変換の処理が共通化されていることにも着目して欲しい。同じ作業を共通化することで、間違いがあれば発見しやすくなり、信頼性を高めている。 - cos_deg() 関数は動作確認以外に用いられていないが、sin_deg() と同時に作成しておくとよい。必要になってから作るよりも手間が少なくてすむ。 ** 位取り [#c828b2ad] 次のような場合の時刻を表示せよ。時刻は 12:34:56 のような形式(24時間制)で表示するとする。(解答例は &ref(hms.c);) + (例) ある時計台では、7時〜21時の間、毎時0分に時報が鳴る。時報の鳴る時刻を表示せよ。 -- 出力例 7:00:00 8:00:00 9:00:00 10:00:00 ... 20:00:00 21:00:00 -- プログラム例 int i; for (i=7; i<=21; i++) { printf("%2d:00:00\n", i); } + ある駅では、7時から21時30分まで、30分おきに電車が出発する。電車が出発する時刻を表示せよ。 -- 出力例 7:00:00 7:30:00 8:00:00 8:30:00 ... 21:00:00 21:30:00 + ある駅では、7時から、30分おきに1本ずつ、合計14本の電車が出発する。電車が出発する時刻を表示せよ。 + ある駅では、7時から、30分おきに1本ずつ、合計15本の電車が出発する。電車が出発する時刻を表示せよ。 -- プログラムのヒント(途中まで) int i, h=7, m=0, s=0; for (i=0; i<15; i++) { printf("%2d:%02d:%02d\n", h, m, s); m = m + 30; ... + ある駅では、7時から、35分おきに1本ずつ、合計15本の電車が出発する。電車が出発する時刻を表示せよ。 + ある駅では、7時から、65分おきに1本ずつ、合計15本の電車が出発する。電車が出発する時刻を表示せよ。 + ある駅では、7時から、65分おきに1本ずつ、合計20本の電車が出発する。電車が出発する時刻を表示せよ。 ~ + 深夜の宿直の交代は 20時から始まって3時間ごとで、翌朝8時まで続く。交代の時刻を表示せよ。 + 20時から翌朝8時までの宿直の間、50分ごとに冷蔵庫の庫内温度を記録する。記録すべき時刻を出力せよ。 -- 出力例 20:00:00 20:50:00 21:40:00 .... + 20時から翌朝8時までの宿直の間、50分ごとに冷蔵庫の庫内温度を記録する。50分ごとにアラームの鳴る時計を利用したが、精度不良のため、実際には49分51秒ごとに記録していた。記録していた時刻を出力せよ。 -- 出力例 20:00:00 20:49:51 21:39:42 .... ** if/else の使い方と return の使い方 [#a950defa] ある通信会社のデータ通信の毎月の料金プランは次のようになっている。 + 契約をした時点で1000円の基本料金が発生する。また、1000MB まで追加料金なしで通信できる。 + 通信量が 1000MB を超えると、超えた通信量に対して1MB あたり2円の従量料金が加算される。 + ただし、通信量が 3000MB を超えると、基本料金を含めて5000円の定額となる。 通信量 x(MB) に対する課金額を計算する関数を次の2通りの処理方法で作りなさい。 - int fee1(int x) では return を1度だけ使いなさい。なお、else if と x 以外の変数を使ってよい。 - int fee2(int x) では return を複数回使ってよいが、else if を用いてはならない。x 以外の変数も用いてはならない。 なお、x として負の通信量が与えられた場合は、 エラーを表すために -1 を返しなさい。 main関数では、0MB, 1000MB, 3000MB のような、条件の境界となる通信量に対して、関数の値が正しいか確認しなさい。また、-1000MB〜5000MBの通信量に対して、fee1(), fee2() が同じ値を返すことも確認しなさい。 > このように、関数1つを取り出して動作チェックを行う手法を「ユニットテスト」と言う。 ** 考察せよ [#qccd6c85] 次の2つのプログラムの違いを考察せよ。 - (1) for (i=0; i<10; i++) { printf("接点番号 %d のx座標は %d で、y座標は %d です。\n", i, point_x[i], point_y[i]); } ~ for (i=0; i<10; i++) { printf("接点番号 %d のx座標は %d で、y座標は %d です。\n", i, point_x[i], point_y[i]); } - (2) (デバッグライト (debug write) として) printf("%d\n", i); printf("%d\n", a[j]); ~ printf("i=%d\n", i); printf("a[%d]=%d\n", j, a[j]); - (3) int a[10]; int sum = 0; ... for (i=0; i<=9; i++) { sum += a[i]; } ~ int a[10] int sum = 0; ... for (i=1; i<=10; i++) { sum += a[i-1]; } - (4) int a[10], b[10]; ... for (i=0; i<10; i++) { a[i] = i; } for (i=0; i<10; i++) { b[i] = b[i] + a[i]; } ~ int a[10], b[10]; ... for (i=0; i<10; i++) { a[i] = i; b[i] = b[i] + a[i]; } 次の場合について、2つのループを1つに統合できるのか考察せよ。 +++ 今回のケースに限った場合 +++ 一般の場合(いつでも) ** 間違いを直せ [#qd244603] - (1) #include <stdio.h> int main(void) { int x = 5; printf("5x^2 + 3x + 1 = %d\n", 5x*x + 3x + 1); return 0; } ちなみに、コンパイル時に以下のようなエラーが出る。 $ gcc -Wall hoge.c hoge.c:6:37: error: invalid suffix "x" on integer constant hoge.c:6:44: error: invalid suffix "x" on integer constant - (2) #include <stdio.h> #include <math.h> int main(void) { int x = 5, y = 7; printf("原点と (%d,%d) の距離は %g です。\n", x, y, sqrt(x ^ 2 + y ^ 2)); return 0; } ちなみに、コンパイル時には警告も出ないが、実行結果は以下のようになる。原点からの距離は本当は5よりも大きいことに注意。 原点と (5,7) の距離は 3.74166 です。 - (3) #include <stdio.h> int main(void) { int i, a = 5; for (i=0; i<10; i++) { if (a = i) { printf("a=%d と i=%d は等しい\n", a, i); } } return 0; } ちなみに、コンパイル時に以下のような警告が出る。 $ gcc -Wall hoge.c hoge.c: In function ‘main’: hoge.c:7: warning: suggest parentheses around assignment used as truth value (hoge.c:7: 警告: 真偽値として使われる代入のまわりでは、丸括弧の使用をお勧めします) そのまま実行すると、次の出力のようになる。 $ ./a.exe a=1 と i=1 は等しい a=2 と i=2 は等しい a=3 と i=3 は等しい a=4 と i=4 は等しい a=5 と i=5 は等しい a=6 と i=6 は等しい a=7 と i=7 は等しい a=8 と i=8 は等しい a=9 と i=9 は等しい - (4) #include <stdio.h> int five_times(int x) { return x * 5; } int main(void) { int y = 3; printf("%d\n", five_times(int y)); /* 15 が表示されるようにしたい */ return 0; } ちなみに、コンパイル時に以下のようなエラーメッセージが出る。 $ gcc -Wall hoge.c hoge.c: In function 'main': hoge.c:10: error: expected expression before 'int' hoge.c:8: warning: unused variable 'y' - (5) #include <stdio.h> int main(void) { int i, max, min; int a[10]; for (i=0; i<10; i++) { printf("%d 個めのデータを入力して下さい >> ", i+1); scanf("%d", &a[i]); } for (i=0; i<10; i++) { if (min < a[i]) { min = a[i]; } if (max > a[i]) { max = a[i]; } } printf("データの最大値は %d です。\n", min); printf("データの最小値は %d です。\n", max); return 0; } ちなみに、コンパイル時に以下のような警告が出る。 $ \gcc -Wall -O hoge.c hoge.c: In function 'main': hoge.c:4: warning: 'min' may be used uninitialized in this function hoge.c:4: warning: 'max' may be used uninitialized in this function 実行例は以下のようになる。 $ ./a.exe 1 個めのデータを入力して下さい >> 2 2 個めのデータを入力して下さい >> 4 3 個めのデータを入力して下さい >> 6 4 個めのデータを入力して下さい >> 8 5 個めのデータを入力して下さい >> 6 6 個めのデータを入力して下さい >> 4 7 個めのデータを入力して下さい >> 2 8 個めのデータを入力して下さい >> 0 9 個めのデータを入力して下さい >> -5 10 個めのデータを入力して下さい >> 10 データの最大値は 10 です。 データの最小値は -1074566468 です。 - (6) #include <stdio.h> int main(void) { int i, sum = 0; for (i=1; i<=10; i++); { sum = sum + i; /* 1〜10 までの和を求める */ } printf("sum = %d\n", sum); return 0; } ちなみに、コンパイル時には警告もない。実行すると "sum = 11" と表示される。 ヒント:ブロック開始の { を行末に書くと、このような間違いに悩まされずにすむ。 - (7) #include <stdio.h> int main(void) { int i, a[10]; for (i=1; i<=10; i++) { a[i] = i * i; } for (i=1; i<=10; i++) { a[i] = a[i] + a[i-1]; } for (i=1; i<=10; i++) { printf("%d\n", a[i]); } return 0; } ちなみに、コンパイル時にも、実行時にもエラーはでない(こともある)。 - (8) #include <stdio.h> #include <stdlib.h> #include <time.h> int main(void) { int i; for (i=0; i<10; i++) { srand(time(NULL)); printf("%d\n", rand()); /* 毎回異なる値を表示させたい */ } return 0; } ちなみに、実行すると以下のように同じ値が10回表示される。 % ./a.exe 817376010 817376010 817376010 817376010 817376010 817376010 817376010 817376010 817376010 817376010