質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.48%

  • C

    3831questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • アルゴリズム

    421questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

電卓プログラム(atoi、scanf、str系禁止)

受付中

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 403

atoi、scanf、str系の関数を使わずに入力した数式を計算するプログラムを作っています。
1.数式の入力を求める
2.数式を入力し、enterを押したら(+,-,*/が使用可能)
3.計算結果を表示する
4.1に戻る
5‘q’が入力されたら終了
といったものです。

問題には以下の条件があります。
1.入力した数値の計算はmein関数以外で実施
2.scanf、atoi、str系禁止
3.0除算はの場合エラー処理を行う
4.一行に入力できる演算子は1のみ
5.小数点、()には対応せず数値演算子以外の文字を含んだ場合エラー処理を行う
6.ただし、数値と演算子の間と数式の前後のスペースは許容する(1 23*4はNG)
以上です

ようやく思うとおりに挙動するようになったのですが、もう少し綺麗に書けないかと考えております。

 該当のソースコード

#include <stdio.h>
#include <stdlib.h>

//配列の長さを一度に設定できるように宣言する
#define STRLEG 16



//四則演算を行う関数
int calc(int num1, int num2, char *op){
    //演算結果を格納する変数ans
    int ans;

    //取得した演算記号べつに計算を行う
    switch(*op){
        case '+':
            ans = num1 + num2;
            break;
        case '-':
            ans = num1 - num2;
            break;
        case '*':
            ans = num1 * num2;
            break;
        case '/':
            ans = num1 / num2;
        default:
            break;
    }
    //演算結果を返す
    return ans;    

}


int main(){
    //入力された数式を入れるための変数str
    char *p, str[STRLEG];
    //演算子を格納する変数op
    char op;
    //数式の中に現れた数値をカウントする変数countnum
    int countnum = 0;
    //数式の中に現れた演算子の数をカウントする変数
    int kigo = 0;
    //数式の中で最初に現れる数値を格納する
    int num1 = 0;
    //数式の中で2番目に現れる数値を格納する
    int num2 = 0;
    //演算結果を入れる
    int ans;

    //qが入力されるまでループさせる
    do {
        //メッセージを表示して数式を入力させる
        printf("数式を入力してください\n");

        //入力した数式をstrに入れる
        fgets(str,sizeof str,stdin);

        //qが入力されたらループを抜ける
        if(str[0]=='q') break;

        //一文字ずつ読み取り数値と演算子に分ける
        for (p = str; *p != '\n'; p++ ) {
            //空白でない場合に処理を行う。
            if(*p != ' '){
                //数値である場合の処理
                if(*p >= '0' && *p <= '9'){
                    //カウントする    
                    countnum++;
                    /*これから読み取る数値の前に演算子が来ていなければ
                     一つ目の数値として認める*/
                    if (countnum  == 1 && kigo == 0) {
                        //数字以外の文字がくるまで一度に代入する    
                        while(*p >= '0' && *p <= '9'){
                            num1=num1*10+*p-'0';
                            p++;
                        }
                        p--;
                    //二つめの数値の前に演算子が一つ来ていることを確かめる
                    } else if (countnum == 2 && kigo == 1) {
                        while(*p >= '0' && *p <= '9'){
                            num2=num2*10+*p-'0';
                            p++;
                        }
                        p--;
                    //その他はエラー
                    } else {
                        printf("数値の間にスペースを入れないでください\n");
                        exit(1);
                    }
                //演算子を読み取ったときの処理
                }else if(*p == '+' || *p == '-' || *p == '*' || *p == '/'){
                    kigo++; 
                    if(kigo==2){
                        printf("演算子は二つ以上入れないでください\n");
                        exit(1);
                    }
                    op = *p;
                //空白以外で数値・演算子以外の文字は無効
                }else{
                    printf("不正な文字が含まれています\n");
                    exit(1);
                }
            }
        }

    //0除算を行えないようにする
    if(op=='/'&&num2==0){
        printf("0除算はできません。\n");
        exit(1);
    }

    //計算を行う関数calcに二つの数値と演算子を送る
    ans = calc(num1,num2,&op);
    //計算結果の出力
    printf("%d %c %d = %d\n",num1,op,num2,ans);    

    //入力された文字列と数値、カウンタを初期化
    str[STRLEG];
    num1 = num2 = kigo = countnum = 0;

    }while(1);

    return 0;
}

特に文字列を変換する作業に関して、同じ処理を別に書いていることと
数値に変換した後のデクリメントを直したいと考えております。

