リスト構造とかのプログラミングでは、ポインタが使われるが、番地とポインタをうまく理解していないと、どのような処理をしているのか理解しづらいはず。
今回の補講では、ポインタを理解してもらう。
以下では、ポインタを使った処理(前半)を見て、ポインタの動きを考える。理解できていなければ、同じ処理をポインタ無し、番地を意識させる memory[] 配列による記述(後半)で、動きを追って2つのプログラムが同じ挙動を表している…という説明の繰り返しで、ポインタの理解を図る。
単純な変数の加算
プログラムで、「 c = a + b ; 」と書いてあったら、メモリの「変数aの番地の中身」と「変数bの番地の中身」を加えて、結果を「変数cの番地」に保存する。
// 変数 a と 変数b の加算
int a = 11 ;
int b = 22 ;
int c ;
c = a + b ;
// 同じ処理をメモリの番地のイメージを示す。
int memory[ 1000 ] = { 0 , 0 , 0 , 11 , 22 , 0 , 0 } ;
#define ADDR_A 3
#define ADDR_B 4
#define ADDR_C 5
memory[ ADDR_C ] = memory[ ADDR_A ] + memory[ ADDR_B ] ;
ポインタのイメージ
// ポインタの処理
int a = 11 ;
int b = 22 ;
int*p ;
p = &a ;
(*p)++ ;
p = &b ;
(*p)++ ;
// 同じ処理をメモリ番地のイメージで
int memory[ 1000 ] = { 0 , 0 , 0 , 11 , 22 , 0 , 0 } ;
#define ADDR_A 3
#define ADDR_B 4
int p ; // int *p ;
p = ADDR_A ; // p = &a ;
memory[ p ]++ ; // (*p)++ ;
p = ADDR_B ; // p = &b ;
memory[ p ]++ ; // (*p)++ ;
ポインタ渡し
// ポインタ引数による値の交換
void swap( int*x , int*y ) {
int tmp = *x ;
*x = *y ;
*y = tmp ;
}
void main() {
int a = 11 ;
int b = 22 ;
swap( &a , &b ) ;
}
// 同じ処理をメモリ番地のイメージで。
int memory[ 1000 ] = { 0 , 0 , 0 , 11 , 22 , 0 , 0 } ;
#define ADDR_A 3
#define ADDR_B 4
void swap( int x , int y ) { // void swap( int*x , int*y ) {
int tmp = memory[ x ] ; // int tmp = (*x) ;
memory[ x ] = memory[ y ] ; // (*x) = (*y) ;
memory[ y ] = tmp ; // (*y) = tmp ;
} // }
void main() {
swap( ADDR_A , ADDR_B ) ; // swap( &a , &b ) ;
}
上記のポインタの説明では、番地をintで表現しているから、型の概念が曖昧になりそう。
本当は、以下のように pointer 型を使って説明したいけど、補講の学生に typedef は、混乱の元だろうな。ひとまず、ここまでのポインタのイメージを再学習するネタを見てもらってからなら、typedef int pointer してもいいかな?typedef int pointer ; void swap( pointer x , pointer y ) { int tmp = memory[ x ] ; memory[ x ] = memory[ y ] ; memory[ y ] = tmp ; }プログラミングでは、型の理解が重要。たとえ、Python,Ruby といった型宣言の無い言語でも、どんなデータなのかを意識して書く必要がある。
理解の確認
// 以下のプログラムの実行結果は?
void foo( int x ) {
x++ ;
}
void bar( int*p ) {
(*p)++ ;
}
void main() {
int a = 111 ;
foo( a ) ; // a の中身は?
bar( &a ) ; // a の中身は?
}
// 同じ処理を
typedef int pointer ;
int memory[ 1000 ] = { 0 , 0 , 0 , 111 , 0 , 0 } ;
#define ADDR_A 3
void foo( int x ) {
_______________________ ;
}
void bar( pointer p ) {
_______________________ ;
}
void main() {
foo( ________________ ) ; // memory[ ADDR_A ] の中身は?
bar( ________________ ) ; // memory[ ADDR_A ] の中身は?
}
ポインタと配列
// ポインタの移動
int sum = 0 ;
int array[ 3 ] = { 11 , 22 , 33 } ;
int*p ;
p = array ;
sum += *p ;
p++ ;
sum += *p ;
p++ ;
sum += *p ;
// 同じ処理をメモリ番地のイメージで
typedef int pointer ;
int memory[ 1000 ] = { 0 , 0 , 0 , 11 , 22 , 33 , 0 , 0 } ;
#define ADDR_SUM 2
#define ARRAY 3
pointer p ; // int*p ;
p = ARRAY ; // p = array ;
memory[ ADDR_SUM ] += memory[ p ] ; // sum += (*p) ;
p++ ; // p++ ;
memory[ ADDR_SUM ] += memory[ p ] ; // sum += (*p) ;
p++ ; // p++ ;
memory[ ADDR_SUM ] += memory[ p ] ; // sum += (*p) ;
理解の確認
整数配列にデータが並んでいる。数字は0以上の数字で、データ列の後ろには必ず0が入っているものとする。配列の先頭から0を見つけるまでの値を合計する関数を作れ。
int sum( int*p ) {
s = 0 ;
for( __________ ; __________ ; __________ ) {
____________________ ;
}
return s ;
}
int array_a[ 4 ] = { 11 , 22 , 33 , 0 } ;
int array_b[ 5 ] = { 4 , 3 , 2 , 1 , 0 } ;
void main() {
printf( "%d\n" , sum( array_a ) ) ; // 66 を表示
printf( "%d\n" , sum( brray_b ) ) ; // 10 を表示
printf( "%d\n" , sum( array_a + 1 ) ) ; // 何が表示される?
}
リスト構造
では、最後のシメということで、リスト構造でのポインタのイメージの確認。
// リストを次々たどる処理
struct List {
int data ;
struct List* next ;
} ;
struct List* cons( int x , struct List* p ) {
struct List* ans =
(struct List*)malloc( sizeof( struct List ) ) ;
if ( ans != NULL ) {
ans->data = x ;
ans->next = p ;
}
return ans ;
}
void main() {
struct List* top =
cons( 11 , cons( 22 , cons( 33 , NULL ) ) ) ;
struct List* p ;
for( p = top ; p != NULL ; p = p->next ) {
printf( "%d¥n" , p->data ) ;
}
}
// メモリのイメージで
typedef int pointer ;
int memory[ 1000 ] = {
0 , 0 ,
22 , 6 ,
11 , 2 ,
33 , 0 ,
0 , 0 ,
} ;
#define OFFSET_DATA 0
#define OFFSET_NEXT 1
void main() {
pointer p ;
for( p = 4 ; p != 0 ; p = memory[ p + OFFSET_NEXT ] ) {
printf( "%d¥n" , memory[ p + OFFSET_DATA ] ) ;
}
}