printf - みる会図書館


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

1. 月刊 C MAGAZINE 1993年11月号

、フ もしもページがずれるようて、したら , SPR. ら , 条件を調べます。つまり , C の中の MAX LINES の値を調節してみて ください またタブの位置がずれるようて、したら , TABS の値を変史してください ( Fig. 6 をご 10 % 3 は 1 ( 10 = 3X3 余り 1 ) 10 % 4 は 2 ( 10 = 4X2 余り 2 ) 10 % 5 は 0 ( 10 = 5X2 余り 0 ) 10 % 6 は 4 ( 10 = 6X1 余り 4 ) printf("*tl}n") ; 28 : } 棒い ) の位置て、、わかると思います。 プログラム SPR ℃には、、 % クという演算子 が登場します。これは「余りを計算する演算 子」て、す。次の例を見ればわかると思いま 余りを計算する演算子 % ″ す。 do-while 文 do ー while 文は繰り返す処理を 1 回やってか 返す処理を始める前に条件を調べますが , 違いはたったひとつだけ。 while 文は繰り す。 これは while 文とほとんど同じ動作をしま 構文も使われています。 25 ~ 28 行目て、す。 プログラム SPR. C て、は do-while 文という ます。 着いたかどうかを調べるために使われてい になるため ) 。 SPR. C て、はタブ位置にたどり どて、もよく登場します ( 日 % 7 の結果が曜日 この余りを求める演算子 % は曜日計算な う ( とて、す。 ・ while 文 ・ do-while 文 0 回以上の繰り返し 1 回以上の繰り返し 覧ください ) 。 改頁文字とタブ文字 て、カーソルを進めているのが , 表示した縦 ご覧ください。タブ文字が次のタブ位置ま 説明のために , List 7 とその結果 Fig. 7 を にカーソルを進める役割りを持っています。 ( または 8 ) て、割り切れる次の位置 ( タブ位置 ) ます。正確にいえば , タブ文字は , 画面上 4 て 4 文字分になったり 8 文字分になったりし す。工デイタ上て、はエデイタの設定によっ て、は最大 8 文字分のスペースに展開されま これは 1 文字 ( レヾイト ) なのて、すが , 画面上 ーを押しながら国を打っ ) て、入力て、きます。 キーポードの画キーもしくは ( & 工 ] キ また ' Yt' はタブ文字と呼ばれるものて、 , ージを吐き出します。 タに送るとプリンタは現在印刷しているべ 文字は改頁用の文字て、 , この文字をプリン プログラム SPR ℃に出てくる ' Yf' という List タブ文字の働きを調べる 4 : void main(void) 3 : void main(void); 2 : / * 文字列中のれはタブ文字を表す * / 1 : #include く stdi0. h 〉 ちなみに構文は , do { 繰り返す処理 } while ( 条件 ) ; となっています。 グレード を比較してみることにします。 こて、は視点を変えて , if 文と while 文と り返しを扱う for 文との比較を先ほど行いま le 文は繰り返しを扱う構文て、すから , 同じ繰 今月は while 文について学びました。 whi if 文の関係について考察をしましよう。 ハイグレードのコーナーて、は , while 文と 処理 if ( 条件 ) { LlST7 ℃をコンノヾイル 凵 ST7. EXE を実行 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : 14 : 15 : 17 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : printf ( ” 12345678901234567890123456789 時 n printf( ” l}tl}n ” ) ; printf( ” 12\tはn ” ) : printf("123}tl*n") ; " 1234 \ t は n " ) ; ” 12345 \ t は n ” ) ; printf("123456*tl#n") ; printf " 1234567 \ t は n " ) : ” 12345678 \ t は n ” ) ; printf ( " 123456789 \ t は n " ) : printf ” 1234567890 \ t は n ” ) ; printf " 12345678901 \ t は n " ) ; printf ( ” 123456789012 \ t は n ” ) ; p ⅵ ntf ( " 1234567890123 \ t は n ” ) ; printf ( ” 12345678901234 \ t は n " ) ; printf ( ” 123456789012345 \ t は n " ) : printf ( " 1234567890123456 \ t は n " ) : printf ( ” 12345678901234567 \ t は n " ) : printf ( ” 123456789012345678 \ t は n ” ) ; printf ( " 1234567890123456789 \ t は n ” ) ; printf ( ” 12345678901234567890123456789 時 n printf printf printf Fig. 7 List 7 の実行結果 A 〉 LCC LIST7. C 11d @link. i A> LIST7 123456789012 12345678901 1234567890 123456789 12345678 1234567 123456 12345 1234 123 12 1 123456789012345678901234567890 123456789012345678901234567890 1234567890123456789 123456789012345678 12345678901234567 1234567890123456 123456789012345 12345678901234 1234567890123 98 C MAGAZINE 1993 11

2. 月刊 C MAGAZINE 1993年11月号

