プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/clangi2014/clangi2014_9p.pdf ·...
TRANSCRIPT
C言語入門 第9週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
1
関数
2
関数
• 関数の定義の書式:
3
関数の定義 戻り値の型 関数名(引数の宣言, ...) { // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 }
関数の宣言 戻り値の型 関数名(引数の宣言, ...);
第4週資料pp.33-41,48-49.
関数の利用 変数名 = 関数名(引数, ...);
.h ファイルへ書き出す
.c ファイルへ書き出す
適宜呼び出す
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
4
scopetest.c int gl = 100; void sub(int lo) { { int lo = 400; printf("%-4s : %3d: gl=%d, lo=%d¥n", __func__, __LINE__, ++gl, ++lo); } printf("%-4s : %3d: gl=%d, lo=%d¥n", __func__, __LINE__, ++gl, ++lo); } int main() { int lo = 200; sub(300); printf("%-4s : %3d: gl=%d, lo=%d¥n", __func__, __LINE__, ++gl, ++lo); return EXIT_SUCCESS; }
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
関数の引数は呼び出し元とは別の変数になっていた
第4週資料pp.42-44.
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
5
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
8 9 10 11 12 13 14 15 16 17 18 19 20
引数で受け取った変数を変更しても 呼び出し元には反映されない
第4週資料pp.42-44. 教科書p.171.
mintty + bash + GNU C $ g++ call_by_value.c && ./a lo=100
関数の引数(値渡し、参照渡し)
• 参照渡し: 呼出し元の値の格納場所を渡す
6
call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
8 9 10 11 12 13 14 15 16 17 18 19 20
scanf で見たことがある書き方! &: アドレス演算子
第4週資料pp.42-44. 教科書p.171.
引数で受け取った変数を変更すると 呼び出し元にも反映される
これは正確には ポインタ渡しと言う
変数loのアドレスを 渡している
mintty + bash + GNU C $ g++ call_by_pointer.c && ./a lo=200
関数の引数(値渡し、参照渡し)
• C++における参照渡し
7
call_by_reference.cpp void sub(int &lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; } mintty + bash + GNU C++
$ g++ call_by_reference.cpp && ./a lo=200
4 5 6 7 8 9 10 11 12 13 14 15 16
参考
引数で受け取った変数を変更すると 呼び出し元にも反映される
C++では 本物の参照渡しが 可能になった
C++の参照渡しでは アドレス演算子「&」が不要
値が変化することが 分かり難いという デメリットもある
ポインタ変数
• メモリ上のアドレスを指し示すための変数
• char * 型: char 型の変数へのポインタ
• int * 型: int 型の変数へのポインタ
• 要はアドレスを格納するための変数
• ポインタ型のサイズはアドレス空間のビット数
• 32ビットアドレス空間なら4バイト
• 64ビットアドレス空間なら8バイト
• sizeof(char *)もsizeof(int *)も同じ
8 教科書pp.207-272.
pointer: 指し示す者
メモリの構成
• 1byte単位でアドレスが振られている
• つまり各アドレスには1byteの値を格納出来る
0x00 0x00000000
0x00 0x00000001
0x00 0x00000002
0x00 0x00000003
0x00 0xffffffff
: :
: :
0x00 0x0000000000000000
0x00 0x0000000000000001
0x00 0x0000000000000002
0x00 0x0000000000000003
0x00 0xffffffffffffffff
: :
: :
32bitのOSは32bitのアドレス空間 最大232Bytes=4GiB
64bitのOSは64bitのアドレス空間 最大264Bytes=16EiB
アドレス 格納値 アドレス 格納値
教科書 pp.52-56. 第2週資料 p.45.
ポインタ変数には これらの値が入る
ポインタ変数
• 例: 16bit short型little endianの場合
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; short *p = &a;
a
0x1234
a
p に &a を入れておくと *p は a への 参照として機能する C言語ではこれを ポインタと言う
10
p
0x~00
&a
0x~00
&aは 変数aの メモリ上の 先頭アドレス
実際に存在している変数はpで中にはアドレスが格納されている
*pはアドレスpに 置かれた変数(ここではa) への参照
要はリンク みたいなもの
*p
0x1234
0x~00
宣言時に*を付けると ポインタ変数になる
教科書pp.207-272.
pは変数*pの アドレスを指し示す
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
11
pointertest1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15
教科書pp.207-272.
変数の宣言で 変数名の前にポインタ宣言子「*」を付けると ポインタ変数になる。 この場合、 int 型の変数 *p を宣言した結果、 実際には int 型変数へのポインタである int * 型変数 p が宣言されると 理解しておくとスッキリする。
mintty + bash + GNU C $ gcc pointertest1.c && ./a a=2
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
12
pointertest1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15
教科書pp.207-272.
mintty + bash + GNU C $ gcc pointertest1.c && ./a a=2
0x~
&a
a
1
p
0x~
アドレス演算子「&」を用いて int 型変数 a のアドレス &a を int 型の変数へのポインタ p に代入する
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
13
pointertest1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15
教科書pp.207-272.
mintty + bash + GNU C $ gcc pointertest1.c && ./a a=2
int 型の変数へのポインタ p の前に 間接演算子「*」を付けると ポインタ変数 p が指し示すアドレスを int 型の変数としてアクセス出来る。 今 p に変数 a のアドレス &a が 入っているので、 *p に対するあらゆる操作は 変数 a に対する操作と同義となる。 実際 *p を書き変えることで a が 書き変えられていることが確認出来る。
0x~ a
2
p
0x~ *p
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
• 同じ「*」だが宣言時とそれ以外で意味が異なる
14
宣言時 それ以外
*p pをポインタとして宣言 pが指すアドレスの格納値
*p=x pにxを代入 *pにxを代入
ポインタ変数pを宣言 (言い換えると、ある型の変数*pを宣言)し 宣言した変数pに アドレスxを代入する
ポインタ変数pが指すアドレス (言い換えると、ある型の変数*p)に 値xを代入する
要注意
*は間接演算子 indirection operator または間接参照演算子 dereference operator
教科書pp.207-272., [1] pp.250,268-269.
*はポインタ宣言子 pointer declarator
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
15
pointertest2.cpp int a = 1; int *p1, b; int *p2 = &a; p1 = &a; printf("sizeof(p1)=%d¥n", sizeof(p1)); printf("sizeof(b)=%d¥n", sizeof(b)); printf("&a=%#0*p¥n", sizeof(&a)*2+2, &a); printf("p1=%#0*p¥n", sizeof(p1)*2+2, p1); printf("p2=%#0*p¥n", sizeof(p2)*2+2, p2); printf("a=%d¥n", a); printf("*p1=%d¥n", *p1); printf("*p2=%d¥n", *p2);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
教科書pp.207-272.
要注意
mintty + bash + GNU C $ gcc pointertest2.c && ./a sizeof(p1)=8 sizeof(b)=4 &a=0x000000000022aabc p1=0x000000000022aabc p2=0x000000000022aabc a=1 *p1=1 *p2=1
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
16
pointertest2.cpp int a = 1; int *p1, b; int *p2 = &a; p1 = &a; printf("sizeof(p1)=%d¥n", sizeof(p1)); printf("sizeof(b)=%d¥n", sizeof(b)); printf("&a=%#0*p¥n", sizeof(&a)*2+2, &a); printf("p1=%#0*p¥n", sizeof(p1)*2+2, p1); printf("p2=%#0*p¥n", sizeof(p2)*2+2, p2); printf("a=%d¥n", a); printf("*p1=%d¥n", *p1); printf("*p2=%d¥n", *p2);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
mintty + bash + GNU C $ gcc pointertest2.c && ./a sizeof(p1)=8 sizeof(b)=4 &a=0x000000000022aabc p1=0x000000000022aabc p2=0x000000000022aabc a=1 *p1=1 *p2=1
宣言時は「*」が間接演算子ではなく ポインタ宣言子として働いている 宣言時に初期化すると *p2 ではなく p2 に &a が代入される点が紛らわしい 要注意
教科書pp.207-272.
p1 に &a を代入している
要注意
宣言時以外は*は関節演算子として働く ポインタの前に*が付くと、指し示す アドレスに格納されている値を操作する
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
17
pointertest2.cpp int a = 1; int *p1, b; int *p2 = &a; p1 = &a; printf("sizeof(p1)=%d¥n", sizeof(p1)); printf("sizeof(b)=%d¥n", sizeof(b)); printf("&a=%#0*p¥n", sizeof(&a)*2+2, &a); printf("p1=%#0*p¥n", sizeof(p1)*2+2, p1); printf("p2=%#0*p¥n", sizeof(p2)*2+2, p2); printf("a=%d¥n", a); printf("*p1=%d¥n", *p1); printf("*p2=%d¥n", *p2);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
mintty + bash + GNU C $ gcc pointertest2.c && ./a sizeof(p1)=8 sizeof(b)=4 &a=0x000000000022aabc p1=0x000000000022aabc p2=0x000000000022aabc a=1 *p1=1 *p2=1
「int *」型の宣言ではなく、 「int」型の宣言である点も紛らわしい。 p1 はポインタ変数だが b は通常のint型変数 複数の変数を宣言する場合 変数名の前に ポインタ宣言子「*」を付けた変数だけが ポインタ変数になる 要注意
教科書pp.207-272.
要注意
ポインタの表示
• %p
• void *;ポインタとして印字(処理系依存)
18
例 printf("&a=%#0*p¥n", sizeof(&a)*2+2, &a);
第3週資料pp.24-33.
mintty + bash + GNU C $ gcc hoge.c && ./a &a=0x000000000022aabc
#: 16進表示が0でない場合、先頭を0xにする 0: フィールド幅いっぱいに左側から0を詰める *: 最小フィールド幅を引数で指定 p: void *;ポインタとして印字
%*pによる最小フィールド幅指定 1バイト=16進数2桁 先頭の0xで更に2桁
配列 int p[N];
ポインタ int *p;
sizeof(p) ○ ○ 変数pの割り当てバイト数
&p △ ○ 変数pのアドレス(配列はpと同じ)
p ○ ○ アドレスp(p[0]のアドレス)
*p ○ ○ アドレスpの格納値
p[x] ○ ○ アドレスpを先頭にしてx個目の要素の格納値
*(p+x) ○ ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値
&p[x] ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
p+x ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
p+=x × ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
1次元配列とポインタ
• 機能としてはほぼ同じ
• ポインタはアドレスを変更可能(少し柔軟)
19
配列は1要素のバイト数×要素数 ポインタはアドレス空間のビット数/8
教科書pp.207-272.
ポインタ変数とアドレス演算
• 例: 16bit short型little endianの場合
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; short *pa = &a;
*pa
±1するとsizeof(*pa)単位で アドレスが増減する つまり short 型配列の 0要素目、1要素目、... という意味になる
20
pa
pa+1
pa+2
pa+3
*(pa+1)
*(pa+2)
*(pa+3)
要注意
教科書pp.207-272.
ポインタ変数とアドレス演算
• 例: 32bit int型little endianの場合
0x78 0x~00
0x56 0x~01
0x34 0x~02
0x12 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
int a = 0x12345678; int *pa = &a;
*pa
±1するとsizeof(*pa)単位で アドレスが増減する つまり int 型配列の 0要素目、1要素目、... という意味になる
21
pa
pa+1
*(pa+1)
要注意
教科書pp.207-272.
ポインタ変数の配列的利用法
• 例: 16bit short型little endianの場合
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; short *pa = &a;
pa[0]
配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる
22
&pa[0]
&pa[1]
&pa[2]
&pa[3]
pa[1]
pa[2]
pa[3]
教科書pp.207-272.
ポインタ変数の配列的利用法
• 例: 32bit int型little endianの場合
0x78 0x~00
0x56 0x~01
0x34 0x~02
0x12 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
int a = 0x12345678; int *pa = &a;
pa[0]
配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる
23
&pa[0]
&pa[1]
pa[1]
教科書pp.207-272.
1次元配列とポインタ
24
pointertest3.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointertest3.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272.
pに1を足しているのに 結果が4増えていることが 確認出来る
1次元配列とポインタ
25
pointertest3.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
cygwin64 + mintty + bash + GNU C $ gcc pointertest3.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 <以下略>
教科書pp.207-272.
配列変数aはメモリに配置されたアドレス 配列変数aのアドレス&aはつまりaと同じ ポインタ変数pは メモリ上のどこかに確保されており &pはそのアドレス pはそのアドレス&pに格納されたアドレス この例では&a *pはそのアドレスpに格納されたデータ
1次元配列とポインタ
• 例: 32bitOSで8bit char型little endianの場合
0x00 0x~00
0x01 0x~01
0x02 0x~02
0x03 0x~03
: :
: :
0x00 0x~04
0x~~ 0x~05
0x~~ 0x~06
: :
: :
0x~~ 0x~07
0x?? 0x~08
char a[] = {0,1,2,3}; char *p = &a;
a
26
&a =a =p
&p
p
教科書pp.207-272.
配列変数aはメモリに配置されたアドレス 配列変数aのアドレス&aはつまりaと同じ ポインタ変数pは メモリ上のどこかに確保されており &pはそのアドレス pはそのアドレス&pに格納されたアドレス この例では&a *pはそのアドレスpに格納されたデータ
これ全体が配列aであり aはその先頭アドレス
1次元配列とポインタ
• 例: 32bitOSで8bit char型little endianの場合
0x00 0x~00
0x01 0x~01
0x02 0x~02
0x03 0x~03
: :
: :
0x00 0x~04
0x~~ 0x~05
0x~~ 0x~06
: :
: :
0x~~ 0x~07
0x?? 0x~08
char a[] = {0,1,2,3}; char *p = &a;
a
27
&a =a =p
&p
p
教科書pp.207-272.
sizeof(p) は OSのアドレス空間のビット数/8 つまりsizeof(char *)
sizeof(a) は aの1要素辺りのバイト数×要素数 つまりsizeof(char)*N
ポインタ変数
28
pointertest4.c int i, *p, a[] = {0,1,2,3,4,5,6,7,8,9}; printf("&a[0]=%#0*p¥n", sizeof(&a[0])*2+2, &a[0]); printf("p = ? "); scanf("%p", &p); printf("*p="); scanf("%i", p); for (i = 0; i < sizeof(a) / sizeof(*a); i++) { printf("a[%d]=%#0*x¥n", i, sizeof(*a)*2+2, a[i]); }
6 7 8 9 10 11 12 13 14 15 16 17 18
教科書pp.207-272.
Cygwin64+mintty+bash+GNU C $ gcc pointertest4.c && ./a &a[0]=0x000000000022aa90 p = ? 0x000000000022aa98 *p=0x12345678 a[0]=0000000000 a[1]=0x00000001 a[2]=0x12345678 a[3]=0x00000003 a[4]=0x00000004 a[5]=0x00000005 a[6]=0x00000006 a[7]=0x00000007 a[8]=0x00000008 a[9]=0x00000009
結果がどうなるかは別として アドレス演算子を使って 変数のアドレスを入れなくても 適当な値を入れても動く。
ポインタ変数
29
pointertest4.c int i, *p, a[] = {0,1,2,3,4,5,6,7,8,9}; printf("&a[0]=%#0*p¥n", sizeof(&a[0])*2+2, &a[0]); printf("p="); scanf("%p", &p); printf("*p="); scanf("%i", p); for (i = 0; i < sizeof(a) / sizeof(*a); i++) { printf("a[%d]=%#0*x¥n", i, sizeof(*a)*2+2, a[i]); }
6 7 8 9 10 11 12 13 14 15 16 17 18
教科書pp.207-272.
Cygwin64+mintty+bash+GNU C $ gcc pointertest4.c && ./a &a[0]=0x000000000022aa90 p=0x000000000022aa92 *p=0x12345678 a[0]=0x56780000 a[1]=0x00001234 a[2]=0x00000002 a[3]=0x00000003 a[4]=0x00000004 a[5]=0x00000005 a[6]=0x00000006 a[7]=0x00000007 a[8]=0x00000008 a[9]=0x00000009
結果が正しいかどうかは別として 配列の途中のアドレスを ポインタに入れることも出来る
動的配列
30
多くの問題では 配列のサイズを事前に (コンパイルの段階では) 決められない。
実行時、プログラムに 与えるデータや パラメータによって 配列のサイズは決まる。
=
プログラムの実行時に 配列のサイズを適宜に決める事が必要
malloc, calloc, realloc, free 関数
malloc 関数
• void *malloc(site_t size);
• 動的にメモリを確保する
• 引数:
• size: 確保するメモリのバイト数
• 戻り値
• 確保したメモリへのポインタを返す
• 確保したメモリの内容は初期化されない
• エラーの場合は NULL を返す
31
JM: malloc (3)
calloc 関数
• void *calloc(site_t nobj, site_t size); • 動的にメモリを確保する
• 引数: • nobj: メモリを確保する要素数 • size: 1要素辺りのバイト数
• 戻り値 • 確保したメモリへのポインタを返す • 確保したメモリの内容は0で初期化される • エラーの場合は NULL を返す
32
JM: malloc (3)
realloc 関数
• void *realloc(void *p, site_t size); • 動的に確保したメモリのサイズを変更する
• 引数: • p: サイズを変更するメモリへのポインタ • size: 変更後のバイト数
• 戻り値 • 確保したメモリへのポインタを返す • サイズ変更前後で小さい方のサイズまでは
前の内容が維持される • 増加した部分のメモリの内容は初期化されない • エラーの場合は NULL を返す
• 元のブロックは維持される
33
JM: malloc (3)
free 関数
• void free(void *p);
• 動的に確保したメモリを解放する
• 引数:
• p: 解放するメモリへのポインタ
34
JM: malloc (3)
演習
関数の作成
35
演習: is_leap_year~.c の関数化
• 第4週資料 p.41. の演習をしましょう
36
演習: is_odd.c
• print_evenodd.c を参考に以下の関数を作りなさい
• int is_odd(int i); • 奇数かどうか判定する
• 引数 • i: 判定する整数
• 戻り値 • 奇数なら1、奇数でなければ0を返す
• is_odd_test.c と共にコンパイルして動作を確認すること
37
Cygwin64+mintty+bash+GNU C $ gcc is_odd_test.c is_odd.c && ./a i = ? 1 odd number
2014-06-20追加
演習: is_even.c
• print_evenodd.c を参考に以下の関数を作りなさい
• int is_even(int i); • 偶数かどうか判定する
• 引数 • i: 判定する整数
• 戻り値 • 偶数なら1、偶数でなければ0を返す
• is_even_test.c と共にコンパイルして動作を確認すること
38
Cygwin64+mintty+bash+GNU C $ gcc is_even_test.c is_even.c && ./a i = ? 2 even number
2014-06-20追加
演習: is_prime.c
• print_isprime.c を参考に以下の関数を作りなさい
• int is_prime(int i); • 素数かどうか判定する
• 引数 • i: 判定する整数
• 戻り値 • 素数なら1、素数でなければ0を返す
• is_prime_test.c と共にコンパイルして動作を確認すること
39
Cygwin64+mintty+bash+GNU C $ gcc is_prime_test.c is_prime.c && ./a i = ? 7 prime number
2014-06-20追加
演習: swapi.c
• 以下の関数を作りなさい
• void swapi(int *a, int *b);
• 引数で与えた変数の値を交換する
• 引数
• a, b: 値を交換する変数へのポインタ
• swapi_test.c と共にコンパイルして動作を確認すること
40
Cygwin64+mintty+bash+GNU C $ gcc swapi_test.c swapi.c && ./a a = ? 1 b = ? 2 a = 2 b = 1
ヒント 第6週資料pp.43-44. 本資料 p.6. を参考にせよ
2014-06-20追加
演習: ヘッダファイルの作成
• ここまでに作った is_odd, is_even, is_prime, swapi 関数のプロトタイプ宣言を myfunc.h というファイルにまとめよ。
• 第4週資料p.38.のis_leap_year_func.hを参考にせよ。
• include ガードの識別子は MYFUNC_H とせよ。
41
誤:3週資料 正:4週資料
エラトステネスのふるい
42
• N以下の整数について既知の素数とその倍
数をふるい落とすことで素数を判定するアルゴリズム
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
エラトステネスのふるい
43
• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥
(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ
3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る
4. 探索リストに残った数を 素数リストに移動
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
エラトステネスのふるい
44
• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥
(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ
3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る
4. 探索リストに残った数を 素数リストに移動
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
探索リスト先頭の数は素数 先頭の2を素数と判定し 2の倍数を探索リストから削除する
エラトステネスのふるい
45
• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥
(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ
3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る
4. 探索リストに残った数を 素数リストに移動
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
探索リスト先頭の数は素数 先頭の3を素数と判定し 3の倍数を探索リストから削除する
エラトステネスのふるい
46
• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥
(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ
3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る
4. 探索リストに残った数を 素数リストに移動
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
探索リスト先頭の数は素数 先頭の5を素数と判定し 5の倍数を探索リストから削除する
エラトステネスのふるい
47
• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥
(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ
3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る
4. 探索リストに残った数を 素数リストに移動
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
探索リスト先頭の数は素数 先頭の7を素数と判定し 7の倍数を探索リストから削除する
エラトステネスのふるい
48
• 𝑁以下の素数の探索 1. 2以上𝑁以下の数を探索リストに入れる 2. 検索リストの先頭の数𝑥
(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ
3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る
4. 探索リストに残った数を 素数リストに移動
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
先頭の11は≤ 100でないので 探索リストに残った数を素数と判定
エラトステネスのふるい 通常の配列による探索リスト
• メモリ上のイメージ
2 0
3 1
4 2
5 3
6 4
7 5
8 6
添字
: :
100 98
49
探索リストの初期化 #define N 100 int i, j = 2, n = N; // n は探索リストの件数 int slist[N-2]; for (i = 0; i < n; i++) { slist[i] = j++; }
n-1
訂正2014-06-20 誤:n = N 正:n = N - 2
訂正2014-06-20 誤:slist[N-1] 正:slist[N-2]
100 98
エラトステネスのふるい 通常の配列による探索リスト
• 値の削除
2 0
3 1
4 2
5 3
6 4
7 5
8 6
添字
: :
100 98
50
n-1
2 0
3 1
5 2
6 3
7 4
8 5
: :
添字
100 97 n-1
4を削除
普通の配列だと 値を削除して 詰める処理に 時間がかかる
エラトステネスのふるい 通常の配列による探索リスト
51
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要
探索リストの初期化 #define N 100 int i, j = 2, n = N-2; // n は探索リストの件数 int slist[N-2]; for (i = 0; i < n; i++) { slist[i] = j++; }
探索リストから値xの削除 for (i = 0; i < n; i++) { if (slist[i] == x) { for (j = 0; i + j + 1 < n; j++) { slist[i + j] = slist[i + j + 1]; // ↑ 探索リストから x を削除し // ↑ 以降の値を1つずつずらして詰める } n--; // 探索リストの件数をxを削除した分減らす break; } }
普通の配列だと 値を削除して 詰める処理に 時間がかかる
訂正あり p.49.参照
エラトステネスのふるい 通常の配列による探索リスト
52
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要
探索リストで値xの判定 for (i = 0; i < n; i++) { if (slist[i] == x) { // x が探索リストにある場合の処理 break; } }
エラトステネスのふるい 通常の配列による探索リスト
• 値の削除を工夫
2 0
3 1
4 2
5 3
6 4
7 5
8 6
添字
: :
100 98
53
n-1
4を削除
使ってない値を マイナスにして 検索時に 無視すると 削除は速くなるが 値の検索でごみ になった-1も 処理しなければ いけなくなる
2 0
3 1
-1 2
5 3
6 4
7 5
8 6
添字
: :
100 98 n-1
マイナス値も 扱う場合は 使えない方法
エラトステネスのふるい 通常の配列による探索リスト
54
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要
探索リストの初期化 #define N 100 int i, j = 2, n = N-2; // n は探索リストの件数 int slist[N-2]; for (i = 0; i < n; i++) { slist[i] = j++; }
探索リストから値xの削除 for (i = 0; i < n; i++) { if (slist[i] == x) { slist[i] = -1; // ↑ 探索リストから x を削除 break; } }
削除は速いが 検索時にゴミも 検索するので 検索が遅くなる
訂正2014-06-20 誤:slist[i+j] 正:slist[i]
訂正あり p.49.参照
エラトステネスのふるい 通常の配列による探索リスト
55
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要
探索リストで値xの判定 for (i = 0; i < n; i++) { if (slist[i] == x) { // x が探索リストにある場合の処理 break; } }
エラトステネスのふるい 1方向リストによる探索リスト
• メモリ上のイメージ
2 [0][0]
1 [0][1]
3 [1][0]
2 [1][1]
4 [2][0]
3 [2][1]
5 [3][0]
添字
: :
99 [98][1]
56
N-1
探索リストの初期化 #define N 100 int i, j = 2; int slist[N-1][2]; for (i = 0; i < N - 2; i++) { slist[i][0] = j++; slist[i][1] = i+1; // 次の値の格納位置 } slist[i][1] = -1; // 終端記号
? [99][0]
-1 [99][1]
2つの値をペアとして扱い 1つ目を次の値の格納場所の添え字 2つ目を格納値 として利用している
1つ目の値を ポインターっぽく 使っている
マイナスの値を次の値がない目印 つまり終端記号として用いている
訂正2014-06-20 誤:slist[N][2] 正:slist[N-1][2]
訂正2014-06-20 誤:i<N-1でしたが 正:i<N-2の誤りでした
訂正2014-06-20 誤:slist[i][0] 正:slist[i][1]
エラトステネスのふるい 1方向リストによる探索リスト
• 値の削除
2 [0][0]
1 [0][1]
3 [1][0]
2 [1][1]
5 [2][0]
4 [2][1]
5 [3][0]
添字
: :
99 [98][1]
57
N-1
? [99][0]
-1 [99][1]
2 3 4
5 100 ? ×
2 3 5
5 100 ? ×
4を削除
エラトステネスのふるい 1方向リストによる探索リスト
58
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N)*4*2バイト必要
探索リストの初期化 #define N 100 int i, j = 2; int slist[N-1][2]; for (i = 0; i < N - 2; i++) { slist[i][0] = j++; slist[i][1] = i+1; // 次の値の格納位置 } slist[i][0] = -1; // 終端記号
探索リストから値xの削除 for (i = 0; 0 <= slist[i][1]; i=slist[i][1]) { if (slist[i][0] == x) { j = slist[i][1]; slist[i][0] = slist[j][0]; slist[i][1] = slist[j][1]; // ↑ 探索リストの x が格納されている要素を // ↑ 次の要素で上書き break; } }
使わなくなった メモリは 無駄になるが 削除が高速
メモリが通常の配列の 倍必要
訂正あり p.56.参照
エラトステネスのふるい 1方向リストによる探索リスト
59
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N)*4*2バイト必要
探索リストで値xの判定 for (i = 0; 0 <= slist[i][1]; i=slist[i][1]) { if (slist[i][0] == x) { // x が探索リストにある場合の処理 break; } }
エラトステネスのふるい フラグ配列による探索リスト(int版)
• メモリ上のイメージ
0 0
0 1
0 2
0 3
0 4
0 5
0 6
添字
: :
0 100
60
探索リストの初期化 #define N 100 int slist[N+1] = {0};
N
添え字をxとして考え 格納値がゼロなら有効 格納値が非ゼロなら無効 と考える
エラトステネスのふるい フラグ配列による探索リスト(int版)
• 値の削除
0 0
0 1
0 2
0 3
0 4
0 5
0 6
添字
: :
0 100
61
N
4を削除 使ってない値を 非ゼロ 削除は速い 検索はごみも 検索するので あまり速くない
0 0
0 1
0 2
0 3
1 4
0 5
0 6
添字
: :
0 100 N
エラトステネスのふるい フラグ配列による探索リスト(int版)
62
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N+1)*4バイト必要
探索リストの初期化 #define N 100 int slist[N] = {0};
探索リストから値xの削除 slist[x] = 1; 検索しなくて良いので
削除は高速
こんなに必要?
探索リストで値xの判定 if (!slist[x]) { // x が 0 の時の処理 // x が探索リストにある場合の処理 }
エラトステネスのふるい フラグ配列による探索リスト(char版)
63
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると 初期状態で(N+1)バイト必要
探索リストの初期化 #define N 100 char slist[N] = {0};
探索リストから値xの削除 slist[x] = 1; 検索しなくて良いので
削除は高速
char で十分 必要メモリが 1/4になった もっと減 らせるのでは?
探索リストで値xの判定 if (!slist[x]) { // x が 0 の時の処理 // x が探索リストにある場合の処理 }
エラトステネスのふるい フラグ配列による探索リスト(1bit版)
64
• 探索リストの消費メモリ
• 2以上N以下の数を配列に保存すると 初期状態で(N+8)/8バイト必要
探索リストの初期化 #define N 100 char slist[(N+8)/8] = {0};
探索リストから値xの削除 slist[x/8] |= 1<<(x%8); 検索しなくて良いので
削除は高速
1bitでも十分 charの場合から 更に1/8になった
bit毎のOR演算による bitマスクを用いて 狙ったビットをONにする 探索リストで値xの判定
if (!(slist[x/8] & (1<<(x%8)))) { // x が 0 の時の処理 // x が探索リストにある場合の処理 }
bit毎のAND演算による bitマスクを用いて 狙ったビットのON/OFFを調べる
訂正2014-06-20 誤:slist[x/7] 正:slist[x/8]
演習: print_prime_lt~.c
• エラストテネスのふるいによりN未満の素数を小さい順に全て表示せよ • print_prime_lt.c を元に以下のファイル名で作成せよ • print_prime_lt_a1.c
• 通常の配列版(削除した値を詰めた場合)
• print_prime_lt_a2.c • 通常の配列版(削除値を-1にした場合)
• print_prime_lt_b.c • 一方向リスト版
• print_prime_lt_c1.c • フラグ配列int版
• print_prime_lt_c2.c • フラグ配列char版
• print_prime_lt_c3.c • フラグ配列1bit版
65
演習: print_prime_lt~.c
• 前頁で作成したプログラムについて速度を比較してみましょう
• Cygwin の場合 time コマンドを用いると実行実感が計測出来ます。例えば ./a の実行時間を計測するには以下のようにします。
66
mintty+bash+GNU C $ time ./a
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
67