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

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

ただいまの
回答率

88.91%

【C言語】特定の文字の下に「^」を表示させる方法

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,012

tarasawa

score 13

前提・実現したいこと

16進数の電卓を作っている。
基本的な機能は計算式を入力して、結果を表す。(括弧付き計算式はOK)
入力した不正な数式の不正箇所を「^」で指摘したいが、やり方が分からない。
例:5+((6/2)--1
|        ^      ^

一応、ソース全体を張り付けておく。

該当のソースコード

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

#define MaxSize 32

char str[MaxSize];
int sign;

void translate(char str[],char exp[]) {//逆ポーランド記法に直す
    struct{
        char data[MaxSize];
        int top;              
    }op;                          
    char ch;                  
    int i=0;
    int t=0;
    op.top=-1;
    ch = str[i];                    
    i++;

    while(ch!='\0'){
        switch(ch){
        case '(':              
            op.top++;
            op.data[op.top]=ch;
            break;
        case ')':            
            while(op.data[op.top] != '('){
                exp[t]=op.data[op.top];
                op.top--;
                t++;
            }
            op.top--;
            break;
        case '+':
        case '-':
            while(op.top != -1&&op.data[op.top] != '('){
                exp[t] = op.data[op.top];
                op.top--;
                t++;
            }
            op.top++;        
            op.data[op.top] = ch;
            break;
        case '*':
        case '/':
            while(op.data[op.top] == '/'||op.data[op.top] == '*'){
                exp[t] = op.data[op.top];
                op.top--;
                t++;
            }
            op.top++;
            op.data[op.top] = ch;
            break;
        case ' ':                      
            break;
        default:
            while((ch >= '0'&&ch <= '9')||(ch >= 'a'&&ch <= 'f')){
                exp[t] = ch;
                t++;
                ch = str[i];
                i++;
            }
            i--;
            exp[t] = '#';      
            t++;
        }
        ch = str[i];
        i++;
    }
    while(op.top != -1){
        exp[t] = op.data[op.top];
        t++;
        op.top--;
    }
    exp[t] = '\0';                      
}

int cal_value(char exp[],int* flag){
    struct{
        signed int data[MaxSize];
        int top;
    }st;                            
    signed int d;
    char ch;
    int t = 0;
    st.top = -1;
    ch = exp[t];
    t++;

    while(ch != '\0'){
        switch(ch){//四則演算
        case '+':
            st.data[st.top-1] = st.data[st.top-1]+st.data[st.top];
            st.top--;
            break;
        case '-':
            if(st.top<=0){
                st.data[st.top]=-st.data[st.top];
                return st.data[st.top];
                break;
            }else{
                st.data[st.top-1] = st.data[st.top-1]-st.data[st.top];
            }
            st.top--;
            break;
        case '*':
            st.data[st.top-1] = st.data[st.top-1]*st.data[st.top];
            st.top--;
            break;
        case '/':
            if(st.data[st.top] != 0)
                st.data[st.top-1]=st.data[st.top-1]/st.data[st.top];
            else{
                printf("除数を正しく入力してください\n\n");
                *flag=1;
            }
            st.top--;
            break;
        default:
            d=0;
            while(ch !='#'){//文字列を数値に
                if(ch>='0'&&ch<='9'){
                    d = 16*d+ch-'0';
                    ch = exp[t];
                    t++;
                }else if(ch>='a'&&ch<='f'){
                    d = 16*d+ch-0x57;
                    ch = exp[t];
                    t++;
                }
            }

            st.top++;
            st.data[st.top] = d;

            if(sign==1){
                st.data[0]=-st.data[0];
                sign=0;
            }
        }
        ch = exp[t];
        t++;
    }

    return st.data[st.top];
}

int CheckLen(char data[]){
    int i=0;

    for(i=0;data[i]!='\n';i++){
        if(i>32){
            printf("最大入力文字数を超過しました。\n30文字以内でもう一度入力しください。\n\n");
            return 1;
        }
    }

    if(data[0]==10&&data[1]==0){
        printf("計算式が入力していません。\nEnterキーを押して、もう一度計算式を入力してください。\n\n");
        return 1;
    }

    return 0;
}

int DeleteSpaeceTab(char *data){
    int i=0;
    int speace_tab_cnt=0;

    while(data[i]!='\0'){//タブ・スペースの削除
        if(data[i]==' '||data[i]=='\t'){
            while(data[i]!='\0'){
                data[i]=data[i+1];
                i++;
            }
            i=0;
        }
        i++;
    }

    data[i-1]='\0';

    return *data;
}

int MaxMinCheck(char *data){
    int operator_flag=0;
    int i=0;
    int j=0;
    char w[31]={' '};
    long long w2=0;
    char *extra;

    while(data[i]!='\0'&&i<=32){
            if(data[i]=='+'||data[i]=='-'||data[i]=='*'||data[i]=='/'){
                operator_flag=1;
            }
            i++;
        }

    i=0;

    if(operator_flag==1){
            while(data[i]!='\0'){//最大値チェック(演算子がある場合)
                if(data[i] >= '0'&& data[i] <= 'f'){
                    w[j]=data[i];
                    i++;
                    j++;
                }else{
                    w2=strtol(w,&extra,16);
                    if(w2<INT_MIN||w2>INT_MAX){
                        printf("入力可能な桁数は8桁で、範囲は80000000~ffffffffです。\n\n");
                        return 1;
                        break;
                    }
                    i++;
                    j=0;
                }

                if(j>8){
                        printf("入力可能な桁数は8桁で、範囲は80000000~ffffffffです。\n\n");
                        return 1;
                        break;
                    }
            }
        }else{
            i=0;
            while(data[i]!='\0'){//最大値チェック(演算子がない場合)
                w[i]=data[i];
                i++;
            }
            w2=strtol(w,&extra,16);
            if(i>8){
                printf("入力可能な桁数は8桁で、範囲は80000000~ffffffffです。\n\n");
                return 1;
            }else if(w2<INT_MIN||w2>INT_MAX){
                printf("入力可能な桁数は8桁で、範囲は80000000~ffffffffです。\n\n");
                return 1;
            }
        }

    return 0;
}

int IncrrectFormulaCheck(char *data){
    int i=0;

    while(data[i]!='\0'){//不正数式の例外処理
        if(data[i]=='/'&&data[i+1]=='0'){
            printf("除数を正しく入力してください。\n\n");
            return 1;
            break;
        }else if(data[i]=='+'||data[i]=='-'||data[i]=='*'||data[i]=='/'){
            if(data[i+1]=='+'||data[i+1]=='-'||data[i+1]=='*'||data[i+1]=='/'){
                printf("エラー!演算子を正しく入力してください。\n\n");
                return 1;
                break;
            }else{
                i++;
            }
        }else if(data[0]=='*'||data[0]=='/'){
            printf("エラー!計算式を正しく入力してください。\n\n");
            return 1;
            break;
        }else if(data[i]=='('||data[i]==')'){
            i++;
        }else if(data[i]>= '0'&&data[i] <= '9'){
            i++;
        }else if(data[i]>='a'&&data[i]<='f'){
            i++;
        }else if(data[i]>='A'&&data[i]<='F'){
            printf("英字は小文字で入力してください。\n\n");
            return 1;
            break;
        }else{
            printf("エラー!計算式を正しく入力してください。\n\n");
            return 1;
            break;
        }
    }

    i=0;
    while(data[i+1]!='\0'){//末尾文字の要素番号を計算
        i++;
    }

    if(data[i]>='0'&&data[i]<='9'){//末尾の不正数式指摘
    }else if(data[i]>='a'&&data[i]<='f'){
    }else if(data[i]=='('||data[i]==')'){
    }else{
        printf("不正な数式が入力されました。\n\n");
        return 1;
    }

    return 0;
}

int main(void){
    char str[MaxSize],exp[MaxSize];
    int i=0;
    int j=0;
    int k=0;
    int err_flag=0;
    int err_flag2=0;

    printf("*******************************************\n");
    printf("\t\t十六進数電卓\n");
    printf("*******************************************\n\n");

    while(1){
        memset(str,-2,sizeof(str));
        memset(stdin,0,sizeof(stdin));
        err_flag=0;
        err_flag2=0;
        i=0;

        printf("計算式を入力してください。\n");
        //gets_s(str,32);
        fgets(str, 32, stdin);


        if(str[0]=='z'&&str[1]==10&&str[2]==0){
            exit(1);
        }else if(str[0]=='z'){
            printf("終了したい場合はzを入力し、Enterキーを押してください。\n\n");
            err_flag=1;
        }else{
            err_flag=CheckLen(str);
            if(err_flag!=1){
                DeleteSpaeceTab(str);
                err_flag=MaxMinCheck(str);
                if(err_flag!=1){
                    err_flag=IncrrectFormulaCheck(str);
                }
            }

            if(str[0]=='-'){//負数対応
                while(str[i]!='\0'){
                    str[i]=str[i+1];
                    i++;
                }
                sign=1;
            }else if(str[0]=='+'){
                while(str[i]!='\0'){
                    str[i]=str[i+1];
                    i++;
                }
                sign=0;
            }
        }

        if(err_flag!=1){
            translate(str,exp);
            k=cal_value(exp,&err_flag2);
            if(err_flag2==0){
            printf("=%x\n",k);
            printf("\n");
            }
        }

        if ( str[strlen(str)-1] != '\n' ){
            while( getchar() != '\n' );
        }

    }

    system("pause");

    return 0;
}

試したこと

まだ何も試していない

補足情報(言語/FW/ツール等のバージョンなど)

C言語/Microsoft Visual C++ 2010/.NET 4.0

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • 退会済みユーザー

    2017/01/23 11:48

    こちらの質問が他のユーザから「やってほしいことだけを記載した丸投げの質問」という指摘を受けました
    「質問を編集する」ボタンから編集を行い、調査したこと・試したことを記入していただくと、回答が得られやすくなります。

  • WoodenHamlet

    2017/01/24 10:57

    パッと見ですが str を大域変数にしたなら(大域変数にするにしてももう少し大域変数らしい変数名にした方が良いです。ハンガリアン記法にしろとは言わないけど)いちいち関数に渡すのはやめましょう。

    キャンセル

回答 3

checkベストアンサー

+1

char Error[入力された文字数+1]
と言う文字列を全て空白で埋めます。

文法チェックしている関数でエラーを検出したらError[i]='^';
としてあげる。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/24 10:52

    ああそういうことでしたか。

    ただ、(文字列として使用するつもりの)char配列が終端されていないタイミングがあるというのがきな臭く感じてしまうので、どちらにせよ初期化時にいったん終端しておいた方が、バグが発生しにくいのではとは思います。

    キャンセル

  • 2017/01/24 11:20

    そう思います。

    さらに言うとchar配列にmemsetは嫌いなので、気の利いた提案が出来ませんでした。

    文字列として扱う癖をつけた方がよさそうですが、今回はエラーで処理終了なので文字列も終了と提案しました。

    キャンセル

  • 2017/01/25 14:25

    hikochangさん、WoodenHamletさん、ありがとうございます。
    変な記号の問題は解決しました。
    これ↓で成功しました。
    error[i]='^';
    error[i+1]='¥0'; //文字列の終了

    ありがとうございます。

    キャンセル

0

C#で16進電卓を100行以内を目標に作ってみました。動作が正しいか確認していませんし、1/3*3などを行うと0.9999999になるなど当然精度や性能は考えていません。
実は、16進数を10進数に変換した段階でWindows PowerShellに計算を任せてしまうと、30行程度で16進数関数電卓は作れる気がしますが、今回はカッコと四則演算の優先度を自分で実装してみました。

C#(.NET)や正規表現という世界もあるという事をお伝えしたく、、、ご参考まで

using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace HexCalc
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                string input = Console.ReadLine();
                // エラーチェック
                Match match = Regex.Match(input,
                    @"((?<=[0-9a-zA-Z][ \t]*)\()|" +                    // "3(":数字直後のカッコ開始
                    @"((?<=[\+\-\*\/][ \t]*)\))|" +                     // "+)":演算子直後のカッコ終了
                    @"((?<=[\+\-\*\/][ \t]*)[\*\/])|" +                 // "+/":加減算の直後の乗除算
                    @"([\+\-\*\/][ \t]*[\+\-\*\/][ \t]*[\+\-\*\/])|" +  // "*-+":演算子の3連続
                    @"((?<=\/[ \t]*)0(?![0-9a-zA-Z]))|" +               // "/0 ":ゼロ割
                    @"((?<=^[ \t]*)[\*\/])|" +                          // "^*":先頭の乗除算
                    @"([\+\-\*\/](?=[ \t]*$))|" +                        // "+$":末尾の演算子
                    @"(^[ \t]*$)");                                     // "":空
                if (match.Success)
                {
                    Console.WriteLine(input);
                    Console.WriteLine(string.Concat(Enumerable.Repeat(" ", match.Index)) + "^");
                    continue;
                }

                // カッコの対応チェック
                int level = 0;
                int i = 0;
                for( i=0; i< input.Length; i++ )
                {
                    level += (input[i] == '(') ? +1 : 0;
                    level += (input[i] == ')') ? -1 : 0;
                    if (level < 0)
                    {
                        break;
                    }
                }
                if (level != 0)
                {
                    Console.WriteLine(input);
                    Console.WriteLine(string.Concat(Enumerable.Repeat(" ", i)) + "^");
                    continue;
                }
                match = Regex.Match(input, @"(?<![0-9a-zA-Z])[0-9a-zA-Z]+(?![0-9a-zA-Z])");
                while (match.Success)
                {
                    input = Regex.Replace(input, match.Value, Int64.Parse(match.Value, System.Globalization.NumberStyles.AllowHexSpecifier).ToString());
                    match = match.NextMatch();
                }

                input = '(' + input + ')';
                while ((match = Regex.Match(input, @"\([ \t\+\-\*\/\d\.]*\)")).Success)
                {
                    string result = match.Value.Trim(new char[] { '(', ')' });
                    Match m;
                    double r;
                    while ((m = Regex.Match(result, @"(?<=(^|[\+\-\*\/])[ \t]*)[\+\-]?\d+(\.\d+)?[ \t]*[\*\/][ \t]*[\+\-]?\d+(\.\d+)?(?!\d)")).Success)
                    {
                        if (0 <= m.Value.IndexOf("*"))
                        {
                            string[] op = m.Value.Split('*');
                            r = double.Parse(op[0]) * double.Parse(op[1]);
                        }
                        else
                        {
                            string[] op = m.Value.Split('/');
                            r = double.Parse(op[0]) / double.Parse(op[1]);
                        }
                        result = Regex.Replace(result, Regex.Escape(m.Value), r.ToString());
                    }
                    while ((m = Regex.Match(result, @"(?<=(^|[\+\-\*\/])[ \t]*)[\+\-]?\d+(\.\d+)?[ \t]*[\+\-][ \t]*[\+\-]?\d+(\.\d+)?(?!\d)")).Success)
                    {
                        MatchCollection op = Regex.Matches(result, @"(?<=(^|[\+\-\*\/])[ \t]*)[\+\-]?\d+(\.\d+)?[ \t]*(?!\d)");
                        if (0 <= m.Value.IndexOf("+"))
                        {
                            r = double.Parse(op[0].Value) + double.Parse(op[1].Value);
                        }
                        else
                        {
                            r = double.Parse(op[0].Value) - double.Parse(op[1].Value);
                        }
                        result = Regex.Replace(result, Regex.Escape(m.Value), r.ToString());
                    }
                    input = Regex.Replace(input, Regex.Escape(match.Value), result);
                }
                Console.WriteLine("10進数:"+input);
            }
        }
    }
}

