c 言語入門 第 14 週

37
C 言言言言 言 14 言 言言言言言言言言言( 言言言言言), 言言言 言言 言 言言言言言言言言 ⅠⅠ , 言言言言言言( 言言 言言言) 1

Upload: bradley-humphrey

Post on 01-Jan-2016

39 views

Category:

Documents


2 download

DESCRIPTION

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); - PowerPoint PPT Presentation

TRANSCRIPT

C 言語入門第 14 週

プログラミング言語Ⅰ ( 実習を含む。 ), 計算機言語Ⅰ・計算機言語演習Ⅰ , 情報処理言語Ⅰ ( 実習を

含む。 )

1

復習

2

確認問題 : ポインタのポインタ• *p, *pp, **pp はいくらか?

3

p

&a

pp

&p

0x22aab8 = &pp

0x22aac0 = &p

a

'h'

0x22aacc = &a

hoge.cint 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));

mintty + bash + GNU C $ gcc hoge.c && ./a&a = 0x22aacc&p = 0x22aac0&pp = 0x22aab8sizeof(a) = 4sizeof(p) = 8sizeof(pp) = 8

6789101112131415

確認問題 : ポインタのポインタ• pp + 1, *(pp + 1), pp[1] はいくらか?

4

p

&a

pp

&p

0x22aab8 = &pp

0x22aac0 = &p

a

'h'

0x22aacc = &a

hoge.cint 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));

mintty + bash + GNU C $ gcc hoge.c && ./a&a = 0x22aacc&p = 0x22aac0&pp = 0x22aab8sizeof(a) = 4sizeof(p) = 8sizeof(pp) = 8

6789101112131415

確認問題 : 配列と文字列• sizeof(s), strlen(s), sizeof(p), strlen(p), p[3] は

いくらか?

5

hoge.cchar s[] = "hello";

char *p = s;p++;

printf("sizeof(&s[0]) = %d\n", sizeof(&s[0]));

678910

s[0]

'h'

s[1]

'e'

s[2]

'l'

s[3]

'l'

s[4]

'o'

s[5]

'\0'

s[0]

0x68

s[1]

0x65

s[2]

0x6c

s[3]

0x6c

s[4]

0x6f

s[5]

0x00=

mintty + bash + GNU C $ gcc hoge.c && ./asizeof(&s[0]) = 8

探索

6

総当たり探索• 先頭から 1 つ探索する方法• データが n 個ある時

• 最悪 n 個全て調べる必要がある• 見つかる場合の平均探索回数 n/2 回の確率

• 簡単だけど速くない

7

s[0]

2

s[1]

5

s[2]

7

s[3]

11

...

...

s[99]

541

541

...

総当たり探索• 簡単だけど遅い

8

asearchi_test.c 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;

89101112131415161718192021

asearchi_test.cp = asearchi(key, data, sizeof(data)/sizeof(int));

if (p) { printf("data[%d] = %d\n", p - data, *p);} else { printf("not found.\n");}

262728293031

asearchi.cint *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;}

45678910111213141516

mintty + bash + GNU C$ gcc asearchi_test.c asearchi.c -DDEBUG && ./a

13key = ?[0] = 2[1] = 3[2] = 5[3] = 7[4] = 11[5] = 13data[5] = 13

演習 : asearchi.c

• int型のデータを総当たり探索する関数 asearchi を作成せよ

• asearchi_test.c と共にコンパイルして動作を確認せよ

• 引数• int key : 検索したい値へのポインタ• int *data : 検索対象のデータ集合へのポインタ• int n : データの要素数

• 戻り値• 見つけたデータへのポインタを返す• 見つからなかった場合はNULLを返す

9

mintty + bash + GNU C$ gcc asearchi_test.c asearchi.c && ./a

key = ? 31data[10] = 31

2 分探索• 昇順ソート済みのデータから探す• 探す領域を半分に探す範囲を絞り込む• データが n 個ある時

• 最悪個調べれば良い• 速いけど事前にソートが必要

10

s[0]

2

s[1]

3

s[2]

5

13

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

教科書 pp.198-202.

2 分探索• 全体を 2 つに分けて• 中央の値と比較して行く

11

s[0]

2

s[1]

3

s[2]

5

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

s[0]

2

s[1]

3

s[2]

5

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

s[0]

2

s[1]

3

s[2]

5

13

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

探索成功

教科書 pp.198-202.

2 分探索• 全体を 2 つに分けて• 中央の値と比較して行く

12

s[0]

2

s[1]

3

s[2]

5

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

s[0]

2

s[1]

3

s[2]

5

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

s[0]

2

s[1]

3

s[2]

5

18

s[3]

7

s[4]

11

s[5]

13

s[6]

17

s[7]

19

s[8]

23

探索失敗

教科書 pp.198-202.

2 分探索• 標準ライブラリ関数ライクな実装例

13教科書 pp.198-202.

bsearchi_test.c 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;

89101112131415161718192021

