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