================================================================================ Load of Developers C Launguage ---- Vol. 3 I-ichirow Suzuki ================================================================================ Index 10 構造体と共用体 構造体の初期化 構造体変数の配列の初期化 構造体のポインタ変数 構造体のアドレス渡し ポインタによる構造体変数の配列の参照 共用体 構造体・共用体の入れ子 11 ポインタと構造体(スーパー特別編) アドレス ポインタ 文字のアドレスをポインタで取得する ポインタと配列 2つの文字列を連結するプログラム 2つの文字列を連結するプログラム 配列編 ポインタと多次元配列 ポインタ配列 関数の引数 /////////////////////////////////////////////////////////////////////////////// //////////////////////////// 10 構造体と共用体 ************ 構造体とは複数の変数をひとまとまりにしたもの 構造体は一つの対象物の様々なデータ(この例ですと一人の子どもの名前、年齢、身長 )をひとまとめにして置くことが出来、データをどの変数に代入したかが覚えやすくな ます。今はその利点が実感できないかもしれませんが、実際のプログラムで複雑なデ ータを扱うようになるとその便利さが実感できると思います。 ######################################################## #include I-ichirow Suzuki _/_/_/_/_/_/_/_/_/_/_ URL : www.kg-group.com Top Page Mail : suzuki@kg-group.com /_/_/_/_/_/_/_/_/_/_/_/_ ICQ : 3743158main() { struct child { char name[10] ;int age, height ; } ; struct child x, y ; sprintf(x.name, "Taro") ; x.age = 6 ; x.height = 120 ; sprintf(y.name, "Jiro") ; y.age = 4 ; y.height = 100 ; printf("%s, %d, %d\n", x.name, x.age, x.height) ; printf("%s, %d, %d\n", y.name, y.age, y.height) ; } // End main() ; --------------------------------------------------------- taro age 6 height 120 jiro age 4 height 100 ######################################################### #! 普通の変数では、最初から変数宣言を行います。 しかし構造体では、変数宣言に先立って型枠(テンプレート)の宣言を行います。 それがこの行でstructで構造体であることを宣言します。 struct child { char name[10] ;int age, height ; } ; #! 次に型枠名(タグ)を書きます。型枠名は任意の名前を付けられます。 ここでは型枠名をchildにしています。 #! 次に型枠がchildの構造体の変数を宣言します。 struct child x, y ; ここではxとyの2つの変数として宣言しています。 実際に変数として値を代入したりするのは、このxやyです。 #! xの構成要素name[10]の先頭アドレスをx.nameと表します。 #! ここではx.nameにtaroという文字列を代入しています。 sprintf(x.name, "Taro") ; x.age = 6 ; x.height = 120 ; #! xの構成要素ageはx.age、構成要素heightはx.heightと表します。 x.ageには6をx.heightには120を代入しています。 #! x.name, x.age, x.heightなどは通常の変数と全く同じように扱えます。 ここではこれらをprint文で画面に表示します。 printf("%s, %d, %d\n", x.name, x.age, x.height) ; printf("%s, %d, %d\n", y.name, y.age, y.height) ; #! プログラムでは構造体変数yもxと全く同様に取り扱っています。 ******************************************************************************* 構造体の初期化 ************ 構造体では初期化と言って、構造体変数の宣言と同時に変数に値を代入する事も出来ま す。 この例ではa, b, cの各変数が学校のクラスと担任の先生の名前を示すようにしてあり ます。 1組はyamada先生2組はsato先生3組はkanda先生 ################################################### #include main(){ struct school {int kumi ; char teach[10]; } ; static struct school a = {1, "yamada" } ; static struct school b = {2, "sato" } ; static struct school c = {3, "kanda" } ; printf("%d, %s\n", a.kumi, a. teach) ; printf("%d, %s\n", b.kumi, b.teach) ; printf("%d, %s\n", c.kumi, c.teach) ; } // End main() ; --------------------------------------------------- 1,yamada 2,sato 3,kanda ################################################### 構造体では初期化と言って、構造体変数の宣言と同時に変数に値を代入する事も出来ま す。 #! まず構造体の型枠を宣言します。 struct school {int kumi ; char teach[10]; } ; 構成要素はクラス名代入用のkumiと先生の名前代入用のteach[10]です。 #! 次に構造体変数の宣言と同時に、変数に値を代入します。 static struct school a = {1, "yamada" } ; #! ここではa.kumiに1を、a.teachにyamadaを代入します。 このとき、変数宣言の先頭にstaticを付けなければなりません。 #! 構造体変数b, cも同様に変数宣言と値の代入を同時に行っています。 static struct school b = {2, "sato" } ; static struct school c = {3, "kanda" } ; #! a.kumi, a.teachに代入した値を画面に表示させています。 printf("%d, %s\n", a.kumi, a. teach) ; #! 同様にこの部分でb b.kumi, b.teachに代入した値、c.kumi, c.teachに代入した値 を画面に表示させています。 printf("%d, %s\n", b.kumi, b.teach) ; printf("%d, %s\n", c.kumi, c.teach) ; ******************************************************************************* 構造体変数の配列の初期化 構造体変数の配列を使ってまとめて初期化する事も出来ます。 ################################################ #include main() { struct school{int kumi ;char teach[10] ;} ; static struct school z[3] = { 1, "Yamada", 2, "Sato", 3, "Kanda"} ; int i ; for(i = 0 ;i <3 ; i++) { printf("%d, %s\n", z[i].kumi, z[i].teach) ; } } // End main() ; ---------------------------------------------------- 1, Yamada 2, Sato 3, Kanda #################################################### #! 構造体変数として配列z[3]を宣言します。 static struct school z[3] = { 1, "Yamada", 2, "Sato", 3, "Kanda"} ; #! 変数の宣言と同じ行で、z[-], z[1], z[2]の構成要素に順番に値を代入していきま す。 { 1, "Yamada", 2, "Sato", 3, "Kanda"} ; #! z[0], z[1], z[2]の各構成要素の表示をforループを利用して行っています。 printf("%d, %s\n", z[i].kumi, z[i].teach) ; ============================================================================== ******************************************************************************* 構造体のポインタ変数 #################################################### #include main() { struct child {char name[10] ; int age, height ; } ; struct child x ; struct child *y ; sprintf(x.name, "Taro") ; x.age = 6 ; x.height = 120 ; y = &x ; printf("%s, %d, %d\n", y->name, y->age, y->height) ; } // End main() ; ---------------------------------------------------- Taro,6,120 ###################################################### #! 最初に構造体の型枠の宣言をします struct child {char name[10] ; int age, height ; } ; #! 次に構造体の変数としてxを宣言しています。 これにより、構造体childを記憶する為の領域が確保されます。 struct child x ; #! これはxの構成要素のx.name[10], x.age, x.heightの記憶領域が確保されることを 意味します。 そして&xは構造体xのアドレスを示します。 #! ここでx.name, x.age, x.heightに値を代入しています。 sprintf(x.name, "Taro") ; x.age = 6 ; x.height = 120 ; #! 次に構造体のポインタ変数としてyを宣言します。 これにより構造体childのアドレスを記憶する為の領域が確保されます。 struct child *y ; #! yに構造体xのアドレス&xを代入しています。 これによりyを介してxの各構成要素の値を参照する事が出来ます。 y = &x ; #! この行ではyを介して構造体xの各校性要素をprint分で表示しています。 printf("%s, %d, %d\n", y->name, y->age, y->height) ; #! その場合の表示法は、ポインタ変数->要素名です。 例えばx.nameを指し示すにはy->nameと書きます。 ============================================================================] ******************************************************************************* 構造体のアドレス渡し ############################################### #include struct child{char name[10] ; int age, height ; } ; void past(struct child *y) ; main() { struct child x ; sprintf(x.name, "Taro") ; x.age = 6 ; x.height = 120 ; past(&x) ; } // End main() ; void past(struct child *y) { printf("%s, %d, %d\n", y->name, y->age, y -> height) ; } // End past() ; ------------------------------------------------ taro,6,120 ################################################ #! まず構造体の型枠をグローバル(関数の外側)で宣言します。 struct child{char name[10] ; int age, height ; } ; #! 次にmainで構造体変数xの宣言と値の代入を行います。 struct child x ; sprintf(x.name, "Taro") ; x.age = 6 ; x.height = 120 ; #! 次にxのアドレスを引数にして関数pastを呼び出します。 past(&x) ; #! 関数pastでは、構造体childのポインタ変数yで引数を受け取ります。 void past(struct child *y) #! これによりyに構造体変数xのアドレス&xが代入されます。 #! この結果、yを介してx.name, x,age, x.heightを参照出来る様になります。 printf("%s, %d, %d\n", y->name, y->age, y -> height) ; ******************************************************************************** ポインタによる構造体変数の配列の参照 ************ 構造体のポインタ変数で、構造体変数の配列を参照する例題です。 プログラムでは、z[3]の各校性要素に入力した値がyに対するprint文で表示されます。 ################################################# #include main() { struct school{int kumi ;char teach[10] ;} ; static struct school z[3] = { 1, "Yamada", 2, "Sato", 3, "Kanda"} ; struct school *y ; int i ; y = z ; for(i = 0 ; i < 3 ;i++) { printf("%d, %s\n", (y + i) -> kumi, (y + i) -> teach) ; } // End for(i = 0 ; i < 3 ; i ++) } // End for -------------------------------------------------- 1, Yamada 2, Sato 3, Kanda #################################################### #! まず最初に構造体の型枠をschoolという名前で宣言をします。 struct school{int kumi ;char teach[10] ;} ; #! 構造体schoolの変数を配列z[3]で宣言すると共に、初期化により値を代入していま す。 struct school{int kumi ;char teach[10] ;} ; static struct school z[3] = { 1, "Yamada", 2, "Sato", 3, "Kanda"} ; #! 構造体schoolのポインタ変数としてyを宣言しています。 struct school *y ; #! 構造体のポインタ変数yに構造体変数の配列の先頭アドレスz = &z[0]を代入してい ます。 y = z ; #! するとy + 1はz[1]のアドレスに、y + 2はz[2]のアドレスに等しくなります。 #1 ところで、z[0]はkumiというint型の変数とteach[10]という文字型の配列により構 成されています。 #1 yを用いてz[0]の構成要素であるkumiとteach[10]の中身を参照出来ます。 y->kumi = z[0].kumi(中身そのもの) y->teach=z[0].teach(アドレスz[0].teachを介しての参照) #! 同様に(y + i)を用いてz[i]の構成要素を参照出来ます。 (y + i) ->kumi = z[i].kumi (y + i) ->teach = z[i].tetach ============================================================================= ******************************************************************************** 共用体 ************ 共用体は構造体とよく似ていますが、一つ異なる所があります。 それは、構造体が、各構成要素に別々の記憶領域を与えるのに対し、共用体は各構成要 素に同じ記憶領域を割り当てるのです。 構造体は各校性要素に別々の記憶領域を与えるのに対し 共用体は各校性要素に同じ記憶領域を割り当てるもの。 ################################################### #include main() { union abc { int a, b ; } ; union abc x ; x.a = 1 ; x.b = 2 ; printf("%d, %d\n", x.a, x.b) ; } // End main() ; ---------------------------------------------------- 2,2 ################################################### #! 共用体も構造体と同様、まず型枠の宣言をします。 違うのは、structの代わりにunionを使う事です。 型枠名はabc、構成要素はint型のaとbです。 union 共用体の型枠宣言 #1 次に共用体の変数を宣言します。 これによって共用体の記憶領域が確保されます。 union abc x ; #! しかし、構造体と違って、この記憶領域は構成要素のx.aとx.bが共用するのです。 つまり、x.aのアドレスとx.bのアドレスは等しくなります。 #! この行でx.aに1を代入しています。実はこの段階でx.bも1になるのです。 x.a = 1 ; #! 次にx.bに2を代入しています。 これによってx.bばかりでなくx.aも2に書き換えられてしまうのです。 x.b = 2 ; #! 従ってprint文による表示ではx.aもx.bも2が表示されるのです。 printf("%d, %d\n", x.a, x.b) ; ============================================================================== ******************************************************************************* 構造体・共用体の入れ子 ##################################################### #include main() { union size { char type ;intwaist ; } ; struct zubon { char color[5] ; unin size q ;} x, y ; sprintf(s. color, "red") ; x.q.type ='L' ; sprintf(y.color, "blue" ) ; y.q.waist = 76 ; printf("%s, %c\n", x.color, x.q.type) ; printf("%s, %d\n", y.color, y.q.waist) ; } // End main() ; ----------------------------------------------------- red,L blue,76 ######################################################## 構造体、共用体の入れ子です。 構造胎動し、共用体同士、あるいは構造体と共用体を入れ子にして複雑な構造のデータ を分かり易くまとめることが出来ます。 #! まず共用体sizeの型枠を宣言しています。 union size { char type ;intwaist ; } ; #! sizeではL, M, Sの文字が入力されるchar型のtypeとウエストの長さが数字で入力さ れるint型のwaistがあり、この内のどちらかが入力されます。 struct zubon { char color[5] ; unin size q ;} x, y ; #! 次に構造体zubonの型枠宣言とzubonの変数x, yの宣言を同時に行っています。この 様に型枠の宣言と変数の宣言を同時に行うこともできます。 struct zubon { char color[5] ; unin size q ;} x, y ; #! 構造体zubonの構成要素の一つはこのcolor[5]です。これにはズボンの色を代入しま す。 char color[5] #! この構造体zubonの構成要素中に、共用体sizeの変数qを宣言しています。 こうすることで、qはzubonの構成要素の一つになります。 unin size q ; #! qはズボンのサイズを代入するのですが、typeとしてL, M, Sを代入する場合と、wais tとしてウエストの長さを代入する場合があります。 この様に場合によって文字を入力したあり数字を入力したり出来る事が共用体の特徴で す。 #! この行でx.colorにredを代入しています。 sprintf(s. color, "red") ; #! この行でx.q.typeにLを代入してます。 x.q.type ='L' ; #! このx.q.typeとは、構造体xの構成要素である共用体qの構成要素typeを意味します。 x.q.type ='L' ; #! ここでy.colorにblueを代入しています。 sprintf(y.color, "blue" ) ; #! ここでy.q.waistに76を代入しています。 y.q.waist = 76 ; #! 最後にprint文でx.color, x.q.typeとy.color, y.q.waistに代入された値を表示し ています。 printf("%s, %c\n", x.color, x.q.type) ; printf("%s, %d\n", y.color, y.q.waist) ; ============================================================================== /////////////////////////////////////////////////////////////////////////////////// =================*=====================*======================*==================== 11 ポインタと構造体(スーパー特別編) /////////////////////////////////////////////////////////////////////////////// アドレス プログラム上で変数を定義すると、コンピュータのメモリ上のどこかにその変数の領域が とられます。ではどこかとはいったい何処なのでしょうか? 殆どの変数はその変数名の前に&演算子を付けることによって、メモリ上の場所(アドレス) を導き出すことが出来ます。 #include int mian(void) { int x ; printf("変数xのアドレスは%pです。\n", &x) ; return(0) ; } // End main() ; *********************** 出力結果 変数xのアドレスは 0063FDF8です ----------------------------------------------------------------------------- 次のプログラムは作者の本来の目的はinit関数の呼び出しによってmain関数内の変数(sum) の値を初期化0にしたかったという物です。 しかし出力結果を見て解るようにsumの値が初期化されていません。 呼び出し元の変数には何の変更も行われていないようですなぜでしょうか? init関数の呼び出し時に行われていることはmain関数内で保存されていたsum変数の値を init関数のsum変数が代入という形で受け取っているだけなのです。 従って、代入された値にいくら変更を繰り返そうともとの値に何ら影響を及ぼすと言う事は ありません。 #include void init(int sum) { sum = 0 ; } // End init int main(void) { int x ; int sum ; x = 3 ; sum = 5 ; sum = sum + x ; printf("変数sumの値は%dです。\n",sum) ; init(sum) ; printf("変数sumの値は%dです。\n",sum) ; return (0) ; } // End main (); *************************** 実行結果 変数sumの値は8です。 変数sumの値は8です。 このsumという変数にもう少し着目することにします。 よく考えるとmain関数内にあるこのsumという変数ですが名前は同じ物のinit関数内に あるsumとは何ら関係がありません。 関数の呼び出し時に先ほども言った値の代入が行われているだけです。 これが値渡しの原則です。 init関数内にあるsum変数はsumという名前である必要もありません。 勿論割り当てられているメモリ領域も別々です。 であれば同じsum変数を変更する、言い換えると同じメモリ領域上の物を変更すれば うまくいきそうです。 これを実現するためには呼び出し元の関数で自分が使っているsum変数のアドレスを 渡してやればよいでしょう。 関数側ではそのアドレスを使って書き込みを行えばよいのです。 #include void init(int &sum) { sum = 0 ; } // End init int main(void) { int x ; int sum ; x = 3 ; sum = 5 ; sum = sum + x ; printf("変数sumの値は%dです。\n",sum) ; init(sum) ; printf("変数sumの値は%dです。\n",sum) ; return (0) ; } // End main (); *************************** 実行結果 変数sumの値は8です。 変数sumの値は8です。 //////////////////////////////////////////////////////////////////////////// ポインタ 変数のアドレスの求め方は最初に扱いました。 アドレスを使って値を書き込むためにはポインタと呼ばれる変数を使います。 ポインタはアドレスを格納するための変数です。 通常の変数との決定的な違いはポインタにアドレスを格納すればそのアドレス上に存在する 変数自体にアクセス出来る様になることです。 ポインタを宣言する方法は、格納する、または、したいアドレス上の存在する変数のデータ 型に*を付けて宣言します。 例えばこの例題の場合、int型の変数sumが目的の変数なのでint型のアドレスを 格納する変数psumとして int *psum ; と宣言します。 このポインタpsumはint型を指し示す(ポイントする)変数である事からint型への ポインタ型(int*型)のデータ型を持つと考えられます。 後はこの変数にアドレスを格納します。 アドレスは&演算子で求められました。 psum = &sum ; で、出来上がりです。 ポインタはその変数名に*(アスタリスク)を付ける事によって格納しているアドレスの 指す変数の値を表す事が出来ます。 つまりポインタpsumにsumのアドレスを入れたので*psumはsumに入っている値自体を表す事が 出来る様になります。 #include void init(int *psum) { *psum = 0 ; } // End init() ; int main(void) { int x ; int sum ; x = 3 ; sum = 5 ; sum = sum + x ; printf("変数sumの値は%dです。\n", sum) ; init(&sum) ; // アドレスを渡す printf("変数sumの値は%dです。/\n", sum) ; return(0) ; } // End main() ; main関数内のsumはそのアドレスを関数側に渡せばよいので init(&sum) ; とします。 受け取り側の関数では、このアドレスをポインタに受け取ればよいので、 関数の引数をポインタに変更します。 ここでは関数のパラメータとして宣言しているのでinit関数を void init(int* psum) の様に変更します。 後はこのポインタを使ってsumの値を初期化してやればよいでしょう。 ポインタはpsumであって*psumではありません。 main関数からのsumのアドレス(&sum)はpsumが受け取ります。*psumは単純なint型の 変数です。 変数psumはint型へのポインタ型(int*型)として宣言したと考えて下さい。 そのint型へのポインタ型に*を適用することによってpsumが格納しているアドレス上の 変数すなわちint型変数*psumを表せるようになります。 //////////////////////////////////////////////////////////////////////// 文字のアドレスをポインタで取得する #include int main(void) { printf("こんにちは") ; return(0) ; } // End main() ; ------------------- #include int main(void) { char *str = "こんにちは" ; printf(str) ; return(0) ; } // End main() ; /////////////////////////////////////////////////////////////////////// ポインタと配列 配列は単一の変数の集合体です。 この単一の各要素がいくつか集まって実質上1つの変数を構成します。 勿論配列もポインタを使ってアクセスする事が可能です。 例えば配列sの3番目の要素は s[2] ; と書きます。 そのアドレスは &s[2] ; と書くことが出来ます。 では配列の先頭要素のアドレスを導くにはどうすればよいのでしょうか? &a[0] ; と書くことが出来ます。省略して s ; と書く事も出来ます。配列の先頭要素だけは特別ですが・・・ アドレスが得られれば、後はポインタに代入して使います。 s[0] = 1 ; s[1] = 2 ; s[2] = 3 ; の様にします。 まず・・・・・ int *p ; int s[0] ; p = s ; // 配列の先頭要素にアドレスを代入 p = 1 ; // 先頭要素に1を代入 これで配列の先頭要素に1を入れる事が出来ました。 次に2番目の要素にも値を入れたいと思います。 勿論2番目の要素ですから p = s[1] ; と新たに代入してから *p = 2 ; と代入することもできます。 しかしながらポインタにはもっと便利な機能があります。 通常の変数は++演算子(インクリメント)や--演算子(デクリメント)を使って 変数の内容を増減することが出来るのはご存じでしょう x = x + 1 ; として内容の変更をすることが出来ます。 実はポインタもこの例に漏れません。 ただしポインタの場合は定義された型に応じてアドレスの値を増減させる事が出来ます。 #include int main(void) { int *p ; int s[10] ; p = s ; *p = 1 ; printf("ポインタpの保持しているアドレス1回目%p\n", p) ; p++ ; *p = 2 ; printf("ポインタpの保持しているアドレス2回目%p\n", p) ; printf("配列sの値[%d, %d]\n", s[0], s[1]) ; return(0) ; } // End main() ; ポインタpの保持しているアドレス1回目 0012FF54 ポインタpの保持しているアドレス2回目 0012FF58 配列sの値[1,2] ポインタ変数をインクリメントしている行 p++ ; によってアドレスの値が4だけ増加している事が判るでしょう。 これはポインタが現在参照しているint型のサイズそのものです。 現在のポインタが指す配列の3番目の要素を取得したい場合は *(p + 2) と書く事も出来ます。 ============================================== 2つの文字列を連結するプログラム void strc(char* str1, char* str2) { int i, n ; i = 0 ; n = 0 ; while ( *(str1 + i) ) { i++ ; } // End while while ( *(str1 + i) = *(str2 + n)) { i++ ; n++ ; } // Enw whle } // End strc() ; 最初のwhile文は元になる文字列の最後\nまでポインタを進めています。 次に2番目のwhile文でその位置から連結する2番目のwhile文でその位置から 連結する2番目の文字列をコピーしていると言う事です。 ============================================== 2つの文字列を連結するプログラム 配列編 void strc(char* str1, char* str2) { int i, n ; i = 0 ; n = 0 ; while (str1[i]) { i++ ; } // End while while(str1[i] = str2[n]) { i++ ; n++ ; } // End while } // End strc() ; 配列のように書けば直感的に何が行われているか解った人も多いと思います。 このように配列とポインタは非常に密接な関係があります。 では1次元配列が判った所で多次元配列について考えることとします。 /////////////////////////////////////////////////////////////////////// ポインタと多次元配列 配列は1次元配列以外にも2次元、3次元と多次元にわたって作成する事が出来ます。 例えば次のような5行10桁の2次元配列があったとします。 char s[5][10] ; この配列の各要素のアドレスを求めるのは簡単です。 例えば4行目の6番目の要素のアドレスは・・・ &s[3][5] ; となります。 ではポインタを使ってアクセスするにはどの様に行えば良いでしょうか? ポインタをしっかり理解していないと初心者が必ず躓くところです。 #include int main(void) { char abc[4][3] ={ {'a', 'b', 'c' }, {'d', 'e', 'f' }, {'g', 'h', 'i' }, {'j', 'k', 'l' }, } ; printf("*(abc + 1) は%cです。",*(abc + 1)) ; return(0) ; } // End main() ; このプログラムは確定した結果を出す事が出来ません。 その理由はabcのデータ型にあります。 もう一度多次元配列を思い出して下さい。 -------------------- ここから5回は読みましょう------------------ char s[10]のsがchar型へのポインタ(char*型)となりましした。 多次元配列の配列の場合、配列名だけを取った値はどのような値になるのでしょうか? 実はこの場合配列名だけを取ってもchar型へのポインタ(char*型)とはなりません。 1次元配列の場合char s[10]のsはchar型へのポインタ(char*型)を取りました。 s配列の1つの要素は単純なchar型です。 つまりこれは配列内の1つの要素へのポインタを捕っている事になります。 ではchar abc[4][3]のabcは何でしょうか? これも配列内の1つの要素へのポインタです。 abc配列の1つの要素は大きさ(要素数)3のchar型配列です。 つまりchar abc[4][3]のabcは大きさ3のchar型配列へのポインタ型(char(*)[3]型)を 取ります。 従ってこのポインタへ*演算子を適用しても正しい値を得る事は出来ません。 あくまで大きさ3のchar型配列へのポインタ(char(*)[3]型)として、データを捕らえる 必要があるのです。 -------------------- ここまでね --------------------------------- 2次元配列のポインタの関係を表すプログラム #include int main(void){ char abc[4][3] = { {'a', 'b', 'c' }, {'d', 'e', 'f' }, {'g', 'h', 'i' }, {'j', 'k', 'l' }, } ; printf("%p\n", abc - 2) ; printf("%p\n", abc - 1) ; printf("%p\n", abc ) ; printf("%p\n", abc + 1) ; printf("%p\n", abc + 2) ; printf("*( abc + 1 ) is %p\n", *( abc + ) ) ; printf("&abc[1][0] is %p\n", &abc[1][0] ) ; printf("*(}(abc + )) is %c\n", *(*(abc + 1 )) ) ; return(0) ; } // End main() ; *********************************::::::::::::::::: 0012FF6E 0012FF71 0012FF74 0012FF77 0012FF7A *( abac + 1 ) is 0012ff77 &abc[1][0] is 0012FF77 *(*( abc + 1 )) is d ************************************************** abcの演算結果としてアドレスの値が3ずつ変化しています。 これはabcが大きさ3のchar型配列へのポインタとして認識されている証拠です。 つまりポインタ+1の増加によってアドレスとして言えば &abc[1][0] &abc[2][0] &abc[3][0] となることが判ります。 また先ほど正しい値が得られなかった *( abc + 1) のアドレスは &abc[1][0]と同じです。 *( abc + 1)---------&abc[1][0] *( abc + 2)---------&abc[2][0] *( abc + 3)---------&abc[3][0] *( *abc ) ---------------------- a *( *abc + 1 )------------------- b *( *abc + 2 ) ------------------ c *( *( abc + 1 ) + 0)-------------d *( *( abc + 1 ) + 1) ------------e *( abc[1] + 2)-------------f *( *( abc + 2 ) + 0 )----------- h *( *( abc + 2 ) + 1 )----------- h (* (abc + 2 )) [2]-------------- i /////////////////////////////////////////////////////////////////////// ポインタ配列 次の2次元配列について考えてみます。 char structure[][23] = { "新宿都庁", "大阪城", "横浜ランドマークタワー", } ; この例では一番長い文字として"横浜ランドマークタワー"があるので配列の大きさを 23(日本語文字列2byte + \nとして計算)と指定して確保しました。 しかし23文字よりも大きな文字列をkの配列に格納したいと思った場合に容量オーバー となる可能性があります。その都度配列のよう素数を考え直さなければなりません。 しかも23文字以下の文字列を持つよう素数にとっては無駄な領域です。 最初に述べたようにポインタも通常の変数です。 勿論ポインタ型を要素とした配列も作る事が出来ます。 今回のようなstructure配列の場合文字列char型へのポインタとして扱った方が良さそうです。 ポインタを配列に保持する様にすれば上記の無駄を省く事が出来る様になります。 char *structure[] = { "新宿都庁", "大阪城", "横浜ランドマークタワー", } ; 文字列へのポインタを配列として持つと言うだけでずいぶんとすっきりしました。 配列の場合は全ての文字列に対して固定的な変数領域を確保していたのに比べて ポインタの場合はそれぞれの文字列で必要最低限のポインタと変数領域のみ確保 されます。 おまけ-------------------------------------------------- #include #include #include char* unseichk( void) { static char *unsei[] = { "大吉", "凶", "吉", } ; srand( (unsigned) time (NULL) ) ; return unsei[ rand() % 3 ] ; } // End unseichk() ; int main( void) { printf(" 今日の運勢は%s です。\n", unseichk() ) ; return(0) ; } // End main() ; /////////////////////////////////////////////////////////////////////// 関数の引数 #include void prnsize( char str[100] ) { char tmp [ 100 ] ; printf("tmp 配列のサイズは = %d です。\n",sizeof(tmp)) ; printf("str 配列のサイズは = %d です。\n",sizeof(str)) ; } // End prnsize(); int main(void) { char str [100] ; prnsize( str ) ; return(0) ; } // End main(); *************************** tmp 配列のサイズは = 100です。 str 配列のサイズは = 4です。 関数ないで定義した配列tmpのサイズは100と表示されています。 しかし、関数の引数として受け取った方の配列strは要素数を100と指定してあるにも 関わらず、4と表示されています。 C言語では関数の引数としての配列そのものを受け渡すことは出来ません。 関数の引数として配列を渡した場合必ず関数側ではその配列の先頭要素へのポインタと いう形で受け取る事になります。 もし引数が多次元配列であれば勿論それに応じた配列へのポインタです。 この例の場合はstrは1次元配列ですのでchar型へのポインタ型(char*型)を受け取る事 になります。 void prnsize ( char *str) とも void prnsize( char str[] ) とも書き換える事が可能です。 実際に渡される値は単なるポインタなのです。 言い換えればkのsizeof式で得られる値はポインタそのもののサイズと言う事になります。 #include void prnsize( char str[3][4] ) { printf("str 配列のサイズは = %dです。\n", sizeof( str )) ; } // End prnsize(); int main( void ) { char tmp[3][4] ; printf("配列のサイズは = %dです。\n",sizeof( tmp ) ) ; prnsize( tmp ) ; return(0) ; } // End main(); ******************************* tmp 配列のサイズは = 12 です。 str 配列のサイズは = 4 です。 2次元配列でも答えは4でした。 ただし、こちらは大きさ4のchar型配列へのポインタ(char(*)[4]型)です。 kのprnsize関数の引数も勿論ポインタを使って表す事が出来ます。 void prnsize ( char (*str)[4] ) および void prnsize( char str[][4] ) です。 結局の所配列のサイズを表示させるつもりであったsizeof(str)という式は ポインタ変数のサイズ自体を返していると言う事になります。 1次元でも多次元でも結局表す値はポインタなので、どちらの場合もポインタ変数 自体のサイズ4を返す事になります。 最後にこのポインタ配列を関数の引数とした場合にはどのようになるのでしょうか? 間巣の引き数は先頭要素へのポインタとなります。 つまりここではポインタのポインタを取ることになります。 ポインタは変数のアドレスを入れるために使われる単なる変数です。 つまり、ポインタ自体もメモリ上のどこかに確保されている変数ですからポインタの アドレスも導く事が出来ます。 そしてそのポインタのアドレスを確保するポインタへのポインタも実際に作る事が出来ます。 #include #include #include char* unseichk( char** unsei ) { srand ( (unsigned) time ( NULL ) ) ; return *(unsei + rand() % 3) ; } // end unseichk () ; int main (void) { char *unsei[] = { "大吉", "凶", "吉", } ; printf("今日の運勢は%sです。\n",unseichk(unsei)); return(0) ; } // End main() ; /////////////////////////////////////////////////////////////////////////////// メモリの動的確保 #include #include #include int main( void ) { int i ; char *p[5] ; char tmp[128] ; for( i = 0 ; i < 5 ; i++) { printf("文字列を入れて下さい。->") ; scanf("%s",tmp) ; p[i] = ( char * ) malloc( strlen ( tmp ) + 1 ) ; if( p[i] == NULL ) { printf("メモリ確保に失敗しました") ; } // End if else { strcpy( p[i], tmp ) ; } // End else } // End for for (i = 0 ; i < 5 ; i++){ printf("[%d]: %s\n",i, p[i]) ; free(p[i]) ; } // End for return(0) ; } // End main() ; //////////////////////////////////////////////////////////////////////////// 構造体 プログラミングを行っていると、あるまとまった単位でデータを扱いたい事があります。 例えば、会社の社員情報データベースを作成している場合に・・・・ int no ; // 社員番号 char name[20] ; // 名前 int dept ; // 部署番号 と言う情報をどのように持てばよいでしょうか? それぞれの変数を社員数文配列として確保するというのでは余り美しくありませんし 管理も大変です。 そんなとき構造体を使うと大変便利になります。 構造体は自分の好きな形でデータ型を定義する事が出来る大変便利な機能です。 構造体の宣言はstructというキーワードに続けてそのデータ型の名前を付けて、 データの構造を記述していきます。 struct emp { int no ; // 社員番号 char name[20] ; // 名前 int dept ; // 部署番号 } ; これで構造体empを定義する事が出来ました。 ただしこれだけではemp構造体のデータ型の定義を行っただけです。 実際に使う場合は通常の変数のように struct emp emp ; として構造体の変数を宣言します。 構造体内のそれぞれの要素(データメンバ)にアクセスするためには (.)ドット演算子を使ってアクセスします。 例えばemp構造体のnoへは emp.no となります。そして、この変数へのアドレスは &emp.no として求める事が出来ます。 なお構造体変数がポインタを表す場合は -> アロー演算子を使って データメンバにアクセスします。 #include struct emp { int no ; char name[20] ; int dept ; } ; int main(void) { int i ; struct emp emp[3] = { { 101, "Kimura", 3 }, { 102, "Haraki", 2 }, { 103, "Miwa", 6 }, } ; for( i =0 ; i < 3 ; i ++) { printf(" %d: %s : %d \n", emp[i].no, emp[i].name, emp[i].dept ) ; } // End for return(0) ; } // End main() ; #include #include struct emp { int no ; char name[20] ; int dept ; struct emp *next ; } ; int main( void ) { int i ; int no = 0 ; char y ; struct emp *top ; struct emp *cur ; struct emp *temp ; top = ( struct emp * ) malloc ( sizeof ( struct emp ) ) ; if ( top != NULL ) { cur = top ; for( ;; ) { printf( "emp[%d] -> ", no) ; scanf( "%d %s %d", &cur -> no, cur -> name, &cur -> dept ) ; no++ ; printf( "Continue?" ) ; fflush ( stdin ) ; scanf( "%c", &y ) ; if( !( y == 'y' || y == 'Y' ) ) { break ; } // End if temp = ( struct emp * ) malloc ( sizeof ( struct emp ) ) ; if ( temp != NULL ) { cur -> next = temp ;cur = temp ; } // End if else { break ; } // End else } // End for } // End if cur = top ; for ( i = 0 ; i < no ; i ++ ) { printf( "%d : %s : %d \n", cur -> no, cur -> name, cur -> dept ) ; temp = cur -> next ; free( cur ) ; cur = temp ; } // End for return(0) ; } // End main() ; ******************************************: 1 Joker 101 y 2 HOE 102 y 3 Developers 103 n 1 : Joker : 101 2 : HOE : 102 3 : Developers : 103 ******************************************** ///////////////////////////////////////////////////////////////////////////// 共用体 構造体と並んでC言語には共用体と呼ばれるデータ型を定義する事が出来ます。 共用体はメンバへのアクセス方法、定義方法などは構造体と全く同じです。 構造体との違いは共用体ではその中に定義されたメンバが同じ領域を共有する所にあります。 言い換えると構造体はメンバの容量分だけメモリ領域が確保されるのに比べて、 共用体はメンバの中の最も大きいメモリ容量分しか確保されません。 共用体はunionというキーワードで宣言します。 #include #include union text { char moji ; char string[20] ; char str2[4][6] ; } ; int main( void ) { union text text ; strcpy( text.string, "hello union worle" ) ; text.moji = 'H' ; strcpy( &text.str2[2][0], "Japan" ) ; printf("共用体内の文字列は= %s\n",text.string) ; printf("共用体内のサイズ = %d\n", sizeof( union text) ) ; return(0) ; } // End main() ; 最初にstringメンバを使って書き込みした文字列が他のメンバの書き込みによって 書き換えが行われたのが分かると思います。 実際sizeof演算子で導き出した共用体の容量もメンバ全ての領域の合計ではありません。 ここでは2次元配列str2で確保された領域が取られていると言う事です。 /////////////////////////////////////////////////////////////////////////////////////////// To be continue.... To Vol.4 ==============================================================================