プログラミング入門2 ポインタ 芝浦工業大学情報工学科 青木 義満、篠埜 功 連絡先 篠埜 功(ささの いさお) 電子メールアドレス: [email protected] 講義情報web page: http://www.sic.shibaura-it.ac.jp/~sasano/lecture/lecture.html プログラミング入門2 2 ポインタ C言語の理解を妨げる難敵の一つ 更にプログラミングの勉強を進めるためには, 超えなければならない壁 理解できれば,C言語の機能をフルに活用できるようになる これまで,流して説明してきた部分を理解できる scanf関数でなぜ変数に&をつけるの? 文字列をscanfで読み込む場合にはなぜ&が不要? 関数に配列を渡すとき,どうして配列名だけを書いて渡すの? ファイルポインタとは? その他 変数のメモリ空間上での振舞いを意識! プログラミング入門2 3 ポインタ の必要性 ソースファイル名: list1001.c (p.226) 2つの整数の和と差を求める関数(間違い) #include <stdio.h> void sum_diff(int n1, int n2, int sum, int diff) { sum = n1 + n2; diff = n1 - n2; } int main(void) { int na, nb; int wa = 0, sa = 0; puts("二つの整数を入力してください。"); printf("整数A:"); scanf("%d", &na); printf("整数B:"); scanf("%d", &nb); sum_diff(na, nb, wa, sa); printf("wa = %d\n", wa); printf("sa = %d\n", sa); return (0); 和と差の値は表示されない! どうして? } プログラミング入門2 4 関数呼び出しと引数の引渡し過程(復習) main関数 int main(void) 重要 { int na, nb; 実引数の値が,仮引数にそれぞれ コピーされる int sa=0, wa=0; na = 10; (変数そのものでなく,中に格納されている値 が受け渡される!) nb = 20; 実引数 sum_diff( na, nb, wa, sa ); 10 20 0 0 仮引数 printf("wa = %d\n", wa); printf("sa = %d\n", sa); void sum_diff( int n1, int n2, int sum, int diff ) { } sum = n1 + n2; Main文のwa, saの値は 書き換えられない! diff = n1 – n2; この関数内では,sumは30, diffは-10となるが。。。 } 解決にはポインタが必要! プログラミング入門2 5 アドレスとは? オブジェクトのアドレス オブジェクト(実体)が格納されているメモリ上の 番地 プログラミング入門2 6 アドレスを表示 ソースファイル名: list1002.c (p.228) オブジェクトのアドレスを表示 #include <stdio.h> int main(void) { int double int nx; dx; vc[3]; printf("nxのアドレス:%p\n", &nx); printf("dxのアドレス:%p\n", &dx); printf("vc[0]のアドレス:%p\n", &vc[0]); printf("vc[1]のアドレス:%p\n", &vc[1]); printf("vc[2]のアドレス:%p\n", &vc[2]); return (0); } プログラミング入門2 7 アドレス演算子 & オブジェクトの頭に&をつけると,そのオブジェクトの格納されてい るアドレスを取り出すことが可能 アドレスをprintfで表示するための変換指定は%p int m; double x; &m 1000番地 &x m 1008番地 メ モ リ 空 間 x アドレス(番地) printf("nxのアドレス:%p\n", &nx); プログラミング入門2 nxのアドレスを16進数で表示 8 ポインタとは? ポインタ変数 オブジェクトのアドレス(メモリ上の位置)を格納する ためのもの ポインタの宣言 int nx; int *p; int型 : 整数の値を格納するための変数nx int型のオブジェクトのアドレスを格納する ポインタ型の変数pを宣言 ポインタ変数pの型は? → 「int *」型,int型オブジェクトへのポインタ型 プログラミング入門2 9 最初のポインタ・プログラム ソースファイル名:pointer1.c 整数の値とポインタの値を表示 #include <stdio.h> int main(void) { int nx; int *pt; int型オブジェクトのアドレスを格納するための int型へのポインタ型=(int *)型 nx = 57; pt = &nx; printf("nxの値:%d\n", nx); printf("nxのアドレス:%p\n", &nx); printf("ptの値:%p\n", pt); return (0); 表示される値を確認! } プログラミング入門2 10 プログラムの解説 int int nx; *pt; 57 メモリ上の番地 1000番地 nx = nx = 57; pt = &nx; &nx 代入 pt nxのアドレス int型のオブジェクトのアドレスを格納するため の変数 printf("nxの値:%d\n", nx); printf("nxのアドレス:%p\n", &nx); printf("ptの値:%p\n", pt); プログラミング入門2 57 同じアドレス 11 int型とintへのポインタ型 int *型 int型 int nx; nx nxの値(右辺値)は整数 int *pt; pt ptの値(右辺値)は整数を格 納するオブジェクトのアドレス &nx, pt は(int *)型 プログラミング入門2 12 ポインタがオブジェクトを「指す」 ポインタptの値がオブジェクトnxのアドレスであるとき, pt は nx を指す という。 57 メモリ上の番地 int nx; int *pt; pt = &nx; 1000番地 = nx &nx 代入 nxのアドレス pt ptにはオブジェクトnxのアドレスが格納されているので, ptはnxを指している といえる。 プログラミング入門2 13 間接演算子 * ソースファイル名:pointer2.c ポインタが指すオブジェクトの値を表示 #include <stdio.h> int main(void) { int nx; int *pt; nx = 57; pt = &nx; ptに,int型の変数nxのアドレスを代入 pt は nx を指す printf( "nxのアドレス:%p\n", &nx); printf( "nxの値:%d\n", nx ); printf( "ptの値:%p\n", pt ); printf( "*ptの値:%d\n", *pt ); return(0); } プログラミング入門2 14 ポインタとエイリアス(別名) int nx; int *pt; nx = 57; pt = &nx; pt は nx を指す nx pt *pt はnxのエイリアス(別名) nx pt プログラミング入門2 15 ポインタが指すオブジェクトへの代入 ソースファイル名:pointer3.c ポインタを介して,オブジェクトの値を変更 #include <stdio.h> int main(void) { int nx, ny; int *pt; ny pt = &nx; ptはnxを指す *pt = 100; printf( "nxの値:%d\n", nx ); printf( "*ptの値:%d\n", *pt ); pt = &ny; *pt = 300; printf( "nyの値:%d\n", ny ); printf( "*ptの値:%d\n", *pt ); return(0); } nx pt この状況で*ptに100を代入することは, nxに100を代入することと同じ 「*ptはnxのエイリアスである」ということも ある。 プログラミング入門2 16 練習問題 1. ポインタの理解 次の手順でプログラムを作成せよ。 Int型の変数 x, y を宣言 Intへのポインタ型 ptr を宣言 x, yにそれぞれ,100,500を代入 ptr がxを指すようにする *ptrの値を表示 ptrを介して,xの値を400に変更 x, *ptrの値を表示 ptr がオブジェクトyを指すようにする *ptrの値を表示 ptrを介して,yの値を200に変更 y, *ptrの値を表示 プログラミング入門2 17 ポインタと関数 ソースファイル名:list1004-1.c 関数の引数とポインタ(間違い) #include <stdio.h> void hiroko(int height) { if ( height < 180) height = 180; } Height before : 179 Height after : 179 int main(void) { int masaki = 179; printf("height before:%d\n", masaki); 値が変更されていない! hiroko(masaki); printf("height after:%d\n", masaki); return (0); } プログラミング入門2 18 値の受け渡し 実引数の値が仮引数の値にコピーされるだけ #include <stdio.h> void hiroko(int height) { if ( height < 180) height = 180; } ・heightには179という値がコピーされる ・hiroko関数内でheightの値は変更されるが, main関数内のmasakiの値は変更されない ポインタを利用して変更 int main(void) { int masaki = 179; printf("height before:%d\n", masaki); hiroko(masaki); printf("height after:%d\n", masaki); return (0); } プログラミング入門2 19 ポインタと関数 ソースファイル名:list1004-2.c 関数の引数とポインタ(正解) #include <stdio.h> void hiroko(int *height) { if ( *height < 180) *height = 180; } height before : 179 height after : 180 int main(void) { int masaki = 179; printf("height before:%d\n", masaki); hiroko(&masaki); printf("height after:%d\n", masaki); return (0); } プログラミング入門2 20 関数の引数としてのポインタ ポインタを介して,間接的に変数の値を変更 int masaki; masaki = 179; masakiのアドレス(&masaki) を関数に渡す hiroko(&masaki); int *height; height = &masaki; 106番地 void hiroko(int *height) { if ( *height < 180) *height = 180; } 106番地 masaki height プログラミング入門2 21 関数におけるポインタの使用例(p.234) ソースファイル名:list1005-1.c 関数の引数とポインタ(間違い) #include <stdio.h> void swap(int nx, int ny) { int temp = nx; nx = ny; ny = temp; } int main(void) { int na, nb; temp nxの値を一時的に tempに格納 nx nyにtemp(元のnxの値) を代入 ny nyの値をnxに代入 puts("二つの整数を入力してください。"); printf("整数A:"); scanf("%d", &na); printf("整数B:"); scanf("%d", &nb); swap(na, nb); 変数の値を関数を使って変更したい → ポインタを使いましょう puts("これらの値を交換しました。"); printf("整数Aは%dです。\n", na); printf("整数Bは%dです。\n", nb); return (0); } プログラミング入門2 22 関数におけるポインタの使用例(p.234) ソースファイル名:list1005-2.c 関数の引数とポインタ(正解) #include <stdio.h> void swap(int *nx, int *ny) { int temp = *nx; *nx = *ny; *ny = temp; } int main(void) { int na, nb; puts("二つの整数を入力してください。"); printf("整数A:"); scanf("%d", &na); printf("整数B:"); scanf("%d", &nb); swap(&na, &nb); puts("これらの値を交換しました。"); printf("整数Aは%dです。\n", na); printf("整数Bは%dです。\n", nb); return (0); } プログラミング入門2 23 関数とポインタの重要ポイント 関数に対して,変数の値の変更を頼みたい時 関数にその変数へのポインタ(その変数のアドレス) を渡す ポインタ(オブジェクトのアドレス)を渡して, 関数側はそのポインタが指すオブジェクトに対して処理を行う プログラミング入門2 24 関数におけるポインタの使用例(p.236) ソースファイル名:list1007.c 2つの整数の和と差を求める #include <stdio.h> void sum_diff( int n1, int n2, int *sum, int *diff ) { *sum = n1 + n2; *diff = (n1 > n2) ? n1 - n2 : n2 - n1; } int main(void) { int na, nb; int wa = 0, sa = 0; puts("二つの整数を入力してください。"); printf("整数A:"); scanf("%d", &na); printf("整数B:"); scanf("%d", &nb); sum_diff( na, nb, &wa, &sa ); 何故,na, nbには&が不要? printf("和は%dです。\n差は%dです。\n", wa, sa); return (0); } プログラミング入門2 25 値の並べ替え(ソート,p.237) ソースファイル名: list1008.c 2つの整数値を小さい順に並べる(昇順ソート) #include <stdio.h> /*--- nx・nyが指すオブジェクトの値を交換 ---*/ int main(void) { int void swap(int *nx, int *ny) puts("二つの整数を入力してください。"); printf("整数A:"); scanf("%d", &na); printf("整数B:"); scanf("%d", &nb); { int temp = *nx; *nx = *ny; *ny = temp; sort2( &na, &nb ); } puts("これらの値を昇順に並べました。"); printf("整数Aは%dです。\n", na); printf("整数Bは%dです。\n", nb); /*--- *n1≦*n2となるように並べる ---*/ void sort2(int *n1, int *n2) { if (*n1 > *n2) swap(n1, n2); } na, nb; return (0); } *n1, *n2, n1, n2には何の値が入っている? プログラミング入門2 26 scanf関数とポインタ printf関数とscanf関数 printf(“x = %d\n”, x); scanf(“%d”, &x); printfはただ変数の値を表示するだけ 変数の値を変更する必要なし → そのまま変数を渡す scanfは,キーボードから読み込んだ値を変数に格納する 変数に値を代入(値を変更)する必要 → ポインタ(アドレス)で渡す! プログラミング入門2 27 ポインタと配列 (p.240) ソースファイル: list1010.c 配列とポインタ #include <stdio.h> int main(void) { int i; int vc[5] = {10, 20, 30, 40, 50}; int *ptr = &vc[0]; for (i = 0; i < 5; i++) printf("vc[%d] = %d ptr[%d] = %d *(ptr + %d) = %d\n", i, vc[i], i, ptr[i], i, *(ptr + i) ); return (0); } プログラミング入門2 28 配列とポインタ 配列 vc の先頭要素vc[0]のアドレス &vc[0] を ptrに代入 int *ptr = &vc[0]; ptr + i ptrが指すオブジェクトのi個後ろの要素 を指すポインタ *(ptr + i) ptrが指すオブジェクトのi個後ろの要素 のエイリアス *(ptr + i) = ptr[i] vc[0] *ptr ptr[0] vc[1] vc[2] *(ptr+1) ptr[1] *(ptr+2) ptr[2] vc[3] vc[4] *(ptr+3) ptr[3] *(ptr+4) ptr[4] ptr ptr[i]は、*(ptr + i) と同じ意味 プログラミング入門2 29 配列とアドレス (p.242) ソースファイル: list1011.c 配列のアドレスを表示 #include <stdio.h> int main(void) { int vc[3]; printf("vc :%p\n", vc); printf("vc[0]のアドレス:%p\n", &vc[0]); printf("vc[1]のアドレス:%p\n", &vc[1]); printf("vc[2]のアドレス:%p\n", &vc[2]); return (0); } 単独に現れた配列名vcは,その配列の先頭 要素vc[0]へのポインタ( 型は、(int *)型) ただし、vcの書き換えはできない。 プログラミング入門2 30 関数への配列の受け渡し 配列そのものは渡せないので、配列の先頭のアドレスを渡す。 void int_set( int *vc ) { ….. } 500番地 502番地 504番地 506番地 508番地 ary[0] ary[1] ary[2] ary[3] ary[4] vc int_set( ary ); または void int_set( int vc[ ]) { ….. } int_set( ary ); プログラミング入門2 31 配列の受け渡し (p.244) ソースファイル名:list1013.c 関数への配列の受け渡し #include <stdio.h> void int_set( int int vc[ *vc] ) { int i; for (i = 0; i < 5; i++) vc[i] = 0; } void int_set (int vc[ ]) と書いても同じ意味。vcはポインタであ って、配列ではない。(vcの値(アドレス) は書き変えてもよい。) int main(void) { int i; int ary[ ] = {1, 2, 3, 4, 5}; int_set( ary ); 配列名(ary) =配列の先頭要素のアドレス =&(ary[0]) int型のオブジェクトのアドレス を渡す for (i = 0; i < 5; i++) printf("ary[%d] = %d\n", i, ary[i]); return (0); } プログラミング入門2 32 scanf関数とポインタ scanfは,キーボードから読み込んだ値を変数に格納する 変数に値を代入(値を変更)する必要 → ポインタ(アドレス)で渡す! 文字列の場合 文字配列の先頭アドレスを渡してやればよい char name[256]; scanf( “%s”, name ); name は &(name[0]) と同じ。 プログラミング入門2 33 二次元配列の関数への渡し方 ソースファイル名: 2jigen.c 関数への二次元配列データの受け渡し #include <stdio.h> void print_name( char x[ ][256] ) { int i; for(i=0; i < 4; i++){ printf("name[%d] = %s\n", i, x[i] ); } } 先頭の要素数は省略可能 (注)関数の引数に配列の表記が使われる 場合は、ポインタの意味になる。 void print_name ( char (*x) [256] ) と書いても同じ。 int main(void) { char name[4][256]={"aoki", "morishima", "tokunaga", "yanagisawa" }; print_name( name ); return(0); 配列名で関数へ受け渡し(配列の先頭の アドレスが渡される) } プログラミング入門2 34 今日の課題 ファイル名:kadai10-1.c ポインタと関数,配列 3名分の氏名,身長,体重データが格納されている配列を受け取って,肥満度(BMI値 )を計算し,全員の肥満度を表示する関数を設計せよ 氏名、身長、体重、BMI値を格納する配列はMain関数で宣言、関数にそれらのデータ を渡して関数内でBMI値を計算して配列BMIに結果を格納。 Main関数中でBMI値に基づき、判定結果を表示。 氏名,身長,体重のデータはキーボード読込み,ファイル読込みのどちらでも良い。 void calculate_bmi( char name[ ][256], double *height, double *weight, double *bmi ) 名前の配列 身長の配列 体重の配列 BMIの配列 <BMI(Body mass index)の計算方法> 身長の二乗に対する体重の比で体格を表す指数 BMI=体重(kg)/(身長m)2 BMI 25以上 BMI 18.5以上25未満 BMI 18.5未満 → 肥満 → 標準体重 → 低体重 プログラミング入門2 35 今日の課題 ファイル名:kadai10-2.c 要素数noのint型配列データを受け取り,値の小さい順に要素を並べ替える(ソート)関数を設計し, その動作を確認せよ。なお,並べ替えのアルゴリズムは以下の手順に従うこと。(他のアルゴリズムでも良い) void select_sort( int *vc, int no ) セレクトソート法(最も単純なソート法) ※3, 5, 2, 4, 1 というデータを例に説明 A.まず、データ全体の中から、最小の値を探す B.探し出した最小の値と、データの先頭の値を、入れ替える C.次に、先頭から2番目より最後までの値の中から、最小の値を探す D.探し出した最小の値と、先頭から2番目の値の値を、入れ替える E.同様に、先頭から3番目より最後までの値の中から、最小の値を探す F.探し出した最小の値と、先頭から3番目の値の値を、入れ替える G.最後まで処理を繰り返す この結果、順番が1, 2, 3, 4, 5 と並び替えられる プログラミング入門2 3, 5, 2, 4, 1 1, 5, 2, 4, 3 1, 5, 2, 4, 3 1, 2, 5, 4, 3 1, 2, 5, 4, 3 1, 2, 3, 4, 5 36
© Copyright 2024 ExpyDoc