printf - みる会図書館


検索対象: 月刊 C MAGAZINE 1993年9月号
40件見つかりました。

1. 月刊 C MAGAZINE 1993年9月号

るまいに。ーどうやら文章を書くのもおっく 間にとってめんどうなことはパソコンにや 【追伸 ( 丹羽信夫より ) 】 うな人物が置いていったに違いないという らせるにかぎる。そこで , C の出番だ。 推理がたつな。送り主は , 文字から判断し 以上 , なんとなく話カ坏自然だがく flowe 皆さん , ロバート氏との勉強会に参加し て女だろうが , 地獄耳の生徒かもしれんし , r. c > を作った。 ロバート・佐々木 ませんか ? 氏の勉強の助けとなるプログ あるいは先生かもしれん。まあ , とにかく 最近の学生は勇気があるというか行動力 ラム ( 迷走プログラム大歓迎 ) に , 楽しい解 説をつけてぜひ送ってください。応募作は 事情が飲み込めないのだが , せいぜいこの があるというか。それともロバート氏の作 付録ディスクまたは誌面にて可能なかぎり 花を養ってやることにした。というのも変 り話なのカ 紹介し , 掲載作には薄謝を進呈いたします。 な言い方か。 ところて、 , この簡易入力〃というやっ , 送り先 . C マガジン編集部 ちょうど , 孫の吉夫の夏休みの理科の宿 フームて、ある。選択肢の中のヾ以上のどれて 「迷走プログラミング係 題で疲れていたところだ。吉夫にこの花の もない〃っていうのが , そのまま記録されて または , 観察記録をつけさせることにすればよいわ しまうのもシュール。しかし , 改造すれば / ヾソコン通信 NlFTY—Serve PFD01142 ( 丹羽信夫 ) けだ。転んでもタダでは起きないというの 、、カプト虫観察メモクにもなるし , 、、アプラゼ 必ず , 本名と住所をお知らせください。 が小生のモットーだ。言い方が変だが。 ミ観察メモクにもなる。利用価値は高そうだ なお , 応募作には手を入れる場合があり 吉夫はめんどうなことは嫌いだから , な ( などと書くと皆さんにしかられてしまうだ ますのでご了承ください。 るべく簡単に観察記録をつけさせたい。人 ろうか ? ) 。 FLOWER. C 0 List 2 List 67 : VO i d k i 「 Oku2 ( ) 68 : { 69 : int sel; char in ut[HAX]; char *sb= { ”変化なし” 73 : ”かわいい花がさいた” 74 : " 枯れてしまった ". " 水をやるのを忘れてしまった ". ”成長が楽しみだ” 77 : ”いいにおいがした” ”以上のどれでもない” / * 文字列を追加するときは N 乢しの上の行に追加すること . N 乢い ; printf("- p 「 intf ( " 観察記録の選択入力メニュー \ n " ) : p 「 intf("- 84 : 85 : 86 : 88 : 89 : 90 : 92 : 93 : 94 : 98 : 99 : void ichiran() 100 : F ILE * i nf ⅱ e. 101 : char Iine[HAX]; 102 : 103 : infile=fopen("flover. dat" 104: if (infile==NUL い return. 25 : 106 : 107 : vhile (l) 108 : 109 : fgets(line. MAX. infile); if (!feof(infile)) printf("%s". line); else break, ll 2 : 119: nain() 0 : i n t 用 : l; 12 い 肥 2 : vhile (n!=EXIT-MENU) 肥 3 : 124 . n=nenu() : 5 : svi tch 価 ) 6 : :kiroku();break, case ー kiroku2();break. case 2 : 128 : case 3 : ichiran();break, 129 : default :break. 130 : 131 : 132 : 1 3 3 : 134 : } 1 : # i ncl ude く s td i 0. h > 2 : $include く time. h> 3 : n cl ude く s td ⅱ b. h> 4 : $include く string. h> 5 : $define EXIT_MENU 9 6 : $define MAX 256 7 : 8 : int nenu() i nt -1 : char input[80] : vhile ( の { printf( ” yn ” ): printf("- p 「 int 「 ( " \ t 花の観察記録 \ n " ) : printf("- p 「 int 「 ( " l. 花の観察記録を記入する \ n " ) ; 18 : p ⅵ nt 「 ( " 2. 花の観察記録を選択記入 \ n " ) : printf( ” 3. 花の観察記録を一覧する \ n ” ) : 20. printf("Xd. 花の観察記録を終了する \ n ". EX - MENU ) : printf( ” 22 : gets(input): 23 : n=atoi(input); 24 . 25 : 26 : return 第 : 28 : 29 : void save(char *input) t i ne_ t t i 「 : 32 : struct tm *t: 33 : FILE *outfile, ou げⅱ e : 「叩 en ( " ロ 0 記「 . dat ". "a + "): / * 追加史新モードでオープン * / 35 : i 「 (outfile==NUL い return; 36 : 37 : tiner : tine(NULL): / * 時刻の取得 * / 38 : 39 : t : localtime(&tiner): / * 日時を構造体に変換する * / 40 : t->tm_hour, t - 〉朝 nin); if ( t - 〉 t 第 - hou 「く : 12 ) / * 午前なら * / fprintf(outfile. " % d 月 % d 日午前 % d 時 % d 分 : ". t - 〉 t 町則 n + 1. t->tm_mday. 42 : 43 : e ー se fprintf(outfile. " % d 月 % d 日午後 % d 時 % d 分 : ". t - 〉い工 on + l.t - 〉 day. 45 : fprintf(outfile. " % s \ 心 . i 叩 ut); 46 : fclose(outfile): い > t 第 - hou に 12. t->tn_nin) : 48. printf("\n ” ): 49 : p 「 in け (" 罅 # 料 n つ・ printf(" 記録しました h")!„ 50 : printf("la"); 52 : 55 : void kiroku() 56 : { cha 「 i 叩 ut[HAX) : 57 : 58 : 59 : do 60 : 62 : 63 : 64 : 66 : / * メニューを終了する数字 : 9 * / / * 入力文字最長 * / do printf("X2d. XsYn", i + 1. s[i]); printf("- p 「 intf ( " 上の中から選んでください . > " ) : gets(input); sel=atoi(input); } vhi 厄 (sel <I Ⅱ se い i ) : sel=sel-l; save(s[sel]); / * 選択した番号は実際の配列番号より 1 多いので * / fclose(infile); printf("YnVa**** 以上です * 料心 ) : 参 p 「 intf ( " 観察記録を入力してください . gets(input); } vhile (strlen(i 叩 ut)==O); save(input); return 0 : 丹羽信夫の迷走プログラミング 153

2. 月刊 C MAGAZINE 1993年9月号

MS -0 非公開情報 0 ist DOS フラグのアドレスの取得 DOS ファンクション 32h のテストプログラム 1 : #include く dos. h> 2 : #include "dosfunc. h ” defined( LSI-C ) 5 : char -asm-v(); 6 : #endif 7 : 8 : void far *dos_getindosadr( void ) 10 : # i f defined( -MSC_VER ) ah, 0X34 _asm mov _asm int 0X21 -asm xchg ax, bx dx, es _asm mov 15 : #e ⅱ f defined( --TURBOC_ _AH ニ 0X34 : geninterrupt( 0X21 ) : 18 : return( MK-FP( _ES, 19 : $e ⅱ f defined( LSI_C ) 20 : -asm-v( "Ytint 21hYn , 0X3400 ) : -asm-v( "\txchg ax, bx\n" ) ; 22 : -asm-v( "\tmov bx, es\n" ) ; 23 : #else 24 : union REGS regs; 25 : struct SREGS segs; 26 : regs. h. ah ニ 0X34 : / * InDOS フラク・のアト・レスの取得 28 : intdosx( &regs, &regs, &segs ) : 29 . return( MK-FP( segs. es. regs. x. bx ) ) : / * InDOS フラ〆のアト・しス 30 : $endif 号 番 0 + し 0 ーよワ 0 リ - ・ -0 内ー 8 0 ーーり 0 ワ 0 - ・の 0 っー 8 0 ー 4 ワ 0 00 -4 ・ - -0 00 ー、 8 0 ーーワ 00 -4 ・ LO 「ー -8 0 》 0 1 より 0 っ 0 4 ・ー 8 0 1 ・ワっ 0 -4 ・ / * LSI C ー 86 unsigned drive; struct dpb-t sdpb; char *pargv * 十十 argv : i 「 ( argc く ! ( isalpha( pargvC 0 ] ) & & pargv[ 1 ] fprintf( stderr, " 使用法 test32 d:YnYn ” d : ト・ライフ・名 ( A : ~ Z : ) Yn ” ) : toupper( pargvC 0 ] ) / * ト・ライフ・番号 ( 0 : if ( dos-getdpb( drive, &sdpb ) ) { ” DPB の取得に失敗しました . \ n " ) : fprintf( stderr, return( 1 ) ; printf( " ト・ライフ・ % c : \ t ユ : ット番号 : % 02X \ t \ t メテ・イア・ディスクリフ。タ・ハ・仆 sdpb. drive + ・ A' / * ト・ライフ・番号 / * ユ : ット番号 sdpb. unit, sdpb. media ) : / * メテ・イア・テ・イスクリフ。タ・ハ・仆 printf( "FAT 数 : % u \ t セクタ / FAT : % 3 uY t \ t ” ”ハ・仆 / セクタ : % 5u \ t 子約セクタ数 . %u\n' / * FAT 数 sdpb. fat-count, / * セクタ数 / FAT sdpb. fat_size, / * ハ・仆 / セクタ sdpb. sector_size, sdpb. first-fat ) : / * 予約セクタ数 ( FAT の開始論理セクタ番号 ) * / printf( ”全クラスタ数 : % 5u \ t セクタ / クラスタ : % 3u \ t \ t ハ・仆 / クラスタ :%5uYn" / * 全クラスタ数 sdpb. max_cluster sdpb. cluster_mask + 1. / * セクタ / クラスタ sdpb. sector-size * ( sdpb. cluster_mask + 1 ) ) : printf( " ルート・テ・イレクトリ領域開始論理セクタ番号 : %04XYt" ”ルート・テ・イレクトリエントリ数 :%6uYn ” sdpb. dir-sector, sdpb. root_entries ) : ”テ・一タ領域開始論理セクタ番号 printf( sdpb. first_sector ) : if ( sdpb. free_cluster = = 0xffff ) pr i n t f ( " 不明” ) ; printf( "%5uYn" sdpb. free-cluster ) : / * 未使用クラスタ数 printf( " テ・ハ・イス・ト・ライハ・ヘのホ。インタ . % Fp \ t 次の DPB を指すホ。インタ . %FpYn ” / * テ・ハ・イス・ト・ライハ・ヘのホ。インタ sdpb. pdriver-adr, sdpb. pnextdpb ) : / * 次の DPB を指すホ。インタ printf( " 最後に変更したクラスタ番号 : % 04X \ t \ t テ・イスク交換を示すフラク・ sdpb. nextfree, / * 最後に変史したクラスタ番号 sdpb. f i rst_access ) ; / * テ・イスク交換を示すフラク・ return( 0 ) : / * MS-C 6. 00A / 7. OA / * InDOS フラク・のアドレスの取得 / * es:bx = InDOS フラク・のアト・レス / * Turbo C/C + + , Borland C + + / * InDOS フラク・のアト・レスの取得 / * lnDOS フラのアト・レス / * LSI C ー 86 / * es:bx InDOS フラク・のアト・レス / * MS-C 5. 10 return( 1 ) : % 02X \ n ” ist DOS ファンクション 34h のテストプログラム / * ルート・テ・イいトリ領域開始論理セクタ番号 * / / * ルート・テ・イレクトリエントリ数 . % 04X \ t 未使用クラスタ数 / * テ・一タ領域開始論理セクタ番号 $include く stdio. h> #include "dosfunc. h ” 2 : 3 : 4 : i n t ma i n ( VO i d ) dos-getindosadr() ; 6 : char far *pindos 7 : printf( " InDOS フラゲのアト・レス : %FpYn" 8 . return( 0 ) : 9 . . % 02X \ n ” / * InDOS フラク・のアト・レスの取得 * / pindos ) ; コラム 5 あぐらをかいてパソコンをつつくと のつけ根が , 晩に帰るとき痛みを感じました。風呂に入って寝れば直 余談になりますが , 筆者はパソコンをつつくときには , 椅子に座っ てあぐらをかいた状態て、キーポードをたたいています。 るだろうと思ったのて、すが , 翌朝になると立ち上がるのに苦労するほ こ数か月データベースゾフトのプログラミングを行っているのて、 どの痛みになりました。 原因は長時間 , あぐらをかいていたことくらいしか考えられず , 非 すが , このデータベースソフトはバグが非常に多く , バグを回避する 常に不可解て、した。この痛みは患部に湿布をしたら , 数日て取れまし プログラミングに神経と多くの時間を費やして精神的にまいっていま たが , 筆者と同じように椅子にあぐらをかいてパソコンをつつく癖の す。その影響かどうか , ある日 , 突然 , 足のつけ根の部分が痛くなり ある人は注意したほうがいいて、すよリ ました。足をひねったというわけてはなく , 朝には何ともなかった足 70 C MAGAZINE 1993 9

3. 月刊 C MAGAZINE 1993年9月号

LiSt 4 54 : 56 : 58 : 59 : 60 : 62 : 63 : 65 : 66 : 68 : 69 : 72 : 75 : 77 : 82 : 84 : 85 : 86 : 88 : 89 : 90 : 92 : 93 : 94 : 95 : 96 : 97 : 98 : 9 9 : } 10 0 : 10 2 : 10 3 : 10 4 : 105 : 106 : 107 : 108 : 109 : 1 10 : 113 : 1 17 : 120 : 121 : 122 : 123 : 12 4 : 12 5 : 126 : " 使用法 : list4 ファイル名 \ n " ) : 価格ニ %f\n ex i t ( 1 ) : fprintf(stderr, : NULL) { if (fp fopen(argv[l], ex i t ( l) ; fprintf(stderr, if (argc く 2 ) { int item_no; FILE *fp; void get-data(int argc, 5 5 : / * 入力ファイルからデータを読み込む * / char *argv[]) ” %s がオープンできません \ n " ) : fscanf(fp, "%d" &n-item) ; fscanf(fp, &limit); / * 品物の種類の数 * / / * ナッブザックのサイズ * / void print-data(void) for ( i tem_no ニ 0 ; item-no く n_item; item_no 十十 ) { i tems[item-no]. i tem-no item_no; / * 品物の値段とサイズを読み込む * / &items[i tem_no]. price, fscanf(fp, &items[item-no]. size); fclose(fp); int item_no; dou い e 町 p ; i tem-t *i temp, printf(" 品目 : 数量単価重量 \ n " ) ; for (item_no 0 : i tem-no く n_item; item_no 十十 ) { i temp : &i tems[i tem_no] : printf("%4d: %4d % 8. 3f % 8. 3f \ n ” i temp->item_no, 101 : / * qsort 用の比較関数 * / i temp->n, itemp->price, 十ニ itemp—>size * itemp—〉 n; p 十ニ itemp->price * itemp- 〉 n; printf("Yn サイズ : %f 総重量 = %f ⅱ m i t, int conp(const VOid *p, const VOid *q) itemp->size) : / * qs or t との型合わせのためのキャストが頻出している * / / * 書法上好ましいとは言えないが , どこかでキャストす * / / * ることは避けられない * / ((item-t *)q)->price / ((item-t *)q)->size; ((item-t *)p)->price / ((item-t *)p)- 〉 size double x if (x くの return e lse i f (x > 0 ) return e ー se return 0 : ー 1 ; 1 : / * 逆順のソートなので * / / * ー 1 ではなく , 1 を返す * / int main(int argc, char *argv[]) get-data(argc, argv) : / * データの入力 * / / * サイズ当たりの価格の高い順にソートする * / qsort(items, n-item, sizeof(item-t), comp); knapsack() : print-data(); return 0 ; / * ナッブザック問題を解く / * 解の表示 * / 実践アルコリ路解法のテクニック ナッブザック問題をまずは強欲戦略により 解いてみることにします。 強欲戦略アプローチ くすることがて、きます。したがって , 単純 ほうが , ナッブザック内の品物の値段は高 てもかさばらないダイヤをたくさん詰めた 高いもののかさばるのて、 , 値段は少し安く 円のダイヤだとします。壺のほうが値段は ザックに詰めるものが 100 万円の壺と , 90 万 て、す。たとえば , 極端な例て、すが , ナップ 純に「高いものから順に詰める」のて、は駄目 しかし , ナッブザック問題の場合は , 単 きます。 ッブザック間題を ( 近似的に ) 解くことがて、 略 ) を紹介しました。類似の方法により , ナ 「高いものから順に食べる」という食べ方 ( 戦 その満足度を最高にするための手段として , たとえば , 立食パーティに招かれた人が , 性を利用した間題解決のアプローチて、す。 強欲戦略とは , 「欲張り」という人間の本 この手法については , 以下て、詳しく説明 ることが知られています。 により厳密解を効率よく求めることがて、き 題にある種の制限をつければ , 動的計画法 も十分なケースが多いと思われますが , 問 さて , 実用上は , この程度の近似解法て あたります。 とになっていますが , 関数 comp ( ) がそれに 数として比較用の関数のポインタを渡すこ の配列をソートしています。 qsort ( ) は , 引 ために , ライプラリ関数 qsort ( ) により品物 「サイズ当たりの金額の高い順」を実現する ログラムを List 4 に示します。 List 4 て、は , ていることになります。 List 3 に基づく C プ 選び , ナッブザックに詰められるだけ詰め ズ当たりの金額の高いものから順に品物を づくアルゴリズムは非常に単純て、す。サイ に示します。見てのとおり , 強欲戦略に基 この考え方に基づくアルゴリズムを List 3 額」を基準にするとよいて、しよう。 な「重量」て、はなくて , 「サイズ当たりの金 実践アルゴリズム戦略解法のテクニック 89

4. 月刊 C MAGAZINE 1993年9月号

List 6 すが , ナッブザックの中身を調べたのて、は , わざわざ表を作る意味がありません。「サイ ズ = 6 」のナッブザックまて、は最適な状態が て、きあがっているわけて、すから , これを利 用します。 「サイズ = 7 」のナッブザックにサイズ 3 の 品物を加えて , 最適な詰め方が変化するか どうかを調べるには , 以下のように考える とよいて、しよう。仮にこの品物を加えてよ り金額の高い詰め方がて、きたとします。す ると , その詰め方から現在着目している品 物を取り除ければ , サイズ 4 の最適な詰め方 になっているはずて、す。そして , その詰め 方は , サイズ 4 のナッブザックと一致してい るはずて、す。 これを裏返して考えれば , サイズ 4 のナッ ブザックに現在の品物を詰めたものが , 最 適な詰め方の候補になることがわかります。 この詰め方と , 現在のナップサックの詰め 方を比較して , よりよい詰め方を選べばよ いのて、す。 Fig. 2 の例て、は , 新しい品物を加えたほう がより金額の高い詰め方になっているのて、 , 元の詰め方と置き換えることになります。 以上を繰り返すことにより , 最終的にはナ ッブザック間題の最適解が得られます。 上記の手順は , 小さな問題の解から大き な問題の解を合成していますから , その面 て、は分割統治法と似ています。しかし , 分 割統治アルゴリズムがトップダウン的に間 題を分割していくのに対して , 動的計画法 は小さな間題の解から徐々に問題のスケー ブザックの解を合成しています。したがっ ルを大きく積み上げていくのて、 , ボ、トムア て , どの品物を付け加えたかを覚えておけ ップ的て、あるといえます。 ば , 最終局面から逆算して , 品物の詰め方 ところて、 , Fig. 2 て、説明したアルゴリズム 最適化問題をトップダウン的に解くと , を求めることがて、きます。この様子を Fig. 3 は , 最適解に対する品物の金額は求められ 同じ部分問題を何度も解くことになるケー に示しました。 以上て、検討した内容から , 動的計画法に スが多くあります。フィポナッチ数列の再 ますが , 品物の詰め方はわかりません。詰 め方がわからないと役に立たないケースが 帰的アルゴリズムは , 最適化問題とはいえ よってナッブザック問題を解くアルゴリズ 多いのて、 , 品物の詰め方を再現する方法が ませんが , 上記と同じ現象により実行効率 ムは List 5 のようになります。 List 5 は , ア ルゴリズムの始まりの部分が上記の説明と が悪くなることを見てきました。一方 , 動 必要になります。 Fig. 2 に戻って見直してみると , 動的計画 的計画法は , 小問題の解を一覧表にするこ は若干異なっています。本文の説明て、は , 法のアルゴリズムては , 小さなナッブザッ とから , 同じ間題を何度も解くという不効 「 1 種類だけの品物を詰められるだけ詰める」 率はありません。 クに品物ひとつを付け加えて , 大きなナッ と書きましたが , List 5 にはその部分があり 79 : 80 : 82 : 83 : 85 : 86 : 88 : 89 : 90 : 92 : 93 : 95 : 96 : 97 : 98 : 9 9 : } 100 : 101 : void print-data(void) 10 2 : 103 : 104 : 105 : 106 : 107 : 108 : 109 : 1 10 : 1 12 : 1 15 : 1 16 : 1 18 : 119 : main(int argc, char *argv[] ) 120 : i n t 121 : get-data(argc, argv) : 122 : 123 : knapsack ( ) : 124 : print-data(); 12 5 : return 0 ; 126 : FILE *fp; int i tem_no; if (argc く 2 ) { fprintf(stderr, " 使用法 : list6 ファイル名 \ n " ) : ex i t ( l) : fp ニ f 叩 en(argv[l], if ()p = NULL) { fprintf(stderr, "%s がオープンできません \ n " ) : ex i t ( I) : fscanf(fp, &n-i tem) : / * 品物の種類の数 * / fscanf(fp, &limit); / * ナッブザックのサイズ * / for (item_no ニ 0 ; i tem_no く n_item; item_no + + ) { i tems[i tem-no]. i tem_no i tem no; / * 品物の値段とサイズを読み込む * / fscanf(fp, " % は %d", &i tems[item_no]. price, &items[item-no]. size); fclose(fp) : / * 解を表示する * / int item_no; doubl e 第 p : i tem_t *itemp; printf(" 品目 : 数量単価重量 \ n " ) ; for (item_no - 0 ; item_no く n_item; item_no + + ) { i temp = &i tems[i tem_no] : printf( ” %4d: %4d % 8. 3f %4dYn ” itemp—>i tem_no, i temp->n, itemp->price, itemp->size) ; 十ニ itemp—〉 Size * itemp—>n; p 十 = itemp->price * itemp->n, printf("\n サイズニ %d 総重量ニ %f 価格 : %f%n ” , 町 p) : ⅱ m i t , 解 を * 日円 カク / のザ示 タブ表 デナ解 、ナップサックの 詰め方を求める 92 C MAGAZINE 1993 9

5. 月刊 C MAGAZINE 1993年9月号

問題 2 ー 7 List 何が表示されるて、 List 3 をご覧ください しようか。 何か表示されるか ? 1 : #include く stdio. h> 2 : 3 : main() int a = 1 , b = 2 ; 5 : 6 : if (a = 7 : printf( ” A*n ” ) ; 8 : 9 : else if (a 10 : printf( ” B*n ” ) ; 11 : 12 : else if (a ! = 2 ) ( 13 : printf( ” C*n ” ) ; 14 : else { 16 : 【問題 2-6 】 printf( ” D}n ” ): 18 : 何が表示されるて、 List 6 をご覧ください 19 : } しようか。 【問題 2-4 】 こて、は , 問題としてプログラムを示し List 4 をご覧ください。何が表示されるて、 ます。このプログラムをコンパイルして実 行したとき , 画面 ( 標準出力 ) には何が表示 しようか。 されるて、しようか。中には引っかけ問題も あるのて、注意してくださいね。 【問題 2-1 】 何が表示さ それて、は List 1 をご覧ください れるて、しようか。 【問題 2-2 】 何が表示されるて、 List 2 をご覧ください しようか。 【問題 2-5 】 List 5 をご覧ください。何が表示されるて、 しようか。 問題 2 ー 8 List 【問題 2-7 】 何が表示されるて、 List 7 をご覧ください しようか。 問題 2 ー 4 1 : #include く stdio. h> 2 : 3 : main() int x = 1 , Y = 2 ; 5 : 6 : 7 : 8 : 9 : 【問題 2-3 】 問題 2 ー 1 List Li st ・ 1 : #include く stdio. h> 2 : 3 : main() 5 : 1 : #include く stdi0. h> 2 : 3 : main() printf("Xd*n", 1 + 2 * 3 ) ; 5 : printf("Xd, Xx*n ” , 255 , 255 ) : 問題 2 ー 9 LiSt 問題 2 ー 5 List 問題 2 ー 2 List 1 : #include く stdio. h 〉 2 : 3 : main() int p = 0 ; 5 : 6 : if (p = の { 7 : printf(" ゼロ *n") ; 8 : 9 : else { printf( ”ゼロでない *n"); 11 : 12 : 13 : } 1 : #include く stdiO. h> 2 : 3 : main() int i ; 5 : 6 : i 十十 ) for (i = 0 : i く 10 : 7 : printf( ” 8 : 9 : 10 : ) 1 : #include く stdio. h> 2 : 3 : main() printf("X5. lf*n", 1.25 ) ; 5 : 問題 2 ー 3 List 問題 2 ー 6 LiSt 問題 2 ー 1 0 List 十・ , 1 よ・、 0 ・ 1 0 0 ・ 1 0 ・ 1 1 より 00 4 -0 6 行ー 8 9 0 1- 00 14 14 1 人 1 : #include く stdi0. h> 2 : 3 : main() int n = 50 ; 5 : 6 : if (n > 5 の { 7 : printf("Rainy*n") ; 8 : 9 : else { printf("Fine*n") : 11 : 12 : 13 : } 1 : #include く stdi0. h> 2 : 3 : main() int i; 5 : 6 : for (i = 0 ; ・ i く 10 : i + + ) ( printf("Xd*n", i * i) 8 : 9 : 80 C MAGAZINE 1993 9

6. 月刊 C MAGAZINE 1993年9月号

特集な践 C テクニカルファイル O ⑤もし , ・演算子十 , ④演算子に応じて計算を行い , result の値を * , / て、ある場合 を判断し , その種別を、、 token. type クに , 数値 さらに要素が続く ( 先読みが演算子 ) な 求める て、ある場合数値の値を、、 t 。 ken. value 〃に格納 らば , ②へ ⑤もし , さらに項が続く ( 先読みが演算子 ) なら 要素が続かなければ expr ( ) の値として しリターンする。 ば , ②へ ・ syntax error( ) result を返す 入力に誤りがあった場合 , 工ラーメッセ 項が続かなければ expr ( ) の値として re ・ factor( ) ージを出力し , 式の解析が失敗したこ sult を返す 関数 term( ) から呼ばれる関数 fact 。 r ( ) は 示すために大域変数、、 token. type 〃に、、 e クを設 要素を扱い , ・ term ( ) 同様に関数 term ( ) のアルゴリズムを以下 ①入力トークンを判断し , 定する。 (1) 入力トークンが数値の場合 ・ expr( ) に示す。 式の値を得る関数 expr ( ) のアルゴリズム ①最初の要素の値を関数 fact 。 r ( ) により変 ②符号っき要素 を以下に示す。 数 result に得て , ( 3 ) カッコて、囲まれた式 ①最初の項の値を関数 term ( ) により変数 re ②演算子を得て , 、、 ( 〃を読み込み ③次の項の値を関数 fact 。 r ( ) により得て , 関数 exp( ) を読み込み sult に得て , ②演算子を得て , ④演算子に応じて計算を行い , result の値を 、、 ) 〃を読み込む ③次の項の値を関数 term ( ) により得て , のように場合ごとに値を求めその値を返 求める List 32 List 32 re い「 resu は : 1 幻 : 肥 2 . に 3 : } 肥 4 : に 5 : / * set_fprint: に 6 : * 整数集合 se げをストリーム「 p に出力する 8 : void set_rprint(FILE * 「 p. intSet *self) 9 : { 0 : intSet *s: 引 : fo 「 (s=self; -s: s=s->next) { 132 : fprintf( fp. "Xd " 133 : ロ 4 : 135 : } 6 : 7 : / * set_foreach 、 138 : * 整数集合 se はのそれぞれの要素に対して操作 func を実行する ロ 9 : ネ / 凵 0 : intSet *set_foreach( intSet *self, intset *(*func)(intSet int) ) 凵い 1 4 2 : intSet *s: 1 43 : intSet *result : N 乢し : 1 44 : 145 : for (s=self; s; s=s->next) { 146 : result ー func( result. s->i ) : 凵 7 . 1 48 : return t, 150 : 15 い / * set_list . 152 : * count 個の要素をもっ整数集合を生成する 153 : * / 154 : intSet *set_list(int count, 155 : { 156 : va_list v: intSet *result ま N 乢し 158 : 159 : va-start( v, count ) : vhile (count-->0) { 160 : 161 : result : set-insert( result, va_arg( v, int ) ) : 162 : 163 : va-end ( v ) : 164 : return resul t; 166 : 167 : / * set_equal: 168 : * 整数集合 sl と s2 の等値判定 9 : ネ / 170 : int set_equal(intSet *sl, 172 : vhile ()l & & (2) { 173 : if (sl->i 174 : return 0 : 175 : 176 : sl : sl->next; 177 : s2 = s2->next; 178 : 179 : return ()l = s2); 181 : 182 : / * set-dispose: 183 : * 整数集合 se はの領域解放 184 : ー * / 185 : ・ void set_dispose(intSet **self) intSet *s に *self; 187 : 188 : intSet *t; 189 : 190 : 191 : t ニ s->nexti 192 : free( s ) : 193 : *self = N 乢し 194 : 195 : } 196 : 197 : / * set_c 叩 y: 198 : * 整数集合 se げの複写 199 : * / 200 : intSet *set_copy(intSet *self) 201 ・ 202 : げ (self N 乢し ) { 203 : return N 乢い 204 : 205 : return nevnode( self->i, set_c 叩 y( self->next ) ) : 206 : } 207 : 208 : / * set_min. 209 : * 整数集合 se はの要素のうち最も小さいものを返す 幻 0 : * / int set-min(intSet *self) 2 ll : 幻 2 : { 213 : int resul t = INT-MAX; 214 : vhile (self) { 215 : ⅱ (self->i く result) { 216 : 217 : resul t ま self->i : 218 : self = self->next; 219 : 220 : 221 : return result; 222 : } 223 : 224 : / * ma i n : 225 : * 主ルーチン 226 : * / 227 : int main(void) 228 : { 229 : * 整数集合デモ 230 : 231 : intset *a = set-list(). 3. 4. 6 , 7 , 9. ) : 232 : intSet *b set-list(). 1 , 2. 5. 8. 9. 12 ) : 233 : a = set_insert( a. ll ) : 234 : a : set-insert( a, ー ) : 235 : 236 : printf()a = { " ) , set-fprint(stdout, a). puts("}"): 237 : printf()b : ド ) , set-fprint(stdout, b), puts("}"); 238 : { " ). set-fprint(stdout, set-intersection(), b)). puts("}"): printf("a&b = 239 : 240 : printf("alb : ぐ工 set-fprint(stdout, set-union(), b)), puts("}"); 241 intSet *s2) 特集実践 C テクニカルファイル VoI. 2 43

7. 月刊 C MAGAZINE 1993年9月号

動作は次のようになります。 され , 改行される らかが大きければその値を , 等しければそ ①整数型の変数 x を定義し , 1 て、初期化する 確かに if 文にきた時点て p の値は 0 になって の値を変数 x に代入するのて、す。 ②整数型の変数 y を定義し , 2 て、初期化する いますが , 表示は「ゼロて、ない」になります。 【問題 3-3 】 ③ x 十十 : を実行した結果 , x は 2 になる コンピュータはそこに書かれている日本語 の文章の意味を知ることはて、きません。コ x 十十 : は x を 1 増やすという文て、す。 fo ・意味 r 文の「次の一歩」て、出てきた書き方て、すが , ンピュータは文章て、はなく , プログラムの calc. c というファイルをコンノヾイルしてい みに従うのて、す。 このように for 文以外のところて、も使うこと る最中 , 17 行目て、エラーが発生しました。 がて、きます。これて、変数 x は 2 になり , y は 2 定義されていない age という名前の変数を使 【問題 2-10 】 のままて、す。 っているというエラーて、す。 解答 ④ y = x 十 y : て、 y は 4 になる List 7 から else がふたつなくなった 次のようなエラーが出て , コンパイルに x 十 y ; は , 日本語て、いえば「 x 十 Y y の結果を y に代入する」という文て、す。 失敗します。したがって , このままて、は実 行することはて、きません。 変数 y に代入するのに y 自身の値を使って いるのて、びつくりする方もいらっしやるか Iist10. c 9 : syntax error near ' } ' 解説 もしれません。けれど , この文は正しい C の 文て、す。 ごめんなさい。これは引っかけ問題て、す。 8 行目の printf (... ) の終わりにセミコロン ( ; ) プログラムはまずふたつの変数 x と y の値 がないのがエラーの原因て、す。これを修正 を調べ , その値 ( 両方とも今は 2 て、す ) を加え してコンパイルすれば , ( 結果は 4 て、す ) , その値を変数 y に代入する だけの話て、す。計算の元になった数値が変 数 y から取られたものて、あるかどうかという 点はプログラムは気にかけません。 ⑤ printf て、 x と y の値が表示される 【問題 2-9 】 解答 ゼロて、ないと表示され , 改行されます。 解説 代入演算子 = と比較演算子 = = を混同し ないようにしましよう。動作は次のように なります。 ①整数型の変数 p を定義し , 0 て、初期化する ②条件 p 0 を調べる ( 満たされない ) 【問題 3-1 】 = れは注をが必要て、す。もしも = , ー 「 int 型 ( 整数型 ) の変数 n を定義し , それに かれた条件カ p 0 なら , もちろん満た 整数 50 を代入します。」または , この次のよ されます。 うにお考えくださってもけっこうて、す。「 in こに書かれた条件は p = 0 て、 けれど , t 型の変数 n を定義し , それを 50 て、初期化しま す。これは「変数 p に 0 を代入する」という式 す。」 て、 , 代入の結果 , 式全体の値は 0 になります。 0 は c 言語て、条件を満たさない ( てある ) こ 【問題 3-2 】 とを意味する値なのて、 , この条件は満たさ 変数 a と b の値を比較し , 小さくないほう れません。 ③ else の処理が実行され「ゼロて、ない」と表示 の値を変数 x に代入します。すなわち , どち 84 C MAGAZINE 1993 9 List 1 #include く stdiO. h> 2 int a = 1 を b = 2 : 5 6 if (a = 7 printf( ” A*n ” ) : 8 9 if (a = 1 & & b = 2 ) ー ( 10 printf( ” (心) : 11 12 if (a ! = 2 ) { printf( ” C*n"); 14 15 else ( printf( ” D*n ” ): 17 0 1 4 9 25 36 49 64 と表示されます。 問題 3-4 の解答 List 12 1 : #include く stdiO. h> 2 : 3 : main() int i ; 5 : 6 : for (i = 0 : i く 10 ; i + + ) ( 7 : printf("Xd : % d \ 心を i, 9 ー i) : 8 : 9 : 10 : ) の解答 問題 3 ー 5 の解答 List 13 1 #include く stdiO. 〉 2 int i, j; 5 6 fO で (i ま 0 : i く 10 ; i + + ) ( 7 printf("Xd : " , i); 8 for (j 0 : j く i; j + + ) ( 9 printf("X2d", j + 1 ) : 10 printf( ”” ) : 12 14 )

8. 月刊 C MAGAZINE 1993年9月号

Fig . 1 List 3 の実行例 整数を 20 個入力してください 2 : 20 3 . 30 4 : 40 5 . 50 6 . 60 7 : 70 8 . 80 9 : 90 1 0 : 100 1 2 : 120 1 3 : 130 1 4 : 140 1 5 : 1 50 1 6 : 160 1 7 : 170 1 8 : 180 1 9 : 190 20 : 200 ・入ッダからのヘッダのインクル < 公開へッダ > て、ある "console. h" (List 4 ) の 1 。 cate 関数て、は , printf 関数の呼び出し を行っている ( 13 行目 ) 。もし , 本ライプラ リを利用するユーザのプログラムが , #include く stdio. h > #include console. h" の順て、ヘッダのインクルードを行っていれ ば , 、、 console. h 〃をコンパイルするときに printf 関数に関するプロトタイプ宣言などの 情報が得られるのて、 , 問題はない。しかし , ューザが < stdio. h > のインクルードを行わ なかったり , #include "console. h" #include く stdiO. h > の順て、インクルードを行うと , "console. h" をコンパイルするときには , printf に関する 情報は何も与えられないことになる。この 場合は , 関数プロトタイプ宣言の欠如とい うことて、 , 警告 : 宣言されていない printf 関数を呼 び出そうとしています といった警告メッセージが発せられるだけ て、済むだろうが , プログラムによっては , このようなミスが致命的なエラーにつなが りかねない したがって , 【重要】ヘッダファイル中で , ほかのヘッ ダファイルで宣言 , 定義されている情報が 必要であるときは , そのヘッダファイルを インクルードせよ となる。ヘッダファイル中て、 , ほかのヘッ ダファイルをインクルードしてはならない す ま し 示 表 順 逆 を 数 ~ 」 0 9 8 7 6 5 4 3 2 1 0 0 0 0 0 0 0 0 0 0 ーレっ 4 1- 1 1 1 1 1 1 1 1 1 9 8 7 6 5 4 3 っ ~ カ・ 1 一つ 4 3 4 5 6 7 8 9 0 1 っ 4 3 4 5 6 7 8 9 0 入 コンソール画面制御ライプラリ ( 第 1 版 ) く公開へッダ > List 2 : / * コンソール画面制御ライプラリ ( 第 1 版 ) console. h ” 5 : #define putxy( x, y, s) { locate(), y) ; printf( ” %s ” 6 : #define putxyc(x,y,c,s) { locate(), y); color(c); printf( ” %s ” 8 : VO i d COI or ( i nt c) : 10 : / * ーーーカーソルを ( x , y ) に位置づける - ー * / 11 : VOid locate(int X, int y) printf("hlB[%d;%dH" コンソール画面制御ライプラリ ( 第 1 版 ) く実現部 > 2 : / * コンソール画面制御ライプラリ ( 第 1 版 ) console. C ” 4 : / * vo i d col or ( i n t c) ; 7 : # i ncl ude 8 : # i nc lude 10 : / * ーー表示色を設定する - ー * / 1 1 : VO i d COI or ( i nt c) 13 : static char printf("%xlBC"); P u tchar ( (c く 1 の ? ' 3 ' putchar(cindx[c % 10 ] ) : 17 : putchar('m'); 18 : く stdio. h> console. h ' ー・、物、 Column1 cindx[] - " 0415263777 " ・ = : 工スケープシーケンス 工スケープシーケンスは各種のデパイ スや周辺機器などを制御するための手段の ひとつである。工スケープ文字 ( 』 S コード では ' \ xl (i) に続けて 1 ~ 数バイト程度の コマンドを出力することによりを制御を行 うことができる。 という掟はない。このような方針をとると , 「同一ヘッダを重複してインクルードしてし まうのて、は ? 』と危惧する方もいらっしゃ るかもしれないが , 重複インクルードに関 しては , 本道場て、も , すて、に解説したとお りて、ある。 114 C MAGAZINE 1993 9

9. 月刊 C MAGAZINE 1993年9月号

特実践 C テクニカルファイル O LL ポインタを渡しておく。 本例題て、作成した入力関数 (List 23 ( 36 頁 ) ) の引数と戻り値は Fig. 4 ( 36 頁 ) のように なる。 List 数値入力関数用テストプログラム int x_disp_errmsg(int n) 2 : / * X 座標入力時のエラー処理関数引数は使用しない LOCATE ( 23 , 4 ) : 4 : printf()X 座標は、 0 ~ 639 の範囲で入力してください . 何かキーを押してください . 5 : getch() ; 6 : LOCATE ( 23 , 4 ) : 7 : printf("Yxlb[0K"); / * カーソル位置から行末までを消す工ストフ。シーケンス 8 : 9 : return 0 : 12 : int y-disp-errmsg(int n) 13 : / * Y 座標入力時のエラー処理関数引数は使用しない LOCATE(23, 4 ) : printf()Y 座標は、 0 ~ 399 の範囲で入力してください . 何かキーを押してください . 16 : getch(); 17 : LOCATE(23, 4 ) ; printf("\x1b[0K"); / * カーソル位置から行末までを消す 1 スケーフ。シーケンス 20 : return 0 : 22 : 23 : int main(void) 25 : NUMFORM fn; 26 : CLS() : 27 : 28 : 29 : f 田 . num = 100L : 30 : 作 . radix : RADIX-DEC16; f 田 . minnum ニ 0 し 32 : fm. maxnum = 639L : 33 : f 田 . errfunc ニ x-d i sp_errnsg : 34 : LOCATE(), 10 ) : 35 : pri れ tf ( " X 座標を入力してください。 ( 0-639 ) \ n " ) : 36 : input-num(10, 15 , &fm); 37 : / * 本例題で作成した関数 38 : 39 : fn. num 三 100L : / * 初期値 ( i) , 入力値 ( 0 ) 40 : fm. radix ニ RADIX_DEC16; / * 基数 fm. minnum ニ 0 し / * 入力可能な最小値 42 : fm. maxnum ま 399 し / * 入力可能な最大値 43 : fm. errfunc : y_disp_errmsg; / * 工ラー処理関数へのホ。インタ LOCATE(15, 10 ) : 45 : printf ( " Y 座標を入力してください。 ( 0 ー 399 ) \ n つ ; 46 : input-num(16, 15 , & ) : 48 : LOCATE(24, の : 49 : 50 : return 0 ; [ 例題 16 ] 数値入力関数を 利用したサンプル [ 例題 15 ] て、作成した関数を利用したサン プルプログラム ( List 24 ( 37 頁 ) ) を示す。 のプログラムはグラフィック座標を入力す る簡単なものだ。 X 座標 , Y 座標の入力を行 い , 入力された値が範囲外の値のときは , それぞれ異なったエラーメッセージを出力 する / * 画面クリア / * 初期値 ( i ) , 入力値 ( 0 ) / * 基数 / * 入力可能な最小値 / * 入力可能な最大値 / * 巧 - 処理関数へのインタ S e c し i ー 0 / n 十算・数学的操作 上 , 、潤ニ / * 本例題で作成した関数 C 言語はシステム記述言語という色彩の強 い言語て、あり , 数学的な操作に関する機能 というものは案外少ない。本セクションて、 は C 言語て、の数学的操作に関する便利な技法 をいくっか紹介する。 [ 例題 17 ] プログラム中で 四則演算式を扱う 簿プログラムて、 , 金額の項目に 400 * 3 C て、は , コンパイル時にはソースコード中などの式を入力して , 計算が行うことがて、 に自由に書くことがて、きた四則演算式も , きれば便利て、あろう。 プログラムの実行時に定まる式を計算する このような式の値を , ことは単純にはて、きない たとえば , 家計 expr(' 400 * 3 " ) ; TabIe 6 数値入力関数で使用するエスケープシーケンスの一覧 機能 工スケープシーケンス カーソルの移動 ESC[pI : pcH カーソルを消す ESC[>5h カーソルを表示する ESC[>51 カーソルの情報を保存する ESC[s ESCCu カーソルの情報を復帰する ESC[2J 画面をクリアする と式の文字列を与えると , その値を数値と して計算し返すための方法を紹介する。 、、 400 * 3 クという文字列を , 400 と 3 とい うふたつの数の積て、あると認識することは , 構文解析と呼ばれる技術に属するものて、あ こて、はその基本的な手法として , るが , Fig. 5 入力当十 3 * 4 " の処理 exp 「が処理 0 C 言語での使用例 printf("*xl b[%d : %dH" printf("*xl b[>5h") printf("*xl b [ > 5 鬥 printf("*xl b[s") printf("*xl b[u") printf("*xl b[2J") te rm term factor factor factor 1 十 3 4 特集実践 C テクニカルファイル Vol. 2 39

10. 月刊 C MAGAZINE 1993年9月号

ー実践 C テクニカルファイル 特集 は定石となっている。 に収まるかどうかにはまるて、無頓着て、ある。 #include く stdiO. h > 「 scanf( ) の代わりに , gets( ) と sscanf のプログラムて、は、、 charbuf [ 10 ] げと宣 void p10t plus(int column) しているのて、 , 行の長さが 10 文字以上 [ 1 ] ( ) の組み合わせを使う」 gets ( ) て、「行入力」して , その 1 行分の文字 printf()% * c*n", column, ' 十 ' ) ・ になると , buf をはみ出してしまう。 / * CO mn ー 1 個のスペースと ' 十 ' 列を sscanf( ) て、変換するのだ。もっとも「行 はみ出した部分はどうなるかというと , 入力」に関していえば , 理由は後て、述べるが を表示 * / MS ー DOS 上の処理系て、はその関数 ( この場合 gets( ) の代わりに fgets ( ) を使うほうが安全 は main ) の中て、宣言されたほかの変数や , そ printf("%10c", ' 十 ' 尸とすると , 9 個の なのて , 「 fgets( ) と sscanf( ) の組み合わせ」 の関数の呼び出し元を記憶している領域 , スペースに続いて、、十〃が出力される。また , さらには呼び出し元て、宣言された変数など と覚えておいてもよいだろう。 printf の出力書式て、は、、 % 〃に続く幅・精度を 変換が成功したかどうかは , sscanf ( ) の の上に重ね書きされることになる。よって , 戻り値て、わかる osscanf() は (scanf() もだ 、、 * クとすると , 対応する引数からその値を 10 文字以上の行を 1 行て、も入力すると , 先に 取り出す。このふたつの性質を利用したの が ) 変換に成功し変数に代入て、きた個数を戻 述べたような悲しい結末を迎えることにな が上記の関数 pl 。 t plus( ) て、ある。横の棒グ り値として返す。以上の説明をまとめて例 このような事態を避けるには , バッファ ラフを書くとき便利だろう。ただし , この にして示すと , を十分な大きさにするか , バッフアサイズ ような printf ( ) の使い方はトリッキーて、ある scanf ("%d%d", &x, から , コメントを忘れないようにしよう。 の代わりに を越えた入力をしない関数を使うようにす ればよい。現状の MS 一 DOS て、は , キーボ、一 コメントがないと , あとて、見直したときに char buf [ 256 ] ; ドからの行入力は最大 128 文字まて、て、ある。 意味不明のソースになってしまう。 fgets (buf, sizeof (buf) , stdin) ・ したがって , それ以上のサイズを用意して if (sscanf(buf, "%d%d", &x, (y) ! = 2 ) printf("input err*n") ・ おけば安全て、ある。筆者は十分な余裕を見 込んてバッフアサイズを 256 文字とすること とするということだ。 [ 例題 3 ] て、述べた技法の別の応用て、ある。 が多い。だが , ファイルからのリダイレク 関数 print blanks( ) は指定個数のプランク ト入力があり得るフィルタ処理プログラム 文字 ( 、、つを出力する。 f 。 r ループて、、、 putcha て、はこのような仮定はて、きない。行の最大 [ 例題 1 ] て、 , gets( ) の代わりに fgets( ) を ' ) クを繰り返すより , はるかにスマート 長をあらかじめ予想て、きないからだ。 使うほうが安全だと述べた。その理由を説 な方法て、ある。 したがって , そのようなときはバッファ 明しよう。 サイズを越えた入力をしない fgets ( ) を使 #include く stdiO. h > List 1 ( 24 頁 ) に示すサンプルプログラムは う。これなら絶対に安全だ。ほかの OS への 入力を行番号つきて、表示するプログラムだ 移植も考慮に入れるなら gets ( ) は使わす gets( ) て、 1 行入力し ,printf( ) て、行番号を頭 に , いつも fgets ( ) を使うようにしたほうが につけて表示する。これを入力の終わり (M よい 0List 1 ( 24 頁 ) は List 2 ( 24 頁 ) のように S-DOS て、は , キーボ、一ドからの入力を終わ なるだろう。 らせるには十を入力する ) まて繰り返 もっとも , このプログラムは 9 文字以上の す。 行に対して , 9 文字に区切って行を数えてし これは正しいプログラムに見えるし , 実 まうし , 改行しないまま行番号を表示して 行中は正しく動作する ( 処理系によっては行 しまう。この不具合を解消するにはいくつ 番号がおかしくなるかもしれない ) 。しか か対策が必要て、あるが , それは読者への演 習問題としよう。 し , 十を入力して終了すると , おそ らくプログラムは沈黙するか暴走してリセ ットがかかるだろう。 その原因は入力行を受け取るバッフアの サイズにある。 gets ( ) は入力された 1 行を渡 printf のちょっと変わった使い方を紹介し されたバッフアに書き込む。そのバッファ こ一一 = ロ [ 例題 4 ] printf ( ) で指定個数の スべスを作る [ 例題 2]gets ( ) の危険な側面 void print blanks(int n) printf("%*s" / * n 個のスペースを表示 * / [ 例題 5]sp 「 intf ( ) で文字列 を連結する 文字列の連結には , strcat ( ) を使うのが一 般的て、ある。たとえば , 、、文字列 sl , s2 , s3 〃を 連結して , 、、文字列 dl 〃に格納するには , strcpy (dl, (l) ; strcat(dl, (2) ・ strcat (dl, (3) ; これは sprintf ( ) を とすればよい。しかし , 特集 実践 C テクニカルファイル VOI. 2 25 [ 例題 3]printf( ) で横の 棒グラフを書く