简化我的程序,将数字从一个基数转换为另一个基数

时间:2010-03-03 23:56:04

标签: c++

我正在参加初学C ++课程。我收到一个作业,告诉我编写一个程序,将二进制和十六进制之间的任意基数中的任意数字转换为二进制和十六进制之间的另一个基数。我被要求使用单独的函数来转换为基数10。这是为了帮助我们习惯使用数组。 (我们之前在课堂上已经介绍了参考文献。)我已经把它转过来了,但我很确定这不是我的意思:

#include <iostream>
#include <conio.h>
#include <cstring>
#include <cmath>

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<starting_base; i++) {
            if (hex[i] == value[pos]) {
                return_value+=i*pow((float)starting_base, digit++);
                break;
            }
        }
    }
    return return_value;
}

char* from_dec(int value, int ending_base) {
    char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    char *return_value = (char *)malloc(30);
    unsigned short int digit = (int)ceil(log10((double)(value+1))/log10((double)ending_base));
    return_value[digit] = 0;
    for (; value != 0; value/=ending_base) return_value[--digit] = hex[value%ending_base];
    return return_value;
}

我很确定这比预期更先进。你怎么认为我应该这样做?

我基本上是在寻找两种答案:

  1. 像我老师所期望的简单解决方案的例子。
  2. 关于如何改进代码的建议。

10 个答案:

答案 0 :(得分:4)

我认为你不需要内循环:

for (int i=0; i<starting_base; i++) {

它的目的是什么?

相反,你应该在value[ pos ]获得角色并将其转换为整数。转换取决于base,因此最好在单独的函数中执行。

您在每个函数中定义char hex[ 16 ]两次,一次。最好只在一个地方做。


编辑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;
}

答案 1 :(得分: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;

}

答案 2 :(得分:1)

您可以通过字符串文字初始化数组(注意,不包括终止\ 0,因为数组的大小不允许):

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

或者只是使用指向字符串文字的指针来获得相同的效果:

char const* hex = "0123456789ABCDEF";

答案 3 :(得分: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;
}

答案 4 :(得分: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。如果在这里有一些编译错误的借口...我无法测试它...但逻辑是合理的。

答案 5 :(得分:1)

在您给出的作业说明中,它说:

“我被要求使用单独的函数来转换为基数10。”

如果这真的是老师的意思和想法,这是值得怀疑的,那么你的代码就不会这样做:

int to_dec(char value[], int starting_base)

返回一个二进制数的int。 :-)我认为哪个更有意义。

老师是否注意到了这一点?

答案 6 :(得分:1)

C和C ++是不同的语言,并且具有不同的编程风格。你最好不要混合它们。 (Where C and C++ differ

如果您尝试使用C ++,那么:

使用std :: string而不是char *或char []。

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

没有任何mallocs,请使用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转换为任何其他算法 例如,请参阅there。您有机会自己编写代码:)

答案 7 :(得分: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中存储数字。)

答案 8 :(得分: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;
}

我发现另一个人在精神上更难以掌握,但它的工作方式大致相同。

我还实现了DigitToInt和IntToDigit,它们听起来就像它们一样。顺便说一句,你可以在那里采取一些简洁的快捷方式,如果你意识到字符是整数,那么你不需要巨大的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类型等编写了一些快捷方式。我没有考虑效率或性能,我只想用最简单的编码来解决算法。

如果你这样编写它们可以帮助理解这些算法是递归的:假设你想要确定“字符串”B4A3的“值”,它在13中。你知道它是3 + 13(A )+ 13(13)(4)+ 13(13)(13)(B)另一种写法是:0 + 3 + 13(A + 13(4 + 13(B))) - 瞧!递归。

答案 9 :(得分:0)

除了已经提到的内容之外,我建议使用new-operator而不是free。新的优点是它也调用构造函数 - 这在这里是无关紧要的,因为你使用的是POD类型,但对于诸如std :: string或你自己的自定义类之类的对象很重要 - 并且你可以重载新的运营商,以满足您的特定需求(这也是无关紧要的:p)。但是不要继续使用malloc作为POD,而不是将它们用于类,因为混合它们被认为是不好的风格。

但是好的,你在from_dec中获得了一些堆内存......但它又在哪里被释放?基本规则:你必须在某些时候将malloc(或calloc等)传递给free的内存。同样的规则适用于new-operator,只是release-operator被称为delete。请注意,对于数组,您需要new []和delete []。不要使用delete []或其他方式分配new和release,因为内存将无法正确释放。

当你的玩具程序无法释放内存时,不会发生任何恶意...我猜你的PC有足够的内存来处理它,当你关闭你的程序时,操作系统仍会释放内存..但不是所有程序都是(a)微小的,(b)经常关闭。

另外我会避免使用conio.h,因为这不是便携式的。你没有使用最复杂的IO,所以标准的标题(iostream等)应该这样做。

同样,我认为大多数使用现代语言的程序员都遵循“如果其他解决方案真的瘫痪或者需要更多工作而只使用goto”的规则。这是一种可以通过使用循环轻松解决的情况,如emceefly所示。在你的程序中,goto很容易处理,但是你不会永远地编写这么小的程序,不是吗? ;) 例如,我最近提交了一些遗留代码.. 2000行的goto-littered代码,是的!试图遵循代码的逻辑流程几乎是不可能的(“哦,跳过200行,很棒......无论如何都需要上下文”),更难以重写该死的东西。

好吧,你的goto在这里没有受伤,但有什么好处呢? 2-3线更短?总体上并不重要(如果您通过代码行支付,这也可能是一个主要缺点;))。我个人认为循环版本更具可读性和清洁性。

如您所见,这里的大多数要点都可以轻松忽略,因为它是一个玩具程序。但是当你想到更大的程序时,它们会更有意义(希望如此);)