第50回
プログラミング・クイズで腕試し~簡易積算プログラムを作ろう

calc関数を作る

数値の演算を行うcalc関数を作ってみましょう。

関数の仕様

先に紹介したcalc関数の仕様を、もう一度確認しておきましょう。

機能 :
数値2つと演算子を受け取って演算子に基づく計算を行い、その結果を表示する。
戻り値 :
計算結果を返す。
宣言 :
long calc(long, long, char);
long型の引数2個は演算の対象となる数値。
char型の引数は演算子。
基本的な処理は、第1引数と第2引数の値を、第3引数の演算子に基づいて演算するだけですから、条件判定と分岐の構造で実現できます。

ヒントは不要でしょう。演算結果を画面に表示すると同時に、戻り値として返すことに注意してください。

---------------------------------------------------------------------

●問題:calc関数を作る

では、calc関数のソースを考えてみましょう。

------------------------ thinking time --------------------------------

●解答

リスト4のようなソースが考えられます。

リスト4:演算を行って結果を表示するcalc関数のソース
long  calc(long n1, long n2, char exp)
{
  long    ans;

  switch (exp) {
    case '+' : ans = n1 + n2;
               break;
    case '-' : ans = n1 - n2;
               break;
    case '*' : ans = n1 * n2;
               break;
    case '/' : ans = n1 / n2;
    default  : break;
  }
  /* 演算結果を表示 */
  printf("----------------------------> %ld¥n", ans);
  return (ans);    /* 演算結果を返す */
}


リスト5に全体のソースを掲げておきます。

細部の書き方は別として、あなたの考えたソースコードはどの程度解答に近い内容になったでしょうか?

同じ結果を得るためのソースの書き方は、いく通りもあります。コンパイルして実行した結果が最初に掲げた仕様を満たしていればOKです。解答と違うからといって失望する必要もなければ、解答よりスマートなコードが書けたことを自慢しても構いません。解答はあくまで一例です。

リスト5:“ezcalc.c”のソース全体
/* ---------------------------------------------------------- *
 * EZCALC.C 演算子と数値を入力し、繰り返し計算を行う。        *
 *  使用法 : 演算子入力->数値[Enter]...の繰り返し             *
 *           数値入力で[Enter]のみなら演算子変更              *
 *           演算子の入力で[Q]なら終了                        *
 *----------------------------------------------------------- */

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <conio.h>    /* getch関数を使うため */

#define     BUFSIZE  128+1  /* 入力用文字列バッファのサイズ */
#define     ERR      (-1)
#define     L_ERR    (-1L)

/* 入力される数値の最小値と最大値 */
#define     DATMAX   15000L
#define     DATMIN  -15000L

/* --------------------------------------- *
 *  getexp -- 演算子を受け取ってそれを返す *
 * --------------------------------------- */
char  getexp(void)
{
  char    c;    /* 受け取った演算子 */

  while (1) {
    printf("演算子([Q]で終了) : ");
    c = getch();
    printf("%c¥n", c);
    /* 入力された値によって戻り値を変える */
    if ((c == 'Q') || (c == 'q'))
      return (ERR);
    else if ((c == '+') || (c == '-') ||¥
                           (c == '*') || (c== '/'))
      return (c);
    /* 演算子と'Q'以外なら再入力 */
    else
      printf("使用できる演算子は + - * / です.¥n");
  }
}

/* ---------------------------------------- *
 *  getnum -- 数値を受け取ってその値を返す  *
 * ---------------------------------------- */
long  getnum(void)
{
  char    buf[BUFSIZE];
  long    num;

  while(1) {
    printf("数 値([Enter]で演算子変更) : ");
    gets(buf);    /* 文字列として入力 */
    /* [Enter]のみの場合 */
    if (strlen(buf) == 0)
      return (L_ERR);
    /* 数値に変換 */
    num = atol(buf);
      if ((num < DATMIN) || (num > DATMAX))
        printf("%ld ~ %ld の範囲で入力してください.¥n",
                DATMIN, DATMAX);
      else
        return (num);
  }
}

/* -------------------------------- *
 *  calc -- 計算する                *
 * -------------------------------- */
long  calc(long n1, long n2, char exp)
{
  long    ans;

  switch (exp) {
    case '+' : ans = n1 + n2;
               break;
    case '-' : ans = n1 - n2;
               break;
    case '*' : ans = n1 * n2;
               break;
    case '/' : ans = n1 / n2;
    default  : break;
  }
  /* 演算結果を表示 */
  printf("----------------------------> %ld¥n", ans);
  return (ans);    /* 演算結果を返す */
}