bsearchi_test.cp = bsearchi(key, data, sizeof(data)/sizeof(int));

if (p) { printf("data[%d] = %d\n", p - data, *p);} else { printf("not found.\n");}

262728293031

bsearchi.cint *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]; }}

456789101112131415161718

mintty + bash + GNU C$ gcc bsearchi_test.c bsearchi.c -DDEBUG && ./a

key = ? 17100: [50] = 23350: [25] = 10125: [12] = 4112: [6] = 17data[6] = 17

演習 : bsearchi.c

• 昇順ソート済みのint型のデータを2分探索する関数 bsearchi を作成せよ

• bsearchi_test.c と共にコンパイルして動作を確認せよ

• 引数• int key : 検索したい値へのポインタ• int *data : 検索対象のデータ集合へのポインタ• int n : データの要素数

• 戻り値• 見つけたデータへのポインタを返す• 見つからなかった場合はNULLを返す

14

mintty + bash + GNU C$ gcc bsearchi_test.c bsearchi.c && ./a

key = ? 31data[10] = 31

標準ライブラリの 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を返す

15教科書 pp.198-202.

標準ライブラリの 2 分探索関数• 比較関数さえ用意すれば簡単に検索出来

16教科書 pp.198-202.

bsearch_test.cint cmp(const int *keyval, const int *datum)

{ return *keyval - *datum;}

4567

bsearch_test.cp = 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");}

293031323334

bsearch_test.c{

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;

111213141516171819202122232425

mintty + bash + GNU C$ gcc bsearch_test.c && ./a

key = ? 113data[29] = 113

関数へのポインタ• ポインタ変数に * が付いたら何になる

か?

17

関数へのポインタ変数の宣言int (*fnc)(int *a, int *b);

関数の定義int cmp(int *a, int *b){ return *a - *b;}

関数のプロトタイプ宣言int cmp(int *a, int *b);

fnc がポインタ変数で*fnc が戻り値が int 型引数が (int *a, int *b) の関数という意味になる

cmp が戻り値が int 型引数が (int *a, int *b) の関数という意味になる

関数へのポインタに代入と呼び出しfnc = cmp;(*fnc)(&x, &y);

*fnc とすれば関数へのポインタが関数になる演算子の優先順位は高 *>() 低なので *fnc に () が必要

関数へのポインタ• 関数関連の宣言では引数名は省略可能

• 関数のプロトタイプ宣言• 関数へのポインタ変数の宣言

18

関数へのポインタ変数の宣言

関数へのポインタ変数の宣言int (*fnc)(int *a, int *b);

関数のプロトタイプ宣言int cmp(int *a, int *b);

int (*fnc)(int * , int * );

関数のプロトタイプ宣言int cmp(int * , int * );

関数へのポインタ• 汎用型の場合キャストが必要

• 例 : qsort や bsearch へ渡す比較関数

19

関数へのポインタ変数の宣言int (*fnc)(void *a, void *b);

関数のプロトタイプ宣言 int cmp(int *a, int *b);

関数へのポインタに代入と呼び出しfnc = (int (*)(void *, void *)) cmp;(*fnc)(&x, &y);

void 型と void 型へのポインタ• void(= 空洞 ) つまり大きさがない

• 関数に引数や戻り値がないことを意味する• ポインタの指し示す先の大きさが不明 ( 特定

の型に縛られない ) であることを意味する

20

変数の宣言void a; // void 型の変数はないのでコンパイルエラー

void *p; // p が void 型へのポインタ

大きさが 0 バイトのデータつまり void a; には意味がないが大きさが不明のデータでも先頭アドレスつまり void *p; には意味がある

0x ~ 0

0x ~ 1

0x ~ 2

0x ~ 3

0x ~ 0

0x ~ 1

0x ~ 2

0x ~ 3

p=0x ~ 0

void 型と void 型へのポインタ• void * 型は使用時に大きさを決めて使

う• 適当な型へのポインタとしてキャストする

21

void 型ポインタの例int a;

void *p = &a; // p に a のアドレスが入る*((int *)p) = 1; // p は void* なので *p は void だが // p を int* にキャストすると *p が int になる

0x ~ 0

0x ~ 1

0x ~ 2

0x ~ 3

0x ~ 0

0x ~ 1

0x ~ 2

0x ~ 3

*p は void なので意味がない*((int*)p) は int なので意味がある

p=0x ~ 0

商と剰余

22

整数除算の商と剰余• 知っているようで知らない商と剰余の定義• 整数についてとした時、が商、 が剰余• 剰余の一般形:

例: 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

23

追加の制約がないと一意に決まらない

一意に決まるがの符号によりの絶対値が変わる

一意に決まるがが共に正の場合感覚に合わない

負の数の除算と剰余算• 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

24

きちんと定義されたので安心して使えるようになったa,b が共に正の時と商と剰余の絶対値も一致して使い易い

実装依存なので安心して使えない

絶対値最小の商

除算と剰余算 : C89

K&R 第 2版 p.50

整数の割り算では小数部分は切り捨てられる。式x % yは x を y で割った余りで、 x がyでちょうど割り切れるなら 0 となる。

/ に対する切捨ての方向および % に対する結果の符号は、負の被演算子に対しては機種依存する。

25

除算と剰余算 : C99ISO/IEC 9899:1999 Programming languages C (C99) + TC1 + TC2 + TC3Committee Draft - September 7, 2007 p.82.

6.5.5 Multipliative operators5 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.

6 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 乗算演算子7 / 演算子の結果は1つ目のオペランド(演算対象)を2つ目のオペランドで除算した商であり; %

演算子の結果は剰余である。両方の演算子は、もし2つ目のオペランドがゼロなら、動作は未定義である。

8 整数が除算される場合、/ 演算子の結果は小数部が破棄された代数的な商になる。 90) もし商a/bが表現可能なら、式 (a/b)*b + a%b は a と等しくなくてはならない。90) これはしばしば「ゼロに向かった切捨て」と呼ぶ。