実行結果

a+(b+(d*(e+f))))+1
               ^
a+(b+(d*(e+f)))
10進数:398

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/26 01:08

    C#でPowerShellを使う場合の16進数電卓、答えは10進数です、、、
    38行でした。ちゃんと1/3*3が1になります。

    using System;
    using System.Text.RegularExpressions;
    using System.Management.Automation;
    // using System.Management.Automation; を使用するためには以下を参照する必要がある。
    // C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll
    namespace HexCalc
    {
    class Program
    {
    static void Main(string[] args)
    {
    while (true)
    {
    string input = Console.ReadLine();
    try
    {
    Match match = Regex.Match(input, @"(?<![0-9a-zA-Z])[0-9a-zA-Z]+(?![0-9a-zA-Z])");
    while (match.Success)
    {
    input = Regex.Replace(input, match.Value, Convert.ToUInt64(match.Value, 16).ToString());
    match = match.NextMatch();
    }
    using (var invoker = new RunspaceInvoke())
    {
    var result = invoker.Invoke(input, null);
    Console.WriteLine("10進数:" + result[0]);
    }
    }
    catch (Exception e)
    {
    Console.WriteLine(e.Message);
    }
    }
    }
    }
    }

    キャンセル

-1

具体的にどの辺りが判らないのか質問文からサッパリかつソース丸投げなんでアレですが(まさか関数丸ごと作れとか言ってるわけじゃないとは思いたい)、incorrect 関数のできていない部分を見るに、括弧の対応が取れているかをチェックできていないとかなんかその辺が解っていないのかな。

