10


2

プログラムを単純化して、数値をあるベースから別のベースに変換する

初心者のC ++コースを受講しています。 バイナリと16進数の間の任意のベースからバイナリと16進数の間の別のベースに任意の数値を変換するプログラムを作成するように指示する割り当てを受け取りました。 基数10への変換と基数10からの変換には別々の関数を使用するように求められました。 配列の使用に慣れるのを助けるためでした。 (クラスで以前に参照渡しすることについては既に説明しました。)私はすでにこれを提出しましたが、これが意図したとおりではなかったと確信しています。

#include
#include
#include
#include

using std::cout;
using std::cin;
using std::endl;

int to_dec(char value[], int starting_base);
char* from_dec(int value, int ending_base);

int main() {
    char value[30];
    int starting_base;
    int ending_base;
    cout << "This program converts from one base to another, so long as the bases are" << endl
        << "between 2 and 16." << endl
        << endl;
input_numbers:
    cout << "Enter the number, then starting base, then ending base:" << endl;
    cin >> value >> starting_base >> ending_base;
    if (starting_base < 2 || starting_base > 16 || ending_base < 2 || ending_base > 16) {
        cout << "Invalid base(s). ";
        goto input_numbers;
    }
    for (int i=0; value[i]; i++) value[i] = toupper(value[i]);
    cout << "Base " << ending_base << ": " << from_dec(to_dec(value, starting_base), ending_base) << endl
        << "Press any key to exit.";
    getch();
    return 0;
}

