プログラミング 第 5 回 ポインタ () -- ポインタ演算 配列のアドレス ポインタ演算 ( 前期教科書 P77~) ポインタと配列 ( 同上 P8) 文字列定数と文字列配列 ( 同上 P85) この資料にあるサンプルプログラムは /home/course/rog/ublc_html/7/hw/lec/sources/ 下に置いてありますから 各自自分のディレクトリにコピーして コンパイル 実行してみてください Prog- 7 Lec 5- Programmng- Grou 999-7
配列のアドレス これまで単体の変数でアドレスを考えてきたが 配列の場合はどうであろうか? まず 配列の要素アドレスはそれぞれの要素の前に & を付加することで知ることが出来る 下の例の中では 配列 str の 番目の要素のアドレスは &str[] である ついでに配列名そのものの値も出力してみて 右の結果からわかるように 配列名は最初の要素のアドレスと一緒であった 実は これは偶然ではない ( これは後ほど述べる ) #nclude #nclude man() man() nt nt str[] str[] "u-azu" "u-azu" for( for( 6 ++) ++) rntf("%d rntf("%d % % %c\n",, %c\n",, &str[],str[]) rntf("\n%\n",str) 実行結果実行結果 : : sstdss: sstdss: effff9d effff9d u u effff9d effff9d - - effff9d effff9d a a effff9d effff9d effff9d effff9d z z 5 5 effff9d5 effff9d5 u u effff9d effff9d sstdss: sstdss: アドレスは環境によって違うが 要素のアドレスの差に注目 Prog- 7 Lec 5- Programmng- Grou 999-7
配列のアドレス 前の例において 文字型の配列 ( 本当は 文字列 ) であったが 他の型の配列も同じように考えられる long 型の場合は以下のようになる #nclude #nclude man() man() nt nt long long array[] array[],,,,,, for( for( ++) ++) rntf( rntf( "%d "%d % % %d\n",,&array[],array[]) %d\n",,&array[],array[]) rntf("\n%\n",array) rntf("\n%\n",array) 実行結果実行結果 sstdss: sstdss: effff9c8 effff9c8 effff9cc effff9cc effff9d effff9d effff9d effff9d effff9c8 effff9c8 sstdss: sstdss: アドレスは環境によって違うが 要素のアドレスの差に注目 Prog- 7 Lec 5- Programmng- Grou 999-7
アドレスの飛び方 文字型とlong 型で分かる通り 配列の各要素は型の大きさ分だけアドレスが離れている 各型の大きさを再掲する この値は会津大学の標準的な環境での値である 型 大きさ ( バイト数 ) イメージ short nt,float, ポインタ double 8 Prog- 7 Lec 5- Programmng- Grou 999-7
アドレスの飛び方 イメージ的には以下のようになる 型 (バイト) long 型 (バイト) 要素 要素 要素 配列の各要素は型の大きさ分だけアドレスが離れている ( 添字 の要素のアドレスが一番小さい ) Prog- 7 Lec 5-5 Programmng- Grou 999-7
ポインタ演算 ポインタ演算 ( 加算 減算 ) を以下のように取り決める ポインタに を加える ( 減じる ) とは ポインタが保持するアドレスに型の大きさ (long なら ) を加える ( 減じる ) ことである 先ほどの long の例において long 型のポインタの値が配列の先頭要素 ( 要素 ) のアドレス つまり &array[] だとすると 以下のようになる ポインタアドレス要素 &array[] effff9c8 array[] + &array[] effff9cc array[] + &array[] effff9d array[] Prog- 7 Lec 5-6 Programmng- Grou 999-7
ポインタ演算例 ポインタ演算を利用すると以下のようなプログラムを書くことが出来る #nclude #nclude man() man() nt nt,, a[] a[],,,,,, nt nt * *,*q,*q 実行結果実行結果 sstdss: sstdss: sstdss: sstdss: for( for( ++) ++) rntf("%d rntf("%d ",a[]) ",a[]) rntf("\n") rntf("\n") &a[] &a[] /* /* a a の最初の要素のアドレスを最初の要素のアドレスを に代入するに代入する */ */ for( for( ++) ++) rntf("%d rntf("%d ",*( ",*( + + )) )) rntf("\n") rntf("\n") for(q for(q q q + + q++) q++) rntf("%d rntf("%d ",*q) ",*q) rntf("\n") rntf("\n") 配列 a Prog- 7 Lec 5-7 Programmng- Grou 999-7
ポインタ演算例 文字列操作の例 #nclude #nclude 実行結果実行結果 man() man() a z u \o sstdss: sstdss: azu azu nt nt azu azu str[] str[] "azu" "azu" azu azu * *,*q,*q は &str[] 正順出力 uza ( 配列の先頭 ) uza uza を指す uza &str[] &str[] sstdss: sstdss: for( for( str[] str[]!! '\' '\' ++) ++) rntf("%c",str[]) rntf("%c",str[]) rntf("\n") rntf("\n") for( for( *( *( + + ) )!! '\' '\' ++) ++) rntf("%c",*( rntf("%c",*( + + )) )) rntf("\n") rntf("\n") for(q for(q *q *q!! '\' '\' q++) q++) rntf("%c",*q) rntf("%c",*q) rntf("\n") rntf("\n") a z u \o にヌル文字の逆順出力ループ終了時には q q アドレスを代入 qはヌル文字のアド q for( for( - - > > &str[] &str[] ++) ++) rntf("%c",*( rntf("%c",*( - - )) )) レスが入っている rntf("\n") rntf("\n") for(q for(q - - q q > > &str[] &str[] q--) q--) rntf("%c",*q) rntf("%c",*q) rntf("\n") rntf("\n") Prog- 7 Lec 5-8 Programmng- Grou 999-7
配列名とは 文字列の場合 rntf など関数への引数として配列名を渡す 配列名とはいったい何なのだろうか? これまでのプログラムの実行結果で分かる通り 配列名が保持する値は実は最初の要素のアドレスである つまり配列名とは配列の最初の要素を指すポインタのようなものである (str &str[]) 逆に次ページの例のように ポインタを配列のように使用することも可能である Prog- 7 Lec 5-9 Programmng- Grou 999-7
配列名とポインタ #nclude #nclude man() man() nt nt, a[] a[],,,,,, nt nt * * a a /* /* つまりこれはつまりこれは &a[] &a[] と同じ事と同じ事 */ */ 実行結果実行結果 sstdss: sstdss: sstdss: sstdss: for( for( ++) ++) rntf("%d rntf("%d ",a[]) ",a[]) rntf("\n") rntf("\n") for( for( ++) ++) rntf("%d rntf("%d ",[]) ",[]) rntf("\n") rntf("\n") for( for( ++) ++) rntf("%d rntf("%d ",*(a ",*(a + )) )) rntf("\n") rntf("\n") for( for( ++) ++) rntf("%d rntf("%d ",*( ",*( + )) )) rntf("\n") rntf("\n") 配列風 ポインタ風 Prog- 7 Lec 5- Programmng- Grou 999-7
配列とポインタの相違点 配列とポインタは極めて類似していることが分かった しかし相違点もある 配列は実際に領域を確保する ( 正確には要素数 型の大きさバイトの領域 ) のに対して ポインタはポインタ変数の分 ( 会津大学の通常の環境ではバイト ) だけしか確保しない ポインタは適切に初期化しない限り配列の代用にはならない 逆にポインタは適切に初期化すれば配列の代用になると言う事も出来る ポインタは同型の変数や 配列をどれを指してもかまわないが 配列名は指定されているメモリ領域しか指すことができず その値を変更することはできない つまり 配列名は ポインタ定数である Prog- 7 Lec 5- Programmng- Grou 999-7
配列とポインタの相違点 配列名はアドレス定数であるので その値 ( アドレス値 ) を変更 ( 代入 ) 出来ない #nclude #nclude man() man() nt nt a[5],,,,5 a[5],,,,5 nt nt 配列配列 コンパイルエラー コンパイル結果コンパイル結果 : : In In functon functon `man': `man': : : wrong wrong tye tye argument argument to to ncrement ncrement for( for( 5 5 ++) ++) rntf("%d\n", rntf("%d\n", *a++) *a++) #nclude #nclude man() man() ポインタポインタ nt nt a[5],,,,5, a[5],,,,5, * * nt nt a a for( for( 5 5 ++) ++) rntf("%d\n", rntf("%d\n", *++) *++) アドレスが指し示すデータの内容を rntf に渡した後で アドレスをインクリメント (nt なので +) することを意味する コンパイル 実行可能! [] のように書いても良い Prog- 7 Lec 5- Programmng- Grou 999-7
配列とポインタの相違点 ポインタに対して定数初期化は出来ない 配列は要素の個数分メモリ領域に実際に変数領域が確保される 一方ポインタを配列の代わりに使用しても 実際には領域の確保は行われない 従って下例のようにポインタに対して初期化することは出来ない #nclude #nclude man() man() ポインタ ポインタ nt nt,*,,,,5,*,,,,5 for( for( 5 5 ++) ++) rntf("%d\n", rntf("%d\n", []) []) 警告 コンパイル結果コンパイル結果 : : warnng: warnng: ntalzaton ntalzaton makes makes onter onter from from nteger nteger wthout wthout a a cast cast : : warnng: warnng: excess excess elements elements n n scalar scalar ntalzer ntalzer after after `' `' #nclude #nclude man() man() 配列 配列 nt nt,a[],,,,5,a[],,,,5 for( for( 5 5 ++) ++) rntf("%d\n", rntf("%d\n", a[]) a[]) コンパイル 実行可能! 実行すると エラー Prog- 7 Lec 5- Programmng- Grou 999-7
配列とポインタの相違点 文字列 ( 文字ポインタ ) の場合のみポインタに定数初期化が出来る 文字列ポインタの初期化は 定数領域に文字列データが格納され そのアドレスをポインタが指し示すことで行なう #nclude #nclude man() man() a[]"abc" a[]"abc" rntf("%s\n",a) rntf("%s\n",a) 配列配列 コンパイル 実行可能! #nclude #nclude man() man() *"ABC" *"ABC" rntf("%s\n",) rntf("%s\n",) ポインタポインタ コンパイル 実行可能! Prog- 7 Lec 5- Programmng- Grou 999-7
文字列定数の変更 文字列定数はポインタの初期化で宣言する 文字列定数は通常メモリの書き込み禁止領域に領域が取られる このため通常の文字列と異なり 文字列定数の変更は出来ない #nclude #nclude man() man() *"ABC" *"ABC" [] [] 'Z' 'Z' rntf("%s\n",) rntf("%s\n",) 文字 'Z' 実行結果 ( 実行結果はマシンによって異なる ) Segmentaton fault A B C \ 書き込み禁止領域 Prog- 7 Lec 5-5 Programmng- Grou 999-7
配列とポインタの相違点 ( まとめ ) 配列とポインタの違いを表にまとめる (a を配列名 をポインタだとする ) 操作 ポインタ 配列 代入 a a インクリメント ++ a++ 加減算 + a + Prog- 7 Lec 5-6 Programmng- Grou 999-7
文字列定数配列とポインタ配列 文字列定数の配列はポインタの配列として定義出来る #nclude #nclude man() man() *str[] *str[] "Tokyo","Nagoya","Osaka","Azu" "Tokyo","Nagoya","Osaka","Azu" nt nt for( for( ++) ++) rntf("%s\n",str[]) rntf("%s\n",str[]) 実行結果実行結果 sstdss: sstdss: Tokyo Tokyo Nagoya Nagoya Osaka Osaka Azu Azu sstdss: sstdss: Prog- 7 Lec 5-7 Programmng- Grou 999-7
文字列配列とポインタ配列 この時のポインタと文字列定数の関係は以下の通り 文字列としては str[] のように表す ( 番目の文字列を示す ) 文字としては str[][] のように表す ( 番目の文字列中 番目の文字を示す ) ポインタ配列 str str[] str[] str[] str[] T o k y o \ N a g o y a \ O s a k a \ A z u \ 書き込み禁止領域 書 Prog- 7 Lec 5-8 Programmng- Grou 999-7
文字列配列を配列で組むと この場合は文字列 変数 となるので 自由に代入を行うことが出来る 文字列としては str[] のように表す ( 番目の文字列を示す ) str[] は &str[][] と等しい 文字としては str[][] のように表す ( 番目の文字列中 番目の文字を示す ) #nclude #nclude man() man() str[][8] str[][8] "Tokyo","Nagoya","Osaka","Azu" "Tokyo","Nagoya","Osaka","Azu" nt nt strcy(str[],"saoro") strcy(str[],"saoro") /* /* 文字列の代入可能文字列の代入可能! */ */ for( for( ++) ++) rntf("%s\n",str[]) rntf("%s\n",str[]) 文字列として表示 for( for( str[][] str[][]!! '\' '\' ++) ++) rntf("%c\n",str[][]) rntf("%c\n",str[][]) 文字として縦表示 実行結果実行結果 sstdss: sstdss: Tokyo Tokyo Nagoya Nagoya Saoro Saoro A A z z u u sstdss: sstdss: Prog- 7 Lec 5-9 Programmng- Grou 999-7
二次元文字配列 初期化時の二次元文字配列の状態は以下の通り &str[][]:str[] &str[][]:str[] &str[][]:str[] &str[][]:str[] T o k y o \ N a g o y a \ O s a k a \ A z u \ 変数領域 の領域は何が入っているか不定 Prog- 7 Lec 5- Programmng- Grou 999-7