の例では Student1 型の構造体 classl はポイ ンタ型であり , Student1 型のメンバである TestTensuu 型の構造体 tensuu はポインタ 型ではありません。ですから , classl のメ ンバを参照する際はアロー演算子を使用し ますが , tensuu のメンバを参照する際はピ リオド演算子を使用しています。 一方 , ②の例では Student2 型の構造体 cl ass2 も Student2 型のメンバである TestTens uu 型の構造体 tensuu も , ポインタ型とし て宣言されています。そのため , メンバの 参照はそれぞれアロー演算子を使用してい ます。ただしこの例はあくまでも参照する 場合の書式を示したものです。ポインタの 解説時にも説明したように , 実際のプログ ラムではポインタ変数以外に実体となる領 域も確保しなければなりません。②の例で は , class2 と tensuu はポインタ変数なので , 何らかの方法でそれらが指す実体が確保さ れていることになります。 構造体変数の実体は基本型と同様に ( 代入演算子 ) で全体を代入することも可能 です ( 豆 CoIumn1 ) 。また typedef により再 定義することも可能です ( 豆 CoIumn2 ) 。 構造体のホインタと加減演算子 加減演算子は , char, short, int などの 基本型の実体となる数値型変数に対して適 用した場合 , 1 加算または 1 減算を行います。 またポインタ変数に対して適用した場合 , それぞれ基本型のサイズぶんだけ加算 , ま たは減算を行います ( たとえばポインタ変 数の実際の値は , 32 ビットの処理系の場 合多くが short int 型では 2 バイト , int 型で は 4 バイトだけそれぞれ加算 , または減算 します ) 。これはそれぞれのデータ型の配列 を指し示すポインタを使用する場合 , 加減 演算子を適用することにより , それぞれの 配列要素を矛盾なく指し示すためです。 構造体のポインタに加減演算子を適用し た場合も , int や char などの基本データ型 と同様に , 構造体のサイズぶん加算 , また は減算します。このことにより , 構造体の 配列をポインタで扱う際にも矛盾なく処理 することができます。構造体を指すポイン タの使用方法を把握するため , Fig. 3 に使 54 C MAGAZINE 2001 12 豆 COIumn 1 構造体変数をコピーする ANSI C では , 構造体型の変数を同じ構 造体型の変数に直接コピーすることが可能 です。どのような構造でも , 同じタグより 定義された変数であれば問題はありません。 struct int Char char c lient( COde ー れ ame [ 20 cte 15 char cadr[ 180 struct c ー ient memberl , member2 membe て 1 = member 以上のように、代入文により直接コピー することができますただし構造が同じ でもタグ名が異なれば別の構造と解釈さ れ、コンバイルエラーとなります。 、äCoIumn 2 typedef による構造体の再定義 以前 typedef によるデータ型の再定義方 法を解説しましたが , 同様に構造体も type def による再定義を行えます。書式はキー ワードである typedef に続けて構造体のタ グ名宣言と同様の指定を行い , 最後に新し い型名を指定します。以下に例を示します。 typede f int Char Char char struct C ー ient{ COde; れ a 爪 e [ 20 島 ctel[15]; cad て [ 180 } CLIENT; 最後の C 凵 ENT は構造体名 ( 変数名 ) では なく , 再定義されたデータ型名となります。 また , タグ名 c ⅱ ent がすでに定義済みであ れば以下のようになります。 typedef struct c lient CLIENT ・ この状態で変数を定義するには , 以下の ようにします。 CLIENT memberl , member2; キーワード struct を記述しなくてもよく なり , 多少簡略化することができます。 Fig. 3 構造体ポインタに対する加算演算子の操作 struct int Char c lient{ COde ー name[20]; void main( ) { struct C ー ient struct c ー ient mempt = memberl; / * このことにより , mempt -> code = 1 *mempt ー memberl [ 5 嵭 / * 構造体 c け en セ型の変数の配列を宣言 * / / * 構造体 c は en 型を指すポインタ型変数を宣言 * / / * 爪 emberl の先頭アドレスを格納する * / 変数 memberl と me 叩セは同領域を示すことになる * / / * このことにより , 次の先頭ポインタつまり memberl [ 1 ] を指すことになる * / 十十 mempt ー / * ポインタに 1 加算する * / strcpy( mempt ー > name , ”山田花子″ mempt ー > code = 2 の / * ポインタ型変数により memberl [ 1 ] の code を参照 * / 用例を示します。 関数と構造体 構造体はほかのデータ型と同様に の引数や戻り値としても使用することがで strcpy( mempt - > name , ”中井信 関数 きます。つまり , ほかのデータ型と同様に 「値渡し」「アドレス渡し」の 2 通りの方法を 使用できます。では , Fig. 4 ~ 7 にそれぞれ の場合の使用例を示します。 ここで気をつけなければならないこと
例ということで , 「今日の一品 (item4today) 」 というのを作ってみました。 GNet にアク セスして , コマンドラインで指定されたキ ーワードで QUERY を送って 10 秒間待ち , 届いた QUERY HITS の中からランダムに 1 つを選んでダウンロードします。何らかの 原因でダウンロードに失敗したら別のファ イルを選び直します。 1 日 1 回起動するスク リプトの中で動かすと新鮮な楽しみがあり ます。 UNIX での fortune コマンド Gnutella 版といった感じでしようか。さあ , item4to day. java( 付録 CD-ROM に収録 ) を詳しく見 ていきましよう。 myConnector クラスのインスタンスを生 成するところまではこれまでの例と同様で す。 QUERY を送って QUERY HITS を受け 取るために Connector クラスの query メソッ ドを利用します。 query メソッドは検索文 字列と検索結果を得るまでの待ち時間をパ ラメータとして受け取ります。 query メソ ッドは指定された待ち時間の間 , 処理をプ ロックします。有効な結果が得られるまで While でループさせるようにしています。 検索結果は Record クラスの set で得られま す。 こではランダムに 1 つを選ぶという 操作が簡単なように配列に変換しています。 1 つの Record は 1 つの共有ファイルに対応し ています ( List5 ) 。 ーション側で行う必要があります。不要な この場合は H 丁 P ヘッダの処理をアプリケ 返却コード取得に失敗する場合があります。 nutella クローンの実装によっては HITP の ンスへッダの仕様が微妙に違うために , G ます。これは前述したように HITP レスポ de で返却コードを取得して条件分岐してい りにくい処理があります。 getResponseCo ァイルにアクセスします。ここで少しわか 設定し , getInputStream を取得して共有フ を用いて openConnection でコネクションを L クラスのインスタンスを返します。これ ドで共有ファイルにアクセスするための UR います。 getURL は Record クラスのメソッ る値の範囲の中でのランダムな値が入って index には配列のインデックスが取りう ヘッダをスキップするメソッド skipHeader です。後は BufferedInputStream で読み出 したデータを FileOutputStream でローカル ファイルに書き出します (List 6 ) 。 ほかに , 細かい処理としては検索結果の フィルタリングがあります。一般に公開さ れている GNet に接続して得られた検索結 果の中身を調べてみると , アクセスできな いものがいくつかあります。たとえばファ イル名にスラッシュを含むものやファイル 名が空のもの , ファイヤウォールの内側な ので LEAF ではサポートしていない PUSH R EQUE 訂を使わないとアクセスできないも のなどです。これらはフィルタリングをし てダウンロード候補から排除しています。 とくに指定しなければプライベートアドレ スもフィルタリングされてしまいます。 れは I. AN で試験するときは不便なのでコマ ンドラインから起動するときに検索キーワ List 2 GNet との接続 (test01 . java より抜粋 ) myconnector connector = new myconnecto て ( ) ー connector. connect(args[O] / / ping connector. ping ( 潺 / / show metrics for (int cnt = cnt く研 cnt 十十 ) ( system. out. print( ” PingMessage 十 connector. iPingMessage); System. out. p て int ( ” PongMessage 十 connector. ipongMessage); system. out. print(" QueryMessage 十 connector. iQueryMessage); system. ou し p て int ( ″ QueryHitSMessage ネットワ - クプロクラミンクのアトリエ ードの後にダミーのパラメータを追加する に独自の拡張をしています。検索した結果 という GnuteIIa クローンでは QUERY HITS な亜流があります。たとえば , BearShare Gnute Ⅱ a はその成立の経緯からさまざま 複数の Gnute クローンで試験しよう ! いくつかあげておきます。 利用するアプリケーション開発のヒントを 発を通じて得られた Gnute Ⅱ a プロトコルを LEAF とその応用アプリケーションの開 プリケー。ョンのヒン GnuteIla プロトコルを利用する ラメータは何でもかまいません。 だけでチェックしているので , ダミーのパ 処理は単にコマンドラインパラメータの数 の対象外となるようにしてあります。この とプライベートアドレスはフィルタリング 十 connector. iQueryHitsMessage); system. out.println( ″” Thread. sleep(2 * 1000 / / wait 2 sec. connector. disconnect( 馮 List 3 QueryMessage を用いた myConnector (test02. java より抜粋 ) c ー ass myConnector extends Connector { pub lic int iQueryMessage; public void handle(QueryMessage m) { iQueryMessage 十十一 System. out. println(iQueryMessage 十 super. handle(m); ”十 m. getQueryKey( ) List 4 接続して QUE 日 Y を待つ (test02.java より抜粋 ) myconnector connector = new myConnector ( connector. connect(args[O] Thread. sIeep()0 * 1000 / / wait 10 sec. connector. disconnect ( 技術を知って実践しよう ! ネットワークプログラミングのアトリエ 1 1 /
のように , コピーコンストラクタとコピー 演算子を private 部に宣言。だけ ' しておきま す。そうすればうつかりコピーしようとす るとコンパイルエラーとなりますし , たと えコンパイルが通っても実装されていない のでリンク時に引っかかります。だから関 数の引数として受け渡すには参照もしくは ポインタ渡しに限定されます。 原本 / 複製なのかを示すフラグを持たせる コヒ。ーできない / させないというのはあ まりに無愛想ですかね。 Counter を戻り値 とする関数のたぐいを書くのがとてもめん どうになりそうですしね。安全なコピーを れます。 cl が原本 delete cl; 一 60 = * cl ー Counter * cl Counter c0 ー のです。 実現し , 値渡しを可能にすることを考えま す。安全にコピーするには・・・・・・問題はデス トラクタで CounterImp を無条件に delete し ていることなのですよね。ならばオプジェ クト内部に原本なのか複製なのかを示すフ ラグを持たせ , デストラクタでは自分自身 が原本であるときに限り delete するという のはいかがでしようか ( List6 , 7 ) 。 残念でした , これでもダメな場合がある new Count er ー このコードのグレー部分でコピーが行わ c0 が複製です。次の delete cl によって原本がなくなりますか ら , その後の複製 (c0) は無効な CounterIm p を保持しています。正しく動作するはず がありません。 参照カウントを利用する ではどうするか , その解の 1 つが前回 boost::shared_ptr で少しだけ紹介した。参 照カウント ' です。「部屋に入ったら正面の 黒板に書いてある数を + 1 しろ」 , 「退室す るときにはその数を -1 しろ」 , 「その結果が 0 となったら部屋の明かりを消しておけ」と いうわけです。最後まで使っていた人に後 始末してもらうからくりです。 List 8 に参 照カウントを仕込んだポインタもどå'co unt-ptr を示します ( つまりは boost::shared 参照カウントを使った count ー pt 「 . h / / count—ptr. h #ifndef ——COUNT—PTR—H— #define ——COUNT—PTR—H—— temp ー ate <C ー ass X> class count—ptr ( ptr—ー uns igned* cnt— VOid inc—ref ( const count—ptr& て void dec—ref ( public: explicit count—ptr()* p = 0 ) : p セて」 p ) , cnt—(O) { if (this ! = (r) { dec-ref(); inc—ref(r); count—ptr& operator= ( const count—ptr& て ) { dec-ref ( ) count—ptr ( ) count—ptr(const count—ptr& て ) { inc—ref(r) { if ( p ) cnt- = new unsigned(l); } C MAGAZINE 2001 12 #endif 十十 *cnt— if ( cnt- ) て . cnt—; cnt— ptr— て . ptr—ー template <class X> p セて一 cnt— 0 ー del ete pt て de ー ete cnt—ー if (--*cnt- if ( cnt- ) { in line VOid count—ptr く X> dec—ref ( ) { temp ー ate <C ー ass X> unsigned count ( ) const { return cnt— ? *cnt— bO unique ( ) const X* get( ) const X* operator-> ( ) const X& operator* ( ) const } return *this, { return * p して一デ } { return ptr—ー } { return ptr—; } { return cnt— ? *cnt— = = 1 . true in line VOid count—ptr く X> : : inc—ref ( const count—ptr<X>& て ) count_pt 「 <CounterImp> に置き換えた Counter. h / / Counter. h #ifndef ——COUNTER—H— #define ——COUNTER—H— #include ncount—ptr. h" #include "counterlmp. h" class Counter { count—ptr<CounterImp imp—; public: counter ( Counter ( void incr( void decr ( 土 n し value( ) const; #endif count_pt 「く CounterImp> に置き換えた Counter. cpp / / counter. cpp #include "Counter. h" Counter: :counter( ) : imp—(new CounterImpy { Counter: :¯counter( ) { void counter : : incr ( ) { imp—->incr( void counter: :decr( ) { imp-->decr( 潺 土 n ヒ counter: :value( ) const { return imp—->value(
6 ロロロロ は , 実体を関数の引数や戻り値とする場合 ( つまり , 値渡しを使用して構造体型のデ ータを引き渡す場合 ) に , 引数が大きな構 造体だと処理速度が遅くなるということで す。これは , 以前関数についての回に説明 したように , C の関数で「値渡し」の場合は コビーが作成されるためです。大きな構造 体を関数とやりとりする際は「アドレス渡 し」を利用してください。 構造体変数の性質 これまでに紹介したように , 構造体型の 変数 ( 実体 ) は通常のデータ型の変数とほぼ 同様に扱うことが可能です。しかし , 例外 もあります。構造体型の変数では以下の処 理を行うことができません。 ・算術演算子 ( + , - * , / , % ) による演算 や加算 , 減算演算子の指定 ・ if 文などで = = による同一内容かどうか の判定 ( たとえば if(a==b) then … ) 2 つの構造体変数の内容が同一かどうか を判定するには , 多少めんどうでも 1 つ 1 つのメンバが同一か比較しなければなりま せん。 strncmp とよく似た memcmp という 標準関数 ( 豆 Column 3 ) が提供されています が , この関数を利用して以下のような判定 を行うことは避けたほうがよいでしよう。 Fig. 4 関数に構造体型データを「値渡し」で引き渡す場合 struct int Char Char Char client{ COde ー name[20]; c し e Ⅱ 15 嵭 cadr[180]; void main( ) { void funcl ( struct c lient w—member ) { / * 仮引数に受け取り用の構造体型変数を宣言する * / funcl ( memberl struct c ー ient memberl ー Fig. 5 関数に構造体型データを「アドレス渡し」で引き渡す場合 void main( ) { struct c ー ient member2 ー func 2 ( &member2 void func2 ( struct c ー ient * w—member ) { / * 構造体のアドレスを渡す * / / * 構造体のアドレスを受け取る * / Fig. 6 関数から構造体型データを「値渡し」で受け取る場合 void main( ) { struct c lient member3 ー member3 = func3 ( / * 戻り値を構造体型変数に格納する * / struct XX { short s ー 土 n し char c [ 10 嵭 struct XX xl , x2; if (memcmp( &xl , / / 同一時の処理 / * 構造体型の関数 * / struct c lient func3 ( ) { struct c は ent w—member 3 ー w—member3. code = の て eturn w—member3 ー / * 構造体型の変数を戻り値としてセットする * / &x2 , sizeof(xl) ) これらの処理を正しく行うことができな い理由として , 以下の 2 つが考えられます。 ◆ 1 . 構造体のギャップ 構造体を構成するメンバとメンバの間に はギャップ領域がある場合が考えられます ( 豆 CoIumn4) 。一般的に上記の構造体では Fig. A のようにメモリ上に配置されます。 short 型の変数 s とⅲ t 型の変数 i の間のギャ ップにはどのような値が入っているかはわ かりません。 Fig. 7 関数から構造体型データを「アドレス渡し」で受け取る場合 void main( ) { struct c lient *member4 ー member4 = func4 ( / * 構造体を指すポインタ型変数に戻り値を格納する * / struct client *func4( ) { / * 構造体を指すポインタ型の関数 * / static struct c ー ient w—member4 ー w—member4. code = return ( &w—member4 / * 戻り値になるため , sta c クラスとして固定領域を使用 * / / * 構造体を指すアドレスを戻り値としてセットする * / ◆ 2. 構造体の比較における問題 char 型の配列変数 c [ 10 ] に以下の操作を 行ったとします。 strcpy(xl. c, "abc"); この場合 c [ 0 ] ~ c [ 2 ] にそれぞれ a , b, c が代入され , c [ 3 ] に NULL が代入されます Getlnto C world ! ! 55
ている。 PCRE には POSIX 互換のインタフェイス も用意されているが , POS Ⅸ正規表現ライ プラリとは以下の違いがある。 ・ヘッダファイルは regex. h ではなく pc 「 e posix. h ・受け付ける正規表現は POS Ⅸ仕様では なく PerI 互換で固定 ・ regcomp ( ) の引数 cflags に REG_EXTE NDED がない どの正規表現ライブラリ を使うか ? これまで見てきたように正規表現ライプ ラリには多くの種類がある。それではどの ようにライプラリを選択したらよいのだろ うか。 入手の容易さと利用できる正規表現の互 換性を考えると , 現在では POS Ⅸ仕様のラ イプラリを選択するのが最善だ。商用 UNIX であれば標準の正規表現ライプラリを試し , それが利用できない環境では GNU を使 うとよい。実行速度や正確さの点では GNU に軍配が上がるだろう。 Fig. 12 GNU Rx #inc lude nregex. h" フリーソフトウェアを開発することが目 的で , 何らかの理由で GNULGPL にしばら れたくないこともある。 POSIX 仕様の正規 表現でなくてもよいのなら , HenrySpencer の V8 regexp や Philip Hazel の PCRE が利用可 能だ。 V8 regexp はさまざまなフリーソフト ウェアで利用されてきた実績があり , 信頼 性は十分に高い。また , スクリプト言語処 理系では Perl 5 互換の正規表現が標準的に 使われるようになってきているので , そう した用途に利用するなら PC が第 1 の選択 肢となる。 日本語はマルチバイト文字なので , 日本 語を含むテキストや正規表現を扱うために は正規表現ライプラリもマルチバイト文字 に対応していなければならない。では , 日 本語が扱える正規表現ライプラリにはどの ようなものがあるだろうか。 残念ながら , 現在のところ決定版と呼べ るような日本語対応の正規表現ライプラリ は存在しないようだ。 こではいくつかの 日本語を扱うには ? 特集 1 正規表現 入門 選択肢を示しておこう。 一日本語対応の標準ライブラリを使う 商用 UNIX によっては日本語に対応した 正規表現ライプラリを提供しているものが あるので , それを使う。ソフトウェアがプ ラットホームに依存してしまうので , 移植 性が重視されるフリーソフトウェアの開発 には向かない。 フリーソフトウェアから転用する 日本語対応のスクリーンエデイタやスク リプト言語処理系には , フリーソフトウェ アとして配布されている正規表現ライプラ リをマルチバイト文字を扱えるように改造 しているものが多い。これらの配布パッケ ージから正規表現ライプラリを抜き出して 使う。利用できる日本語コードに制約があ ったり , そのまま単体でコンパイルできな い可能性が高いので , 再利用するためには それなりの労力が必要になる。 また , 利用条件にも注意しなければなら ない。フリーソフトウェア間で転用されて いる実績があるものとしては , たとえばス クリプト言語の Ruby に含まれる mbregex. c 土 n セ regcomp (regex—t *re, char *pattern, 土 n セ cf lags ー 土 n セ regexec regex—t *re, char *text, size—t nmatch, regmatch—t pmatch [ ] , size—t て ege てて 0 て int errcode, regex—t *re, char *errbuf , size—t size VOid regfree regex—t * て e Fig. 13 Philip Hazel の PCRE #include pcre. h" xnt ef lags ー pcre *pcre—compil e ( char *pattern , 土 n セ options , char * *errptr , int *erroff , uns igned char *tab leptr); pcre—extra *pcre—study (pcre *re , int op セ土 ons , char * * errptr ー int pcre—exec (pcre *re, pcre—extra *extra, char *text , 土 n セ len, 土 n セ start , 土 n セ options , int *ovector, int ovecsize void * (*pcre—ma lloc) (size—t); void (*pcre—free void * 特集 1 正規表現入門 27
WTD を使うソフトの作成 前述の作成した曲データ ( ここでは samp 1e01. d とする ) を演奏するプログラムを作 ってみましよう。 プログラムの開始から終了までの流れは 次のとおりです。 ① WTD.IL のロード ② WTD 」 L の常駐 ③データの再生 ④ WTD 北の解放 プログラムのエラーが発生したときの処 理を除けば , 手順① ~ ③の命令 ( たった 3 行 ) を書くだけで , データを再生すること ができます。 この手順で使用する関数の説明をしてい きます。 WTD.IL のロード 土 IibIL->—open (char far * name, ロ far * (t) ー WTD 本体 (@WTD.IL) は IL 形式のファイ ルです。そのため , 最初に、、ⅲ b ル - > -0 n ( ) " で , IL 構造体のアドレス解決をしなければ なりません。第 1 引数に IL のファイル名 , 第 2 引数に WTD の IL 構造体を格納したいポ インタ ( 以下 WtdIL とする ) を指定します。 IL 構造体が正常に転送 ( o n ) できれば , "E FS_SUCCESS" が返ってきます。 "ー叩 en ( ) " は "indirect. h" に定義されている関数です。 Ⅳ I*D. H の中でインクルードされています。 また , "ilibIL ' は "sys/process. h" の中で定 義されている IL です。 "sys/process. h" は V er. 0.08 以前では、、 D. H の中でインクルー ドされていませんので , 自分でインクルー ドする必要があります。 WTD 北を常駐 wtdll . stay ( 次に WTD の制御用パッフアを heap 領 域に常駐させます。正しく常駐すれば 0X00 00 が返ってきます。阪 0001 が返ってきた場 合 , それは he 叩領域が足りないということ TabIe 6 刑」 TabIe 5 int int int i nt int int int Char Char Char int int Cha 「 Cha 「 int Cha 「 Char Char Cha 「 int Char Char Char Char char int int Char Char Cha 「 int int int Cha 「 Cha 「 Cha 「 int int char パート共用構造体 / 内部変数 ( 一部 ) メンバ Flag ProgramOffset P 「 ogramSegment MusicOffset MusicSegment EffectOffset EffectSegment PcmOffset PcmSegment OIdIntvecto 「 [2] VolumeMusic VolumeEffect VolumePCM Tempo TempoCounter Channel FlagFIat FIagSha 「 p FIagTai FIagControl メンバ バート個別構造体 / 内部変数 ( 一部 ) EffectPart MusicPart VoIumeUpDown GateTimeStepFirst GateTimeStepLast GateTime8 OctaveSet Pa n BendDetune Bend BendSet KeySetC8] Key KeyShift LengDefauIt Leng SweepTime Swe e p Level Program LoopCountPointe 「 LoopCountC8] Add 「 ess 説明 フラグ プログラムオフセットアドレス プログラムセグメントアドレス 演奏テータオフセットアドレス 演奏テータセグメントアドレス 効果音オフセットアドレス 効果音セグメントアドレス PCM ポイスオフセットアドレス PCM ポイスセグメントアドレス 常駐前の割り込みべクタ ソフトウェア音量・演奏 ソフトウェア音量・効果音 ソフトウェア音量・ PCM7fi' イス テンボ テンポカウンタ 演奏のバート数 効果音のバート数 説 演奏データ全体のフラグ タイフラグ シャープフラグ フラットフラグ チャンネル・トラック番号 現在演奏しているアドレス 残りループ回数 明 になります。常駐したら , 再生のための準 備はこれでおしまいです。 データの再生 wtdll . 齟 s 土 c 円 ay (char far *Music) , ループのネスト用スタックポインタ 音色番号 スウィープ値 スウィープ時間 現在の音長 音長省略時の音長 移調値 発音すべきキーコード 出力したキーコード 出力した周波数 出力すべき周波数 ディチューン値 バンボット 出力したオクターブ " 0 " , " U " コマンドで指定した数値 q" コマンドで指定した数値 u" コマンドで指定した数値 相対音量の増減値 この関数の引数には演奏データのアドレ スを指定します。なお、引数の型が "charf ar * " になっていますが ここに間違えて ファイル名を書かないように気をつけてく ださい。 1 10 C MAGAZINE 2001 12
第 9 回 構造体 , 共用体 自身と同じ型 ) の変数を持っことはできま suu 型の構造体を宣言し , Student 型の構造 始めに せん。この場合 , Student 型構造体の中に 体内のメンバとして組み込みます。このよ さらに Student 型構造体があるという状態 うにすると , 概念上すっきりとしたデータ が無限に続くため ( 鏡に映った自分を鏡に みなさん , こんにちは前回はプリプロ の振り分けが可能です。 たとえば kokugo を参照する際には , 「 St 映すことと同じような現象が起きるため ) , セッサと構造体について説明しました。今 回はさらに構造体に関するさまざまな取り udent の Tensuu の kokugo 」といった考え方 領域が無限に必要になってしまいます。そ のため , コンパイル時にエラーとなります。 扱いについての説明を行います。構造体を で参照を行います。つまり , Student. Tens またポインタ表現を使用する場合には Fi 使いこなすことで , データの関連性などが uu. kokugo ということになります。ただし , g. 2 のように使用します。 Fig. 2 の場合 , ① Student 型のメンバとして Student 型 ( 自分 わかりやすいプログラムを組むことが可能 です。また , 構造体とよく似た形を持つ共 用体についても説明します。 ネストされた構造体の例 ・生徒の点数の管理① struct Student { 構造体 (Part 2 ) int COde ー cname [ 20 char int kokugo; 前回は構造体の概念と使用方法や書式に int sansu; rika ー int ついて説明しましたが , 今回はこの構造体 int e1go ー の具体的な使用方法について , さらに説明 します。構造体を使用した , 基本的なデー タ構造の使用方法も紹介します。 ネストされた構造体 ネストされた構造体 ( ポインタ表現 ) の例 構造体のメンバには , ほかのデータ型の ・ポインタ表現① 変数と同様に , 構造体型の変数を使用する struct TestTensuu { 土 n し kokugo; ことができます。たとえば , Fig. 1 のよう int sansu; int rika; に使用します。 int Fig. 1 では生徒のテストの点数を管理す struct StudentI{ るために構造体を使用していますが , Fig. int COde ー char cname[ 20 1- ①では構造体に各科目の点数をそのまま struct TestTensuu tensuu; メンバとして宣言しています。しかし , テ struct Student1 *c lassl; ストの科目だけを 1 つのグループにしたほ 上記の構造体を使用した場合の参照 うがデータの分類を簡潔に表現することが classl->tensuu. kokugo = 8 できるので , Fig. 1- ②のように , まず各科 c lassl->tensuu. sansu = 50 ー 目の点数をメンバとして管理する TestTen 小薗三典 / 中井信ム 今回は , 前回に続いて構造体について ! それと共用体についても紹介した いと思います。 0 一 11 ・生徒の点数の管理② struct TestTensuu { int kokugo; 土 n し sansu; 土 n し rika ー int elgo; struct Student { int COde ー char cname [ 20 struc セ TestTensuu Tensuu ー Fig. 2 ・ポインタ表現② struct TestTensuu { int kokugo; 土 n し sansu ー 土 n セ rika; 土 n セ elgo; struct Student2 { int COde ー char cname [ 20 struct TestTensuu *tensuu; struct Student2 *c ー ass2 ー 上記の構造体を使用した場合の参照 c lass2->tensuu->k0kugo = 8 の C ー ass2—>tensuu—>sansu = 50 ー 53 Getlnto C World ! !
スタートアップ Ja a Java 言語事始 List 2 paintComponent() は何回も呼び出される (DrawingSample2. java より ) //DrawingSample2. java より paintcomponent( ) を抜粋 public void paintComponent(GraphicS g){ 〃スーパークラスの同名メソッドを呼び出す。 super. paintComponent ( g / / 青い〇を描く g. setCoIor(Color. blue); g. f 土Ⅱ Ov 引 ( 10 , 10 , 100 , 100 / / 黄色い△を描く g. setCoIor(Color. yellow); int[l xPoints = { 70 , ユ 20 , 170 int[l ypoints = { 97 , 10 , 97 g. fillPoIygon(xPoints, ypoints, 3 / / 赤い口を描く g. setcolor(color. red); g. fil IRec し ( 130 , 10 , 100 , 100 / / 対角線に白い直線を描く / / ウインドウの大きさを変更すると追随する g. setCOIor(Color. white); int x = getsize( ) . w 土 d し 土 n に y getSize( ). height; g. drawLine(O,O,x,y); / / メソッドが呼び出されるごとにコンソールに表示する system. 0uしPて土n凵れ( ”土北 co ponent ( ) が呼び出されました” TabIe 1 G 「 aphics で使える描画メソッド ー Graphics 内容 メソッド drawRect もう少しこの構造について見てみましょ 四角形 fil 旧 ect う。 paintComponent ( ) には引数として Gra drawOval 楕円 ( 円を含む ) phics 型のオプジェクトが渡され , 描画は fillOval それに対して行います。 drawRoundRect 角が丸い四角形 fil 旧 oundRect Gr 叩 hics は描画のためのクラスです。画 d 「 aw3DRect 材と画面を兼ねたようなものだと思ってく 影付き四角形 fi113DRect ださい。これはシステムによって paintCom d 「 awA 「 c 楕円弧 ( 円弧を含む ) ponent() に与えられます。自分で生成する fillArc drawP01ygon ことはありません。システムが画面の一部 多角形 fiIIPoIygon を切り取って paintComponent() に渡すと 直線 drawLine いうような感じです。 折れ線 d 「 awPoIyIine d 「 awSt 「 ing ー paintComponent での処理 文字列 d 「 awBytes drawChars ListI での実際の処理を見てみましよう。 イメージ drawlmage まず super. paintComponent ( ) を呼び出して 示した画面を Fig. 3 にあげておきます ( ソー います。これは背景を描画するものです。 次に描画メソッドです。最初は g. fiIIOvaI スコードは付録 CD-ROM に収録の Graphics paintComponent() をオーバライドする場 ( ) で円を描いています。その後は色を変え Sample.java) 。 合 , 基本的には書いておいてください。 Li て描画する処理の繰り返しです。 描画メソッドには Table 1 のような種類 st 1 ではコンストラクタで背景色を黒にし ー AWT との相違 ています。 super. paintComponent ( ) がない のものがあります。この中で drawXx ( ) と ここまでの説明を読んで , 昔 Java を習っ と黒になりません。 あるのは図形の輪郭線を描き , ⅱ 1 Ⅸ x ( ) と た人は「描画するにはたしか paint ( ) メソッ 次に描画する色を変史しています。 Gra なっているものは中身を塗りつぶすもので phicsg に対する描画は , 筆に色をつけ , そ ドをオーバライドするということだったけ す。 1 つ 1 つ説明していてはきりがないの の後に図形を描くという順番で行います。 で , 使うときには API ドキュメントを見て ど・・・・・」と思うかもしれません。 AWT で描画をする場合は paint ( ) メソッ g. setColor() で青色をセットしました。 ください。参考までに , これらを使って表 コンポーネントへの描画の例 S 印叩に Fig. 2 対角線がフレームの動きに追随している 新 8 艸 ingS 印 1 回 e2 同ロ・ NOrawingSa. , ロ回ロ Fig. 3 G 「 aphics への描画の例 ロ〇ロロ U ▽ / Java Graphics スタートアップ Java 65
List 8 計算プログラム case FA—diV : iOAcc / = theNum; b て eak ー defau 比 : return fa ー se ー theLastStat = FA—num; return true ー # incl ude く iostream> #include <vector> #include く string> # incl ude <cstdl ib> static VOid TestMain(void) StrArrayType theStrVect ー FormuIaDivider(theStrVect, ”ー 123 十 456 / ー 7.89 ″ StrArrayType: :const—iterator theltr; double theAcc; / / 計算結果保持 for(theltr = thest て Vect. begin( theltr ! end( = theStrVect . if( !FormulaAnalize(theAcc,*theItr) ) { cout くく” * e てて or **n ″ return ー class NumberToken : PUblic TokenCIass { / / 数値 double mNumber ・ pub lic : NumberToken(double inN = 0 ) { mNumber = inN; } virtual double Number( ) const { return mNumber; } v 土てし u 引 bo 引 IsNumber( ) const ( return true; } class PIusToken : public TokenClass { / / 十演算 public: virtual doub Cal c ( doub 地 inA , doub 厄 inB ) const { return inA 十 inB ー } virtua ー b00 ー I sOperator ( ) const { return true ー } トークンクラス theltr 十十 ) { class TokenCIass { public: TokenClass( ) { } -TokenClass( ) { } virtua ー ・ } / / 計算をする virtual double calc(double,double) const { return 0 , / / 値を返す double Number( ) const { return } virtua ー / / 演算子か ? bO 引 IsOperator( ) const { return false; virtua ー / / 数値か ? virtual bo 引 IsNumber( ) const { return false; くく answer くく theAcc くく endl; enum FA—t { FA—nothing, 〃まだ計算がない / / 十演算をする FA—P lus , / / ー演算をする FA—minus, / / * 演算をする FA—muIt, / / / 演算をする FA—diV , / / 数値を投入された FA—num トークンクラスの派生クラス ( + 演算子のみを抜粋 ) 分解されている文字列から計算を行う 土 oAcc : 計算結果を格納する変数 inst て : 分解した文字列を投入する 戻り値 : true なら OK , fa e ならエラー b00 ー FormulaAnalize(double& ioAcc,const string& inStr) static FA—t theLastStat = FA—nothing; / / 演算子記録 if(instr = = ″十″ ) { theLastStat = FA—plus; }else if(inStr = = theLastStat = FA—minus ー }else if(inStr ー theLastStat = FA—mu ーセー )else if(inStr = theLastStat = FA—div ー )else{ double theNum = atof(instr . c-str( ) switch(theLastStat) { case FA—nothing : iOAcc = theNum; break; case FA—p ー us : iOAcc 十 = theNum; break; case FA—mult: iOAcc * = theNum ー break; 演算子トークンクラスの別の実装例 cl ass OperatorToken : pub lic T0kenC lass ( public: に } virtua 1 b00 ー I sOperator ( ) const { return true 引 as s 円 usToken : pub lic OperatorToken { / / 十演算 public: virtual double calc(double inA,doubIe inB) const { return inA 十 inB; } ひとかたまりのトークンにまとめてしまう Formu ー aDivider( theStrVect, 実装をしたからだ。そこで , List 7 のように ”ー 123 * ー 456 ” ) 演算子に限っては連続させないように変更 してみる。 初歩的な計算プログラム List 7 の段階で , 初歩的な計算処理用の 文字列分解関数である FormuIaDivider がで きあがったが , いうまでもなく単に文字列 に分解しただけなので , 計算処理は別途作 たいなら , List6 のようなプログラムのほう がいいと思われるが , べつにそこまで神経 とすると , 質にやらなくてもいいじゃないかという意 見も出てきそうである [ 注 3 ] 。 [ 注 3 ] 実際のところ , このあたりはどうでも いいだろうと筆者は思うのだが , 明確さに だわる潔癖症とでもいうべきかピリピリする 人か本当にいるのだ。注思 , 注思。 演算子の連続を認めない さて , List6 では , 1 22 C MAGAZINE 2001 12 ー 123 と分解されずに ー 456 ー 123 456 になってしまう。文字の種類が連続したら
配列を利用した例 ( 1 ) int the 島 if( !thelnited) { static char に heDT [ 256 static 土 n セ thelnited; / * static FDK—t DecideFDK ( char inCh ) theDT[ ' + ' ] = theDT['-'l = theDTt ' * ' ] = theyrt 初期化検出フラグ * / return theDT[ inCh ]; thelnited = theDT[theI ] = FDK—number ・ for(thel = ' 0 thel く = ' 9 thel 十十 ){ ワ 勹 = FDK—operator 引 C + + 化する FO て m ⅵ土 vide て ( thest て vect , ″ 123 十 456 / 7.89 ″ StrArrayType theStrVect; static void TestMain ( void ) void FO て m 猷 aDivider ( StrArrayType& outSV ons し char* typedeEpegtorkgtringy 阯て耻て@ンの・鷯、 using namespace std ー #incl ude く string> #include く vector> #inc lude <iostream> ・ StrArrayType : : cons し一土 te て atO て the ltr ー inText lSt 配列を利用した例 ( 2 ) for(theltr = theltr ! = theStrVect.end( const char* theptr = thel tr->c—str ( cout くく thePtr くく endl; statåc FDK—t DecideFDK(char inCh,int inKind) theltr 十十 ) ( static FDK—t DecideFDK ( char inCh , int inKind ) static 土 n セ thelnited; / * static char セ heDT [ 256 if(inKind ! = thelnited) { 土 n セ thel; 初期化検出フラグ * / theDT[ ' ー勹 = (inKind = = 1 ) ? FDK—operator : FDK—number ・ theDT[ ' 十勹 = theDT[ ' * 勹 = theDT['/ 勹 = FDK—operator; return theDT[inChl; thelnited = inKind ー theDT[theI] = FDK—number; for(thel = ' 0 thel く = ' 9 thel 十十 ){ theDT[ ' . 勹 = FDK-number; にする。 上記の作業をいっぺんに行うのはめんど うなので段階的に C + + 化を行ってみよう。 ただし DecideFDK は今回作成したノヾージョ ンに変更してある ( List5 ) 。 C + + 化といってもオプジェクト指向がど うのこうののややこしい世界には突入して いない。 List5 について若干 , 補足をして おこう。まず , List 5- ①の部分だが , 文字 列を分解した結果は固定した個数にはなら ないので , 可変配列である vector を使って いる。また文字列も string を使うことにす る。だったら直接 , vector<string> を記述す ればいいのではないかと思うところだろう が , vector 以外のコンテナを使う可能性も ' では typedef で文字列格納コン : 考え , こ テナとして StrAr ・ rayType があるかのように 宣言しておく。 次に注目してもらいたいのは , List 5- ② Fig. 1 国可可国国国ロ 数 数 1 数 数 2 ・一② 演 : 数 6 <—元の文字 theEnd = --inText; while(theFDK = = DecideFDK(* 十十 inText,1)){ } 。 if(theFDK ! = FDK-unknown) ( FDK—t theFDK = DecideFDK(*theBeg,2); const char* theEnd = inText• const char* theBeg ま inText ー whiIe(*inText) ( void ー 0 て m ⅲ aDivider ( StrArrayType& outSV , const char* inText ) xeturn 8 いは 0 ー cas い印に只新 e はれ 0 矼ノ / ( 、 $t 北虻が必要い・ 十十 inText ー トークン変換の原理 数 数 3 演 数 数 数 5 数 数 6 <—文字の種類を判別する ・ < -- -- ・切れ目を見つける ・ 7----- 切れ目でトークンを切り出す 切れ目は文字の種類が変化する場所である の部分で関数 FDK ー t DecideFDK ( ) の返り 値にしている static_cast く FDK t> (theDT [i nCh]) だ。 C + + の場合 , 型の区別は厳格に なるので当然 , 違う型を返り値にできない 場合も出てくる。キャストをしないといけ ないが , せつかく C + + を使うのだからこ では static_cast を使うことにする。 List 5- ③の , outSV. push—back ( string ( theBeg, theEnd 十 1 ) 1 20 C MAGAZINE 2001 12