C プログラミング入門 基幹 7 ( 水 5) 13: 構造体 Linux にログインし 以下の講義ページを開いておくこと http://www-it.sci.waseda.ac.jp/ teachers/w483692/cpr1/ 2016-07-06 1
例題 : 多角形の面積 n = 5 (5 角形 ) の例 n 1 n 1 1 p 1 T 0 S = i=0 p 0 T i = i=0 2 p i p i+1 where p n = p 0 p 2 S θ 0 二次元の位置ベクトル原点 ( ゼロベクトル ) p 4 p 3 この計算は T i を符号付き面積とし 頂点の順に番号付けを行うことで 原点の位置によらない 3
例題 : 多角形の面積 // n 個の x 座標列 px と, y 座標列 py の作る多角形の面積 // ただし 点は左回りに並んでいるとする double PolygonArea(double *px, double *py, int n) double S = 0.0; int i; double px[] と書いてもよい for(i = 0; i < n; ++i) S += TriangleArea(px[i], py[i], px[(i+1)%n], py[(i+1)%n]); } return S; i == n-1 の時 0 となる } 1 つ目の点の座標 *px *py 1.0 2.4 5.0 1.5 2.0 1.0 1.3 0.2 2.0 0.3 4
例題 : 多角形の面積 // 3 点 (0,0), (x1, y1), (x2, y2) のなす三角形の面積 // 時計回りに並んでいる場合正 そうでない場合は負となる double TriangleArea(double x1, double y1, double x2, double y2) return (x1*y2 - x2*y1) / 2.0; } p 2 = x 2, y 2 T θ 0 p 1 = x 1, y 1 T = 1 2 p 1 p 2 sin θ = 1 2 p 1 p 2 = 1 2 x 1y 2 x 2 y 1 5
例題 : 多角形の面積 観察ポイント 点の座標 x, y がバラバラに扱われている 関数の引数の数が多い 点を表す新しい型を作れないか? たとえば 引数の総数が減っている バラバラに扱う場合 double PolygonArea(double *px, double *py, int n); double TriangleArea(double x1, double y1, double x2, double y2); POINT という型で表した場合 double PolygonArea(POINT *p, int n); double TriangleArea(POINT p1, POINT p2); 6
構造体の概要 複数の変数をまとめて一つの変数として扱う方法 int や double といった型と同じように使用可能 構造体 Point 型の定義 構造体 Point 型を使ったコード typedef struct double x, y; } Point; double S; Point P1 = 1, 2 }; Point P2 = -2, 1 }; S = TriangleArea(P1, P2); 今日の目標 : このようなコードを書けるようになること 7
構造体の説明目次 構造体の定義 構造体型の変数と typedef 構造体の初期化 構造体の読み書き 構造体のコピー 関数による構造体の扱い 構造体へのポインタ 8
構造体の定義 キーワード struct TagName int x, y; double a, b, c; }; 構造体を区別するためのタグ名 任意個数のメンバ変数 セミコロンで終わる 構造体は一つのメモリ領域に複数の変数を格納する それぞれをメンバ (member) という 9
struct Point double x, y; }; 構造体型の変数 struct Point p1 double x double y メモリ上のレイアウト ( 順番や隙間の大きさ ) は環境による int main(void) struct Point p1, p2; struct Point p2 double x double y struct + タグ名 で一つの型名を構成する 型名として "struct TagName" を使う 10
typedef struct Point double x, y; } Point; int main(void) struct Point p1; Point p2; 1 単語で型名を書ける 構造体型の変数 構造体型を Point という型名として定義する struct Point p1 double x double y この場合 タグ名を省略してもよい タグ名と型名は同じで構わない タグ名を struct _Point やp2Point_tag の様に区別する流儀もある double x double y また 構造体の別名をすべて大文字で表す流儀もある struct キーワードを付けるのは面倒なので 多くの場合 typedef を使って別名を定義 11
構造体の初期化 typedef struct double x, y; } Point; int main(void) Point p1 = 1.0, 2.0 }; Point p2; メンバのかかれている順に与える 初期化が一部のみ指定されている場合は 残りはすべてゼロとなる struct Point p1 double x double y 1.0 2.0 struct Point p2 double x double y?? 構造体の初期化は 配列と似た記法を用いる 初期化をしない自動変数の値は不定 通常の変数と同様 12
構造体の初期化 typedef struct double x, y; } Point; int main(void) Point p1 = 1.0, 2.0 }; Point p2 = p1; すべてのメンバがコピーされる struct Point p1 double x double y 1.0 2.0 struct Point p2 double x double y 1.0 2.0 同じ構造体の別の変数を与えることにより すべてのメンバのコピーで初期化する 配列との大きな違い 13
構造体のアクセス 構造体のメンバは. 演算子を使う typedef struct double x, y; } Point; struct Point p1 double x double y 1.0 2.0 メンバ変数を読む int main(void) メンバ変数に代入する Point p1 = 1.0, 2.0 }; printf("p1=(%f,%f) n", p1.x, p1.y); p1.x = -p1.y; printf("p1=(%f,%f) n", p1.x, p1.y); 出力 p1=(1.000000,2.000000) p1=(-2.000000,2.000000) 14
構造体のコピー 構造体は代入演算子によりコピーできる typedef struct double x, y; } Point; int main(void) Point p1 = 1.0, 2.0 }; Point p2 = p1; Point p3; 初期化のイコール p3 = p1; 代入演算子によるコピー 15
関数で構造体を扱う 構造体は関数の引数 戻り値として使える typedef struct double x, y; } Point; 実引数がコピーが渡される int main(void) 配列と異なる Point st = 1.0, 0.0 }; Point ed = 2.0, 2.0 }; Point md; // 中点を計算し戻り値として返す Point midpoint(point p1, Point p2) double mx = (p1.x + p2.x)/2.0; double my = (p1.y + p2.y)/2.0; Point m = mx, my }; return m; } md = midpoint(st, ed); 戻り値がコピーされる 仮引数へコピーされる 16
構造体の配列メンバ 構造体のメンバに配列を持たせることで 配列のコピーを行うこともできる typedef struct char name[20]; int age; } Person; 配列を含む構造体自体のコピーは可能 int main(void) Person X = "Taro", 22 }; Person Y; Y = X; Y.name = X.name; 配列の直接のコピーはできない Person X char name[20] "Taro" 配列のコピー Person Y char name[20] "Taro" int age 22 int age 22 17
構造体のポインタ変数メンバ ポインタ変数は単純にアドレス値のコピー ポインタの先はコピーしないので浅いコピー (shallow copy) と呼ばれる typedef struct 配列ではなくポインタ char *name; int age; } Person; 中のポインタはアドレ int main(void) スがコピーされる Person X = "Taro", 22 }; Person Y; Y = X; Y.name = X.name; この代入と同等 Person X char *name アドレスのコピー Person Y char *name int age 22 int age 22 システムのメモリ領域 "Taro" 18
変数の比較 変数の種類 宣言 初期化 代入演算子に よるコピー 基本型 int x; = 25; 可能 配列文字配列 構造体 ポインタ変数 int a[10]; = 1, 2, 3 }; char s[256]; struct Point P; Point P; = "string literal"; = 20, 30 }; = Q; (Point 構造体の別の変数 ) 不可能 可能 ( 内容全体 ) int *p; = &var; 可能 char *pstr; = "string literal"; ( アドレス値 ) typedef struct Point } Point; を定義した場合 アドレス演算子や malloc() の戻り値からアドレスを得る 定義の出現順に対応させる 構造体のサイズが大きい場合はコピーに時間がかかることに注意 19
構造体へのポインタ 以下の場合に使われる 構造体を直接変更する必要がある場合 構造体のコピーに時間がかかるのを避けたい場合 // 点の座標を原点に変更する void setorigin(point *p) (*p).x = 0; (*p).y = 0; } ポインタ p の内容を読むために まずデリファレンス演算子 * が必要 p の指す構造体のメンバにアクセスするために. 演算子を使うのだが 演算子の優先順位の関係で括弧が必要となる 20
構造体へのポインタ ( アロー演算子 ) (*p).x という記述を簡略化するためにアロー演算子 p->x が用意されている ポインタ p が指す構造体のメンバを直接矢印で指しているイメージ // 点の座標を原点に変更する void setorigin(point *p) p->x = 0; p->y = 0; } (*p).x と同じ意味 21
通常の配列と同じ 構造体の配列 動的メモリ 動的メモリで確保する場合は sizeof を使う 10 要素の配列変数の場合 10 要素の動的メモリの場合 int i; Point points[10]; for(i = 0; i < 10; ++i) points[i].x = i; points[i].y = i*i; int i; Point *points=malloc(sizeof(point)*10); for(i = 0; i < 10; ++i) points[i].x = i; points[i].y = i*i; 添え字演算子の優先順位のほうが高い 22
構造体の利点 欠点 関連する情報を一つにまとめることができる 他の変数と同じように扱える 関数などでのやり取りが簡潔になる ポインタを介した操作が ( さらに ) 理解しにくい 普通の変数なら. 演算子 ポインタ変数なら -> 演算子 23
<stdio.h> の FILE 構造体の例 OpenCV ( 一般のライブラリの一つ ) の CvPoint, CvSize, CvRect など 秋期 C プログラミング で扱うリンクリストや木のようなデータ構造 C++ では変数だけでなく関数も持てるように拡張されている ( クラスと呼ばれる ) 24
構造体の細かい話 (1) 構造体の変数を以下の構文で直接定義することができるが あまり使用されない struct TagName } varname; 今までの例では最後の変数名を省略し 型の定義のみを行っている 構造体にビット単位でアクセスすることを可能にするビットフィールドという機能がある 講義では省略する メモリ効率やバイナリレベルでの互換性のために使われるが 通常は使用しない 25
C99 での新機能 構造体の細かい話 (2) メンバ名を指定した初期化 (Designated Initializer) 例 : Point P =.y = 10,.x = 20 }; 最後のメンバとしてサイズを指定しない配列が書ける (0 長配列 ) 構造体をリテラルとして書く複合リテラル (compound literal) 26