double - みる会図書館


検索対象: 月刊 C MAGAZINE 1994年1月号
13件見つかりました。

1. 月刊 C MAGAZINE 1994年1月号

問題 1 : 各学生の合計点を表示 点 , 線分 , 長方形を表現する List List 0 なくても , いきなり 3 人の学生のデータを処 しようか。 例 : 構造体の配列を作る data[2]. tenC1] 理て、きそうて、すね。 先ほどは「メンバに配列が含まれている構 dataC2] は 2 番目の学生を表す構造体て、 , ピ それて、は 2 番目の学生の 1 番目の教科の点 リオドが間に入って , ten [ 1 ] はメンバ ten [ ] 造体」の話をしました。今度は逆て、す。「要 数を 50 点にしたいときはどう書けばいいて、 素が構造体て、あるような配列」の話をしまし 問題 2 : 各教科の平均点を表示 よう。まるて、パズルのような言葉て、すが , じっくり読んて、ください 1 : #include く stdio. h> 2 : 点数の配列 int ten[3] ; があったとしま 3 : struct student ( int id; 4 : す。これは int 型の変数が , 三つ団地のよう char nameC20] ; 5 : int kokugo; 6 : になっているのて、した。同じように int suugaku; 7 : int eigo; 8 : struct student data [ 3 ] ; 11 : struct student data[4] という配列を定義したとしましよう。これ 1 , ”結城浩” , 90 , 100 , 2 , ”阿部和馬” , 82 , 73 , 63 , はどういう意味て、しようか ? 「ええと , st 3 , ”伊藤光一 74 , 31 , 41 , 14 : 4 , ”佐藤太郎” , 100 , 95 , 98 } , ruct student 型の変数が三つ団地のようにな っているんだ」そのとおりて、す。このように 18 : void main(void); 書くと , 構造体が三つ並んだ配列が定義さ 20 : void main(void) れたことになります。つまり , int i; 22 : int kokugo = 0 , suugaku = 0 , eigo data [ 0 ] 24 : for (i = 0 ; i く 4 : i + + ) { data[l] kokugo + = suugaku 十 = data[2] eigo 十 = 28 : 29 : はそれぞれ struct student 型の変数となるわ printf ( " 国語の平均点は % 0. If 点です。” , kokugo / 4. の ; 30 : printf ( " 数学の平均点は % 0. If 点です。 *n" suugaku / 4. の ; けて、す。こういう配列を作っておけば , い printf ( " 英語の平均点は % 0. If 点です。 *n", eigo / 4. の : 32 : 33 : } ちいち taro だの jiro だのという変数を定義し 90 C MAGAZINE 1994 1 1 : #include く stdiO. h> 3 : struct student ( 4 : int id; char name [ 20 ] ; 5 : 6 : int kokugo; 7 : int suugaku; 8 : int eigo; 11 : struct student dataC4] 1 , ”結城浩” , 90 , 100 , 2 , ”阿部和馬” , 82 , 3 , ”伊藤光一 14 : 74 , 31 , 4 , ”佐藤太郎” , 100 , 16 : ) : 18 : void main(void) : 19 : void print—total (struct student s) ; 21 : void main(void) 23 : int i; 24 : for (i = 0 : i く 4 ; i + + ) ( print—total ( 28 : ) 29 : 30 : void print—total(struct student s) printf ” Xd Xs の合計点は” , s. id, &s. nameC0]) : printf ” Xd 点です。 *n" 33 : 1 : #include く stdiO. h> 2 : 3 : / 事点を表す構造体 * / 4 : struct point ( double x; ″ x 座標 * / 5 : double y; / * Y 座標 * / 6 : 8 : 9 : / * 線分を表す造体 ( 1 ) * / 10 : struct linel double xl; / * 始点の x 座標り double yl; / 事始点の y 座標 * / double x2; / * 終点の x 座標 * / double y2; / * 終点の y 座標 * / 15 : } : 17 : / * 線分を表す構造体 ( 2 ) * / 18 : struct line2 ( struct point start; / * 始点 * / / * 終点 * / 20 : struct point end; 22 : 23 : / 事長方形を表す構造体 ( 1 ) * / 24 : struct rectanglel { doubl e xl : 点 1 の x 座標 * / double yl; / * 点 1 の y 座標 * / double x2; 点 2 の x 座標 * / double y2; / * 点 2 の y 座標 * / 28 : 30 : 31 : / * 長方形を表す構造体 ( 2 ) * / 32 : struct rectangle2 { struct point PI; / 事点 1 * / struct point p2; / * 点 2 * / 34 : * 数数数 号 / 点点点 番 * ののの 席名語学語 出氏国数英 C = 50 ; List * 数数数 号 / 点点点 番 * ののの 席名語学語 出氏国数英

2. 月刊 C MAGAZINE 1994年1月号