int to_dec(char value[], int starting_base) {
    char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    long int return_value = 0;
    unsigned short int digit = 0;
    for (short int pos = strlen(value)-1; pos > -1; pos--) {
        for (int i=0; i

これは本来よりも高度なものであると確信しています。 私はそれをすることになっていたとどう思いますか?

私は本質的に2種類の答えを探しています:

  1. おそらく私の先生のようなシンプルなソリューションの例 期待されます。

  2. コードを改善する方法に関する提案。

10 Answer


4


私はあなたが内側のループを必要とは思わない:

for (int i=0; i

その目的は何ですか?

むしろ、 `value [pos]`にある文字を取得して整数に変換する必要があります。 変換はベースに依存するため、別の関数で行う方が良い場合があります。

`char hex [16]`を各関数で1回ずつ、2回定義しています。 1つの場所でのみ行う方が良い場合があります。

'' '' '

*編集1:*これはタグ付けされた「宿題」なので、完全な答えはできません。 ただし、これはto_dec()の動作例です。 (理想的には、これを構築する必要があります!)

入力:

  char * value = 3012,
  int base = 4,

数学:

Number = 3 * 4^3 + 0 * 4^2 + 1 * 4^1 + 2 * 4^0 = 192 + 0 + 4 + 2 = 198

ループの予想される動作:

  x = 0
  x = 4x + 3 = 3
  x = 4x + 0 = 12
  x = 4x + 1 = 49
  x = 4x + 2 = 198

  return x;

'' '' '

編集2:

けっこうだ! だから、ここにいくつかあります:-)

これがコードスケッチです。 ただし、コンパイルもテストもされていません。 これは、以前に提供した例の直接翻訳です。

unsigned
to_dec( char * inputString, unsigned base )
{
  unsigned rv = 0; // return value
  unsigned c;      // character converted to integer

  for( char * p = inputString; *p; ++p ) // p iterates through the string
  {
    c = *p - hex[0];
    rv = base * rv + c;
  }

  return rv;
}


3


絶対に必要な場合を除き、GOTOステートメントには近づかないでしょう。 GOTOステートメントは使いやすいですが、「スパゲッティコード」につながります。

代わりにループを使用してみてください。 この線に沿って何か:

bool base_is_invalid = true;
while ( base_is_invalid ) {

    cout << "Enter the number, then starting base, then ending base:" << endl;
    cin >> value >> starting_base >> ending_base;

    if (starting_base < 2 || starting_base > 16 || ending_base < 2 || ending_base > 16)
        cout << "Invalid number. ";
    else
        base_is_invalid = false;

}


1


文字列リテラルで配列を初期化できます(配列のサイズでは許可されていないため、終端の\ 0は含まれません)。

char const hex[16] = "0123456789ABCDEF";

または、同じ効果を得るために、文字列リテラルへのポインターを使用します。

char const* hex = "0123456789ABCDEF";


1


to_dec()は複雑に見えますが、これが私のショットです。

int to_dec(char* value, int starting_base)
{
    int return_value = 0;
    for (char* cur = value + strlen(value) - 1; cur >= value; cur--) {
        // assuming chars are ascii/utf: 0-9=48-57, A-F=65-70
        // faster than loop
        int inval = *cur - 48;
        if (inval > 9) {
            inval = *cur - 55;
            if (inval > 15) {
                // throw input error
            }
        }
        if (inval < 0) {
            // throw input error
        }
        if (inval >= starting_base) {
            // throw input error
        }
        // now the simple calc
        return_value *= starting_base;
        return_value += inval;
    }
    return return_value;
}


1


asciiから整数への初期変換には、ルックアップテーブルを使用することもできます(逆に変換にルックアップテーブルを使用するのと同じように)。

 int to_dec(char value[], int starting_base)
 {
      char asc2BaseTab = {0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15, //0-9 and A-F (big caps)
                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  //unused ascii chars
                          10,11,12,13,14,15};  //a-f (small caps)

      srcIdx = strlen(value);
      int number=0;
      while((--srcIdx) >= 0)
      {
           number *= starting_base;
           char asciiDigit = value[srcIdx];
           if(asciiDigit<'0' || asciiDigit>'f')
           {
                //display input error
           }
           char digit = asc2BaseTab[asciiDigit - '0'];
           if(digit == -1)
           {
                 //display input error
           }
           number += digit;
      }
      return number;
 }

p.s. これにいくつかのコンパイルエラーがある場合は言い訳…​私はそれをテストすることができませんでした…​しかし、ロジックは健全です。


1


与えられた課題の説明には次のように書かれています:

「別の関数を使用して、ベース10との間で変換するように求められました。」

それが本当に教師が意図し、望んでいたものである場合、それは疑わしいです、あなたのコードはそれをしません:

int to_dec(char value[], int starting_base)

2進数であるintを返しています。 :-)私の意見では、これはより理にかなっています。

先生はそれに気づきましたか?


1


CとC は異なる言語であり、プログラミングのスタイルも異なります。 それらを混ぜてはいけません。 (http://www.cprogramming.com/tutorial/c-vs-c.html[CとC ++の違い])

C ++を使用しようとしている場合:

char *またはchar []の代わりにstd

stringを使用します。

int to_dec(string value, int starting_base);
string from_dec(int value, int ending_base);

mallocはありません。new/ deleteを使用します。 しかし、実際にはC ++はメモリを自動的に管理します。 変数がスコープ外になるとすぐにメモリが解放されます(ポインタを処理している場合を除く)。 そして、ポインタは最後に対処する必要があるものです。

ここでは、ルックアップテーブルは必要なく、マジックストリングだけが必要です。

string hex = "0123456789ABCDEF";//The index of the letter is its decimal value. A is 10, F is 15.
//usage
char c = 'B';
int value = hex.find( c );//works only with uppercase;

リファクタリングされたto_decはそのようになります。

int to_dec(string value, int starting_base) {
  string hex = "0123456789ABCDEF";
  int result = 0;
  for (int power = 0; power < value.size(); ++power) {
    result += hex.find( value.at(value.size()-power-1) ) * pow((float)starting_base, power);
  }
  return result;
}

また、ベース10から他のベースに変換するよりエレガントなアルゴリズムがあります。http://www.searchmarked.com/math/how-to-convert-from-base-10-decimal-to-any-other-baseを参照してください。たとえば、php [there]。 自分でコーディングする機会があります:)


1


from_dec関数では、数字を左から右に変換しています。 別の方法は、右から左に変換することです。 あれは、

std::string from_dec(int n, int base)
{
    std::string result;
    bool is_negative = n < 0;

    if (is_negative)
    {
       n = - n;
    }

    while (n != 0)
    {
        result = DIGITS[n % base] + result;
        n /= base;
    }

    if (is_negative)
    {
        result = '-' + result;
    }

    return result;
}

この方法では、ログ機能は必要ありません。

(BTW、to_decおよびfrom_decは不正確な名前です。 お使いのコンピューターは、基数10に数値を保存しません。)


1


インタビューで一度この質問を受けて、しばらくの間、頭を抱えて車輪を回しました。 図に行きます。 とにかく、数年後、私はプログラマ向けの数学と物理学を経て、自分がやっていることよりも数学が集中しているポジションをブラッシュアップします。 CH1「割り当て」には

// Write a function ConvertBase(Number, Base1, Base2) which takes a
// string or array representing an integer in Base1 and converts it
// into base Base2, returning the new string.

そこで、上記のアプローチを取りました。任意のベースの文字列をUINT64に変換してから、UINT64を任意のベースに変換します。

CString ConvertBase(const CString& strNumber, int base1, int base2)
{
   return ValueToBaseString(BaseStringToValue(strNumber, base1), base2);
}

各サブ関数には再帰的な解決策があります。 例を挙げましょう。

UINT64 BaseStringToValue(const CString& strNumber, int base)
{
    if (strNumber.IsEmpty())
    {
        return 0;
    }

    CString outDigit = strNumber.Right(1);
    UINT64 output = DigitToInt(outDigit[0]);
    CString strRemaining = strNumber.Left(strNumber.GetLength() - 1);
    UINT64 val = BaseStringToValue(strRemaining, base);
    output += val * base;
    return output;
}

もう1つは精神的に把握するのが少し難しいですが、ほぼ同じように機能します。

また、私はDigitToIntとIntToDigitを実装しました。 ちなみに、charsがintであることに気付いた場合、巨大なswitchステートメントは必要ありません。

int DigitToInt(wchar_t cDigit)
{
    cDigit = toupper(cDigit);
    if (cDigit >= '0' && cDigit <= '9')
    {
        return cDigit - '0';
    }
    return cDigit - 'A' + 10;
}

そしてユニットテストは本当にあなたの友達です:

typedef struct
{
    CString number;
    int base1;
    int base2;
    CString answer;
} Input;

Input input[] =
{
    { "345678", 10, 16, "5464E"},
    { "FAE211", 16, 8, "76561021" },
    { "FAE211", 16, 2, "111110101110001000010001"},
    { "110110111", 2, 10, "439" }
};

(snip)

for (int i = 0 ; i < sizeof(input) / sizeof(input[0]) ; i++)
{
    CString result = ConvertBase(input[i].number, input[i].base1, input[i].base2);
    printf("%S in base %d is %S in base %d (%S expected - %s)\n", (const WCHAR*)input[i].number,
                                               input[i].base1,
                                               (const WCHAR*) result,
                                               input[i].base2,
                                               (const WCHAR*) input[i].answer,
                                               result == input[i].answer ? "CORRECT" : "WRONG");
}

そして、これが出力です。

345678 in base 10 is 5464E in base 16 (5464E expected - CORRECT)
FAE211 in base 16 is 76561021 in base 8 (76561021 expected - CORRECT)
FAE211 in base 16 is 111110101110001000010001 in base 2 (111110101110001000010001 expected - CORRECT)
110110111 in base 2 is 439 in base 10 (439 expected - CORRECT)

今、私はCString型などを使用してコーディングのいくつかのショートカットを取りました。 効率やパフォーマンスを考慮していませんでした。できるだけ簡単なコーディングでアルゴリズムを解決したかっただけです。

次のように記述すれば、これらのアルゴリズムがどのように再帰的であるかを理解するのに役立ちます。ベース13にある「文字列」B4A3の「値」を決定するとします。 あなたはそれが3 + 13(A)+ 13(13)(4)+ 13(13)(13)(B)であることを知っています)))-そして出来上がり! 再帰。