/* -------------------------------- *
 * main                             *
 * -------------------------------- */
int  main(void)
{
  char    exp;    /* 演算子 */
  long    n1;     /* 被演算数~累積されていく */
  long    n2;     /* 演算数 */

  /* 以下のループは最初だけ実行される */
  while (1) {
    if ((exp = getexp()) == ERR)
      exit(0);    /* 演算子入力でERRが返れば終了 */
    if ((n1 = getnum()) == L_ERR)
      continue;   /* 数値入力でERRなら演算子入力から */
    else
      break;
  }
  /* 数値が入力されたら
     それ以降は以下のループが実行される */
  while (1) {
    /* L_ERRが返るまで演算を繰り返す */
    while ((n2 = getnum()) != L_ERR)
      /* 演算結果を保存しておく */
      n1 = calc(n1, n2, exp);
    if ((exp = getexp()) == ERR)
      exit(0);   /* 演算子入力でERRが返れば終了 */
  }
}

このプログラムでは「入力待ちのループ」が要となっています。また、演算子によって計算方法を切り替える条件判断と分岐の処理も重要です。

ユーザーがどのような値を入力するか、どのようなキーを押すのか――予測できない状態で入力を待ち続ける場合、基本のループにはforではなくwhileを使います。

入力された演算子を判定して処理を分岐する場合、入力された1文字(被判定値)は常に1つで、それを複数の演算子と順次比較していくことになりますから、条件判定と分岐の構造にはifよりswitch caseを使った方が効率的です。

この2点が押さえられていればOKです。

解答では、ループの構造(ループ内の処理と脱出条件)を分かりやすくするために、
while(1)
という形で無限ループを作り、その中で脱出条件をifで判定して、合致したときだけbreakなどでループを抜ける構造としました。

もちろん、継続条件を保持する変数を用意して、ループ内でその値を書き換えても構いません。以下のような感じです。
int flag = 1;        /* 繰り返し判定用フラグ */
while (flag == 1) {
    :
  (脱出条件を満たしたら flag = 0 とする)
}

ループや条件判断に限らず、ソースには色々な書き方が存在します。それらがソース内で統一されてさえいれば、どれも間違いではありません。

ポイントは、以下の3点です。

  1. 簡潔で分かりやすいこと
  2. 全体が統一された書き方をされていること
  3. 仕様を満たしていること

あとがき ~意外と必要な四則計算プログラム~

今回は積算プログラムを紹介しましたが、実のところ「そんなプログラム、必要なの?」と感じた人も少なくないと思います。でも、意外と必要になるのです。

システムは組織の上部から充実されていく

経理や在庫管理、人事管理などの事務処理では、複数の伝票を串刺し計算したり、帳簿の特定の列(金額や税額、出勤日数などなど)の値を連続して加算するような処理をよく行います。

もちろん、今では大抵の計算処理をコンピュータで行うため、人間が数値を確認しながら電卓を叩く……といったことはまずありません。と、思っている人は多いと思います。が、実はコンピュータで処理されない計算処理もたくさんあります。

いわゆる基幹系と呼ばれる処理は、需要の多い組織の上部から充実されていく傾向があります。なぜなら、上部組織ほど事務処理を統一しやすいからです。実際に顧客や取引先とやりとりしている現場に近くなるほど臨機応変の例外的な処理が多くなり、規定に沿った一律的・統一的な方法ではカバーしにくくなるのが実情です。

個々の現場までカバーできない

そのような訳で、支社・支店・支部などの現場では、本社や本部の作ったシステムに入力するためのデータを、表計算ソフトや電卓で計算することも少なくありません。僕自身、現場の担当者から、「表計算ソフトで日々の伝票を集計する処理を作りたいんだけど」といった相談を何度も受けました。

本社の商品データベースと、営業担当者が個人的に持っている得意先のデータベースとを表計算ソフトのレベルで連携させる方法をアドバイスしたことも、少なくありません。

また、個人経営や零細企業では、市販の財務管理ソフトなどを使っているところも多く、そのような組織では日々の業務の結果は手書きあるいは表計算ソフトで作成した伝票に記録されていて、専用ソフトにデータを入力するためにはそれらを別途集計しなければならない――といった状況もよくあります。

現場には現場のプログラムを

全体をカバーする処理を作っていると、末端で日常行っている小さな処理が目に止まらないことはよくあります。

かといって、些細な作業まですべてを1つのシステムでカバーしようとすると、カスタマイズだらけで効率が上がりません。結局、現場には現場のための実作業に応じた小さなプログラムが必要になる訳です。