同じ処理のまとめ方と帳尻合わせのデクリメントを対処する方法
その他何か、お気づきの点があればご教授いただけると幸いです。

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+1

こんにちは。

同じ処理のまとめ方

num1、num2ではなく、int num[2];を使って1つめと2つめの数字を記録すればまとめることができる筈です。

帳尻合わせのデクリメントを対処する方法

for文のp++を削除しop=*p++;まではスマートにできると思います。
問題はスペースの時のp++をどう忍び込ませるかですね。結局、デクリメントが良いのか、あちこちにあるインクリメントが良いのかの差となります。私は悩んだ結果デクリメントを取ることが多いです。ungetc()がわざわざ用意されているように、意外に必要なのですよ。
人は我儘ですから、マンマシン・インタフェースを綺麗に書けない数多くの例の1つと思います。
単語の区切りは必ずスペースで区切ることという制約をつければ綺麗にかけますが、それを許さないのが人なのです。

その他何か、お気づきの点があればご教授いただけると幸いです。

インデントが深いことが気になります。
if(*p != ' '){ ... }ではなく、if(*p == ' ') continue;にすれば1段インデントを節約できます。
if文による場合分けが複雑な印象を受けます。数値、スペース、行末、その他の4つに分けるとスッキリできるかも知れません。数値、行末、その他の頭で最初のエラー・チェックです。その他が不正文字かどうかはcalc関数のswitchのdefault:でできると良いですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

同じ処理のまとめ方

基本的には簡単で、関数化する、もしくはマクロを上手く使うことです。

帳尻合わせのデクリメントを対処する方法

p--; が必要になる理由は、 for (p = str; *p != '\n'; p++) というループの中で数字やら演算子やらを切り出しているからです。ですから Chironian さんがおっしゃるように、 for (p = str; *p != '\n'; )  とする手もあるでしょうが、、、

もうひとつ、Chironianさんご指摘の、インデントが深いことも考慮する必要があります。インデントが深いほど理解しにくく、メンテナンスしにくくなるのだから。
インデントが深くなる原因は、関数化していないことに加えて、 for (p = str; ...) という大きなループの中で処理していることです。このforループの中で

  • ひとつめの数字
  • 演算子
  • ふたつめの数字

と、今見ているものをif文で区別しながら処理する必要があるので、それだけでもインデントは1〜2段深くなるのです。私には諸悪の根源に思えてきました(笑)。

要は、入力した文字列から2つの数字と演算子を切り出すことです。そこで、こんなふうに組み直したらどうか(まずは、空白は無い、エラーチェックも要らないと仮定し、正常な処理の本筋だけを考えてみる)。

    p = str;         // 入力した行の先頭から見ていく
    while (*p >= '0' && *p <= '9') { // 数字ひとつめを変換する
        num1 = (num1 * 10) + (*p - '0');
        p++;
    }
    // ここで p は数字の次の文字(=演算子)を指す
    op = *p++;      // 演算子
    while (*p >= '0' && *p <= '9') { // 数字ふたつめ
        num2 = (num2 * 10) + (*p - '0');
        p++;
    }


--p; は不要になり、インデントは浅くなり、数字を切り出す機能の関数化も見えてきたので、私が以前参考書で覚えたw空白を読み飛ばす関数skipspaces()を使い、isdigit() も使わず即席のマクロにして、次の様にしてみました

#define ISDIGIT(X)    (((X) >= '0' && (X) <= '9') ? 1 : 0)

char *skipspaces(char *ptr)  // 空白を読み飛ばす
{
    while (*ptr == ' ')
        ++ptr;

    return ptr;               // 次の文字位置を返す
}

char *conv2num(char *ptr, int *num)     // 数字に変換する
{
    int result = 0;

    ptr = skipspaces(ptr);    // 空白を読み飛ばす
    if (ISDIGIT(*ptr) == 0)   // そこに数字が無ければエラー
        return NULL;          // エラー終了

    while (ISDIGIT(*ptr)) {
        result = (result * 10) + (*ptr - '0');
        ++ptr;
    }
    *num = result;            // 変換値を呼出し元に返す
    return ptr;               // 次の文字位置を返す
}

