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