0


既に述べたものとは別に、無料ではなく新しい演算子を使用することをお勧めします。 newの利点は、コンストラクターも呼び出すことです(PODタイプを使用しているためここでは無関係ですが、std

stringや独自のカスタムクラスなどのオブジェクトに関しては重要です)。特定のニーズに合う新しい演算子(ここでも関係ありません:p)。 ただし、PODにはmallocを使用し、クラスにはnewを使用しないでください。それらを混在させることはスタイルが悪いと見なされます。

しかし、大丈夫、あなたは自分自身からいくつかのヒープメモリを取得しましたfrom_dec …​ しかし、どこで再び解​​放されますか? 基本ルール:malloc(またはcallocなど)のメモリーは、ある時点で解放するために渡す必要があります。 同じ演算子がnew-operatorにも適用されますが、release-operatorはdeleteと呼ばれます。 配列の場合、new []およびdelete []が必要であることに注意してください。 メモリが正しく解放されないため、newで割り当てたり、delete []で解放したり、その他の方法で割り当てたりしないでください。

おもちゃのプログラムがメモリを解放しなくても、悪は起こりません…​ お使いのPCには十分なRAMがあり、プログラムをシャットダウンすると、OSはメモリを解放します。 ただし、すべてのプログラムが(a)そのように小さなものであり、(b)頻繁にシャットダウンするわけではありません。

また、これは移植性がないため、conio.hを避けます。 最も複雑なIOを使用していないので、標準ヘッダー(iostreamなど)を使用する必要があります。

同様に、現代の言語を使用するほとんどのプログラマーは、「他のソリューションが実際に機能しない、または多くの作業が必要な場合にのみgotoを使用する」というルールに従うと思います。 これは、emceeflyが示すように、ループを使用することで簡単に解決できる状況です。 あなたのプログラムではgotoは扱いやすいですが、そのような小さなプログラムを永遠に書くことはないでしょうか? ;)たとえば、最近、いくつかのレガシーコードが提示されました。 後藤で散らかった2000行のコード、いや! コードの論理的な流れを追おうとすることはほとんど不可能でした(「ああ、200行先に進み、素晴らしい…​ とにかくコンテキストが必要な人」)、さらに難しいのは、いまいましいことを書き直すことでした。

あなたのgotoはここでは痛くないのですが、そのメリットはどこにありますか? 2〜3行短い? 全体的には重要ではありません(コードの行で支払いを受けている場合、これも大きな不利益になる可能性があります;))。 個人的には、ループバージョンがより読みやすく、きれいであることがわかりました。

ご覧のとおり、ここではプログラムはおもちゃのプログラムなので、ほとんどの点は簡単に無視できます。 しかし、より大きなプログラムについて考えるとき、それらはより理にかなっています(願わくば);)