せっかく計算式文字列の最初から舐めてるんだから、"("が出てくるごとに+1する変数をつくって、")"が出てくるごとに―1する変数を作ればいいんじゃないかな。
途中で負の数になれば")"が過剰だとわかるし、最後まで来たのに1以上だったなら"("が過剰だ。

ついでに、incorrect 関数最後のパートでi=0にして最後の文字がどこか数えているけど、それ以前の部分で文字数は判明していると思うけどどうだろうか。

(追記)
警告はちゃんと見ていますか?まともなコンパイラを使っているなら少なくとも DeleteSpaeceTab 関数の返り値の型が違うと文句を言ってくるはずです。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/25 14:04

    >せっかく計算式文字列の最初から舐めてるんだから、"("が出てくるごとに+1する変数をつくって、")"が出てくるごとに―1する変数を作ればいいんじゃないかな。
    >途中で負の数になれば")"が過剰だとわかるし、最後まで来たのに1以上だったなら"("が過剰だ。
    まずは当方はプログラミング学習歴半年もない初心者です。初めての計算器プログラムなので、作り方が何一つも分かっていない状態で始めました。様々な異常系はまだ網羅できていないです。
    仰っている問題点と解決策は理解できています。これから実装してみます。

    >ついでに、incorrect 関数最後のパートでi=0にして最後の文字がどこか数えているけど、それ以前の部分で文字数は判明していると思うけどどうだろうか。
    設計ミスです。確かに文字を数え直す必要はないです。

    >警告はちゃんと見ていますか?まともなコンパイラを使っているなら少なくとも DeleteSpaeceTab 関数の返り値の型が違うと文句を言ってくるはずです。
    何も警告されていないですよ。WoodenHamletさんはコンパイラ何を使っていますか。

    キャンセル

  • 2017/01/25 15:09

    ああすいません、コピペミスってしまっていました。警告の件は無視してください

    キャンセル

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

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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