C言語入門

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",
printf("&p = %p\n",
printf("&pp = %p\n",
printf("sizeof(a) =
printf("sizeof(p) =
printf("sizeof(pp) =
&a);
&p);
&pp);
%d\n", sizeof(a));
%d\n", sizeof(p));
%d\n", sizeof(pp));
&p
0x22aab8 = &pp
&a
0x22aac0 = &p
p
'h'
mintty + bash + GNU C
$ gcc hoge.c && ./a
&a = 0x22aacc
&p = 0x22aac0
&pp = 0x22aab8
sizeof(a) = 4
sizeof(p) = 8
sizeof(pp) = 8
pp
0x22aacc = &a
a
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",
printf("&p = %p\n",
printf("&pp = %p\n",
printf("sizeof(a) =
printf("sizeof(p) =
printf("sizeof(pp) =
&a);
&p);
&pp);
%d\n", sizeof(a));
%d\n", sizeof(p));
%d\n", sizeof(pp));
&p
0x22aab8 = &pp
&a
0x22aac0 = &p
p
'h'
mintty + bash + GNU C
$ gcc hoge.c && ./a
&a = 0x22aacc
&p = 0x22aac0
&pp = 0x22aab8
sizeof(a) = 4
sizeof(p) = 8
sizeof(pp) = 8
pp
0x22aacc = &a
a
5
確認問題: 配列と文字列
• sizeof(s), strlen(s), sizeof(p), strlen(p), p[3] は
いくらか?
6
7
8
9
10
hoge.c
mintty + bash + GNU C
char s[] = "hello";
char *p = s;
p++;
$ gcc hoge.c && ./a
sizeof(&s[0]) = 8
printf("sizeof(&s[0]) = %d\n", sizeof(&s[0]));
'e'
'l'
'l'
'o'
'\0'
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
0x68
0x65
0x6c
0x6c
0x6f
0x00
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
=
'h'
6
探索
7
総当たり探索
• 先頭から1つ探索する方法
• データがn個ある時
• 最悪n個全て調べる必要がある
• 見つかる場合の平均探索回数n/2回の確率
• 簡単だけど速くない
541
...
2
5
7
11
...
541
s[0]
s[1]
s[2]
s[3]
...
s[99]
8
総当たり探索
• 簡単だけど遅い
asearchi.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;
}
asearchi_test.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");
}
asearchi_test.c
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int data[] =
2,
3,
31, 37,
73, 79,
127, 131,
179, 181,
233, 239,
283, 293,
353, 359,
419, 421,
467, 479,
};
int key;
int *p;
{
5,
41,
83,
137,
191,
241,
307,
367,
431,
487,
7,
43,
89,
139,
193,
251,
311,
373,
433,
491,
11,
47,
97,
149,
197,
257,
313,
379,
439,
499,
13,
53,
101,
151,
199,
263,
317,
383,
443,
503,
17,
59,
103,
157,
211,
269,
331,
389,
449,
509,
19,
61,
107,
163,
223,
271,
337,
397,
457,
521,
23,
67,
109,
167,
227,
277,
347,
401,
461,
523,
mintty + bash + GNU C
$ gcc asearchi_test.c asearchi.c -DDEBUG && ./a
13key = ?
[0] = 2
[1] = 3
[2] = 5
[3] = 7
[4] = 11
[5] = 13
data[5] = 13
29,
71,
113,
173,
229,
281,
349,
409,
463,
541,
9
演習: asearchi.c
• int型のデータを総当たり探索する関数 asearchi を作
成せよ
• asearchi_test.c と共にコンパイルして動作を確認せ
よ
• 引数
• int key : 検索したい値へのポインタ
• int *data : 検索対象のデータ集合へのポインタ
• int n
: データの要素数
• 戻り値
• 見つけたデータへのポインタを返す
• 見つからなかった場合はNULLを返す
mintty + bash + GNU C
$ gcc asearchi_test.c asearchi.c && ./a
key = ? 31
data[10] = 31
10
教科書 pp.198-202.
2分探索
• 昇順ソート済みのデータから探す
• 探す領域を半分に探す範囲を絞り込む
• データがn個ある時
• 最悪 𝑛個調べれば良い
• 速いけど事前にソートが必要
13
2
3
5
7
11
13
17
19
23
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
s[6]
s[7]
s[8]
11
教科書 pp.198-202.
2分探索
13
• 全体を2つに分けて
• 中央の値と比較して行く
2
3
5
7
11
13
17
19
23
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
s[6]
s[7]
s[8]
2
3
5
7
11
13
17
19
23
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
s[6]
s[7]
s[8]
2
3
5
7
13
17
19
23
s[0]
s[1]
s[2]
s[3]
s[5]
s[6]
s[7]
s[8]
探索成功
11
s[4]
12
教科書 pp.198-202.
2分探索
18
• 全体を2つに分けて
• 中央の値と比較して行く
2
3
5
7
11
13
17
19
23
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
s[6]
s[7]
s[8]
2
3
5
7
11
13
17
19
23
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
s[6]
s[7]
s[8]
2
3
5
7
11
13
17
s[0]
s[1]
s[2]
s[3]
s[4]
s[5]
s[6]
探索失敗
19
s[7]
23
s[8]
13
教科書 pp.198-202.
2分探索
• 標準ライブラリ関数ライクな実装例
bsearchi.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];
}
}
bsearchi_test.c
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_test.c
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int data[] =
2,
3,
31, 37,
73, 79,
127, 131,
179, 181,
233, 239,
283, 293,
353, 359,
419, 421,
467, 479,
};
int key;
int *p;
{
5,
41,
83,
137,
191,
241,
307,
367,
431,
487,
7,
43,
89,
139,
193,
251,
311,
373,
433,
491,
11,
47,
97,
149,
197,
257,
313,
379,
439,
499,
13,
53,
101,
151,
199,
263,
317,
383,
443,
503,
17,
59,
103,
157,
211,
269,
331,
389,
449,
509,
19,
61,
107,
163,
223,
271,
337,
397,
457,
521,
23,
67,
109,
167,
227,
277,
347,
401,
461,
523,
29,
71,
113,
173,
229,
281,
349,
409,
463,
541,
mintty + bash + GNU C
$ gcc bsearchi_test.c bsearchi.c -DDEBUG && ./a
key = ? 17
100: [50] = 233
50: [25] = 101
25: [12] = 41
12: [6] = 17
data[6] = 17
14
演習: bsearchi.c
• 昇順ソート済みのint型のデータを2分探索する関数
bsearchi を作成せよ
• bsearchi_test.c と共にコンパイルして動作を確認せ
よ
• 引数
• int key : 検索したい値へのポインタ
• int *data : 検索対象のデータ集合へのポインタ
• int n
: データの要素数
• 戻り値
• 見つけたデータへのポインタを返す
• 見つからなかった場合はNULLを返す
mintty + bash + GNU C
$ gcc bsearchi_test.c bsearchi.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
cmp
:
:
:
:
:
検索したい値へのポインタ
検索対象のデータ集合へのポインタ
データの要素数
データの1要素当りのバイト数
データ比較用の関数へのポインタ
• 戻り値
• マッチした項目へのポインタを返す
• マッチした項目がない場合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
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
int data[] = {
2,
3,
5,
31, 37, 41,
73, 79, 83,
127, 131, 137,
179, 181, 191,
233, 239, 241,
283, 293, 307,
353, 359, 367,
419, 421, 431,
467, 479, 487,
};
int key;
int *p;
7,
43,
89,
139,
193,
251,
311,
373,
433,
491,
bsearch_test.c
11,
47,
97,
149,
197,
257,
313,
379,
439,
499,
13,
53,
101,
151,
199,
263,
317,
383,
443,
503,
17,
59,
103,
157,
211,
269,
331,
389,
449,
509,
19,
61,
107,
163,
223,
271,
337,
397,
457,
521,
23,
67,
109,
167,
227,
277,
347,
401,
461,
523,
29,
71,
113,
173,
229,
281,
349,
409,
463,
541,
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
関数へのポインタ
• ポインタ変数に * が付いたら何になるか?
関数の定義
int cmp(int *a, int *b)
{
return *a - *b;
}
関数のプロトタイプ宣言
int cmp(int *a, int *b);
関数へのポインタ変数の宣言
int (*fnc)(int *a, int *b);
関数へのポインタに代入と呼び出し
fnc = cmp;
(*fnc)(&x, &y);
cmp が
戻り値が int 型
引数が (int *a, int *b) の
関数という意味になる
fnc がポインタ変数で
*fnc が
戻り値が int 型
引数が (int *a, int *b) の
関数という意味になる
*fnc とすれば
関数へのポインタが関数になる
演算子の優先順位は
高*>()低なので*fncに()が必要
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
0x~1
0x~1
0x~2
0x~3
p=
0x~0
0x~2
0x~3
大きさが0バイトのデータ
つまり void a; には意味がないが
大きさが不明のデータでも先頭アドレス
つまり void *p; には意味がある
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
0x~0
0x~1
0x~1
0x~2
0x~3
p=
0x~0
0x~2
0x~3
*p は void なので意味がない
*((int*)p) は int なので意味がある
22
商と剰余
23
整数除算の商と剰余
•
•
•
知っているようで知らない商と剰余の定義
整数𝑚, 𝑛, 𝑞, 𝑟 (𝑛 ≠ 0)について𝑚 ÷ 𝑛とした時、𝑞が商、 𝑟が剰余
剰余の一般形: 𝑚 = 𝑞𝑛 + 𝑟 ただし 𝑟 < 𝑛
例:
•
/ 3 = 1 余り 2、 2 余り -1
/ 3 = -1 余り -2、 -2 余り 1
/ -3 = -1 余り 2、 -2 余り 1
/ -3 = 1 余り -2、 2 余り 1
追加の制約がないと
一意に決まらない
5
-5
5
-5
/ 3 = 1 余り
/ 3 = -2 余り
/ -3 = -1 余り
/ -3 = 2 余り
一意に決まるが
𝑚, 𝑛の符号により
𝑞, 𝑟の絶対値が変わる
最小非負剰余: 𝑚 = 𝑞𝑛 + 𝑟 ただし 0 ≤ 𝑟 < 𝑛
例:
•
5
-5
5
-5
2
1
2
1
𝑛
絶対値最小剰余: 𝑚 = 𝑞𝑛 + 𝑟 ただし − 2 ≤ 𝑟 <
例:
5
-5
5
-5
/ 3 = 2 余り -1
/ 3 = -2 余り 1
/ -3 = -2 余り 1
/ -3 = 2 余り 1
𝑛
2
一意に決まるが
𝑚, 𝑛が共に正の場合
感覚に合わない
24
負の数の除算と剰余算
• C89では実装依存だった
• 「/に対する切捨ての方向および%に対する結果の符号は,負の被演算子に
対しては機種依存する」([1]p.50)
• 例: 以下のいずれも有り得る
• 5 / 3
• -5 / 3
• 5 / -3
• -5 / -3
= 1 余り 2
= -1 余り -2、-2 余り 1
= -1 余り 2、-2 余り -1
= 1 余り -2、 2 余り 1
• C99以降は以下のように定義された
• a/bはゼロに向かった切捨て
• (a/b)*b + a%b は a と等しい
• 例: 必ず以下のようになる
• 5 / 3
• -5 / 3
• 5 / -3
• -5 / -3
= 1 余り 2
= -1 余り -2
= -1 余り 2
= 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
5
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 乗算演算子
5
/ 演算子の結果は1つ目のオペランド(演算対象)を2つ目のオペランドで除算した商であり; %
演算子の結果は剰余である。両方の演算子は、もし2つ目のオペランドがゼロなら、動作は未
定義である。
6
整数が除算される場合、/ 演算子の結果は小数部が破棄された代数的な商になる。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
6
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 乗算演算子
6
整数が除算される場合、/ 演算子の結果は小数部が破棄された代数的な商に
なる。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
整数𝑚, 𝑛, 𝑞, 𝑟について𝑚を𝑛で割った商を𝑞余りを𝑟をとし以下のように定義する
𝑚 = 𝑞𝑛 + 𝑟 ただし 𝑟 < 𝑛 かつ 𝑞𝑛 ≤ 𝑚
• 𝑖, 𝑗, 𝑘, 𝑙を適当な整数とすれば
• 𝑑1 を𝑛, 𝑟の任意の公約数とすると、𝑛 = 𝑖𝑑1 , 𝑟 = 𝑗𝑑1と書けるから
𝑚
= 𝑞𝑛 + 𝑟
= 𝑞𝑖𝑑1 + 𝑗𝑑1
= 𝑞𝑖 + 𝑗 𝑑1
𝑑2 を𝑚, 𝑛の任意の公約数とすると、𝑚 = 𝑘𝑑2 , 𝑛 = 𝑙𝑑2 と書けるから
𝑚 = 𝑞𝑛 + 𝑟
•
•
•
•
𝑛, 𝑟の公約数と𝑚, 𝑛の公約数は等しい集合である
𝑚, 𝑛の最大公約数は等しい
31
ユークリッドの互除法
Euclidean algorithm
• ここで𝑚 ÷ 𝑛の余り𝑟は 𝑟 < 𝑛 であるから、
以下の手順を行うと𝑟 = 0に収束する
1. 𝑚 ÷ 𝑛の余り𝑟を求める
2. 𝑛, 𝑟を新たな𝑚, 𝑛とする
3. 𝑚が𝑛で割り切れるまで手順1,2を繰り返す
• つまり上記手順1.で𝑟 = 0になった時の 𝑛 が
最初に与えた𝑚, 𝑛の最大公約数である
n = 0 の時 m % n が
0 割りになるので具合が悪い
32
ユークリッドの互除法
Euclidean algorithm
• ここで𝑚 ÷ 𝑛の余り𝑟は 𝑟 < 𝑛 であるから、
以下の手順を行うと𝑟 = 0に収束する
1. 𝑛が0になるまで以下の手順2,3を繰り返す
2. 𝑚 ÷ 𝑛の余り𝑟を求める
3. 𝑛, 𝑟を新たな𝑚, 𝑛とする
• つまり上記手順1.で𝑛 = 0になった時の 𝑚 が
最初に与えた𝑚, 𝑛の最大公約数である
n = 0 の時 m % n が
0 割りにならないように工夫
33
ユークリッドの互除法
Euclidean algorithm
• 𝑚 ÷ 𝑛の余り𝑟を求める
• r = m % n;
• 𝑛, 𝑟を新たな𝑚, 𝑛とする
n = 0 の時 m % n すると
0 割りになるので具合が悪いため
厳密には前判定ループで n == 0 で
ループを辞める方が都合が良い
• 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 と共にコンパイルする事で動
作を確認せよ
mintty + bash + GNU C
• 引数
• int m,n : 任意の整数
$ gcc gcd_test.c gcd.c && ./a
m = ? 48
n = ? 15
gcd(48, 15) = 3
• 戻り値
• m,n の最大公約数をint型で返す
37
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久
訳、プログラミング言語C 第2版 ANSI 規格準
拠、共立出版(1989)