University of Electro-Communications Human Interface section 基礎プログラミングおよび演習 第5回 University of Electro-Communications Human Interface section 構造体 既存の型(int, double)をまとめて、新たな 型を作る仕組み 復習 変数と型 double x; x int a; x a x = 2.5; 2.5 x a x = 2; 2.0 x a a = 2.5 2.0 x メモリ メモリ メモリ メモリ 2 a メモリ 構造体の宣言と利用 // mainの前に書く struct Vector2{ double x; double y; }; 構造体 Vector2 の定義 double double x y この変数の詰め合わせ方を Vector2 型と定義 詰め方=型を決めただけで、変数は作られない。 int main(){ struct Vector2 v; double x; v.x = 1.2; v.y = 4.2; x = 3; } ここで、初めて変数 v が作られる。 y x v . (ピリオド):メンバーアクセス演算子 変数名 . メンバ変数名 で、メンバ変数を指定する。 構造体は変数の詰め合わせ 構造体の型名 // mainの前に書く struct Vector2{ double x; double y; }; int main(){ struct Vector2 v; double x; v.x = 1.2; v.y = 4.2; x = 3; } struct Vector2 型 double double x y メンバ変数 型名: struct Vector2 v: struct Vector2型の変数 v.x と x は別の変数です. x 1.2 y v x 1.2 y 4.2 v x メモリ 3 x メモリ 構造体を使ってみる 例:Lesson4のシミュレータ #include "curses_subset.h" int main(){ double vx = 5; double vy = -20; // 質点の速度 double px = 5; double py = 24; // 質点の位置 double dt = 0.05; // シミュレーションの時間刻み (秒) double g = 9.8; // 重力加速度(m/s^2) while(!kbhit()){ // 何かキーが押されたら終了 // 位置の更新: 位置=位置+速度 px =px+vx*dt; py =py+vy*dt; vy = vy+g*dt; clear(); // 画面クリア move(py, px); // カーソルを移動 printf("*\n"); // 表示 sleep_ms(50); // 0.05秒待つ } 構造体の効果 構造体版 #include "curses_subset.h" struct Vec2{ // 2次元ベクトル double x; double y; }; struct Mass{ // 質点 struct Vec2 pos; struct Vec2 vel; }; int main(){ struct Mass m = {{5,24},{5,-20}}; double dt = 0.05; // シミュレーションの時間刻み(秒) double g = 9.8; // 重力加速度(m/s^2) while(!kbhit()){ // 何かキーが押されたら終了 // 位置の更新: 位置=位置+速度 m.pos.x = m.pos.x + m.vel.x*dt; m.pos.y = m.pos.y + m.vel.y*dt; m.vel.y = m.vel.y + g*dt; clear(); // 画面クリア move(m.pos.y, m.pos.x); // カーソルを移動 printf("*\n");// 表示 sleep_ms(50); // 0.05秒待つ } } 課題8 構造体を使う 課題7の投げ上げシミュレーションのプログラム を、構造体を使って書き直してください まず、新しいプロジェクト ex8 を作り、 ex7の main.c と curses_subset.h を ex8にコピーしてから, ex8のプログラムを書き換えて行ってください. University of Electro-Communications Human Interface section 関数を作る 関数を作る これまで、いろいろな関数を使ってきました printf(), getchar(), sin(), cos(), rand(), move(), kbhit() 今回は、自分の関数を作ります。 関数の定義 #include <stdio.h> int printStar(int n){ while(n){ printf(”*”); n = n-1; } printf(”\n”); return 0; } double square(double d){ return d*d; } int main(){ printStar(5); printStar(2); printStar(square(2)); return 0 } 関数printStar の定義 関数square の定義 関数の利用 (関数の呼び出し) ***** ** **** 関数定義の目的 main()が巨大化することを防げる。 巨大化しすぎるとプログラムが読みにくい。 同じ機能を2回書かないで済む 注:プログラムを書くときにカット&ペーストは めったに使いません。同じことを2回書く必要が あるならば、その部分を関数にして呼び出せば よいからです。 後で部品として再利用できるようにする 関数にしておくと、別のプログラムを作るときに、 また使えて便利です。 1.関数の定義を書く場所 #include <stdio.h> ここがよい 関数の定義は、 mainの外に書きます。 int main(int argc, const char* argv[]){ printStar(5); mainの中: printf(”3^2=%f\n”, square(3)); return 0; ここには書けません。 } ここでも良いが,後述の関数宣言が必要 2.関数の定義の書き方 引数リスト:引数の型 引数の名前(, 引数の型 引数の名前 …) 戻り値の型 名前 複数渡したい場合は、 カンマ’,’で区切って並べます 例: double mult(double a, double b){ double square(double v){ double rv; rv =v*v ; 型が対応 return rv; rv = 0; } 関数の中身: 関数が呼び出されると この部分が実行されます。 return 文: この行を実行すると呼び出し元に戻り ます。 関数の外から見ると関数の値は rvになります。rvを戻り値といいます。 ここは実行されません。 3.関数の実行順 double square(double v){ v=2.0; 実行順:3 double rv; 4 rv =v*v ; return 文: 5 return rv; rv = 0; この行を実行すると呼び出し 元に戻ります。 } int main(){ 実行順:1 double d; 2 d = square(2.0) ; 6 printf(”%f\n”, d); } d=4.0 関数の引数と変数 double square(double v){ v =v*v ; return v; } int main(){ double d=2.0; double dd; dd = square(d) ; printf(”%f\n”, dd); printf(”%f\n”, d); } v=d; ここで、vを書き換えても 呼び出し元のdには 影響しない 関数の引数と変数 double square(double v){ v =v*v ; return v; } int main(){ double d=2.0; double dd; dd = square(d) ; printf(”%f\n”, dd); printf(”%f\n”, d); } 2 2 v dd d ここで、vを書き換えても 4 2 v dd d 2 d dd 4 2 dd d 呼び出し元のdには square() main()の変数 影響しない の変数 関数の実行順 実行順 3 4 2 5 6 7 10 13 16 19 8 11 14 17 9 12 15 18 20 21 1 22 23 #include <stdio.h> // 2乗を計算する。 // 戻り値として、d^2を返す。 double square(double d){ return d*d; } // n^2個の*を表示。 n^2を返す。 int printSq(int n){ int nn = square(n); int count = nn; while(count){ printf(”*”); count = count-1; } printf(”\n”); return nn; } int main(){ int r = printSq(2); return r; } square()をここで使用 printSq()をここで使用 関数を作ることの効果 縦線を3本書くプログラムをmainに全部書いた場合 line1.c // テキストで、2列に縦線を書く for(y=1; y<5; y++){ move(y, 2); // y行0列にカーソルを移動 printf("|"); } #include "curses_subset.h" int main(){ int y; clear(); // テキストで、0列に縦線を書く for(y=1; y<5; y++){ move(y, 0); // y行0列にカーソルを移動 printf("|"); } // テキストで、4列に縦線を書く for(y=1; y<5; y++){ move(y, 4); // y行0列にカーソルを移動 printf("|"); } return 0; } main() が長くて流れが分かりにくい ||| ||| ||| ||| 関数を作ることの効果:関数にした場合 #include "curses_subset.h" int verticalLineZero(){ // テキストで、第0列に縦線を書く int y; for(y=0; y<5; y++){ move(y, 0); // y行0列にカーソルを移動 printf("|"); } return 0; } int verticalLineTwo(){ // テキストで、第2列に縦線を書く int y; for(y=0; y<5; y++){ move(y, 2); // y行2列にカーソルを移動 printf("|"); } return 0; } line2.c int verticalLineFour(){ // テキストで、第4列に縦線を書く int y; for(y=0; y<5; y++){ move(y, 4); // y行4列にカーソルを移動 printf("|"); } return 0; } int main(){ clear(); verticalLineZero(); verticalLineTwo(); verticalLineFour(); return 0; } 関数を作ることの効果:似た関数をまとめる #include "curses_subset.h" int verticalLineZero(){ // テキストで、第0列に縦線を書く int y; for(y=0; y<5; y++){ move(y, 0); // y行0列にカーソルを移動 printf("|"); } return 0; } int verticalLineTwo(){ // テキストで、第2列に縦線を書く int y; for(y=0; y<5; y++){ move(y, 2); // y行2列にカーソルを移動 printf("|"); } return 0; } int verticalLineFour(){ // テキストで、第4列に縦線を書く int y; for(y=0; y<5; y++){ move(y, 4); // y行4列にカーソルを移動 printf("|"); } return 0; } int main(){ clear(); verticalLineZero(); verticalLineTwo(); verticalLineFour(); return 0; } 関数を作ることの効果:引数の役割 line3.c #include "curses_subset.h" void verticalLine(int x){ // テキストで、x列に縦線を書く int y; for(y=0; y<5; y++){ move(y, x); // y行0列にカーソルを移動 printf("|"); } } int main(){ clear(); verticalLine(0); verticalLine(2); verticalLine(4); verticalLine(10); verticalLine(11); return 0; } 引数を使って、 1つ3役の関数をつくる 関数の宣言 関数の宣言=関数を定義より前に使うための仕組み #include <stdio.h> int main(){ printStar(5); printStar(2); printStar(square(2)); return 0; } int printStar(int n){ while(n){ printf(”*”); n = n-1; } printf(”\n”); return 0; } double square(double d){ return d*d; } 知らない関数は、 とりあえず int func(int) だと仮定してコンパイル する ここに来て、コンパイラ は仮定がまずかったこ とに気づく。 が、手遅れなのでエ ラーをだす。 error: conflicting types for 'square‘ エラー:`square’ の型が 合いません。 : Cコンパイラはあまり賢くありません。 そこで、関数の宣言を使います。⇒ #include <stdio.h> int printStar(int); double sqaure(double); int main(){ printStar(5); printStar(2); printStar(square(2)); return 0; } int printStar(int n){ while(n){ printf(”*”); n = n-1; } printf(”\n”); return 0; } double square(double d){ return d*d; } 関数の 宣言 関数の宣言 #include <stdio.h> int printSq(int n); double square(double d); // n^2個の*を表示。 n^2を返す。 int main(){ printSq(5); return 0; } int printSq(int n){ int nn = square(n); int count = nn; while(count){ printf(”*”); count = count-1; } printf(”\n”); return nn; } // 2乗を計算する。 double square(double d){ return d*d; } 使用より上で宣言 printSq()をここで使用 square()をここで使用 関数の宣言が先にあれば、 関数の定義が後でも大丈夫。 ∵どんな関数かコンパイラに 伝わっているので main も関数 実は #include <stdio.h> int main(int argc, char* argv[]){ return 0; } のmain()も関数です。 char* argv[] が何なのかはそのうち説明します。 int argc には、ターミナルからプログラムを実行すると きに渡した引数の数+1が入ります。 プログラムが ex で、ターミナルに ex a b c [ENTER] と書いて実行したなら argc は 4です。 main が返す return の値は、プログラムの終了コード といいます。 課題9 関数定義とステップイン 次のように、プログラムを作りデバッガで追ってください。 整数を引数にとり、引数の値の数だけ文字を表示する int printBar(int n)を定義する。文字は何でも良い。 実数を引数にとり、引数の2乗を返す関数 double sqaure(double d)を定義する。 main()から呼び出す。 デバッガ上で実行し、 main()の関数呼び出しの行で、 デバッガの「ステップイン」ボタンを押して動作を確認 する。 関数と構造体 構造体型も変数の型の一種です。 関数の引数や戻り値の型として使えます。 double norm(sturct Vec2 v){ double ll = v.x*v.x + v.y*v.y; double l = sqrt(ll); // √をとる return l; } struct Vec2 add(sturct Vec2 a, struct Vec2 b){ struct Vec2 rv; rv.x = a.x + b.x; rv.y = a.y + b.y; return rv; } norm()はベクトルのノルム (長さ)を計算する関数。 struct Vec2 型の引数 v を とり、 double型を返す 2次元ベクトルの足し算 struct Vec2型の引数を2つ 取り struct Vec2がたを返す. double square(double v){ v =v*v ; return v; } 関数と構造体の効果-1 #include "curses_subset.h" struct Vec2{ // 2次元ベクトル double x; double y; }; struct Mass{ // 質点 struct Vec2 pos; struct Vec2 vel; }; struct Vec2 addVV(struct Vec2 a, struct Vec2 b){ struct Vec2 rv; rv.x = a.x+b.x; rv.y = a.y+b.y; return rv; } struct Vec2 mulVS(struct Vec2 a, double b){ struct Vec2 rv; rv.x = a.x*b; rv.y = a.y*b; return rv; } int main(){ struct Mass m = {{5,24},{5,-20}}; double dt = 0.05; double g = 9.8; while(!kbhit()){ // 位置の更新: 位置=位置+速度 m.pos = add(m.pos, mul(m.vel,dt)); m.vel.y = m.vel.y + g*dt; clear(); // 画面クリア move(m.pos.y, m.pos.x); printf("*\n");// 表示 sleep_ms(50); // 0.05秒待つ } } 関数と構造体の効果-2 #include "curses_subset.h" struct Vec2{ // 2次元ベクトル double x; double y; }; struct Mass{ // 質点 struct Vec2 pos; struct Vec2 vel; }; struct Vec2 addVV(struct Vec2 a, struct Vec2 b){ struct Vec2 rv; rv.x = a.x+b.x; rv.y = a.y+b.y; return rv; } struct Vec2 mulVS(struct Vec2 a, double b){ struct Vec2 rv; rv.x = a.x*b; rv.y = a.y*b; return rv; } struct Env{ double dt; double g; }; struct Mass simMass(struct Mass m, struct Env e){ // 位置の更新: 位置=位置+速度 m.pos = add(m.pos, mul(m.vel,e.dt)); m.vel.y = m.vel.y + e.g*e.dt; return m; void printMass(struct Mass m){ move(m.pos.y, m.pos.x); printf("*\n"); } int main(){ struct Mass m = {{5,24},{5,-20}}; struct Env e={0.05, 9.8}; while(!kbhit()){ simMass(m, e); clear(); // 画面クリア printMass(m); sleep_ms(50); // 0.05秒待つ } } 課題10:関数を使ってシミュレータを書く 課題8で構造体を使って書き直したシミュレータ を関数を使って書き直してください. いつものように, ex10を作ってから, main.c と curses_subset.h を ex8からコピーしてください. シミュレーションするボールを複数にしてください.
© Copyright 2024 ExpyDoc