MSBOOKS

プログラミングとか深層学習とか日常の出来事とか

【C言語入門】part9 : 関数

前回は配列についてやりました.
今回は関数についてやっていきます.

関数とは?

今回も新しく関数という概念を導入します.プログラムをどんどん書いていくと長くなっていき,どこに何が書いてあるのかわからなくなってきてしまいます.練習問題で書いているプログラムでもかなり長くなって,後で見返したときにどこに何が書いてあるのかわかりにくくなってきていますよね.
f:id:msteacher:20190216152443p:plain

そんなときは処理を1パックにまとめた関数というものを作るとより見やすいプログラムができます.

関数の作り方

じつはprintf文のところで少し話しましたが,printfは画面表示してねっていう関数です.イメージとしてはそんな感じでこの関数を呼んだら"○○してくれる"っていった感じで作成するのが基本です.関数の作り方の例を示すために,part6のtest6-6.cを関数にしてまとめてみましょう.
※ただし今回はより見やすくするために#define ENEMY_TURN 0を最初に定義していますが,これは定数のdefine,つまり"定義する"の意味でこのプログラム中ででてきたENEMY_TURNは0と同じ意味ですよということ.プログラム中にif(turn==0)などと0が急に出てきたら何かわからないのでこういう意味のある文字列に変えておくと見やすいです.
・test9-1.c

#include <stdio.h>
#include <stdlib.h>
/*定数の定義*/
#define ENEMY_TURN 0
#define HERO_TURN 1

/*HPを入力させる関数*/
int inputHP(char name[]){
   int HP;
   do{
      printf("%s",name);
      printf("のHPを入力してね:");
      scanf("%d",&HP);
      if(HP <= 0){
	printf("HPは0より大きい値で入力してね\n");
      }
   }while(HP <= 0);
   return HP;
}

/*バトル処理関数*/
int battle(int turn, int HP){
   int choose;
   if(turn == HERO_TURN){
      printf("主人公のターン!主人公はどうする?\n");
      printf("1.攻撃する\n2.魔法で攻撃する\n3.逃げる\n");
      printf("1,2,3のいずれかを入力:");
      scanf("%d",&choose);
      system("cls"); //画面をいったん綺麗にする
      switch(choose){
         case 1:
            printf("攻撃をした!\n3ダメージ!\n");
            HP = HP - 3;
            break;
         case 2:
            printf("魔法で攻撃した!\n6ダメージ!\n");
            HP = HP - 6;
            break;
         case 3:
            printf("敵から逃げようとしたが,逃げられなかった!\n");
            break;
         default:
            printf("不正な入力です\n");
      }
   }
   else if(turn == ENEMY_TURN){
      printf("敵のターン!\n");
      printf("主人公に攻撃をした!\n3ダメージ!\n");
      HP = HP - 3;
   }
   //HPがマイナスにならないように調整
   if(HP < 0){
      HP = 0;
   }
   return HP;
}

/*メイン関数*/
int main(void){
   int enemyHP;
   int heroHP;
   int turn = HERO_TURN; //初期値を定義

   /*inputの関数を呼び出す*/
   enemyHP = inputHP("敵");
   heroHP = inputHP("主人公");
   system("cls"); //画面をいったん綺麗にする
   printf("今の敵のHP:%d\n",enemyHP);
   printf("今の主人公のHP:%d\n",heroHP);
   printf("戦闘開始!\n");

   while(enemyHP > 0 && heroHP > 0){
      /*battleの関数を呼び出す*/
      if(turn == HERO_TURN){
         enemyHP = battle(HERO_TURN, enemyHP);
         turn = ENEMY_TURN;
      }
      else if(turn == ENEMY_TURN){
         heroHP = battle(ENEMY_TURN, heroHP);
         turn = HERO_TURN;
      }
      printf("今の主人公のHP:%d\n",heroHP);
      printf("今の敵のHP:%d\n\n",enemyHP);
   }
   if(heroHP==0){
      printf("敵の勝利!\n");
   }
   else if(enemyHP==0){
      printf("主人公の勝利!\n");
   }
   return 0;
}