26

除算と剰余算 : C11ISO/IEC 9899:2011 Programming languages C (C11)Committee Draft - April 12, 2011 p.92.

6.5.5 Multipliative operators6 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 乗算演算子7 整数が除算される場合、/ 演算子の結果は小数部が破棄された代数的な商にな

る。 105) もし商 a/b が表現可能なら、式 (a/b)*b + a%b は a と等しくなくてはならない; そうでないなら、a/b および a%b の両方の動作は未定義である。105) これはしばしば「ゼロに向かった切捨て」と呼ぶ。

27

C 言語の仕様書OpenStandardsISO/IEC JTC1/SC22 - Programming languages and, operating systemsWG14 - Chttp://www.open-std.org/JTC1/SC22/WG14/

WG14 N1256ISO/IEC 9899:1999 Programming languages C (C99) + TC1 + TC2 + TC3Committee Draft - September 7, 2007http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

WG14 N1570ISO/IEC 9899:2011 Programming languages C (C11)Committee Draft - April 12, 2011http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf

28

注 : Committee Draft (委員会草案 ) なので最終版ではない正式版の仕様書は有料 : ISO/IEC 9899:2011

ここに書いてあることがC 言語のすべて書いてないことは実装依存

ユークリッドの互除法Euclidean algorithm

• 整数についてをで割った商を余りををとし以下のように定義する

• を適当な整数とすれば• をの任意の公約数とすると、と書けるから

をの任意の公約数とすると、と書けるから

• つまり• の公約数は必ずの約数となり• の公約数は必ずの約数となる

• 従っての公約数との公約数は等しい集合である• よってとの最大公約数は等しい

• このことからの最大公約数を求めればの最大公約数が求まる

30

ユークリッドの互除法Euclidean algorithm

• ここでの余りはであるから、以下の手順を行うとに収束する1. の余りを求める2. を新たなとする3. がで割り切れるまで手順 1,2 を繰り返す

• つまり上記手順 1. でになった時のが最初に与えたの最大公約数である

31

n = 0 の時 m % n が0 割りになるので具合が悪い

ユークリッドの互除法Euclidean algorithm

• ここでの余りはであるから、以下の手順を行うとに収束する1. が 0 になるまで以下の手順 2,3 を繰り返す2. の余りを求める3. を新たなとする

• つまり上記手順 1. でになった時のが最初に与えたの最大公約数である

32

n = 0 の時 m % n が0 割りにならないように工夫

ユークリッドの互除法Euclidean algorithm

• の余りを求める• r = m % n;

• を新たなとする• m = n;• n = r;

• がで割り切れるまで繰り返す• r != 0 の間ループさせる• 無限ループで r == 0 の時 break や return させる

• n < 0 ? -n : n;• abs(n);• n * sign(n);

33

n = 0 の時 m % n すると0 割りになるので具合が悪いため厳密には前判定ループで n == 0 でループを辞める方が都合が良い

ユークリッドの互除法Euclidean algorithm

• 実装は色々出来る

34

gcd.cfor (;;) {

r = m % n; if (r == 0) return n < 0 ? -n : n; m = n; n = r;}

gcd.cdo {

r = m % n; m = n; n = r;} while (r);return m < 0 ? -m : m;

gcd.cr = 1;

while (r) { r = m % n; m = n; n = r;};return m < 0 ? -m : m;

gcd.cwhile (1) {

r = m % n; if (r == 0) break; m = n; n = r;};return n < 0 ? -n : n;

gcd.cwhile (r = m % n) {

m = n; n = r;};return n < 0 ? -n : n;

若干まずいやり方n = 0 だと m % n が0 割りで不具合を生じる

ユークリッドの互除法Euclidean algorithm

• 実装は色々出来る

35

gcd.cwhile (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 型で返す

36

mintty + bash + GNU C$ gcc gcd_test.c gcd.c && ./a

m = ? 48n = ? 15gcd(48, 15) = 3

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

37