Я беру курс начинающего С++. Я получил задание, в котором мне сообщалось написать программу, которая преобразует произвольное число из любой базы между двоичной и шестнадцатеричной на другую базу между двоичным и шестнадцатеричным. Меня попросили использовать отдельные функции для преобразования в базу и из базы 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
Я не думаю, что вам нужен внутренний цикл:
for (int i=0; i<starting_base; i++) {
В чем его цель?
Скорее, вы должны получить символ в value[ pos ]
и преобразовать его в целое число. Преобразование зависит от базы, поэтому лучше сделать это в отдельной функции.
Вы определяете char hex[ 16 ]
дважды, один раз в каждой функции. Лучше сделать это только в одном месте.
РЕДАКТИРОВАТЬ 1:
Поскольку это "домашнее задание" отмечено, я не могу дать вам полный ответ. Однако, вот пример того, как to_dec() должен работать. (В идеале вы должны были построить это!)
Input:
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;
}
Ответ 2
Я бы держался подальше от заявлений 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;
}
Ответ 3
Вы можете инициализировать массивы строковыми литералами (обратите внимание, что завершающий \0 не включен, потому что размер массива не позволяет этого):
char const hex[16] = "0123456789ABCDEF";
Или просто используйте указатель на строковый литерал для того же эффекта:
char const* hex = "0123456789ABCDEF";
Ответ 4
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;
}
Ответ 5
для начального преобразования из 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. оправдания, если в этом есть некоторые ошибки компиляции... Я не мог проверить это... но логика звучит.
Ответ 6
В вашем описании присваивания указано:
"Меня попросили использовать отдельные функции для преобразования в и из базы 10".
Если это действительно то, что учитель хотел и хотел, что сомнительно, ваш код этого не делает:
int to_dec(char value[], int starting_base)
возвращает int, который является двоичным числом.:-) Что, на мой взгляд, имеет больше смысла.
Учитель даже заметил это?
Ответ 7
C и С++ - разные языки и с разными стилями программирования. Лучше не смешивать их. (Где C и С++ отличаются)
Если вы пытаетесь использовать С++, то:
Используйте std::string вместо char * или char [].
int to_dec(string value, int starting_base);
string from_dec(int value, int ending_base);
Нет никаких mallocs, используйте new/delete. Но на самом деле С++ автоматически управляет памятью. Память освобождается, как только переменная выходит за рамки (если вы не имеете дело с указателями). И указатели - это последнее, с чем вам нужно иметь дело.
Здесь нам не нужны таблицы поиска, просто волшебная строка.
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 в любой другой
См. там, например. У вас есть возможность самостоятельно его закодировать:)
Ответ 8
В вашей функции 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.)
Ответ 9
Получил этот вопрос на собеседовании один раз и мозг и вращал колеса на некоторое время. Идите фигуру. В любом случае, через пару лет я прохожу через математику и физику для программистов, чтобы расчистить позиции, которые более интенсифицированы по математике, чем то, что я делал. 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, которые работают так, как они звучат. Кстати, вы можете сделать некоторые быстрые ярлыки, если вы понимаете, что символы - это ints, тогда вам не нужны огромные инструкции 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))) - и вуаля! Рекурсия.
Ответ 10
Помимо упомянутых вещей, я предлагаю использовать вместо оператора новый оператор. Преимущества нового в том, что он также вызывает конструкторы вызовов, что здесь не имеет значения, поскольку вы используете тип POD, но важно, когда речь заходит о таких объектах, как std::string или ваших собственных пользовательских классах, и что вы можете перегрузить новый оператора в соответствии с вашими конкретными потребностями (что тоже не имеет значения: p). Но не следует использовать malloc для POD и новый для классов, поскольку их смешивание считается плохим.
Но хорошо, у вас есть куча памяти в from_dec... но где он снова освобождается? Основное правило: память, которую вы malloc (или calloc и т.д.), Должна быть передана бесплатно в какой-то момент. Это же правило применяется к новому оператору, так как оператор release называется delete. Обратите внимание, что для массивов вам нужно новое [] и удалить []. НЕ назначайте новые и освобождайте с помощью delete [] или наоборот, так как память не будет выпущена правильно.
Ничего плохого не произойдет, когда ваша игрушечная программа не выпустит память... Я думаю, ваш компьютер получил достаточное количество оперативной памяти, чтобы справиться с ней, и когда вы завершаете свою программу, ОС освобождает память в любом случае.. но не все программы (а) крошечные и (б) часто закрываются.
Также я бы избегал conio.h, так как это не переносимо. Вы не используете самый сложный IO, поэтому должны использоваться стандартные заголовки (iostream и т.д.).
Аналогично, я думаю, что большинство программистов, использующих современные языки, следуют правилу "Используйте только goto, если другие решения действительно искалечены или больше работы". Это ситуация, которую можно легко решить, используя циклы, как показано emceefly. В вашей программе goto прост в обращении, но вы не будете писать такие небольшие программы навсегда, не так ли?;)
Я, например, недавно был представлен некоторым устаревшим кодом.. 2000 строк goto-littered code, yay! Попытка следовать логическому потоку кода была почти невозможна ( "О, прыгай вперед на 200 строк, отлично... кому нужен контекст в любом случае" ), еще труднее было переписать эту проклятую вещь.
Так хорошо, ваш goto здесь не болит, но где польза? 2-3 линии короче? Совсем неважно, в целом (если вам платят строки кода, это также может быть серьезным недостатком;)). Лично я считаю, что версия цикла более читабельна и чиста.
Как вы видите, большинство пунктов здесь можно легко игнорировать для вашей программы, так как это игрушечная программа. Но когда вы думаете о более крупных программах, они имеют больше смысла (надеюсь);)