・実行結果

攻撃をした!
3ダメージ!
今の主人公のHP:3
今の敵のHP:3

敵のターン!
主人公に攻撃をした!
3ダメージ!
今の主人公のHP:0
今の敵のHP:3

敵の勝利!

関数は以下のような書き方をします.

返り値の変数型 関数名(変数の型 変数名, 変数の型 変数名, ・・・){
   処理;
   return 返り値
}
この関数を実行すると,関数の引数(inputHP("主人公")の"主人公"の部分のこと)に指定したものを上記の黄色い部分へ代入され,関数内でこの値を使用することができます.処理が終わると,値を返すことができ,値を返すと関数外(例えばmain)で関数の処理結果を受け取ることができます.この値を返り値といいます.例えば代入したい変数 = 関数名();のように値をもらうことができます.関数内の変数は他の関数では利用することができないので,別ところから自分の関数へ値を受け取りたいときは引数,自分の結果を別の関数で使いたいときは返り値を使います.どの関数からもアクセスできる変数は関数外で定義することで定義できます.これをグローバル変数といいます.数学とかでよく使うy=f(x)のイメージで関数を図で表すと以下のようになります.
f:id:msteacher:20190216133623p:plain
先ほどのtest9-1.cの関数の処理のうちの1つを具体的に見ていきます.HPを入力させる関数を見てみましょう.まず引数として文字型のname(配列の長さ自由)を受け取ります.そのnameのHPをキーボードから入力させ,その値をHPに代入し,そのHPを返り値として返します.この関数の役割はHPを入力させる処理をひとまとまりにしているものとわかりますね.関数はこのように作成します.ちなみに関数の返り値なしの場合は返り値の型はvoidとします.
もう1つ例を見てみましょう.test7-9.cを関数を使って綺麗に書き換えると例えば以下のようになります.
・test9-2.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
/*定数定義*/
#define MY_TURN 0
#define ENEMY_TURN 1

/*バトル関数*/
int battle(int reTh, int myTh, int pcTh, int num, char name[]){
   printf("*****バトル開始!*****\n");
   printf("自分の立てた指の数:%d\nPC立てた指の数:%d\n",myTh,pcTh);
   printf("2人の合計:%d\n",myTh+pcTh);
   printf("%sの予測した数:%d\n",name,num);
   if((myTh + pcTh) == num){
      printf("%sの予測はあってます!\n",name);
      reTh = reTh - 1;
   }
   else{
      printf("%sの予測は間違えていた!\n",name);
    }
    printf("**********************\n");
    return reTh;
}

/*立てる指の数を入力させる関数*/
int inputFinger(int myReTh){
   int finger;
   do{
      printf("自分の立てる親指の数を入力してね:");
      scanf("%d",&finger);
   }while(finger < 0 || finger > myReTh);
   return finger;
}

/*メイン関数*/
int main(void){
   int num;
   int myTh;
   int myReTh = 2;
   int pcTh;
   int pcReTh = 2;
   int turn;
   srand((unsigned)time(NULL));
   printf("*****指スマゲームを開始します*****\n");
   //先行を決める
   turn = rand() % 2;
   if(turn == MY_TURN)
      printf("先行はあなたです\n");
   else if(turn == ENEMY_TURN)
      printf("先行はPCです\n");

   //自分のターンとPCのターンの動きを定義
   while(myReTh != 0 && pcReTh != 0){
      printf("---------------------------------\n");
      printf("|自分の残りの数:%d PCの残りの数:%d|\n",myReTh,pcReTh);
      printf("---------------------------------\n");
      //自分のターン
      if(turn == 0){
         printf("あなたのターン!\n");
         do{
            printf("立つ親指の数を予測してね:");
            scanf("%d",&num);
         }while(num < 0 || num > (myReTh + pcReTh));
         myTh = inputFinger(myReTh);
         pcTh = rand() % (pcReTh + 1);
         myReTh = battle(myReTh, myTh, pcTh, num, "あなた");
         turn = ENEMY_TURN;
      }
      //PCのターン
      else{
         printf("pcのターン!\n");
         myTh = inputFinger(myReTh);
         pcTh = rand() % (pcReTh + 1);
         num = rand() % (myReTh + 1) + pcTh;
         pcReTh = battle(pcReTh, myTh, pcTh, num, "PC");
         turn = MY_TURN;
      }
   }

   //どちらが勝ったか表示
   if(myReTh == 0){
      printf("あなたの勝ちです!\n");
   }
   else if(pcReTh == 0){
      printf("PCの勝ちです!\n");
   }
   return 0;
}

