370 likes | 440 Views
C 言語入門 第 14 週. プログラミング言語 Ⅰ( 実習を含む。 ), 計算機言語 Ⅰ ・計算機言語演習 Ⅰ, 情報処理言語 Ⅰ( 実習を含む。 ). 復習. 確認問題 : ポインタのポインタ. *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<br>", &a); printf ("&p = %p<br>", &p);
E N D
C言語入門第14週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。)
確認問題: ポインタのポインタ • *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
確認問題: ポインタのポインタ • 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
確認問題: 配列と文字列 • 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] =
総当たり探索 • 先頭から1つ探索する方法 • データがn個ある時 • 最悪n個全て調べる必要がある • 見つかる場合の平均探索回数n/2回の確率 • 簡単だけど速くない ... 541 11 2 5 7 s[99] s[0] s[1] s[2] s[3] ... ... 541
総当たり探索 • 簡単だけど遅い 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
演習: 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
教科書 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
教科書 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
教科書 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
教科書 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"); }
演習: 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
教科書 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を返す
教科書 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
関数へのポインタ • ポインタ変数に * が付いたら何になるか? 関数の定義 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);
関数へのポインタ • 関数関連の宣言では引数名は省略可能 • 関数のプロトタイプ宣言 • 関数へのポインタ変数の宣言 関数のプロトタイプ宣言 関数のプロトタイプ宣言 int cmp(int *a, int *b); int cmp(int *, int *); 関数へのポインタ変数の宣言 関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b); int (*fnc)(int *, int *);
関数へのポインタ • 汎用型の場合キャストが必要 • 例: qsortや bsearchへ渡す比較関数 関数のプロトタイプ宣言 int cmp(int *a, int *b); 関数へのポインタ変数の宣言 int (*fnc)(void *a, void *b); 関数へのポインタに代入と呼び出し fnc = (int (*)(void *, void *)) cmp; (*fnc)(&x, &y);
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
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
整数除算の商と剰余 • 知っているようで知らない商と剰余の定義 • 整数についてとした時、が商、が剰余 • 剰余の一般形: 例: 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 追加の制約がないと 一意に決まらない 一意に決まるが の符号により の絶対値が変わる 一意に決まるが が共に正の場合 感覚に合わない
負の数の除算と剰余算 • 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が共に正の時と 商と剰余の絶対値も一致して 使い易い
除算と剰余算: C89 K&R第2版 p.50 整数の割り算では小数部分は切り捨てられる。式x % yはxをyで割った余りで、xがyでちょうど割り切れるなら0となる。 /に対する切捨ての方向および%に対する結果の符号は、負の被演算子に対しては機種依存する。
除算と剰余算: 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) これはしばしば「ゼロに向かった切捨て」と呼ぶ。
除算と剰余算: 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) これはしばしば「ゼロに向かった切捨て」と呼ぶ。
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
ユークリッドの互除法Euclidean algorithm • 最大公約数(GCD: Greatest Common Divisor)を求めるアルゴリズム Wikipedia / ユークリッドの互除法
ユークリッドの互除法Euclidean algorithm • 整数についてをで割った商を余りををとし以下のように定義する • を適当な整数とすれば • をの任意の公約数とすると、と書けるからをの任意の公約数とすると、と書けるから • つまり • の公約数は必ずの約数となり • の公約数は必ずの約数となる • 従っての公約数との公約数は等しい集合である • よってとの最大公約数は等しい • このことからの最大公約数を求めればの最大公約数が求まる
ユークリッドの互除法Euclidean algorithm • ここでの余りはであるから、以下の手順を行うとに収束する • の余りを求める • を新たなとする • がで割り切れるまで手順1,2を繰り返す • つまり上記手順1.でになった時のが最初に与えたの最大公約数である n = 0 の時 m % n が 0 割りになるので具合が悪い
ユークリッドの互除法Euclidean algorithm • ここでの余りはであるから、以下の手順を行うとに収束する • が0になるまで以下の手順2,3を繰り返す • の余りを求める • を新たなとする • つまり上記手順1.でになった時のが最初に与えたの最大公約数である n = 0 の時 m % n が 0 割りにならないように工夫
ユークリッドの互除法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);
ユークリッドの互除法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;
ユークリッドの互除法Euclidean algorithm • 実装は色々出来る gcd.c while (n) { r = m % n; m = n; n = r; }; return m < 0 ? -m : m; C99未満の仕様だと m,nが共に正でない場合 おかしな結果が出るかもしれない 点に注意 演習: m = 0 のときはどうなるだろう? 何らかの例外処理が必要でないか 検討しなさい
演習: 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
参考文献 • [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)