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