List 1 Li st bunseki. C 93 : 95 : 96 : 98 : 99 : ! 100 : 101 : き 102 : 103 : 104 : 105 : 3 106 : ー 107 : 108 : 3 109 : void init() ー 110. 、 111 : ″自力でせんと不安だ . 配列を初期化する * / ー 112 : int i; for (i=0; i く MAX INPUT;i + + ) table[i]=O; 113 : 114. 、 115 : ー 116 : void wait_cr() ー 117 : char buf[256]; 118 : printf(" 見たら . リターンキーを押す . ” ) : 1 119 : gets(buf) : ー 120 : 121 122 : ヨ main() 123 : 124 init();/* 配列を初期化する * / 125 : load();/* すでに , データファイルがあればロードする * / 126 : list();/* 前回までの入力データを一覧表示するり 127 : wait_cr(); 128 : input(); 129 : list();/* 入力が終わったら現在までのデータを一覧表示する * / 130 : save(); 現在までの入力データは自動的にセープされる * / 131 : 132 : return 0 : 133 : : #include く stdiO. h 〉 2 : #include く stdlib. h> 3 : #include く string. h> 4 : #define HAX—INPUT 15 5 : / * 入力できる解答番号の最大値り 6 : #define FNAME "test. dat" 8 : int tabIeCMAX_INPUT]; 9 : / * ここに問題の傾向を統計数として入れるり 10 : 11 : / * 以下は関数のプロトタイプ * / 12 : void load(void); / 事現在までの傾向分析データを自動ロードり 13 : void save(void); / * 現在までのデータを自動セーブり 14 : void sentakusi-hyouji(void) ; / * 傾向入力時の選択肢表示り 15 : void list(void); / 事傾向一覧り ヨ 16 : void input(void); / * 傾向入力り 17 : void init(void); / 事傾向分析用配列に 0 を入れるり ー 18 : void wait_cr(void); / * リターンキーをまつり 19 : 20 : void load() FILE *infile; 22 : int i; 23 : 24 : infile = fopen(FNAME. "rb") : 25 : if (infile - = NULL) return; 26 : / * オープンできなければデータは読まないり / ネオープンできればデータを読む . り 28 : 29 : for (i=0;i く MAX INPUT;i+ + ) 30 : ー 31 : table[i]=getw(infile); 32 : if (feof(infile)) break; 33 : 34 : fclose(infile) : 35 : 36 : ) 38 : void save() 39 : { FILE *outfile; int i; 42 : outfiIe=fopen(FNAME, ”響 b ” ) : 43 : / * 書き込みば稲荷モードでオープン * / 44 : if (outfile = NULL) ヨ 45 : 46 : printf ( " % s がオープンできない . ” , FNAME); 47 : exit(l); 48 : 49 : 1 ョ 50 : 52 : 53 : 56 : 59 : void list() 60 : ( int i; int tota 1 = 0 ; 62 : 63 : ヨ for (i=0;i く MM-INPUT; i + + ) total + = table[i]; 64 : / * まずすべての入力件数を計算する * / 65 : 66 : printf("}n}a**** 現在までの出題傾向一覧 ( 入力件数” ,total); table[i], tabIe[i]*100/total) : for (i=0; i く MAX—INPUT; i + + ) 68 : 69 : printf( ” X2c,X2c, X3d の数 =X4d 出現率 =X3dXX*n ” ,, ア ' + i.' A' + i + 1 , 71 : pr ⅲ tf ( " 新選択問題の時は , 上の傾向にしたがって記号を書けばよい . *n*n"); 72 : 76 : void sentakusi—hyouji() int i; for (i=0; i く MAX INPUT; i + + ) 79 : 80 : pr intf ( ” % 2c , X2c, X3d = 〉 X3dYn ” , 82 : 83 : ) 84 : 85 : void input() char buf[256]; int input; 88 : 89 : while(l) 90 : do printf ( " \ n \ n ・・・・傾向入力・・・・・ \ n " ) ; sentakusi_hyouji(); printf ( " 正答の番号で入力 . 終了は Q > ” ) ; gets (buf) : ) while (strlen(buf)== の ; if (buf[0]==' Q' Ⅱ buf[0]=='q' ) return; input=atoi(buf) : if (input>=O input く MAX-INPUT) tableCinput] + + : / * 1 ひかないと配列番号と一致しない . 不便だ . * / List バグ修正版 void load() FILE *infile; int num read; infile = ” rb ” ); if (infile ーニ NULL) return; / * オープンできなければデータは読まないり num_read=fread(table, sizeof(table[0]), MAX-INPUT, infile); if (num—read く MAX—INPUT) printf(" データ読み込み工ラー \ n " ) : fclose(infile) : for (i=0; i く MAX INPUT; i + + ) putw(table[i], outfile) : if (ferror(outfile)) printf("Xd 番のデータ書き込み工ラ¯*n"' i + 1 ) ; fclose(outfile) : void save() FILE *outfile; int num written; outfiIe=fopen(PNAME, ” wb ” ): / * 書き込みば稲荷モードでオープンり if (outfile - = NULL) printf ( ” % s がオープンできない . ” , FNAME); exit(l); num_written=fwrite(table, sizeof(table[O]), MAX INPUT, outfile) ; if (num-written く MAX—INPUT) printf( ”データ書き込み工ラー \ n ” ) : fclose(outfile); void list() int i; int total=0; tOtaI + = table[i]; for (i=0; i く MAX_INPUT; i + + ) / * まずすべての入力件数を計算する * / printf( ” *n}a**** 現在までの出題傾向一覧 * * * * ( 入力件数 )}n%n", total) ; for (i=0;i く MAX INPUT;i + + ) printf("X2c, X2c, X3d の数ニ % 4d 出現率 =X3dXX*n ” , ' ア ' + i, ' A' + i, i + 1 , table[i], total!=0 ? table[i]*100/totaI : 10 の : printf("*n 選択問題の時は , 上の傾向に従って記号を書けばよい . *n*n"); 丹羽信夫の迷走プログラミング 169

3. 月刊 C MAGAZINE 1993年11月号

List 1 にすると List 1 になります。このリストをコ ンパイルして実行すると , ボ、一ドの実装状 態が表示されます。これが実際の状態と同 じ結果になるか , 確認してください 次に , music. c の初期化ルーチンとして組 み込みます omusic init 関数て、は , このほか にグローバル変数の初期化なども行うこと にします。組み込むときは定数としていま まて、使ってきた値を変数に直すことを忘れ ないようにしてください 割り込み周辺て、は , int0 がマスターになる のて、 , EOI 送出などの各関数て、スレープ / マ スタを判定して , それぞれ処理を行うよう に変更しておきます。 80 : 82 : 83 : 84 : / * 85 : int music—intchk(void) music-outp(0xf, 0X8 の ; / * Joystic on * / 88 : return music_inp(Oxe) 〉〉 6 : 89 : } 90 : 91 : void music_intset(int lev) 92 : { unsigned char lev—t[] 93 : 94 : 0xb, 95 : 0X15 , 96 : 0X12 , 0X14 , 98 : unsigned char mask_t[] 99 : 0X8 , 100 : 101 : 0X20 , 102 : 0X4 , 103 : 0X10 , 104 : 105 : 106 : 107 : 108 : 109 : 110 : } 111 : 112 : 113 : / * 114 : void main(void) 115 : { 116 : 117 : if ()i ニ milk-fmchk()) ! ニの { 118 : 119 : printf(" サウンドボードは” ) ; if (i & FMBOARDI) { 120 : 121 : f 叩 ort—reg = FMPORT—I REG ; 122 : fmport_dat = FMPORT_I DAT ; printf("0xXx, 0xXx, 123 : , fmport—reg, fmpor t dat ) ; 124 : if (i & FMBOARD2) { 125 : 126 : fmport—reg = FMPORT_2REG : 127 : fmpor t—dat = FMPORT—2DAT ; printf( ” 0xXx, 0xXx, 128 : fmpor t—reg, fmpor t—dat) ; 129 : if (i & FMBOARD3) { 130 : 131 : fmport—reg = FMPORT—3REG; 132 : fmport_dat = FMPORT_3DAT ; printf("0xXx, 0xXx, 133 : fmport—reg, fmport_dat) ; 134 : printf ( ”が搭載されています。 *n ” ); 135 : ) else { 136 : printf(" サウンドボードがみつかりません。 *n"); 137 : exit(l); 138 : 139 : = music—intchk() : 140 : 1 printf("int level は Xu です。新 " , piclev[i]); 141 : music_intset(i) ; 142 : printf( ” intlev: 0xXx intmask: 0xXx*n ” 143 : intlev, intmask); exit( の ; 144 : 145 : } return 1 ; if (lev 〉 3 ) 1 ev = 3 ; intlev ニ lev-t[lev]; intmask = mask—t[lev]; 続いて , データ列のとおりに演奏するも のに改造します。とりあえず , 音程関係の mml だけ対応させることにしましよう。 いつものように作業の流れを考えます。 まずデータバッフアを設定します。このバ ッフアに対するポインタは , もとの位置を 示すものと割り込み側て、使うもののふたっ 用意します。こうすれば曲の頭に戻るとき の処理が , 簡単になりますね。次に fmsetc ommand 関数をすべて書き直します。この 関数て、 mml を解析することにしましよう。 解析といっても音程関係て、はたいした とはありません。バッフアのポインタから データをひとっ取り出し , これが 'A' ~ ' G' なら , ひとっ先のデータがシャープ / フラッ トを示しているか判定して , それから周波 数を設定します。 これをもとに実際に書き直すと List 2 のよ うになります。 tolower 文て、ポインタをイン クリメントしないのは , 一部処理系のイン プリメントの問題て、 , 文が 2 回評価されて しまう現象を防ぐためて、す。 これを music. c に結合します。まず , デー タバッファ設定用の関数として music setb uf 関数を作ります。 bufp 変数へ直に設定し てもよいのて、すが , このほうが後々便利て、 す。次に周波数用テープルを少し変更しま 102 C MAGAZINE 1993 11 テ←タの設定 mm 切解析 List 1 : char *bufp; 2 : char *buforg; 3 : 4 : void fmsetcommand(void) 6 : Char C, scale,• 8 : c = tolower(*bufp) ; 9 : bufp + + ; 10 :

4. 月刊 C MAGAZINE 1993年11月号

特集℃言語とオプジェクト指向 る。これの処理をどうするべきかが問題だ が , 最初の試みとして , 再定義するメンバ 関数に関しては , 新しく疑似導出クラスの メンバとして定義することにしてみよう ( 後々 , これて、は問題が発生することがわか このような考え方に基づいて , 素直にコ ーディングしたのが List 5 て、ある。また , そ のデータ構造の模式図を Fig. 3 に示す。図中 て、アミが掛かっている部分は , 疑似導出ク ラスて、追加したメンバて、ある。このモデル て、は , たとえば NamedPoint のメンバ関数 s how は次のように実現している。 まず , 自クラスに属する showName を呼 び出し , しかる後に , 自分のメンバとして 保持している p 。 int に属する show を呼び出す る ) 。 int dy); int dx, self->x, self->y); int dx, 22 : } int y0) int XO, void NamedPoint show 0 比較してみてほしい self- > showName (self) ・ (NamedPoint *self) このモデルて、もう一点注意すべき self-> point. show (&self- >point) ・ さて , show も move も , そのまま直接起動すること Point のオプジェクトて、ある a に対しては , move を呼び出すところて、ある。疑似クラス ことは , main の中て、オプジェクト b に対して がて、きる。 ことによって , 疑似基本クラスのメンバ関 数を起動している。この部分を , List 4 に示 : show 関数と した C 十十の NamedPoint : C 言語による簡単な継承と動的結合の試み ( 1 ) ーーバグあり LiSt List 5 printf("'%s' b. show (&b) ・ b. point. move(&b. point, 8 , ー 3 ) ・ なぜならば , move は has-a 関係にあるメ ンバ point のさらにメンバ関数として保持し ているからて、ある。このように has-a 関係を 利用すると , 継承してきたメンバと , 自ク ラスて、定義したメンバとの間の取り扱いに 差が生じ , シンプルさが失われてしまうと いう欠点がある。これは本来の継承の意味 a. show(&a) ・ a. move(&a, ー 15 , 27 ) ・ しかし , 疑似クラス NamedPoint のオプジ ェクトて、ある b に対しては , show はそのまま 起動て、きるのだが , move はそうはいかな 0 self->myname) : 過去の資産をそのまま用い , 新しく定 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 12 : 13 : 17 : 18 : 20 : 23 : 26 : 27 : 28 : 29 : 33 : 34 : 35 : 37 : 38 : 39 : 40 : 43 : 45 : 46 : 48 : 49 : $include く stdiO. れ〉 $include く string. h> typedef struct POint POint; struct POint { (*shov)(P0int *self) ; VOid (*move) (Point *self, void ー 53 : : 54 : 99 : ー 98 : ー 97 : を 96 : ー 95 : ー 93 : ー 92 : ! ー 89 : を 88 : ー 86 : ー 85 : ー 83 : ー 82 : ー 80 : ー 76 : ー 75 : ー 69 : ー 68 : : 66 : ー 65 : : 63 : : 60 : ー 59 : ー 58 : : 57 : ー 56 : : Ⅲ 1 : q00 : void NamedPoint-ini t(NamedPoint *self, char *name. int XO, sel f->showName : NamedPoint_showName; self->shov : NamedPoint_shov, Point-init(&self->point, xO, YO); int (O) : strncpy(self->myname, name, [sizeof(self->myname) int main(void) Po i n t a , NamedPoint b; PO i n t *p : Point_init(&a, 10 , 2 の : NamedPoint-init(&b, "point-B", 15 , si zeof(self->myname) int x; int y; void Point-shov(Point *self) printf("(%d,%d)}n ” void Point_move(Point *self, self->x + = dx; self- 〉 y + dy; 24 : void Point-init(Point *self, self- 〉 x = XO; self->y = 32 : $define NAMESIZE self—>move ニ Point-move; self->shov 咢 P0int-shov; 36 : struct NamedP0int { typedef struct NamedPoint NamedPoint; int dy) 25 ) ; PO i n t po i n t : (*shov) (NamedPoi nt *sel f) : VOid char mynameCNAMESIZE] : VOid (*shovName) (NamedPoint *self) : void NamedPoint_shov(NamedPoint *sel f) VOid NamedPoint_shovName(NamedPoint *self) self->point. shov(&self->point) : sel f->shovName(self) : printf("Point aYn"); a. show(&a) : a. move(&a, ー 15 , 27 ) : 圧 show(&a) : printf("YnNamedPoint bVn"); b. shov(&b); b. POint. move(&b. POint, 8 , し曲 0 & b ) ; printf("YnPoint a vith b assignedYn"); a : b. po i nt : a. shov(&a); a. move(&a, 13 , 7 ) : a. shov(&a) : printf("\npointer tO a\n"); p- 〉 show(p) : p->move(p, p->shOV(p); printf("\npointer tO b\n"); 0 : &b. PO i n t : p->show(p); p->move(), 35 , 12 ) : p->shov(p); return 0 : / * call inherited method * / 特集 C 言語とオプジェクト指向 49

5. 月刊 C MAGAZINE 1993年11月号

特集℃言語とオプジェクト指向 LiSt 7 89 : } int y0) int x0, 98 : { 105 : } 106 : / * 88 : 92 : 93 : 96 : 97 : 100 : 102 : 103 : 108 : 109 : 110 : 112 : 1 1 4 : 120 : 1 2 1 : 122 : printf( ” '%s' : ' ' , self->myname); 91 : const NamedPoint_mtd NamedP0int mtdtbl NamedPoint-show, Point_move, NamedPoint_showName. void NamedP0int-init(NamedP0int *self, char / * F i rs 凵 y, i n i t ' PO i n t ' port i on Of me. P0int_init((P0int*)self, x0, YO); / * SecondIY, re-init method table * / self->methods ニ &NamedP0int-mtdtbI; strncpy(self->myname. name, s i zeof(self->myname) *name, end of NamedPoint - Cs izeof()e lf->myname) int main(void) POint a; NamedP0int b; POint *p; Point_init(&a, 10. 20 ) ; "point-B'ii NamedPoint_init(&bi printf("P0int a\n ” ): Call (&a). show(&a); Call (&a). move(&a, 一 15 , 27 ) : CaIl (&a). show(&a); printf("YnNamedP0int b%n ” ): Call (&b). show(&b) : CaIl (&b). move(&b, 8 , ー 3 ) ; Call (&b). show(&b) : 15 , 25 房 123 : 124 : 125 : 126 : 127 : 128 : 129 : 130 : 131 : 132 : 133 : 134 : 135 : 136 : 137 : 138 : 139 : 140 : 141 : 142 : 143 : 144 : 145 : 146 : } List 25 : 24 : 23 : 22 : 20 : 19 : 17 : 1 6 : 1 5 : 1 4 : 1 3 : 1 2 : 10 : 7 : 3 : 2 : 1 : printf("\nPoint a with b assigned\n"); a = cvtP0int(&b); / * force truncate tO point * / return 0 ; Call (p). Sh0W(p); CaII (p). move(), 35 , 12 ) : CalI (p). show(p) : printf("%npointer tO bYn"); CalI (p). shov(p); Call (p). move(p, CaII (p). show(p); printf("inpointer tO a%n"); CaIl (&a). show(&a) : CaII (&a). move(&a, 13 を 7 ) 冫 Call (&a). show(&a); c 言語による簡単な継承と動的結合の試み ( 2 ' ) ーーマクロ使用版 #include く stdio. h> $include く string. h 〉 Generic define 5 : #define xcat(), y) x # # y 6 : #define cat(), y) xcat(), y) 8 : / * This ugly set of macros makes declare pseudo-class #define DefMembers(Cls) cat(Cls, _Members); $define Members(Cls) } ; struct Cls { const cat(CIs, #define DefMethods(Cls) cat(Cls, _Meth0ds) ; mtd) #define Methods(Cls) struct cat(Cls, typedef struct Cls Cls; -mtd) cat(CIs, typedef struct cat(Cls, 9 : #define DeclareClass(Cls) \ EndClass DefMembers(CIs) \ Members(Cls) DefMeth0ds(CIs) \ Methods(Cls) DecIareCIass (Cls) 18 : #define DefCIass(Cls) \ #def ine EndClass } easler -mtd) *methods; もちろん , 変化させられることて、得られ るメリットもあるのだが , 多くのオプジェ クト指向言語て、はこれはクラスに依存して 一意に定まるとしている。 それならば , 両者の対応づけはどこか 1 か 所て、行えば十分て、あり , 個々のオプジェク トがそれぞれすべてのメンバ関数へのポイ ンタを抱えるという必要はないことに気づ この点を改良て、きないかを検討してみよ う。この問題を解決するには , 次のような 構造にすればよい 0 まず , 各クラスに対してひとつだけメソ ッドテープルを用意し , そこにそのクラス が受け取ることがて、きるすべてのメッセー ジに対するメンバ関数へのポインタを並べ て宣言するようにする。そして各オプジェ クトて、は , メンバ関数のポインタをすべて 持っ代わりに , ひとつだけ「メソッドテープ ルへのポインタ変数」をメンバに持つように する ( もちろん , オプジェクト固有のメンバ 変数は , 各オプジェクトごとに持たせなけ れば意味がない ) 。そしてこのポインタ変数 を初期化のときに , オプジェクトが属する 疑似クラスに応じて , しかるべく設定して やればよいのて、ある。 / * メソッドテープルの定義 * / typedef struct POint mtd POint mtd ・ struct POint mtd { void (*show) (void *this) ・ void (*move) (void *this, int dx, int (y) ; 特集 c 言語とオプジェクト指向 int X ・ / * メソッドテープノレポインタ * / const point mtd * methods ・ struct POint { typedef struct P0int POint ・ / * 疑似オプジェクトの定義 * / 55

6. 月刊 C MAGAZINE 1993年11月号

特集℃言語とオプジェクト指向 装て、も , きちんと動的結合を実現て、きるの 初期化を前もって行っておき , その後に書 ットは疑似クラス Point の中て、 , メンバ sho w の位置を指すようにコンパイルされる。 て、ある 0List 6 がその修正版て、ある。この実 き換えるのがポイントて、ある。 一方 , NamedPoint の show というメンバ 行結果は完全に List 4 のサンプル RUN と一 void NamedPoint init は , それとはまったく異なるオフセットに 致する。 (NamedPoint *self, 存在する。 p が & b. point をポイントしている どこが違うかというと , 疑似導出クラス char *name, int x0, int y の 以上 , P->ShOW(P) ; によって呼び出され て、は再定義する show のエントリを宣言せ るのは疑似クラス Point のための show なのて、 ず ,show は point 疑似クラスのメンバをその ある (Fig. 4 参照 ) 。すなわち , List 5 のよう まま使うことにしたところて、ある ( データ構 な実装て、は , 動的結合が実現て、きないのて、 造を Fig. 5 に示す ) 。そして , 疑似クラス N ある。 amedPoint の初期化のところて、は , 元々の て、は , has-a にするとまったくダメなのか point のエントリを書き換えてしまう (overr というと , 必ずしもそうて、はない もう少 ide とコメントしたところ ) 。この際 , ま $h し修正することて、 , has ー a の関係を持った実 as-a 関係にある Point 疑似クラスのメンノヾの List 6 C 言語による簡単な継承と動的結合の試み ( 1 つ一一バグ F Ⅸ版 Point init (&self- >point, x0, (0) ・ self- >point. show = NamedPoint show ・ / * override * / self- >showName NamedPoint showName ・ strncpy (self- > myname, name, 0 L.ist ー 59 : ー 60 : ー 62 : : 63 : void NamedPoint-shovName(void *this) NamedPoint *self = this; ー 66 : printf("'%s' ・ self->myname) : : 69 : ー 70 : void NamedPoint-init(NamedPoint *self, char *name, int x0. int YO): Point-init(&self->point, XO, y の : self->point. show = NamedPoint_shov, / * override * / self->shovName = NamedPoi nt_shovName; : 75 : strncpy(self->myname. name, si zeof(self->myname) - l) [sizeof(self->myname) ー 79 : int main(void) PO i n t a ; NamedPoint b; ー 83 : PO i n t *p ; ー 84 : Point_init(&a, 10 , 20 ) ; ー 85 : NamedPoint-init(&b, "point-B", 15 , 25 ) : ー 86 : ー 87 : printf("Point ayn"); ー 88 : a. show(&a) ; ー 89 : : 90 : a. move(&a, -15 , 27 ) : a. shov(&a); printf("inNamedP0int bYn"); ー 9 3 : b 、 POint. show(&b); b. POint. move(&b. point. 8. ー 95 : b. POint. shov(&b); ー 96 : printf("%nPoint a vith b assigned\n"): ー 98 : : 99 : a : cvtP0int(&b); a. shov(&a) : リ 00 : : 101. a. move(&a, 13 , 7 ) : : 102 : a. 曲 0 & a ) : リ 03 : printf("}npointer tO aYn ” ): d04 : : 105 : q06 : p->show(p); : 107 : p->move(p, : 108 : p->shov(p); は 09 : : 110 : printf("%npointer tO b\n"); P : &b. PO i n t : '1 1 2 : P->Sh0V(P); p->move(), 35 , 12 ) : P->ShOV(P) : は 1 5 : return 0 : self->shovName(self) : Point-shov(&self->point) : / * 直接呼び出す * / 1 : # i ncl ude く std i 0. h> 2 : #include く string. h> 3 : 4 : typedef struct POint Point; 5 : 6 : struct POint { (*shov)(void *this) : 7 : VO i d (*move)(void *this. int dx, int dy); 8 : VOid int x; 10 : i n t y : 13 : void Point-shov(void *this) POint *self ま this; printf("(%d, %d)}n" 20 : void Point_move(void *this. int dx, int dY) Point *self = this; self->x + = dx; 25 : self->y + = dy; 26 : } 28 : void Point-init(Point *self, int XO, int YO) 29 : { self->x = x0; 30 : self->y : YO; 32 : self->shov = Point_shov, 33 : sel f—>move ニ Point_move; 34 : } 35 : 36 : Point cvtPoint(void *any) POint 38 . *fake : any : 39 : Point nevPoint; 40 : Point-ini t(&nevPoint, fake->x, fake->y) : 42 : return nevPoint; 45 : $define NAHESIZE 32 47 : typedef struct NamedPoint NamedPoint; 48 : 49 : struct NamedPoint { Po i n t 50 : PO i n t ; (*shovName)(void *this); VOid myname[NAMESIZE] : C har 52 : 55 : void NamedPoint_shov(void *this) NamedPoint *self = this; 57 : 58 : self->x, self->y); / * call inheri ted method * / 特集 c 言語とオプジェクト指向 51

7. 月刊 C MAGAZINE 1993年11月号

List 8 123 : / * ー -B ” , 15 , Point_init((POint*)self, XO, YO); / * SecondIY, re-init methOd table * / self->methods : &meth0dTable(NamedP0int) ; strncpy(sel f->myname, Si zeof(sel f¯>myname) 120 : 121 : 122 : 124 : 125 : 126 : 127 : 128 : 129 : 130 : 1 3 1 : 132 : 133 : 134 : 135 : 136 : 137 : 138 : 139 : 140 : 1 4 1 : 142 : 143 : 144 : 145 : 146 : 147 : 148 : 149 : 150 : 151 : 152 : 153 : 154 : 155 : 156 : 157 : 158 : 159 : 160 : 161 : 162 : 163 : [sizeof(self->myname) ー end 0f NamedP0int int main(void) Point a; NamedPoint b; PO i n t *p : Point_init(&a, 10 , 20 ) ; NamedPoint-init(&b. POint printf("Point aYn"); Call (&a). shov(&a) ; Call(&a). move(&a, ー 15 , 27 ) : Call (&a). show(&a) ; printf("%nNamedPoint b}n'i) : Call (&b). shov(&b) : Call (&b). move(&b. 8 , CaIl (&b). shov(&b); 25 ) : printf("%nPoint a vith b assigned\n"); / * force truncate tO POint * / a = cvtPoint(&b); return 0 : CaII(p). shov(p); CaII (p). move(), 35 , 12 ) : CaII (p). ShOV(P); p = (P0int*)&b; printf("}npointer t0 b%n"); CaII (p). show(p); Call(p). move(p, CaII (p). show(p); P ニ &a : printf("%npointer tO Call (&a). show(&a) ; call (&a). move(&a. 13 , 7 ) : Call (&a). show(&a) ; 特集℃言語とオプジェクト指向 / * メンバ関数工ントリの定義 * / ている。 ラインを抜き出してみると次のようになっ たとえば基本クラス Point の定義のアウト が List 8 て、ある。 上の複雑さを減らすように努力してみたの ために , 適度にマクロを利用して , 見かけ void ( * move) (void * this, void ( * show) (void * this) ・ #define Point Methods \ int dx, int dy) void Point show (void * this) / * ここでメンバ関数を定義する * / DefCIass (Point) ・ / * 疑似クラスの定義 * / int x ; \ #define POint Members \ / * メンバ変数の定義 * / だと思われる。 導出クラスても常に同じて、あり , クライア び出し形式は , 疑似基本クラスて、も , 疑似 ただければわかるように , メンバ関数の呼 Fig. 6 に示す 0List 7 て、は main の中を見てい したのが List 7 て、ある。また , データ構造を このような改良を施して , コーディング そのような解決策を取ることがて、きる。 の場合には静的な結合をすればよいのて、 , バ関数を呼び出すようにした。これも , int のメソッドテープルを直接参照してメン っている , Point の show の呼び出しは , Po それから , NamedPoint の show の中て、行 示さない ) 。 る ( ただし , 共有変数に関しては今回は例を ることて、エレガントに解決することがて、き るが , そのような変数も同様の手法を用い 変数というものを用意したい場合が出てく するオプジェクトすべてが共有するメンバ また , 高度な応用て、は , あるクラスに属 ント側から見ると List 6 に示した実装よりも すっきりしたものとなる。 その半面 , 疑似クラスの宣言において , 気を配らなければならない部分が増える。 また , 疑似基本クラスへのポインタ変数へ なのて、ある。このため , cvtPoint という変換 様に , メソッドテープルの入れ換えが必要 特別な注意が必要になる。 List 6 の場合と同 るオプジェクトを変数 a に代入する際には , なお , List 7 て、も , 変数 b に格納されてい いだろう。 ングする以上 , 避けて通るわけにはいかな する必要が生じてくる。 こは C て、コーディ タを代入する場合には , 明示的にキャスト 疑似導出クラスのオプジェクトへのポイン 関数を用いている。 そこて、 , その煩わしさを少して、も軽減する わしいとお感じの方も少なくないだろう。 も , 継承のための記述もあまりに複雑て、煩 さて , この List 7 は , 疑似クラスの定義 / * メソッドテープルの定義 * / DefMethodTabIe(Point) POint show, POint move, / * 疑似クラス初期化のための 関数の定義 * / void Point init(Point *self, int x0, int y0) / * 疑似クラス型変換のための 特集 C 言語とオプジェクト指向 57

8. 月刊 C MAGAZINE 1993年11月号

プロクラミンク熨場 Dr. 望洋の、 一元配列においても , int d [ 3 ] といった初期化が可能て、ある。 話を多重配列に戻そう。配列に対する初 期化子が不足していれば , その要素は 0 て、ク リアされるという規則は , 多重配列に対し ても成立するため , int x [ 3 ] [ 2 ] { 2 , 3 } , 文字列の初期化 List 1 : # i ncl ude 2 : 3 : i n t ma i n ( vo i d ) char stringC3] 7 : 8 : く stdio. h> ” ABC ” , string); printf( ” string ニ %syn" return ( 0 ) : Column4 C 十十における文字列の初期化 char strin g 〔 ] = { , A ツ B , C 叮 C 十十では , char string [ 3 ] ” ABC' と素直に書くべきである文字列として利 用したい場合の初期化子は文字列リテラ といった配列の大きさとい ' \ 0 ' ーを含まな い文字列の長さが等しい初期化は許さない。 ルとし = 。文字の配列として利用したい場合 の初期化子は , 各文字の要素を 1 文字ずつ書 一本来 , こういった初期化は , 変数 string を文 くことによりいプログラマの意図が読者に 。字列としてでなく文字の配列として利用 したい場合に行うものであり , 伝わりやすくなるであろう は ,Fig. 4 ( a ) のような初期化を行うことに なる。また , int x [ 3 ] [ 2 ] は , Fig. 4(b) に示す初期化を行う。 文字列の初期イ 配列の初期化子の特殊な例として , 文字 列リテラルによる初期化がある。たとえば , 過去において本連載て、も解説したように char str [ ] = 'ABC は , の省略記法て、ある。 配列の大きさを指定しなかった場合 , そ れは初期化子から決定されるのて、 , 配列 str の大きさは 4 となる。 ただし , 初期化子て、ある文字列リテラル の ' \ 0 ' を含まない長さと配列の大きさが等 しい場合は , 最後の ' \ 0 ' は付加されずに初 期化されるという規則が ANSI によって設け られている。たとえば , char string [ 3 ] ="ABC' 構造体の初期化 List 1 : # i ncl ude く stdio. h 〉 2 : 3 : i n t m a i n ( VO i d ) 7 : 8 : 9 : printf( ” d. x ニ %dYn ” printf("d. Y ニ %fYn" return ( 0 ) ; ABC' char * ptr 造体の初期化 次のような規則は理解しているだろうか。 【重要】文字列リテラル " ・・ ・ " は , 文字へ 構造体の初期化は , メンバの順に初期化 のポインタの型を持ち , 値としては先頭文 子を指定することによって行う。 字のアドレスを持つ 大まかな規則は , 配列の場合と同じだ。 したがって , 「ポインタ ptr が , 文字列リテ たとえば と解釈される。 ラル " ABC " の先頭文字 ' A ' を指すように初 struct { そのような宣言を行っているのが List 5 期化せよ」という意味て、ある。 int もし , このプログラムを正常にコンパ よって , double y , イルて、きないのて、あれば , その処理系は AN SI 準拠とはいえない (CoIumn4) 。 などと記述することはて、きない。 ptr は配列 ところて、 , 次のような宣言も比較的よく は , c. x を 0 て、初期化し , c. y を 1.0 て初期化する。 て、はなくボインタなのだから。 初期化子の個数がメンバ数に足りないと 目にするはずだ。 は , Dr. 望洋のプログラミング道場 149

9. 月刊 C MAGAZINE 1993年11月号

を ロロ ・ 0 「 EO F まで繰り返せ」 生ロ い口 。第翡回一 今回は繰り返しを学習しましよう。繰り 返すというと fo 「文かありますが , 今回 は wh ⅱ e 文というものを使ってみます。 くまず覚えてみる。それからそのプログラ のポイント ムの動きを考えてみる。そして自分の手を 実際に動かしてプログラムをコンパイルし ホワイル 今月は「 EOF まて、繰り返せ」と題して while て実行してみる。それがプログラミング言 語を学ぶ王道といえましよう。 文について説明します。 while 文はある処理 初めは誰しも初心者て、す。初めて習う構 を繰り返して行いたいときに使う構文て、す。 文は , なじみがなくて当然て、すから , 慣れ 「繰り返し」といって思い出すのは for 文て、す と根気が必要て、す。 が , while 文も繰り返しを行います。 for 文と それて、は , 今月のレッスンを始めましょ while 文の違いについては後て、お話します。 whi 厄文の構造 めに C 言語の勉強は進んて、いますか ? もしか したら , 「何だか難しくなっていやになって きた」と思っていませんか ? そんなときは どうしたらいいて、しようね。 「何となく難しそうだ , めんどくさいな」 としていいかげんに進むのはあまりよい方 法て、はありません。ずっと勉強していて , ココまて、はわかるけれど , ココからがわか ' ューコーナー いきなり本題に入りましよう。 while 文の らないという分岐点が必ずあるはずて、す。 構造は次のようになっています。 難しそうな用語だとか , めんどうそうなプ ログラムに惑わされると , つい「覚える・考 先月の復習から始めます。先月のレッス while ( 条件 ) { ンて、は「関数」について学びました。 える・やってみる」という基本を忘れてしま 繰り返す処理 私たちが C 言語て、プログラムを書くとき , いがちて、す。 必ず printf( ) とか gets() といった関数のお 取り上げられているプログラムをとにか 世話になります。先月は関数を使うだけて、 初めに while と書かれています。これは「ホ while の例 ワイル」と読み , 英語て、「・・・の間」という意味 はなく , 自分て、関数を作る方法について学 を持っ単語て、す。 C 言語て、は「ある条件を満 びました。関数の三つのポイントを覚えて たす間繰り返す」という意味を持っ単語にな いますか。 っています。 while という語て、始まるのて、こ ・関数の宣言 ・関数の定義 の繰り返しの構文は「 while 文」と呼ばれてい ・関数の呼び出し ます。 if 文が if て、始まり , f 。 r 文が for て、始ま っているのに似ていますね。 ・引数 while の後には、、 ( クと、、 ) 〃て、括られた部分が ・戻り値 (return 文 ) こには , 繰り返しを行う条 出てきます。 件が書かれています。その後には、、 { 〃と、、 } 〃 という用語も覚えました。 List0 1 : #include く stdio. h> 2 : 3 : void main(void); 4 : 5 : void main(void) 7 : int c; 8 : c = getchar() ; 9 : while (c ト 10 : printf( ” ' Xc' %n ” , c) : 11 : c = getchar(); 12 : 13 : 14 : ) 90 C MAGAZINE 1993 11 レスン

10. 月刊 C MAGAZINE 1993年11月号

C 言語プロクラミンクレッスン て、すね。この条件は , 変数 c の値がヒ。リオド て、括られた部分があって , そこには繰り返 た。関数 printf( ) と getchar( ) て、す。それて、 . ' と等しいかどうか ( ! = ) を調べるものて、 す処理の内容が書かれます。 はいよいよ List 1 が何をやっているか順を追 す。演算子、、 ! ー〃は「左辺と右辺は等しくな ところて、 , 「条件」って何だか覚えていま って読みましよう。 い」というものて、 , 数学て、いうところの「ヰ」 すか ? さっきは「ここには繰り返しを行う 条件が書かれています」とサラリと説明しま に相当します。 したがって , List 1 の while 文の動作を言 したが , 1 行目 : いつもの決まり文句 こういう文章って , ちょっと眠か ったりすると , 読み流してしまいそうて、す 葉て、説明すると , 2 行目 : 空行。無視します ね。て、も , それは危険て、す。プログラミン 「変数 c の値がヒ。リオドて、ない間 , 処理を繰 3 行目 : 関数 main( ) の宣言 り返す」 グについての文章を読むときは , じっくり 関数 main() は引数がなく (void), 進みましよう。 というふうになります。この程度の説明文 戻り値もない (void) て、あると宣言 「条件」については if 文を勉強したときに習 なら , while 文の条件 , つまり List 1 の 10 行 しています いました。条件とは , たとえば 目を読んだだけて、書くことがて、きますね。 4 行目 : 空行 この説明文は List 1 の概略を次のように捕ら 5 行目 : 関数 main ( ) の定義の開始 p > = 50 えていることになります。ここて、は while 文 さっきの宣言と同じ ( 行末のセミコ の条件がクローズアップされています。 ロンは不要 ) 6 行目 : この、、 { 〃から 14 行目の、、 } クまて、が関 while (c ! = 何かの処理 数 main ( ) の定義の内容て、す 7 行目 : 変数 c の定義。型は int 型 8 行目 : 空行 while の例 : 繰り返す処理を読む 9 行目 : 標準入力から入力された初めの文 字を getchar ( ) によって得て , その 文字を変数 c に代入 簡単にいえば , キーボ、一ドからの 初めの文字が変数 c に入るというこ と 10 行目 : さっき読んだ while 文の開始 意味は , 「変数 c が ' . ' と等しくない Fig. 1 List 1 の動作結果 凵 STI ℃をコンノヾイル LISTI . EXE を実行 ー - ーキーボードから入力 画面表示 LISTI . EXE を実行 ーーキーボードから入力 while の例 :List 1 の解読 とか , p く 0 ー 100 く p といったものて、した。それぞれ , 「変数 p が 50 以上て、ある」 「変数 p が 0 より小さいか , または 100 より大 という条件て、す。要は「満たされるか満たさ 条件はわかりました。それて、はかんじん れないか , 成立するかしないかがはっきり するもの」が条件て、した。 の繰り返す処理を読むことにしましよう。 while 文の、、 ( 〃と、アの間には「条件」が書か List 1 て、す。 while 文の繰り返しの内容は { と } て括ら 0 0 、 000 工ー = ね ! れます。その条件が満たされる間 , その間 にかぎり「繰り返す処理」が実行されるとい printf()' %c' *n", c) c = getchar( ) ・ うわけて、す。 このふたつの文が繰り返す処理て、す。初め の関数 printf ( ) はもうすっかりおなじみて、 す。 % c が文字列の中に入っていますから , 基本原則がわかったところて、 , プログラ 文字を表示していますね。次の getchar ( ) は ムの例を見てみましよう。 List 1 は何をする 文字を 1 文字 , 標準入力から得るものて、す。 プログラムて、すか。 簡単にいえば , あなたがキーボ、一ドを打っ いまは while 文を勉強しているのて、すか たとき , その文字が getchar ( ) の戻り値とな ら , List 1 に示された例は while 文を説明す るためのものて、す。さあ , List 1 の while 文 るのて、す。 はどこにあるて、しようか・・・・・ありました。 たとえば , あなたがキーボ、一ドから ' A' と 10 行目から 13 行目て、す。 wh ⅱ e 文を見つける いう文字を入力したとしましよう。そうす のは簡単て、す。キーワード ' while ' を探せば ると , c = getchar( ) ・ よいのて、すから。 という文は , while 文を読むときは何をどう読めばいい のてしようか。先ほどの while 文の説明から という代入文と同じことになります。キー わかるように while 文には必ず「条件」がつい ポードが打たれないとき , getchar ( ) はキー てきます。 List 1 の while 文の条件は何て、し ボ、一ドが打たれるのを待ちます。 よう。そう , カッコて、括られている部分 , さて , while 文の処理内容を見てきまし wh ⅱ e の例 : 条件を読む A 〉 LCC LISTI. C 11d @link. i A 〉 LISTI He110. 0 A> LISTI This is a pen. 1 ・ 1 O 画面表示 C 言語入門講座 91