C 言語プログラミングレッスン・ 問題 3 : 長方形の面積を計算 List ( これは点数を表す配列 ) の 1 番目の要素て す。ややこしいてすね。ても , 配列と構造 体の仕組みに慣れてくれば , このような書 き方も理解てきるようになります。 Fig. 8 を ご覧ください 例 : 構造体の初期化 変数を定義するとき , 同時に初期化を行 ことがてきました。たとえば , int X 100 : と書けば , int 型の変数 x を定義すると同時に その値を 100 て初期化しました。これと同じ ように , 構造体も定義と同時に初期化を行 うことがて、きます 0List 4 が構造体の初期化 の例てす ( 動作画面は Fig. 9 ) 。 もうわかりますね。構造体を初期化する ということは構造体の各メンバを初期化す るということてす。各メンバをどのような 値て、初期化するかをコンマ、、 , クて、区切って中 カッコ、、 { ドて、括ってやればいいのてす。配 列の初期化と似ていますね。 構造体の配列を初期化することもてきま す。 List 5 を見てください ( 動作画面は Fig. 10 ) 。これが「構造体の配列の初期化」てす。 配列の初期化は各要素を、、 { ドて、括ったもの て , 構造体の初期化は各メンバを、、 { ドて、括 ったものて、すから , 構造体の配列の初期化 は、、 { } 〃が二重になっています。 こまて、学んて、 , C 言語というもの 表現することがて、きます。 てい構造体が登場してきます。この「プログ さて , をややこしいものだと思いますか ? それ いま述べたことを構造体て、表現したのが ラミングレッスン」て、はコンヒ。ュータグラフ イックスについて触れることは誌面の都合 とも , 規則を組み合わせるといろんなこと List 6 て、す。点を構造体 p 。 int て、 , 線分を構 がて、きるもんだと思いますか ? 「構造体の 造体 linel と line2 て、 , 長方形を構造体 rectan 上て、きませんが , そのほんのサワリを紹介 配列なんてめんどうなのを本当に使うのだ glel と rectangle2 て、表現しています。 しましよう。 ろうか ? 」と疑問に思う方もいるかもしれま Iine2 や rectangle2 は要注意て、す。メンバ 最初に行うことは , 図形をコンピュータ せん。て、も , 実際のプログラムて、は結構頻 をよく見ると , 構造体のメンバ自体が構造 が取り扱えるように数学的に表現すること 繁に使うのて、す。最後に「簡単成績処理」と 体になっています。線分を表すのに始点と て、す。基本的な図形 , たとえば点 , 線分 , いうサンプルプログラムを紹介しています 終点をメンバにすればよいと考えました。 長方形などを考えましよう (Fig. 11 ) 。 が , そこて、も構造体の配列を使っています。 ところて、 , 始点や終点は点て、あり , すて、に 平面上の点はふたつの数の組みて、表すこ 点は struct point として宣言されています。 とがて、きます。たいていこのふたつの数は 例コンピュータグラフィックスの第一歩 て、すから , メンバにその構造体を入れてし x と y という名前がついています。平面上の コンヒ。ュータグラフィックス ( CG ) に興味 まったのて、す。これは C 言語として正しい構 線分はその両端の点の組て、表現て、きます。 のある読者も多いと思います。コンピュー 造体の宣言て、す。構造体もメンバに入れる 長方形はその四つの角の点て、表現すること ことがて、きるのて、す。 タて、図形を描いたり , 絵を描いたりするプ もて、きますが , とくに座標軸に平行な長方 ログラムを C 言語て組もうとしたとき , たい いったんこのように構造体て、表現してし 形て、あれば , 対角線上にあるふたつの点て、 C 言語入門講座 91 1 : #include く stdio. h> 2 : #include く math. h> 3 : 4 : / * 長方形を表す構造体 * / 5 : struct rectangle { double xl, YI; / * 左上の点 * / 6 : double x2, Y2; / * 右下の点 * / 7 : 9 : 10 : void main(void); 11 : double distance(double a, double b) : 12 : double calc—area(struct rectangle r) ; 14 : void main(void) struct rectangle rect; double area; rect. xl rect. yl rect. x2 rect. y2 area = calc_area(rect) : printf()Æ方形 (Xg, Xg)-(Xg, (g) の面積は Xg です。 *n ” , rect. xl, rect. yl, rect. x2, rect. y2, area) : 26 : } 29 : * 関数 distance() は差の絶対値を求める。 30 : 31 : double distance(double a, double b) return(a - b) : else { return(b - a) ; 40 : / * 41 : * 関数 calc-area() は、 42 : * 与えられた長方形の面積を計算する。 43 : 44 : double calc area(struct rectangle r) double x, y; / * ニ辺の長さ * / 46 : X = distance 49 : y = distance return(x * y) : フ 二

3. 月刊 C MAGAZINE 1994年1月号

C 言語プログラミングレッスン・ 問題 3 の解答 1 : #include く stdio. h> 2 : #include く math. h 〉 3 : 4 : / * 長方形を表す構造体 * / 5 : struct rectangle { double xl, yl; / * 左上の点 * / 6 : double x2, y2; / * 右下の点 * / 7 : 9 : 10 : void main(void); 11 : double distance(double a, double b) ・ 12 : double calc—area(struct rectangle r 14 : void main(void) struct rectangle rect; double area; rect. xl rect. yl 20 : rect. x2 rect. y2 22 : printf(" 長方形 (Xg,Xg))i(Xg,Xg) の面積は xg です。 23 : 24 : rect. xl, rect. yl, rect. x2, rect. y2, area) ; 25 : 28 : / * 29 : * 関数 distance() は差の絶対値を求める。 30 : 31 : double distance(double a, double b) 33 : return(a ー b) ; 34 : 35 : else { 36 : return(b ー a) : 38 : 39 : } 41 : * 関数 calc area() は、 * 与えられ長方形の面積を計算する。 42 : 43 : 44 : double calc area(struct rectangle r) double x, y; / * 二辺の長さ * / 46 : x = distance(). xl, r. x2); 48 : Y = distance(). YI, r. Y2); 49 : return(x * y) ; 50 : 51 : } List 1 2 Fig. 1 5 KBASE の結果 BASE. C のコンノヾイル ー入力ファイルの表示 A> LCC KBASE. C lld @link. i A> TYPE SAMPLE. DOC 1 結城浩 65 90 100 80 73 2 阿部和馬 8 2 73 63 21 44 3 伊藤光一 7 4 31 41 59 38 4 佐藤太郎 100 95 98 82 61 5 村松真治 55 48 79 90 88 6 進東三太郎 74 41 59 31 38 A> KBASE く SAMPLE. DOC KBASE. EXE の実行結果 ^ 0 物 0 6 り 0 《 0 り 6 0 6 《 0 《 0 、よ ^ 0 8 っーり朝 8 っー 1 ー 0 ん《 0 8 8 8 8 っー》 4- 4 点点点点点点点点点点点点 均均均均均均均均均均均均 8 ^ 0 00 0 00 ^ 0 8 0 《 0 00 00 0 8 4 っリれ 0 4 00 0 8 4- 4 0 んり 4 00 り編 4- 4- っ 0 00 0 り 0 点点点点点点点点点点点点 計計計計計計計計計計計計 0 0 っ 0 : 0 0 合合合合合合合合合合合合 00 っ 0 0 ー 行ー《 0 っー CD -- 0 れ 0 4 8 1 ー 8 8 、よっ 0 8 -4 8 8 点点点点点 7 4 っ 0 、 8 《 0 《 0 っー 8 4- ^ 0 っ 0 均均均均均 0 ・ー 0 ん 0 1 よ り 0 0 1 人 9 1 ム 平平平平平 8 りも -- 0 8 っ 0 8 8 0 》っ -0 っ 0 0 っ 0 、↓ 8 8 0 9 っ 0 1 よ 、 1 1 よ , よ 8 0 《 0 4 9 ー Ln 0 》 0 っー《 0 4- - ト 0 LO れ 0 識 - り 4 《 0 0 , よロ 0 8 14 : - ト 0 0 8 00 ・ 1 、 1 - - 点点点点点 0 》 7 っ 0 、 -4 -4 表 点低低低低低 -0 り 0 4- 0 -- 0 4 位 05 2 低最最最最最 : 6 8 ー 0 -- 0 ー 0 に 0 0 0 8 0 9 0 9 8 8 名馬一郎治太成郎治馬一太点 1 の浩和光太真三る太浩真和光三高点点点点点 順城部藤藤松東よ藤城松部藤東最高高高高高 均 号結阿伊佐村進に佐結村阿伊進・最最最最最 点 、↓ 0 臨 00 4 -- 0 れ 0 4- 1 -0 00 ^ 0 《 0 生席 計 均 、 1 り臨っ 0 4 -- 0 点 合 学人出 科科科科科計 1- り 0 っ 0 - LD 1 ・つ編っ 0 4- ′ 0 教教教教教合 Fig. 1 6 長方形の辺の長さ P(XI, YI) 2 縦の辺の長さ YI Y2 0 ( , Y2) X2 横の辺の長さ = は 1- x 引 て、すから , まず辺の長さを求めることを考 独て、 kokugo と出てきたら変数として定義し 起こしてしまったかもしれません。 KBAS えます。いま , 長方形は対角線上の 2 点て、表 E. C をいじってコンパイルし直し , 納得いく てある ( ありますね ? ) kokugo て、あり , 構造 現されています。辺の長さは Fig. 16 を見る まて、復習してください。 体の後のヒ。リオド、、 . 〃の次に kokugo と出て さて次回は「ファイル操作」と題して , フ とわかるように , 縦の長さは 2 点の y 座標の きたらメンバとしての kokugo て、す。 C コン 差 , 横の長さは 2 点の x 座標の差て、得られま ァイルを取り扱う方法について学ぶことに パイラは文脈から変数名かメンバ名か判断 す。関数 distance( ) は差の絶対値を求める しましよう。 してくれるのて、 , List 8 のように変数名とメ ものて、す。 2 点がどういう位置関係にあって これまても , ファイルをデータの入力や ンバ名に同じ名前を使っても C 言語の文法上 も大丈夫なように , 単に引き算をして差を 出力に用いてきましたが , それはすべて、、 < 〃 正しいプログラムとなります。 と、、 > 〃を使ったリダイレクトによるものて、 求めるのて、はなく差の絶対値を求めていま した。次回はプログラム中からファイルに データを書き込んだり , ファイルからデー たとえば List 12 のようになります。ちょ タを読み込んだりする方法を学びます。 っと難しかったて、すか ? 長方形の面積を れによって , 計算して得られた結果を半永 求めることは簡単て、すが , それをプログラ 久的に保存したり , 以前の結果を再利用し ムとして表現することは慣れないと厄介て、 今月は構造体について学びました。変数 , たりすることがてきるようになります。お 関数 , 配列 , for 文の知識を前提としてお話 すね。 長方形の面積 = 縦の長さ x 横の長さ したのて、 , もしかしたらだいぶ消化不良を 楽しみに。 94 C MAGAZINE 1994 1 問題 3 の解答 0

4. 月刊 C MAGAZINE 1994年1月号

特集戻用ク目回ク術 ています。 もな仕事は , Polygon オプジェクトやほかの べてのポリゴンを一度に描画て、きます。角 ポリゴンの描画は , ほかの図形に比べる PolyGro 叩オプジェクトを自分の中に取り 度を変えたコピーなどもコヒ。ーコンストラ と少々ややこしいのて、ソースを参照してく 込むことと , 取り込んだすべてのポリゴン クタ呼び出し一発て、済んて、しまいます。 ださい に描画や色変更といった各種操作を行うよ PolyGroup て、はポリゴンをリスト構造に う指示を出すことて、す。 して持っているため , Polygon クラスと同様 、 , 甲 0 Ⅳ Group クラス このようにしておくことにより , たとえ なポインタメンバの問題が起きます。代入 ポリゴンをグループとしてまとめて扱う ば PoIyGroup のオプジェクトに Draw という やコヒ。ーが起きるときには , 持っているポ ためのクラスて、す。 PoIyGroup クラスのお ッセージを送ると , それが取り込んだす メ リゴンをすべてコピーするようにプログラ ムしてやる必要があります。 AbstructPolygon クラスの宣言 サンプルプログラムて、は足跡が 8 個の P01 ygon から構成されていて , これらをまとめ て PoIyGroup にしています。最初に左足の 足跡の PolyGroup を作り , これを元にコヒ。 ーコンストラクタて、大きさや角度の違う足 跡を作っています。 List 20 1 : / / = = ・ポリゴンの抽象クラス = 2 : class AbstructPolygon 4 : pub ⅱ c : 5 : AbstructPOIygon(void) virtual 6 : AbstructPoI ygon* MakeCl one(i nt angle virtual 7 : double sc 引 e = 1. の 8 : void ChangePaIette(word16 pal) 9 : virtual void FIipX(void) virtual void FlipY(void) v i r tual int Draw(Gscreen& gs, int X, int y) virtual 13 : int DrawWire(Gscreen& gs, virtual int y) = 0 : 〃輪郭線描画 int X, / / デストラクタ / / 自分のコピーを作成 〃描画色変更 / / X 座標の符号反転 / / Y 座標の符号反転 / / 描画 おわりに グラフィックライプラリを使ったふたっ のサンプルプログラムを紹介しました。 も っとも polydemo のほうは , グラフィックラ イプラリの使用例というよりはポリゴンを より抽象化して扱うことのて、きるライプラ リの可能性を示すような内容になっていま す。 P01ygon はまだまだ熟成が不足したクラス て、す。たとえば項点がリスト構造て、管理さ れているわりには , これらを操作するため の関数が乏しいといえます。また , 別のア プリケーションプログラムを作ることによ り , 拡張されていく余地を残しているとい えるて、しよう。 [ 参考文献 ] [ 1 ] 『注解 C 十十リファレンスマニュア ル』 , M. A. 工リス , B. ストラウストラ ップ著 , 足立高徳 , 小山裕司訳 , トッパ ン , 1992. [ 2 ] fEffectiveC 十十』 , スコット・マイヤ ーズ著 , 岩谷宏訳 , ソフトバンク , 1993. [ 3 ] 「入門グラフィックス』 , 佐藤義雄 , スキー , 1984. PO Ⅳ gon クラスの宣言 List = 2 次元座標ニ 2 : struct POint 4 : 6 : 8 : class Polygon ・ public AbstructP01ygon / / = 頂点の構造体ニ 10 : struct Vertex / / 頂点の座標 Point P ; / / 次の頂点へのポインタ Vertex* next : / / 頂点リストの最初の要素へのポインタ Vertex* head; Vertex* tai l; / / 頂点リストの最後の要素へのポインタ / / 頂点数 0rd16 count; Point 〃全ての頂点を囲む矩形の左下座標 Point / / 全ての頂点を囲む矩形の右上座標 ru; 20 : / / 描画パレット番号 当 0rd16 palette; 2 1 : public: PoIygon(word16 pal=7) ; 22 : / / コンストラクタ Polygon(const Polygon& poly, 23 : int angIe=0,double scale=l. の : 24 : / / コピーコンストラクタ POIygon(void); 25 : / / テストラクタ PoIygon& operator=(Polygon& poly) : 26 : 〃代入演算子 AbstructPoIygon* MakeCIone(int angle:(), 27 : double sca = 1. の : 28 : / / 自分のコピーを作成 CIear(void); VO i d 29 : / / 頂点リストの解放 Add(const Point& point); VO i d 30 : / / 頂点の追加 Add(const Point* points); VO i d / / 複数の頂点の追加 32 : VO i d ChangePalette(word16 pal); 33 : / / 描画色変更 FIipX(void); VO i d / / X 座標の符号反転 VO i d FlipY(void); / / Y 座標の符号反転 int Draw(Gscreen& gs, i nt 36 : X, int / / 描画 int DrawWire(Gscreen& gs, int X. int y); / / 輪享 5 線描画 38 : } ; / / x, y 座標 i n t X, y ; ニポリゴンニ 特集 実用グラフィック術 65

5. 月刊 C MAGAZINE 1994年1月号

C 十十入門 List 6 このように , 引数を参照渡しすると , 関 数から実引数の値を変更することがて、きま す。関数を使う側にとっては , 値が変更さ れないか気がかりになってしまうて、しよう。 て、すから , ポインタを渡すときと同じよう に , 値を変更しない参照型引数には const を つけて , その旨を明確にしておきます。 List 1 に戻ってみると , 1 行目の仮引数 t は const なのて、 , 値が変更されないことが保障 されます。また , List 3—List 6 て、は , コピ ーコンストラクタや代入演算子の引数は , コヒ。ー元として値が変更されないのて、 , co nst になっています。 const な参照て、は , 初期化の条件が若干ゆ るみ , 変換て、同じ型にて、きるオプジェクト によっても初期化て、きます。その際には , 変換によって一時的オプジェクトが生成さ れ , それへの参照となります。 / / int → doub 厄の変換が必要 参照の定義て、は , 同じ型のオプジェクトに / / ェラー ラスは , UPen を生成してポインタ pen て、管 double& rd=l ; 理しており , デストラクタて、 delete していま よって初期化されることに注意してくださ const double& crd= 1 ・ す (List 6 の 21 行目 ) 。このため , コピーコン 参照冫し利点 これて、 , オプジェクトに別の名前をつけ ストラクタ ( 13 ~ 19 行目 ) と代入演算子 ( 23 ることがて、きます。別名はつけ換えること ~ 36 行目 ) の定義が必要になります。説明 はリスト中に示しておきましたのて、 , 適宜 がて、きません。 ⅢⅢ日ⅢⅢⅢⅢⅢⅢⅢⅢⅢ【例」題 6 】ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ 別名は元の名前と同じように扱うことが 該当する例題を復習しながら読んて、くださ 引数の参照渡しと , 値渡しとはどう違い て、きます。オプジェクトのメンバアクセス ますか ? 続いて , 今話題に出てきた「参照」につい についても同様て、す。 ( 例 ) Point p ; 消費するメモリと , 生成・消減の手間が て説明することにします。 違う。 POint& rp=p , 参照 引数を値て、渡すと , size 。 f ( クラス ) だけの rp. move tO ( 1 , 2 ) ; List 7 て、は , 9 行目て、 , a に b という別名を スタック領域を消費します。もし List 1 が , ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡ【リ題 5 】ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡⅢⅢ 定義しています。 a も b も同じオプジェクト void draw branch (UTurtle t, List 7 はどのように実行されますか ? を表しているのて、 , 実行結果て、は常に同じ というように値渡しの関数だとします。 ⅢⅢⅢⅢ日ⅢⅢⅢⅢⅢⅢⅢ、く解答 > ⅧⅢⅢⅢⅢⅢⅢⅢⅢⅢ れを , 値が表示されています。 13 行目て、 f00 ( ) を呼び出していますが , 3 a : 2 , b ・ 2 UTurtIe p , 行目を見ると , 引数が参照型になっていま draw branch (p, と呼び出すと , 引数は , あたかも , す。この部分を , あたかも , 参照型のオプジェクトは , 基本的に UTurtIe t=p , i nt& c = a ・ 型 & 別名 = オプジェクト ; というように初期化されているとみなしま というように初期化されます。これはコヒ。 という形式て、定義します。 す。したがって , 新たに c が a の別名となる ーコンストラクタによって t が生成されるこ ( 例 )char* s= ” ABC ” とを示しています ( 【例題 1 】参照 ) 。結局 , siz のて、 , 4 行目て、値を代入すると , a の値が変 更されることになります。 eof(UTurtle) のメモリを消費し , コンスト char*& p=s ・ ワ 3 りひっ 0 00 り 0 00 っ 0 theta_ src. theta_ pen_state state S rc. pe n ー canvas src. canvas delete pen_ i f (src. pen_) pen_ else pen_ re t urn * t h i s ; / / ペンの廃棄と生成 new UPen(*src. pen_) ; / / * t h i s を返すこと ! 【例題 5 】参照型 . どのように実行されますか ? ・く、ーノ′く 0 ー 1 b くく endl; くく endl; b くく b くく endl; a くく a くく くく a くく Ⅲ日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢく解答 > ⅢⅢⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢ / / 別名定義 Case Study A 級 B 型 C 十十入門 105

6. 月刊 C MAGAZINE 1994年1月号

ます。メンバ関数の中て、メンバをアクセス するときに , name としますが , this- > nam e を省略した形だということもて、きます。っ まり「自分のメンバ」ということて、す。 舌をもとに戻しましよう。コビー元が自 分自身て、ないなら , 続いて , データメンバ の値をおのおのコヒ。ーします。 List 4 て、は , 7 行目以降て、す。その際 , 自分が管理すべき 動的オプジェクトがあれば , 忘れず delete し ます。もし , 6 行目の自己代入チェックがな いと , ここて、 , 自分のデータ内容を delete し てしまうという , まずい事態が起こります。 9 , 10 行目はコピーコンストラクタと同様の コヒ。ー作業て、す。 最後に , 代入演算子は * this を返り値とし て , 自分自身を返します。こうしておけば , a = b = c ; というように , C 言語からの慣習に基づく連 鎖した代入文が可能になります。 代入演算子の返り値の型は , そのクラス の参照型て、す。別名を返すわけて、すが , ち ょっと理解しにくいて、すね。しかし「自分自 身の別名を返す」といってみると , 少しわか ったような気がしませんか ? ( 【例題 6 】 ) どんオとキに必要か 以上のようなコヒ。ーコンストラクタと代 入演算子の定義は , どんなときに必要にな るのて、しようか。 一般的にいって , 動的オプジェクトを指 すポインタや参照がデータメンバにあって , その動的オプジェクトをデストラクタて、 de lete するときに , コヒ。ーコンストラクタと代 入演算子を定義しておきます。したがって , 先月の題材にしたようなクラスには , 本当 はこうした配慮が必要なのて、す。その理由 メモリの解 は , すて、におわかりのとおり , 放し忘れ ( 漏れ : リーク ) と , 二重解放を防 ぐためて、す。 タートルグラフィックスのプログラムて、 はどうなっているて、しようか。 List 5 , Lis t 6 にリストの一部を示します。 UTurtIe ク 【例題 4 】解答 : 代入演算子の定義 List 1 : class person { 3 : pub ⅱ c : Person& operator=(const Person& src) &src) return *this; i f ( th i s : 〃メンバの代入。 src. age; age deleteC] name; / / 廃棄してから、 8 : new charcstrlen(src. name) 十 1 ] , / / 新たに生成する。 name strcpy(name, src. name); / / 内容をコピー 〃自分を返す。 return *this; ・・・中略・ 一三ロ / / 自分からの代入を防ぐ。 turtle. h ( 部分 ) List 1 : class UTurtIe { 2 : p r i va te : double x_ double theta ・ enum {PEN-UP, PEN-DOWN} pen-state- ・ 7 : protected: UCanvas* canvas_ UPen* pen_ 1 1 : pub ⅱ c : UTurtle(); 12 : UTurtIe(UCanvas*); UTurtle(const UTurtIe&); -UTurtle(); UTurtle& operator=(const UTurtIe&); 【例題 1 1 】参照 【例題 3 】参照 【例題 4 】参照 ・・・以下略・・ #include ” turtle. h ” 1 : UTurtl e : : UTu r 凵 e ( ) / / デフォルトコンストラクタ ( 【例題 1 1 】参照 ) 3 : canvas-(0), pen-( の theta-(0), pen-state-(PEN-UP), 7 : UTurtle: :UTurtle(UCanvas* cv) theta-(0), pen_state-(PEN-UP), canvas_(cv) 8 : = new UPen; / / UPen を生成する 10 : 13 : UTurtle::UTurtIe(const UTurtle& src) ・ // コピーコンストラクタ . x-(src. (-), y-(src. (-), theta-(src. theta-), / / 【例題 3 】参照 pen_state-(src. pen-state-), canvas_(src. canvas_) if (src. pen-) pen- = new UPen(*src. pen-); / / コピーを生成する 18 : else pen- 19 : } 20 : 21 : UTurtle::-UTurtle() { delete pen- ; } / / デストラクタ 22 : 23 : UTurtle& UTurtIe::operator=(const UTurtle& src) / / 代入演算子の多重定義 ( 【例題 4 】参照 ) 24 / / 自分からの代入チェック i f ( th i s = &src) return *this; 26 : / / データメンパごとの代入 27 : 28 : turtle. c ( 部分 ) List pen- src. X src. Y_ ・ C MAGAZINE 1994 1 104

7. 月刊 C MAGAZINE 1994年1月号

ln ル a ⅱ行 om m 川計 Makers システム・ワン Power C Ver. 2. OJ Power C AssembIer. きます。 モジュールは IDT 疑似命令て開 アセンプラで 始され , END 疑似命令て、終了しま 記述する C の関数 すのて , プログラムはこの間に記 述することになります。関数名に はパプリック属性を持たせるため , ほとんどのアプリケーションの DEF 疑似命令て宣言しています (L 開発て、は , C 言語だけて、も十分に対 応てきますが , 場合によってはア ist 1 ) 。 センプリ言語が必要になることが PCA< は , モジュールが論理セ グメントを構成するのて、 , 1 モジュ あります。 この場合には , Power C Assem ールが 1 論理セグメントとなってい bler( 以下 PCA) を使用して開発を ます。 することがてきます。 このため , 論理セグメントの指 PCA は , C の主プログラムに結合 定は必要なく , ロケーションの指 TabIe 1 引数テータの大きさ して動作するオプジェクトプログ 定だけになります ( ただし , ひとつ 受け渡されるデータ型 スタックに積まれるサイズ のファイルに複数のモジュールを ラムを出力することを目的として 2 バイト Cha 「 , short, int, near pointer おり , アセンプリ言語のみて、独立 定義可能て、す ) 。 4 ノヾイト long, far pointer したプログラムを作成することは 8 バイト float, double て、きません。 cha 「型のテータは int 型に , float 型のテータは doub 厄型に変換された値が渡され 引数の受け渡しと る ( ただし , プラグマの指定により変換を禁止することか可能 ) 戻り値 TabIe 2 戻り値を格納するレジスタ アセンプラ命令 戻り値となるデータ型 格納に使用されるレジスタ 関数呼び出して、は , 引数の受け Char AL short, int, near pointer AX 渡しにはスタックを使用します。 long, float, far pointer AX, D X PCA のアセンプラ命令 ( 疑似命 まず , 引数を逆順に ( 第 1 引数が double AX, BX, CX, DX 令 ) は独特て、すが , ほかのアセンプ スタックトップにくるように ) スタ ラとの対応関係をつかめば理解て、 に積まれます。 タの大きさ (TabIe 1) と積まれる ックにブッシュします。 きると思いますのて、 , ここて、は最 この後 , 呼び出された関数側に 順序から算出されます。 次に , CALL 命令て、関数を呼び出 制御が移り , 呼び出し元の関数て、 関数の戻り値の受け渡しにはレ 低限必要なことのみを説明してい すのて、 , 戻りアドレスがスタック ジスタを使用します。戻り値は , フレームボインタとして使用して いた BP を保存し , 新しい値を設定 AX, BX, CX, DX の全部 , ある アセンプリ言語で記述された C の関数の雛形 いは一部に格納されて返されます します。 (TabIe 2 ) 。 そして , 局所変数の領域を確保 なお , このときに使用されない するために SP を減じて , 初期処理 ものは破壊しても差し支えありま は完了します。 初期処理が済むと , スタックは せん。 DS, SS, SP, BP は保存しなく Fig. 1 に示すようになっているの て , BP に適当なディスプレースメ てはなりません。 SI, DI, ES は破壊が可能て , 自 ントを加えることて、 , 引数データ 由に使用することがて、きます ( コン や局所データを指定することが可 パイラて、は , SI と DI はレジスタ変 能になります。 引数データの位置は , 引数デー 数と使用してされています ) 。 Fig. 1 スタックの構成 SP → 局所データ 1 List 1 の最初の 3 個の命令を実行し た時点のスタックの様子 局所テータは , BP に負のティスプレ ースメントを加えてアクセスされる 局所テータ N 旧 BP の保存 引数テータは , BP に正のティスプレ ースメントを加えてアクセスされる 戻りアドレス 引数テータ 1 の位置は , 現在の BP の 値に BP の保存テータの大きさと戻り 引数テータ 1 アドレスの大きさを加えた値で指定 される ニアコールされた場合 BP 十 4 ファーコールされた場合 BP 十 6 BP → 引数テータ N 4 List 1 idt de f く function name> push mov sub く module name> ; モジュールの開始 : パプリック関数の定義 く function name> : 旧 BP の保存 新 BP の設定 bP. SP : 局所データ領域の確保 く local area size> SP, , 各々処理を記述 , 局所データ領域の解放 , 旧 BP の復旧 . 呼び出し関数へ戻る , モジュールの終了 ー中略ー S P, bp mov POP ret end lnformation from Compiler Makers 167

8. 月刊 C MAGAZINE 1994年1月号

$ に乢プログラミング あるいは警告が出されるのが普通て、ある。 これによってライプラリに関しては引数の 型や個数の不整合が原因て、引き起こされる 工ラーが未然に回避されるようになったの て、ある。ただし可変引数を受け取る関数の 可変部分の引数に関しては , この恩恵を受 けることはて、きない ライプラリを呼び出すときに引数の型を 間違えてしまうという古典的なケースの典 型例として ,List 2 と List 3 のふたつがあっ た ( あえて , パラメータタイプリストなしの 古典的なスタイルて、書いておく ) 。これら は , いずれもプロトタイプのない時代には 誤ったコーディングて、あった。 List 2 て、は s qrt を呼び出すときの実引数の型は d 。 uble て、 なければならない 0List 3 て、は , fseek の 2 番 目の引数の型は long て、なければならない しかし , ANSI ライプラリを利用しているか ぎり , これらはまったく正しいプログラム て、ある。それぞれ必要なヘッダを # include し ているから , その中て、プロトタイプ宣言が 行われる。それゆえコンパイラは , そこか ら得た仮引数型情報に基づいて , 呼び出し アメリカ・日本など 1 : 3 : 5 : 1 : 2 : 5 : 7 : とだが , ヘッダの中て、宣言 うまて、もない なければならないとされている。また , 言の外側 , いい換えれば関数定義の外側て、 はない。 # include するならば , それは外部宣 なところて、 #include してもよいということて、 ただし , これは無制限にソース内のどん し , assert. h だけは少々例外規定がある ) 。 と明確に定められていることて、ある ( ただ clude しているのと同じ意味を持っている , ude してもよい。それらはただ一度だけ #in 度 #include してもよく , どんな順序て、 # incl いルールがある。標準ヘッダに関しては何 ANSI が定めたもうひとっ忘れてはならな どんな順序でも可 #include は何度でも , いた場合にかぎられる話て、ある。 もちろんこれは必要なヘッダを # include して 換されて渡され , すべてが丸く納まるのだ。 は , 同じく int の実引数て、ある 0 が long へと変 2 が double へと変換されて渡され , fseek て、 る。このため , sqrt て、は int の実引数て、ある に際して実引数に型変換を施すことがて、き ないしは定義されている識別子やマクロを 参照する場合には , それらの宣言ないしは 定義が含まれているヘッダ ( のうちの少なく ともひとつ ) を前もって #include しておかな ければならない。そうして , これまた当た り前だが , #include する側のプログラムて、 , 前もってこれから定義しようとするキーワ ードと字句的に等しい名前のマクロを定義 してあってはならない。さらに , List 4 のよ うなことをしては , 結果が保証されないの も明白て、ある。 実際のプログラミング習慣としては , #in clude はソースの先頭部分て、まとめて行うの が普通て、あるから , ます前述したような制 限が間題になることはないだろう。 ANSI が 「標準ヘッダはどんな順序て、何度 # include し てもよい」としたのは , 分割コンパイルなど を想定したユーザの便宜を図るためのルー ルかと思われる。このうち , どんな順序て、 もかまわないというのは , 一見してメリッ トて、あることがわかるだろう。あるヘッダ を #include するときに , それよりも先に何か 別のものを # include しておかなければならな Fig. 3 国によって異なる数表記 1 , 234 , 567.89 1 .234.567 , 89 オランダ・ノルウェーなど Fig. 4 標準ヘッダ名ー く assert. h> く ctype. h > く e 「 rno. h > く float. h> く limits. h> く locale. h> <math. h> く setjmp. h> く signal.h> <stdarg. h> く stddef. h> く stdio. h > <stdlib. h> く string. h> く time. h> List 2 sq の呼び出し ( p 「 e ー ANSI では誤り , ANSI では問題なし ) LiSt 3 fseek の呼び出し ( p 「 e ー AN では誤り , ANSI では問題なし ) #include く math. h> d ou b I e x ; void f00 ( ) ニ sqrt(2); X # i ncl ude く s t d i 0. h > 3 : F I LE *fp : void bar() fseek(fp, 0 , SEEK_SET); ANSI C Library プログラミング 71

9. 月刊 C MAGAZINE 1994年1月号

しかし , 考えてみれば , Table 1 のようなデ ⅢⅢⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢく解答 > ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡ TabIe 1 【例題 10 】名簿 ータを動的に生成して毎回初期化する必要 番号 名前 Person ap なんてどこにもなく , static て、 const な配列 , 1 2 ) , Person ( Fumi にして【例題 10 】のく解答 > のとおり初期 Person ( Hiyo , 1 4 ) , 1 Hiyo 化するのが望ましいて、すね。 Person ( ' , 1 0 ) , Kazu 2 Kazu ところて、 , 動的配列は初期化されないの Person ( ' , 1 0 ) , 3 Rome て、しようか ? 実は , 配列が生成されると Person ( " , 1 0 ) Minmi きに , 各要素について引数のないコンスト 4 Minmi ラクタが呼び出されて初期化されます。 配列の初期化について説明します。 し モリ管理の問題は解決しました。しかし , 配列の初期化のためのパラメータは , 、、 { } 〃 たがって , 油断は禁物て、す。 て、囲んだ形て、書きます。それぞれの要素に : Person ( ) Person . メモリリークが , 「動的オプジェクトには name(0), age(0) { } ついて , コンストラクタを呼び出すような 名前がない」ことに由来するのに対し , 二重 というようなコンストラクタが定義されて 形て、書いていきます。 解放の原因は , 「ひとつのオプジェクトを , いなければなりません。これをデフォルト 引数がひとって、あるコンストラクタを持 複数の別名やポインタで間接的に示すこと コンストラクタと呼びます。 っている場合には省略記法を用いることが ができ , それぞれが delete し得る」ことにあ デフォルトコンストラクタは , て、きます。たとえば , るといえそうて、す。つまり , オプジェクト Person ap [ 5 ] ; . Person (const char * n) Person . の間接アクセスが可能なかぎり , メモリ管 というように , 初期化パラメータのない配 列定義のときにも , 各要素を初期化するの 理上の危険性はなくなりません。 というコンストラクタがあれば , しかしながら , 参照型やポインタは , 後 に用いられます。 Person ap 半の例題て、見たように , とても便利な機能 なお , char や double などの組み込みデー HiY0' て、あり , うまく使えば効率がよくなります。 タ型については , デフォルトコンストラク Minmi Rome また , 間接アクセスて、なければ実現て、きな タを定義て、きないのて、 , 配列は未初期化状 いデータ構造もたくさん存在します。とて 態になっています。て、すから , 直後に for 文 というように書けます。もちろん , これて、 も見捨てるわけにはいきません。気をつけ なり memset( ) て、初期値を代入しなければな は年齢が設定されていないのて、 , 今の例題 て慎重に活用しましよう。 りません。 の答えとしては NG て、すが。 今回は , 二重解放に対処するために , 代 なお , コンパイラによっては , auto 記憶 まと 入のたびに動的オプジェクトのコピーを生 クラスの配列を初期化て、きないことがあり 成する , という方法を採用しました。この ます。そこて、 , 動的に生成する配列を初期 方法にはメモリを浪費するという欠点があ 化してみようというのが , 次の【例題 11 】 初期化と代入を中心にして , クラスの書 き方について説明しました。本誌の過去の ります。 て、す。 これ以外に , コピーを作る代わりに参照 某入門記事て、も , 代入演算子を void 型て、定 ⅢⅡⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ【例」題 1 1 】ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ日 回数を記録しておき , 回数が 0 て、ないオプジ 義し , また , 自己への代入のチェックを怠 動的に生成する配列を , 【例題 10 】と同じ ェクトは delete しない , という効率のよい方 るというミスをしています。表面上の文法 ように初期化することは可能ですか ? を知っていても , 適切な文脈のうえて、どの 法もあります。 ⅢⅡⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢく解答 > ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡ ように使うべきかを知っていなければ意味 あるいは , private のコピーコンストラク できない。 タと代入演算子を宣言して , コヒ。ーを禁止 要素数 5 の Person クラスの配列を動的に生 がありませんね。 するという方法もあります。これらの方法 初期化と代入は , メモリ管理と関係があ 成しましよう。 については , クラスの継承のときに説明し ることがわかりました。デフォルトのコヒ。 Person * pap=new Person [ 5 ] ; ましよう。 ー動作て、は , メモリリークと二重解放の危 と書くのて、したね。 こには初期化パラメ 次回は , 同じ題材を用いて , C 十十の細か 険性があって注意が必要て、す。 ータを書く余地がありません。て、すから問 い機能や , プログラミングテクニックにつ コヒ。ーコンストラクタと代入演算子を正 題の答は「不可能」となります。生成してか いて説明する予定て、す。お楽しみに しく定義すれば , 初期化と代入に関してメ ら要素ごとに値を代入するしかありません。 年齢 2 4 0 0 0 0 'Kazu 108 C MAGAZINE 1994 1

10. 月刊 C MAGAZINE 1994年1月号

いといった制限があるとコーディング作業 が煩雑になるからて、ある。これを実現する ためには , 標準ヘッダ間て、の相互依存関係 をなくしておく必要がある。 一方 , 「何度て、も」というのは , 直ちにメ リットが理解て、きないかもしれない だが , これまたコーディング上て、はたいへん有利 な特性て、ある。どうしてそうなのかを以下 の架空のシナリオに沿って考えてみよう。 あるユーザ定義ライプラリを設計するこ とを考えよう。以下 , 便宜上このユーザ定 義ライプラリを libl と呼ぶ。また libl は , へ ッダファイルを提供していて , それを #incl ude することて、 , ユーザ定義ライプラリに含 まれる関数のプロトタイプを宣言している ものとする。このヘッダファイルを libl. h と しよう。さらに , libl に含まれるユーザ定義 のライプラリ関数のうちには , FILE * とい う型の実引数を受け取るものがあるとしよ う。 FILE * という型を引数に受け取ろうと すると , 前もって <stdio. h> を #include して おかなければならない。ユーザ定義のヘッ ダて、は , このような標準ヘッダが宣言して いる名前への依存が生じてしまうのはある 程度は避けられない事態て、ある。もちろん , ここに示した特定のケースて、は , ポータビ リティを無視すればほかの解決手段もある が , 移植性も考慮しているものとする。 そうすると , この作業をどこて、行うべき かが問題になる。この場合 , ライプラリの 設計者としてのユーザには , ふたつの選択 が可能て、ある。ひとつはユーザ定義ライプ List Bogus #include 4 #define int double # i nc 1 ude く s td i 0. h 〉 72 C MAGAZINE 1994 1 ラリを使う側のユーザプログラムの中て、 , 必要な # include を並べて書いてもらうように するというやり方て、ある。この場合ふたっ の # include の順序が重要て、あって , #include く stdiO. h > #include ”ⅱ bl. h ” のように必ず < stdio. h > を先にしなければ ならない もうひとつのアプローチは , どうせ必ず #include しなければならないのて、あれば , そ れは libl. h の先頭て kstdio. h > を #include し てしまうというやり方て、ある。この手法を 取ると , libl を利用するユーザプログラムに とっては , libl. h を #include するに先だっ て , <stdio. h> を #include しておかなければ ならないという制約がなくなり , 余計な心 配ごとが減る。めてたしめて、たして、ある。 だから , この解決策は実際によく使われる 手法て、ある。 て、は , ューザプログラムそれ自身が libl の 内容とは無関係に , く stdi 。 . h > が提供してい る機能を使いたくなったとしたらどうだろ うか。この場合 , libl. h の中て、すて、に # inclu de されているから , ューザプログラムの中 て kstdio. h> を #include せずにおくのは , 健 全な姿勢て、はないだろう。 libl. h の中て、 < s tdio. h> を #include しているのは , libl のお 家の事情て、あって , 将来的には仕様が変更 になるかもしれないのて、ある。 やはりいちばん素直なのは , ューザプロ グラムの中て、 < stdio. h > と lib. h の両方を # in clude することて、ある。ということになる と , 結果的に <stdio. h> が二重に #include さ れることになる。しかし , このようなコー ディングの習慣はよく見かけるものて、ある。 ANSI て、も RationaIe によれば , そのような 習慣を尊重する意味て、 , 何度 #include しても よいというルールにしているらしい もし , 標準ヘッダを通常のテキストファ イルとして実装するというオーソドックス なアプローチを採用する場合には , 現 実に , ほとんどの処理系はこのようなアプ ローチを採っているはずだが あるヘ ッダを何度 # include してもよいとするには , ちょっとした配慮が必要て、ある。というの も , ヘッダの中て、何らかの定義を行ってい る場合に , マクロ定義を除いてヘッダが一 度以上 #include されると , その部分て、二重定 義のエラーになってしまうからて、ある。端 的な例としては struct/union/enum などの定 義があげられる。ちなみに , 標準ヘッダは 同じ効果を得られるかぎりどのような実装 の方法をしてもよいことになっている。た とえば , 標準ヘッダに対する # include をコン パイラのフロントエンドが「見た」瞬間に コンパイラが管理している内部的な識別子 テープルに標準ヘッダの中て、定義 , 宣言さ れるべき情報を直接展開するというような 実装をしてもいっこうにかまわないという ことになっている。 標準ヘッダの中て、二重定義を防ぐには , ANSI の RationaIe が示しているように , Li st 5 に示すような方法が考えられる ( Ration ale 4.2.1 ) 。原理は , 以下のとおりて、ある。 List 標準ヘッダの中でニ重定義を防ぐ 5 #ifndef _ERROR_H #define _ERROR_H / * body of く errno. h> * / #endif