1 / 37

C 言語入門 第 14 週

C 言語入門 第 14 週. プログラミング言語 Ⅰ( 実習を含む。 ), 計算機言語 Ⅰ ・計算機言語演習 Ⅰ, 情報処理言語 Ⅰ( 実習を含む。 ). 復習. 確認問題 : ポインタのポインタ. *p, *pp, **pp はいくらか?. hoge.c. 6 7 8 9 10 11 12 13 14 15. int a = 'h'; // = 0x68 = 104 int *p = &amp;a; int **pp = &amp;p; printf (&quot;&amp;a = %p<br>&quot;, &amp;a); printf (&quot;&amp;p = %p<br>&quot;, &amp;p);

Download Presentation

C 言語入門 第 14 週

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. C言語入門第14週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。)

  2. 復習

  3. 確認問題: ポインタのポインタ • *p, *pp, **pp はいくらか? hoge.c 6 7 8 9 10 11 12 13 14 15 int a = 'h'; // = 0x68 = 104 int *p = &a; int **pp = &p; printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("&pp = %p\n", &pp); printf("sizeof(a) = %d\n", sizeof(a)); printf("sizeof(p) = %d\n", sizeof(p)); printf("sizeof(pp) = %d\n", sizeof(pp)); 0x22aab8 = &pp 'h' &a &p 0x22aac0 = &p pp p a mintty + bash + GNU C 0x22aacc = &a $ gcc hoge.c && ./a &a = 0x22aacc &p = 0x22aac0 &pp = 0x22aab8 sizeof(a) = 4 sizeof(p) = 8 sizeof(pp) = 8

  4. 確認問題: ポインタのポインタ • pp + 1, *(pp + 1), pp[1] はいくらか? hoge.c 6 7 8 9 10 11 12 13 14 15 int a = 'h'; // = 0x68 = 104 int *p = &a; int **pp = &p; printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("&pp = %p\n", &pp); printf("sizeof(a) = %d\n", sizeof(a)); printf("sizeof(p) = %d\n", sizeof(p)); printf("sizeof(pp) = %d\n", sizeof(pp)); 0x22aab8 = &pp 'h' &a &p 0x22aac0 = &p pp p a mintty + bash + GNU C 0x22aacc = &a $ gcc hoge.c && ./a &a = 0x22aacc &p = 0x22aac0 &pp = 0x22aab8 sizeof(a) = 4 sizeof(p) = 8 sizeof(pp) = 8

  5. 確認問題: 配列と文字列 • sizeof(s), strlen(s), sizeof(p), strlen(p), p[3] はいくらか? hoge.c mintty + bash + GNU C 6 7 8 9 10 char s[] = "hello"; char *p = s; p++; printf("sizeof(&s[0]) = %d\n", sizeof(&s[0])); $ gcc hoge.c&& ./a sizeof(&s[0]) = 8 0x68 0x6c 0x65 0x6f '\0' 0x6c 0x00 'h' 'o' 'l' 'e' 'l' s[5] s[1] s[5] s[4] s[3] s[1] s[0] s[0] s[4] s[3] s[2] s[2] =

  6. 探索

  7. 総当たり探索 • 先頭から1つ探索する方法 • データがn個ある時 • 最悪n個全て調べる必要がある • 見つかる場合の平均探索回数n/2回の確率 • 簡単だけど速くない ... 541 11 2 5 7 s[99] s[0] s[1] s[2] s[3] ... ... 541

  8. 総当たり探索 • 簡単だけど遅い asearchi.c asearchi_test.c 4 5 6 7 8 9 10 11 12 13 14 15 16 int *asearchi(int key, int *data, int n) { int i; for (i = 0; i < n; i++) { #ifdef DEBUG printf("[%d] = %d\n", i, data[i]); #endif if (key == data[i]) { return &data[i]; } } return NULL; } 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, }; int key; int *p; asearchi_test.c mintty + bash + GNU C 26 27 28 29 30 31 p = asearchi(key, data, sizeof(data)/sizeof(int)); if (p) { printf("data[%d] = %d\n", p - data, *p); } else { printf("not found.\n"); } $ gcc asearchi_test.casearchi.c -DDEBUG && ./a 13key = ? [0] = 2 [1] = 3 [2] = 5 [3] = 7 [4] = 11 [5] = 13 data[5] = 13

  9. 演習: asearchi.c • int型のデータを総当たり探索する関数 asearchiを作成せよ • asearchi_test.cと共にコンパイルして動作を確認せよ • 引数 • int key : 検索したい値へのポインタ • int *data : 検索対象のデータ集合へのポインタ • int n : データの要素数 • 戻り値 • 見つけたデータへのポインタを返す • 見つからなかった場合はNULLを返す mintty + bash + GNU C $ gcc asearchi_test.casearchi.c&& ./a key = ? 31 data[10] = 31

  10. 教科書 pp.198-202. 2分探索 • 昇順ソート済みのデータから探す • 探す領域を半分に探す範囲を絞り込む • データがn個ある時 • 最悪個調べれば良い • 速いけど事前にソートが必要 17 11 13 19 23 7 2 5 3 s[8] s[6] s[4] s[3] s[2] s[1] s[5] s[0] s[7] 13

  11. 教科書 pp.198-202. 2分探索 • 全体を2つに分けて • 中央の値と比較して行く 11 17 13 11 17 11 17 13 13 19 19 19 23 23 23 7 2 2 2 7 7 3 5 5 3 3 5 s[0] s[6] s[4] s[3] s[2] s[1] s[0] s[8] s[6] s[5] s[4] s[3] s[1] s[2] s[3] s[2] s[1] s[4] s[8] s[6] s[5] s[5] s[8] s[0] s[7] s[7] s[7] 探索成功 13

  12. 教科書 pp.198-202. 2分探索 • 全体を2つに分けて • 中央の値と比較して行く 11 17 13 11 17 11 17 13 13 19 19 19 23 23 23 7 2 2 2 7 7 3 5 5 3 3 5 s[0] s[6] s[4] s[3] s[2] s[1] s[0] s[8] s[6] s[5] s[4] s[3] s[1] s[2] s[3] s[2] s[1] s[4] s[8] s[6] s[5] s[5] s[8] s[0] s[7] s[7] s[7] 探索失敗 18

  13. 教科書 pp.198-202. 2分探索 • 標準ライブラリ関数ライクな実装例 bsearchi.c bsearchi_test.c 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int *bsearchi(int key, int *data, int n) { int cmp; if (n <= 0) return NULL; #ifdef DEBUG printf("%d: [%d] = %d\n", n, n/2, data[n/2]); #endif if (key < data[n/2]) { return bsearchi(key, data, n/2); } else if (data[n/2] < key) { return bsearchi(key, &data[n/2+1], n - n/2 - 1); } else { return &data[n/2]; } } 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, }; int key; int *p; mintty + bash + GNU C bsearchi_test.c $ gcc bsearchi_test.cbsearchi.c -DDEBUG && ./a key = ? 17 100: [50] = 233 50: [25] = 101 25: [12] = 41 12: [6] = 17 data[6] = 17 26 27 28 29 30 31 p = bsearchi(key, data, sizeof(data)/sizeof(int)); if (p) { printf("data[%d] = %d\n", p - data, *p); } else { printf("not found.\n"); }

  14. 演習: bsearchi.c • 昇順ソート済みのint型のデータを2分探索する関数 bsearchiを作成せよ • bsearchi_test.cと共にコンパイルして動作を確認せよ • 引数 • int key : 検索したい値へのポインタ • int *data : 検索対象のデータ集合へのポインタ • int n : データの要素数 • 戻り値 • 見つけたデータへのポインタを返す • 見つからなかった場合はNULLを返す mintty + bash + GNU C $ gcc bsearchi_test.cbsearchi.c && ./a key = ? 31 data[10] = 31

  15. 教科書 pp.198-202. 標準ライブラリの2分探索関数 • 標準ライブラリ関数 • void *bsearch(const void *key, const void *base, size_t n, size_t size, int(*cmp)(const void *keyval, const void *datum)); • 引数 • key : 検索したい値へのポインタ • base : 検索対象のデータ集合へのポインタ • n : データの要素数 • size : データの1要素当りのバイト数 • cmp: データ比較用の関数へのポインタ • 戻り値 • マッチした項目へのポインタを返す • マッチした項目がない場合NULLを返す

  16. 教科書 pp.198-202. 標準ライブラリの2分探索関数 • 比較関数さえ用意すれば簡単に検索出来る bsearch_test.c 29 30 31 32 33 34 p = bsearch(&key, data, sizeof(data)/sizeof(int), sizeof(int), (int (*)(const void*, const void*))cmp); if (p) { printf("data[%d] = %d\n", p - data, *p); } else { printf("not found.d\n"); } bsearch_test.c bsearch_test.c 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, }; int key; int *p; 4 5 6 7 int cmp(const int *keyval, const int *datum) { return *keyval - *datum; } mintty + bash + GNU C $ gcc bsearch_test.c && ./a key = ? 113 data[29] = 113

  17. 関数へのポインタ • ポインタ変数に * が付いたら何になるか? 関数の定義 cmpが 戻り値が int 型 引数が (int *a, int *b) の 関数という意味になる int cmp(int *a, int *b) { return *a - *b; } fncがポインタ変数で *fncが 戻り値が int 型 引数が (int *a, int *b) の 関数という意味になる 関数のプロトタイプ宣言 int cmp(int *a, int *b); 関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b); *fncとすれば 関数へのポインタが関数になる 演算子の優先順位は 高*>()低なので*fncに()が必要 関数へのポインタに代入と呼び出し fnc = cmp; (*fnc)(&x, &y);

  18. 関数へのポインタ • 関数関連の宣言では引数名は省略可能 • 関数のプロトタイプ宣言 • 関数へのポインタ変数の宣言 関数のプロトタイプ宣言 関数のプロトタイプ宣言 int cmp(int *a, int *b); int cmp(int *, int *); 関数へのポインタ変数の宣言 関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b); int (*fnc)(int *, int *);

  19. 関数へのポインタ • 汎用型の場合キャストが必要 • 例: qsortや bsearchへ渡す比較関数 関数のプロトタイプ宣言 int cmp(int *a, int *b); 関数へのポインタ変数の宣言 int (*fnc)(void *a, void *b); 関数へのポインタに代入と呼び出し fnc = (int (*)(void *, void *)) cmp; (*fnc)(&x, &y);

  20. void 型と void 型へのポインタ • void(=空洞)つまり大きさがない • 関数に引数や戻り値がないことを意味する • ポインタの指し示す先の大きさが不明(特定の型に縛られない)であることを意味する 変数の宣言 void a; // void 型の変数はないのでコンパイルエラー void *p; // p が void 型へのポインタ 0x~0 0x~0 大きさが0バイトのデータ つまり void a;には意味がないが 大きさが不明のデータでも先頭アドレス つまり void *p; には意味がある 0x~1 0x~1 p= 0x~0 0x~2 0x~2 0x~3 0x~3

  21. void 型と void 型へのポインタ • void * 型は使用時に大きさを決めて使う • 適当な型へのポインタとしてキャストする void 型ポインタの例 int a; void *p = &a; // p に a のアドレスが入る *((int *)p) = 1;//p は void* なので *p は voidだが // p を int* にキャストすると *p が int になる 0x~0 *p は void なので意味がない 0x~0 0x~1 0x~1 p= 0x~0 0x~2 *((int*)p) は int なので意味がある 0x~2 0x~3 0x~3

  22. 商と剰余

  23. 整数除算の商と剰余 • 知っているようで知らない商と剰余の定義 • 整数についてとした時、が商、が剰余 • 剰余の一般形: 例: 5 / 3 = 1 余り 2、2 余り -1 -5 / 3 = -1 余り -2、-2 余り 1 5 / -3 = -1 余り 2、-2 余り 1 -5 / -3 = 1 余り -2、 2 余り 1 • 最小非負剰余: 例: 5 / 3 = 1 余り 2 -5 / 3 = -2 余り 1 5 / -3 = -1 余り 2 -5 / -3 = 2 余り 1 • 絶対値最小剰余: 例:5 / 3 = 2 余り -1 -5 / 3 = -2 余り 1 5 / -3 = -2 余り 1 -5 / -3 = 2 余り 1 追加の制約がないと 一意に決まらない 一意に決まるが の符号により の絶対値が変わる 一意に決まるが が共に正の場合 感覚に合わない

  24. 負の数の除算と剰余算 • C89では実装依存だった • 「/に対する切捨ての方向および%に対する結果の符号は,負の被演算子に対しては機種依存する」([1]p.50) • 例: 以下のいずれも有り得る • 5 / 3 = 1 余り 2 • -5 / 3 = -1 余り -2、-2 余り 1 • 5 / -3 = -1 余り 2、-2 余り -1 • -5 / -3 = 1 余り -2、 2 余り 1 • C99以降は以下のように定義された • a/bはゼロに向かった切捨て • (a/b)*b + a%bは a と等しい • 例: 必ず以下のようになる • 5 / 3 = 1 余り 2 • -5 / 3 = -1 余り -2 • 5 / -3 = -1 余り 2 • -5 / -3 = 1 余り -2 実装依存なので 安心して使えない 絶対値最小の商 きちんと定義されたので 安心して使えるようになった a,bが共に正の時と 商と剰余の絶対値も一致して 使い易い

  25. 除算と剰余算: C89 K&R第2版 p.50 整数の割り算では小数部分は切り捨てられる。式x % yはxをyで割った余りで、xがyでちょうど割り切れるなら0となる。 /に対する切捨ての方向および%に対する結果の符号は、負の被演算子に対しては機種依存する。

  26. 除算と剰余算: C99 ISO/IEC 9899:1999 Programming languages C (C99) + TC1 + TC2 + TC3 Committee Draft - September 7, 2007 p.82. 6.5.5 Multipliative operators • The result of the / operator is the quotient from the division of the first operand by the second; the result of the % operator is the remainder. In both operations, if the value of the second operand is zero, the behavior is undefined. • When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.90) If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a.90) This is often call "truncated toward zero". 6.5.5 乗算演算子 • / 演算子の結果は1つ目のオペランド(演算対象)を2つ目のオペランドで除算した商であり; % 演算子の結果は剰余である。両方の演算子は、もし2つ目のオペランドがゼロなら、動作は未定義である。 • 整数が除算される場合、/ 演算子の結果は小数部が破棄された代数的な商になる。90) もし商a/bが表現可能なら、式 (a/b)*b + a%b は a と等しくなくてはならない。90) これはしばしば「ゼロに向かった切捨て」と呼ぶ。

  27. 除算と剰余算: C11 ISO/IEC 9899:2011 Programming languages C (C11) Committee Draft - April 12, 2011 p.92. 6.5.5 Multipliative operators • When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.105) If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a ; otherwise, the behavior of both a/b and a%b is undefined.105) This is often call "truncated toward zero". 6.5.5 乗算演算子 • 整数が除算される場合、/ 演算子の結果は小数部が破棄された代数的な商になる。105) もし商 a/b が表現可能なら、式 (a/b)*b + a%b は a と等しくなくてはならない; そうでないなら、a/b および a%b の両方の動作は未定義である。105) これはしばしば「ゼロに向かった切捨て」と呼ぶ。

  28. C言語の仕様書 ここに書いてあることが C言語のすべて 書いてないことは 実装依存 OpenStandards ISO/IEC JTC1/SC22 - Programming languages and, operating systems WG14 - C http://www.open-std.org/JTC1/SC22/WG14/ WG14 N1256 ISO/IEC 9899:1999 Programming languages C (C99) + TC1 + TC2 + TC3 Committee Draft - September 7, 2007 http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf WG14 N1570 ISO/IEC 9899:2011 Programming languages C (C11) Committee Draft - April 12, 2011 http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf 注: Committee Draft (委員会草案) なので最終版ではない 正式版の仕様書は有料: ISO/IEC 9899:2011

  29. ユークリッドの互除法Euclidean algorithm • 最大公約数(GCD: Greatest Common Divisor)を求めるアルゴリズム Wikipedia / ユークリッドの互除法

  30. ユークリッドの互除法Euclidean algorithm • 整数についてをで割った商を余りををとし以下のように定義する • を適当な整数とすれば • をの任意の公約数とすると、と書けるからをの任意の公約数とすると、と書けるから • つまり • の公約数は必ずの約数となり • の公約数は必ずの約数となる • 従っての公約数との公約数は等しい集合である • よってとの最大公約数は等しい • このことからの最大公約数を求めればの最大公約数が求まる

  31. ユークリッドの互除法Euclidean algorithm • ここでの余りはであるから、以下の手順を行うとに収束する • の余りを求める • を新たなとする • がで割り切れるまで手順1,2を繰り返す • つまり上記手順1.でになった時のが最初に与えたの最大公約数である n = 0 の時 m % n が 0 割りになるので具合が悪い

  32. ユークリッドの互除法Euclidean algorithm • ここでの余りはであるから、以下の手順を行うとに収束する • が0になるまで以下の手順2,3を繰り返す • の余りを求める • を新たなとする • つまり上記手順1.でになった時のが最初に与えたの最大公約数である n = 0 の時 m % n が 0 割りにならないように工夫

  33. ユークリッドの互除法Euclidean algorithm n = 0 の時 m % n すると 0 割りになるので具合が悪いため 厳密には前判定ループで n == 0 で ループを辞める方が都合が良い • の余りを求める • r = m % n; • を新たなとする • m = n; • n = r; • がで割り切れるまで繰り返す • r != 0 の間ループさせる • 無限ループで r == 0 の時 break や return させる • n < 0 ? -n : n; • abs(n); • n * sign(n);

  34. ユークリッドの互除法Euclidean algorithm • 実装は色々出来る 若干まずいやり方 n = 0 だと m % n が 0 割りで不具合を生じる gcd.c gcd.c for (;;) { r = m % n; if (r == 0) return n < 0 ? -n : n; m = n; n = r; } while (1) { r = m % n; if (r == 0) break; m = n; n = r; }; return n < 0 ? -n : n; gcd.c gcd.c gcd.c while (r = m % n) { m = n; n = r; }; return n < 0 ? -n : n; r = 1; while (r) { r = m % n; m = n; n = r; }; return m < 0 ? -m : m; do { r = m % n; m = n; n = r; } while (r); return m < 0 ? -m : m;

  35. ユークリッドの互除法Euclidean algorithm • 実装は色々出来る gcd.c while (n) { r = m % n; m = n; n = r; }; return m < 0 ? -m : m; C99未満の仕様だと m,nが共に正でない場合 おかしな結果が出るかもしれない 点に注意 演習: m = 0 のときはどうなるだろう? 何らかの例外処理が必要でないか 検討しなさい

  36. 演習: gcd.c • 整数m,nの最大公約数を計算する関数 gcdを作成せよ • gcd_test.cと共にコンパイルする事で動作を確認せよ • 引数 • int m,n: 任意の整数 • 戻り値 • m,nの最大公約数をint型で返す mintty + bash + GNU C $ gcc gcd_test.cgcd.c && ./a m = ? 48 n = ? 15 gcd(48, 15) = 3

  37. 参考文献 • [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)

More Related