実行結果は変わらないので省略します.同じ処理が何度も繰り返されているところを関数でまとめるとより綺麗なプログラムになると思います.毎回main関数は最後に書いていましたが,これを前にもってくるとエラーがでてきてしまいます.これはプログラムは上から読まれることからこんな関数はないよってエラーがでてしまうものです.そこでこの関数は後で定義するよっといった関数のプロトタイプ宣言をすることでmain関数を初めでも書けるようになります.
・test9-3.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
/*関数のプロトタイプ宣言*/
int inputFinger(int);
int battle(int, int, int, int, char *);

/*定数定義*/
#define MY_TURN 0
#define ENEMY_TURN 1

/*メイン関数*/
int main(void){
   int num;
   int myTh;
   int myReTh = 2;
   int pcTh;
   int pcReTh = 2;
   int turn;
   srand((unsigned)time(NULL));
   printf("*****指スマゲームを開始します*****\n");
   //先行を決める
   turn = rand() % 2;
   if(turn == MY_TURN)
      printf("先行はあなたです\n");
   else if(turn == ENEMY_TURN)
      printf("先行はPCです\n");

   //自分のターンとPCのターンの動きを定義
   while(myReTh != 0 && pcReTh != 0){
      printf("---------------------------------\n");
      printf("|自分の残りの数:%d PCの残りの数:%d|\n",myReTh,pcReTh);
      printf("---------------------------------\n");
      //自分のターン
      if(turn == 0){
         printf("あなたのターン!\n");
         do{
            printf("立つ親指の数を予測してね:");
            scanf("%d",&num);
         }while(num < 0 || num > (myReTh + pcReTh));
         myTh = inputFinger(myReTh);
         pcTh = rand() % (pcReTh + 1);
         myReTh = battle(myReTh, myTh, pcTh, num, "あなた");
         turn = ENEMY_TURN;
      }
      //PCのターン
      else{
         printf("pcのターン!\n");
         myTh = inputFinger(myReTh);
         pcTh = rand() % (pcReTh + 1);
         num = rand() % (myReTh + 1) + pcTh;
         pcReTh = battle(pcReTh, myTh, pcTh, num, "PC");
         turn = MY_TURN;
      }
   }

   //どちらが勝ったか表示
   if(myReTh == 0){
      printf("あなたの勝ちです!\n");
   }
   else if(pcReTh == 0){
      printf("PCの勝ちです!\n");
   }
   return 0;
}

/*バトル関数*/
int battle(int reTh, int myTh, int pcTh, int num, char name[]){
   printf("*****バトル開始!*****\n");
   printf("自分の立てた指の数:%d\nPC立てた指の数:%d\n",myTh,pcTh);
   printf("2人の合計:%d\n",myTh+pcTh);
   printf("%sの予測した数:%d\n",name,num);
   if((myTh + pcTh) == num){
      printf("%sの予測はあってます!\n",name);
      reTh = reTh - 1;
   }
   else{
      printf("%sの予測は間違えていた!\n",name);
    }
    printf("**********************\n");
    return reTh;
}

/*立てる指の数を入力させる関数*/
int inputFinger(int myReTh){
   int finger;
   do{
      printf("自分の立てる親指の数を入力してね:");
      scanf("%d",&finger);
   }while(finger < 0 || finger > myReTh);
   return finger;
}

プロトタイプ宣言は実際の関数定義に,引数名だけを除いたような形で定義できます.
関数の作り方は人によって異なるので,ぜひオリジナルの自分でわかりやすいと思う関数を作って,プログラムをより綺麗にしてみましょう.
お疲れさまでした.

