bp05

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からコピーしてください.
シミュレーションするボールを複数にしてください.