プログラミング入門2 ポインタについて補足 構造体 第11回 芝浦工業大学情報工学科 青木 義満、篠埜 功 I’ll get started with Introduction and Conventional works around our study, then mention our motivation and goal. Next, I’ll explain our 3D Face Modeling Method and its medical application. Finally, I’ll conclude this presentation with some future works. 芝浦工業大学情報工学科 青木 義満、篠埜 功
ポインタについて補足(20分程度) ポインタには型がある。 指す先の型に応じて型が異なる。 ポインタとは アドレス。ポインタ型(指す先の型がのとき * 型)を持つ。型に応じて、演算が異なる。 ポインタ型の変数とは *型の変数( *p; のように宣言) は、型のサイズ(sizeof で取得可能)の領域のアドレスを保持するための変数。右辺値としてそのアドレスを持つ。(左辺値は、その変数自身のアドレス) (例) int x; int * p; x = 1; p = &x; 30番地 p用の領域(4byte) 34番地 x用の領域(4byte) プログラミング入門2
ポインタについて補足(20分程度) ポインタ型の演算 ポインタ型の式 と int型 の式は加算(型に応じて異なる)を行える。 型が同じポインタ同士は減算(型に応じて異なる)を行える。 eが へのポインタ型 ( * 型)の式のとき、式 *e の型は 型である。 eが へのポインタ型 ( * 型)の式のとき、式 *e は、eの指す先(eの評価結果の値(これはアドレス)およびによって決まる値)を右辺値、eの右辺値を左辺値として持つ。 30番地 p用の領域(4byte) (例) int x; int * p; x = 2; p = &x; /* pに34が代入される */ p = p + 1; /* 4が足され、pに38が代入される */ x = p - &x; /* pと&xは同じ型(int *型)であり、 (38 – 34) / 4 で、xに1が代入される */ p = p – 1; /* 4が引かれ、pに34が代入される */ 34番地 x用の領域(4byte) プログラミング入門2
ポインタについて補足(20分程度) &演算子について 左辺値を持つ式に&を付けることができる。 左辺値を持つ式の例 変数 配列型の式 *が一番外側についた式(e[i]の形の式もこれに該当) 左辺値をもたない式の例 1 など、基本型の式 一番外側が&になっている式 --- &(&e)の形の式は、eがとのような式であっても許されない。 プログラミング入門2
ポインタについて補足(20分程度) ポインタ型と配列型の違い ポインタ型の式は代入文(代入式)の左辺に書けるが、配列型の式は代入文(代入式)の左辺に書くことは許されない。 int x; /* x をint型として宣言 */ int *p; /* pをポインタ型 (int * 型)ととして宣言 */ p = &x; /* 式 p も 式 &x もともにポインタ型 (int * 型) */ はOKだが、 int a [3]; /* a を配列型 (int [3]型)として宣言 */ a = &x; /* 式a は配列型(int [3]型), 式&xはポインタ型(int *型) */ は許されない(コンパイル時にエラーになる)。 配列型の式(の右辺値)はアドレスの定数である(代入できない)。ただし、配列型の式は左辺値を持つ(次ページ参照)。 プログラミング入門2
ポインタについて補足(20分程度) 配列の左辺値について (例) int main () { int a [10]; printf (“%p\n”, a); /* aの値は、a[0]のアドレス &(a[0]) */ printf (“%p\n”, &a); /* &aの値は、aのアドレス */ printf (“%p\n”, a+1); /* 4が足される */ printf (“%p\n”, (&a)+1); /* 40が足される */ return 0; } 式a と 式&aの値(右辺値)は同じだが、型が異なる。 式a は 要素数10の配列型(int [10]型)。 式&aは要素数10の配列へのポインタ型(int (*) [10] 型)。 intが4byteの場合 int a[10][10][10][10]; と宣言されている場合を考えてみると、式 a, a[0], a[0][0], a[0][0][0], a[0][0][0][0] の左辺値(アドレス)はすべて等しいが、型が異なる。
構造体 3人分の学生の身体検査のデータ char name[3][20]; /* 3人分の学生の名前 */ int height[3]; /* 3人分の学生の身長 */ double weight[3]; /* 3人分の学生の体重 */ 全てのデータを関数に渡して,処理する場合 void func ( char name[ ][20], int height[ ], int weight[ ] ); void func ( char (* name) [20], int (*height), int (*weight)); という宣言と同一の意味を持つ。 各学生の情報が、3つの配列にばらばらに格納される。 => ひとつにまとめたい。 プログラミング入門2
構造体とは? 3人の学生の身体検査のデータ name[0] = “Taro”; height[0] = 176; char name[3][20]; /* 3人分の学生の名前 */ int height[3]; /* 3人分の学生の身長 */ double weight[3]; /* 3人分の学生の体重 */ 1人目のデータ 2人目のデータ 3人目のデータ name[0] = “Taro”; height[0] = 176; weight[0] = 64.5 name[1] = “Jiro”; height[1] = 165; weight[1] = 55.5; name[2] = “Saburo”; height[2] = 168; weight[2] = 70.0 各人の3つのデータが別々の配列に入っている。 各人のデータを1つにまとめたい。 プログラミング入門2
構造体とは? 構造体とは,複数の型のデータをひとまとめにしたデータ構造 char [20] 型 int 型 double 型 “Taro” 名前 身長 体重 char [20] 型 int 型 double 型 名前 身長 体重 “Taro” 176 64.5 名前 身長 体重 “Jiro” 165 55.5 名前 身長 体重 “Saburo” 168 70.0 プログラミング入門2
構造体型 struct { 変数宣言1; 変数宣言2; … } (例) struct { char name[20]; これまで、複合型(基本データを組み合わせて得られる型)として、配列型、ポインタ型、関数型などを扱ってきた。構造体型も複合型である。構造体型は、いくつかの変数宣言がまとまったものであり、以下の形のものである。 struct { 変数宣言1; 変数宣言2; … } (例) struct { char name[20]; int height; double weight; } 名前 身長 体重 char [20] 型 int 型 double 型 プログラミング入門2
構造体型の変数宣言 構造体型の変数宣言 を構造体型を表す型式とすると、 変数名; で宣言する。(通常の変数宣言と同じ) taro taro.name (例) struct { char name[20]; int height; double weight; } taro; taro 20byte taro.height 4byte taro.weight 8byte 宣言する変数名 プログラミング入門2
構造体のメンバーアクセス eがメンバーmを持つ構造体型の式のとき、e.mで構造体のメンバーが得られる。 (例)taroを前頁の構造体型の変数として宣言する。 struct { char name[20]; int height; double weight; } taro; taro.name, taro.height, taro.weightで各メンバーにアクセスできる。 (例) taro.name[0] = ‘T’; taro.name[1] = ‘a’; taro.name[2] = ‘r’; taro.name[3] = ‘o’; taro.name[4] = ‘\0’; taro.height = 176; taro.weight = 64.5; プログラミング入門2
typedefの使用 構造体を使うとき、構造体型に、typedefで名前を付けると便利がよい。 の形で使う。 (例) typedef int aaa; と宣言すると、aaaはintの別名。 typedef int bbb [3];と宣言すると、bbbはint [3]型の別名。 前ページの構造体型にstudentという名前を付ける。 構造体型の変数宣言 struct { char name[20]; int height; double weight; } taro; typedef struct { char name[20]; int height; double weight; } student; 比較 この宣言以降では、student a; などの宣言で構造体型の変数が宣言できる。 プログラミング入門2
構造体を使ったプログラム(構造体のメンバ) ソースファイル名:list1203.c (p.274, 若干変更) 構造体メンバへの値の代入と表示 #include <stdio.h> #include <string.h> /* 文字列操作関数用 strcpy使用のため*/ typedef struct{ char name[20]; /* 名前 */ int height; /* 身長 */ double weight; /* 体重 */ } student; int main(void) { student taro; /* 構造体メンバへの値の代入 */ strcpy(taro.name, “Taro"); /* 文字列Taroをtaro.nameにコピー */ taro.height = 176; /* 身長 */ taro.weight = 64.5; /* 体重 */ /* 構造体メンバの表示 */ printf("氏 名 = %s\n", taro.name); printf("身 長 = %d\n", taro.height); printf("体 重 = %f\n", taro.weight); return (0); } プログラミング入門2
構造体変数の初期化の構文 ソースファイル名:list1204.c (p.275, 若干変更) 構造体メンバの初期化 /* 学生を表す構造体型のtaroを初期化 */ #include <stdio.h> typedef struct { char name[20]; /* 名前 */ int height; /* 身長 */ double weight; /* 体重 */ } student; int main(void) { student taro = {“Taro", 176, 64.5}; printf("氏 名 = %s\n", taro.name); printf("身 長 = %d\n", taro.height); printf("体 重 = %f\n", taro.weight); return (0); } 構造体メンバの初期化は{ } を使って記述できる。 初期化時は、”Taro”と書くことによってtaro.nameの先頭から順に’T’, ‘a’, ‘r’, ‘o’, ‘\0’が代入される。(char配列の初期化と同じ) プログラミング入門2
構造体同士の値の代入 (p.280) student taro; student jiro = {“Jiro”, 165, 55.5}; 同じ型の構造体データであれば,互いに値を代入することが可能 student taro; student jiro = {“Jiro”, 165, 55.5}; …….. taro = jiro; jiro.name, jiro.height, jiro.weight をそれぞれtaro.name. taro.height, taro.weightに代入する。 プログラミング入門2
復習: 配列のコピー 注意点 (p.93) vb = va ; for (i = 0; i < 5; i++) va[5], vb[5] と2つの配列は要素数も同じだし,もっと簡単に次のように代入できないの? vb = va ; for (i = 0; i < 5; i++) vb[i] = va[i]; 面倒でも,1つ1つ代入していく 復習 (1) 配列型の式eの右辺値は、その先頭要素e[0]のアドレス(&e[0])。 (2) 配列型の式は左辺値はもつが、定数なので、代入式(代入文)の左辺には書けない。 プログラミング入門2
関数での構造体データの使用(値変更なし) ソースファイル名:struct1.c 関数への構造体データの受け渡し(値は変更しない) #include <stdio.h> typedef struct { char name[20]; /* 名前 */ int height; /* 身長 */ float weight; /* 体重 */ } student; void print_data( student std ) { printf("氏 名 = %s\n", std.name); printf("身 長 = %d\n", std.height); printf("体 重 = %f\n", std.weight); } int main(void) student taro = {“Taro", 176, 64.5}; print_data( taro ); return (0); 構造体データを受け取り, 値を表示する関数 値を変更しない→ポインタでなくてOK プログラミング入門2
関数での構造体データの使用(値変更あり) ソースファイル名:struct2.c 関数への構造体データの受け渡し(値の変更あり) #include <stdio.h> typedef struct { char name[20]; /* 名前 */ int height; /* 身長 */ double weight; /* 体重 */ } student; void change_data( student *std ) { (*std).height = 180; (*std).weight = 80.0; } int main(void) student taro = {“Taro", 176, 64.5}; change_data( &taro ); printf("氏 名 = %s\n", taro.name); printf("身 長 = %d\n", taro.height); printf("体 重 = %f\n", taro.weight); return (0); 構造体データを受け取り, 値を変更する関数 値を変更する→ポインタ(アドレス)で受け取り! プログラミング入門2
解説 構造体のポインタ渡し (注意) *std.heightと書くと、 *(std.height)と解釈される。 *stdはtaro のエイリアス void change_data( student *std ) { (*std).height = 180; (*std).weight = 80.0; } taro 106番地 106 change_data( &taro ); std (注意) *std.heightと書くと、 *(std.height)と解釈される。 student型のオブジェクトのアドレス を入れるための箱 (student型オブジェクトへのポインタ) プログラミング入門2
= アロー(->)演算子 eが構造体(型)へのポインタ型( * 型)の式のとき、その構造体のメンバm は (*e).m void change_data( student *std ) { (*std).height = 180; (*std).weight = 80.0; } void change_data( student *std ) { std->height = 180; std->weight = 80.0; } = change_data( &taro ); eが構造体(型)へのポインタ型( * 型)の式のとき、その構造体のメンバm は (*e).m で得られるが、これは e -> m と書いてもよい(syntax sugar)。 プログラミング入門2
関数での構造体データの使用(値変更あり) ソースファイル名:struct3.c(p.277,変更あり) 関数への構造体データの受け渡し(値の変更あり,アロー演算子使用) #include <stdio.h> typedef struct { char name[20]; /* 名前 */ int height; /* 身長 */ float weight; /* 体重 */ } student; void change_data( student *std ) { std->height = 180; std->weight = 80.0; } int main(void) student taro = {“Taro", 176, 64.5}; change_data( &taro ); printf("氏 名 = %s\n", taro.name); printf("身 長 = %d\n", taro.height); printf("体 重 = %f\n", taro.weight); return (0); 構造体データを受け取り, 値を変更する関数 値を変更する→ポインタ(アドレス)で受け取り! プログラミング入門2
S 今日の課題1(第11回)(kadai11-1.c) キーボードから3点の2次元座標値(x, y)を読み込み, p2 S = (ax, ay) = (bx, by) p3 のとき、S = ½ | ax by – ay bx | p1 但し,座標を格納する構造体として以下のものを用いること。 typedef struct { double x; double y; } point; (注) double型の値の読み込みは、 scanf (“%lf”, &e); の様にする。ここで、eは、左辺値を持つdouble型の式(double型の変数など)。 表示の場合は%fでOK。(printf (“%f”, p.x)等。) 面積を求める関数は、座標を表すpoint型の引数p1, p2, p3を受け取り、面積をdouble型で求め、それを返り値として返す関数として定義せよ。 double size (point p1, point p2, point p3) { … } プログラミング入門2
今日の課題2(第11回) (kadai11-2.c) キーボードから2つの複素数を読み込み、その2つの複素数の和を出力するプログラムを作成せよ。ただし、複素数を表すために、以下の構造体 complex を用い、複素数の和は、関数を呼び出す形で求めよ。複素数のキーボードからの入力、画面への出力の形式は自由とする。 typedef struct { double re; double im; } complex; 和を求める関数は、複素数を表すcomplex型の引数num1, num2を受け取って、それらの和を表すcomplex型の値を返す関数として定義せよ。 complex sum (complex num1, complex num2) { …. } プログラミング入門2