練習問題

・問題1
 test8-9.cを関数を用いてわかりやすいプログラムに書き換えてみよう.同じ処理は少ないのでいくつか見やすいようにmainから処理を分けてみよう.mapの配列などはグローバル変数で定義すると簡単です.(test9-4.c)

・問題2
 以下の式のxを浮動小数点型の引数とし,返り値として浮動小数点型のyを返してくれる関数を作成してみよう.またこの関数をmainから呼び出し,任意のxを入力したら,yの結果を画面出力してくれるように変更してみよう.(test9-5.c)
f:id:msteacher:20190216143755p:plain
・実行結果例

>test9-5
y=x+1/xを計算するよ
xを入力してね:5
y=5.200000

>test9-5
y=x+1/xを計算するよ
xを入力してね:-1
y=-2.000000

>test9-5
y=x+1/xを計算するよ
xを入力してね:-0.5
y=-2.500000

練習問題の解答例

・問題1
 ・test9-4.c

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
/*関数のプロトタイプ宣言*/
void InitializeMap(void);
void drawMap(void);
void moveOperation();

/*変数定義*/
int map[7][7];  //マップの定義
int hero[2] = {1, 1};  //主人公の初期位置
int goal[2] = {3, 3};  //ゴールの位置

/*マップの初期化をする関数*/
void InitializeMap(void){
   int i,j;
   //マップの初期化(0:移動可能,1:壁(移動不可))
   for(i = 0 ;i < 7 ;i++){
      for(j = 0 ;j < 7 ;j++){
         if(i == 0 || i == 6 || j == 0 || j == 6)
            map[i][j] = 1;
         else
            map[i][j] = 0;
      }
   }
   system("cls"); //画面をいったん綺麗にする
}

/*マップを表示する関数*/
void drawMap(void){
   int i,j;
   for(i = 0 ;i < 7 ;i++){
      for(j = 0 ;j < 7 ;j++){
         //主人公の居る位置は0でなく@を描画
         if(hero[0] == i && hero[1] == j)
            printf("@");
         //ゴールの場所は0ではなくGを描画
         else if(goal[0] == i && goal[1] == j)
            printf("G");
         else
            printf("%d",map[i][j]);
      }
      printf("\n");
   }
}

/*動く操作を受け付ける関数*/
void moveOperation(void){
   char choose;
   choose = _getch(); //キーボードから入力を受け付ける
   system("cls"); //画面を綺麗にする
   switch(choose){
      case 'w':
         //次移動するところが壁でなければ移動する
         if(map[hero[0]-1][hero[1]] != 1)
            hero[0] -= 1;
         break;
      case 'd':
         if(map[hero[0]][hero[1]+1] != 1)
            hero[1] += 1;
         break;
      case 's':
         if(map[hero[0]+1][hero[1]] != 1)
            hero[0] += 1;
         break;
      case 'a':
         if(map[hero[0]][hero[1]-1] != 1)
            hero[1] -= 1;
         break;
      default:
         printf("不正な入力です\n");
   }
}

/*メイン関数*/
int main(void){
   InitializeMap();
   /*RPGゲームメインループ処理*/
   while(1){
      printf("******操作方法******\n↑:w →:d ↓:s ←:a\n");
      printf("********************\n");
      drawMap();
      //終了判定
      if(goal[0] == hero[0] && goal[1] == hero[1])
         break;
      moveOperation();
   }
   printf("GOAL!\n");
   return 0;
}

・問題2
 ・test9-5.c

#include <stdio.h>
float function(float x){
   float y;
   y = x + 1.0 / x;
   return y;
}

int main(void){
   float x;
   printf("y=x+1/xを計算するよ\nxを入力してね:");
   scanf("%f",&x);
   printf("y=%f\n",function(x));
   return 0;
}

以下のようなグラフのようにyが出力されると思います.ただし0を入力すると動作が停止するので要注意!
f:id:msteacher:20190216144917g:plain