main()
{
    // 変数定義などは省略
    while (1) {
        printf("数式を入力してください\n");
        fgets(str, sizeof str, stdin); // 入力した数式をstrに
        if (str[0] == 'q')             // qでループを抜ける
            break;

        p = str;                       // 入力行の先頭から
        p = conv2num(p, &num1);        // 数字ひとつめ
        op = *p++;                     // 演算子
        p = conv2num(p, &num2);        // 数字ふたつめ

        if (op == '/' && num2 == 0) {  // 0除算しない
            // 0除算エラー
        }
        // 計算を行う関数calcに二つの数値と演算子を送る
        ans = calc(num1, num2, op);


演算子直前の空白はどうすべきか、エラーチェックなどまだまだ不完全・・・ですが、流れがシンプルで見通しがよくなったと思います。この線で私なりの完成形を作ることができました。

その他何か、お気づきの点があれば

  • do 〜 while (1); で無限ループするのは、よくありません。ループの先頭で無限ループだとわかるように、while (1) か for (;;) を使いましょう。

  • calc() の3番目の仮引数を char *op としていますが、ポインタ渡しにする必要はありません。

  • ここ↓に2つ問題を指摘したい。

   //入力された文字列と数値、カウンタを初期化
    str[STRLEG];
    num1 = num2 = kigo = countnum = 0;
  1.  str[STRLEG];  はおかしい。バッファクリアなど行われず、少なくとも再初期化ではありません。それどころか、バッファオーバーランの危険性さえ考えられるのでは?

  2. 繰り返すことを見越してループの最後で num1 〜 countnum に0を代入していますが、初期値の代入はループに入った先頭で行うべきです。
    さらに、main() の先頭は  int num1;  と変数定義だけにして、初期化は不要です。不要なことは書かない。一箇所でできることは一箇所で行ったほうがよい。

  • STRLEG は string length を短縮したのだと思いますが、LEG だと長さではなく「脚」になって良くない(脚にも長さあるがw)。重箱の隅のようだけど、格好悪いし、名前は案外大事だということで。6文字ならSTRLENかな。

  • #define STRLEG  16 ですが、int 型が 32bit なら10進数で10桁まで扱えるので、16文字では少ないと言えます。fgets()で入力するなら \n と \0 の2文字分を含めて、最低でも 10 + 1 + 10 + 2 = 23 文字、さらに空白文字の分もあったほうが良いでしょうね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

-5

fgets、strtol、charの比較

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/22 09:31

    脊髄反射で無意味な内容を書き込むとユーザーランキング月間2位を取れるんですねw。

    キャンセル

  • 2018/06/22 09:52

    回答として機能していないのでは

    キャンセル

  • 2018/06/22 09:59

    ご心配なく。
    うかうかしてたら月間1位になってたりしますんでw

    キャンセル

関連した質問

  • 解決済

    C言語 簡易関数電卓作成 エラーばかり

    プログラミング初心者です。 c言語で電卓を作成してみましたが、うまく動いてくれません。 コンパイルできない。 エラー個所をコメントアウトして実行すると、一文字入力後操作を受

  • 解決済

    fgetsを使った文字列の分割

    前提・実現したいこと AOJ 1_5Aの問題で、よくないとされるscanf以外を使用した解決を図りたいです。 問題内容は、 トランプの枚数が足りないので現在持っているカードを入

  • 解決済

    C言語でわからないこと

    include <stdio.h> float calc(char op, float a, float b); main(){ float number; number 

  • 解決済

    c言語で10進数が格納された配列を文字(char)に変換する関数の作成

    前提・実現したいこと c言語で10進数が格納された配列を文字(char)に変換する関数を作りたい 発生している問題・エラーメッセージ 変換がされないとの、putchar((in

  • 解決済

    繰り返しの表現の追加

    合計得点が21点以上の時か10回じゃんけんをしたときにプログラムを終了する。という条件を付けくわえたいです。 どのように表現すればいいのかアドバイスください。 #define

  • 解決済

    1次元配列(全ての組み合わせの2組の数同士の掛け算)

    5つの整数a0〜a4をキーボードから入力し、すべての組み合わせの2組の数同士の掛け算ai×aj(i.j=0.1.2.3.4)を計算するプログラムを作成せよ scanfを用いて値

  • 受付中

    c言語のif文について

    c言語のif文の条件について 最近c言語の勉強をし始めたものです 以下のようなif文の条件の書き方を教えてください。 キーボード入力(key)から 自然数を入力→ ● a を入

  • 解決済

    出力結果が思い通りに行きません

    7人分の成績を入力した後にその評定が出るようにしたいのですが下記のような表示になってしまいます。 7人分の評点を入力してください。 50 80 70 100 60 0 35 1 :

同じタグがついた質問を見る

  • C

    3831questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • アルゴリズム

    421questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。