Подтвердить что ты не робот

Каков наилучший способ вернуть ошибку из функции, когда я уже возвращаю значение?

Я написал функцию в C, которая преобразует строку в целое число и возвращает целое число. Когда я вызываю функцию, я также хочу, чтобы она сообщила мне, если строка не является допустимым числом. Раньше я возвращал -1, когда эта ошибка возникала, потому что мне не нужно было преобразовывать строки в отрицательные числа. Но теперь я хочу, чтобы он преобразовывал строки в отрицательные числа, так что лучший способ сообщить об ошибке?

В случае, если я не понял этого: я не хочу, чтобы эта функция сообщала об ошибке пользователю, я хочу сообщить об ошибке коду, который вызвал эту функцию. ( "Отчет" может быть неправильным словом для использования...)

Здесь код:

s32 intval(const char *string) {
    bool negative = false;
    u32 current_char = 0;

    if (string[0] == '-') {
        negative = true;
        current_char = 1;
    }

    s32 num = 0;
    while (string[current_char]) {
        if (string[current_char] < '0' || string[current_char] > '9') {
            // Return an error here.. but how?
        }

        num *= 10;
        num += string[current_char] - '0';
        current_char++;
    }

    if (negative) {
        num = -num;
    }

    return num;
}
4b9b3361

Ответ 1

Существует несколько способов. У всех есть свои плюсы и минусы.

  • Если функция возвращает код ошибки и передает указатель на местоположение, чтобы вернуть результат. Приятно, что в этом нет перегрузки результата. Плохо то, что вы не можете использовать реальный результат функции непосредственно в выражении.

    Эван Теран предложил вариант этого, который вызывающий передал указатель на переменную успеха (которая может быть необязательно NULL, если вызывающий не заботится) и возвращает фактическое значение из функции. Это имеет то преимущество, что функция может использоваться непосредственно в выражениях, когда вызывающий абонент в порядке со значением по умолчанию в результате ошибки или знает, что функция не может выйти из строя.

  • Используйте специальное возвращаемое значение "оповещение", чтобы указать на ошибку, например отрицательное число (если нормальные значения возврата не могут быть отрицательными) или INT_MAX или INT_MIN, если хорошие значения не могут быть такими экстремальными. Иногда для получения более подробной информации об ошибке необходимо обратиться к другой функции (например, GetLastError()) или глобальной переменной (например, errno). Это не очень хорошо работает, когда ваше возвращаемое значение не имеет недопустимых значений и считается многими плохой формой.

    Примерной функцией, использующей этот метод, является getc(), которая возвращает EOF, если конец файла достигнут или встречается ошибка.

  • Функция никогда не возвращает индикацию ошибки напрямую, но требует, чтобы вызывающий абонент запрашивал другую функцию или глобальную. Это похоже на то, как работает режим VB "On Error Goto Next", и он почти повсеместно считается неудачным способом.

  • Еще один способ - присвоить значение по умолчанию. Например, функция atoi(), которая имеет почти те же функции, что и ваша функция intval(), вернет 0, когда не сможет преобразовать какие-либо символы (она отличается от вашей функции тем, что она потребляет символы для преобразования до тех пор, пока она не достигнет конец строки или символ, который не является цифрой).

    Очевидным недостатком здесь является то, что может быть сложно определить, было ли преобразовано фактическое значение или передан ли нежелательный пакет на atoi().

    Я не большой поклонник этого способа обработки ошибок.

Я обновлюсь, так как другие варианты переходят мне в голову...

Ответ 2

Ну, способ, которым .NET обрабатывает это в Int32.TryParse, должен вернуть успех/сбой и передать обработанное значение обратно с помощью параметр pass-by-reference. То же самое можно было бы применить в C:

int intval(const char *string, s32 *parsed)
{
    *parsed = 0; // So that if we return an error, the value is well-defined

    // Normal code, returning error codes if necessary
    // ...

    *parsed = num;
    return SUCCESS; // Or whatever
}

Ответ 3

общий способ - передать указатель на флаг успеха, например:

int my_function(int *ok) {
    /* whatever */
    if(ok) {
        *ok = success;
    }
    return ret_val;
}

назовите его следующим образом:

int ok;
int ret = my_function(&ok);
if(ok) {
    /* use ret safely here */
}

EDIT: пример реализации здесь:

s32 intval(const char *string, int *ok) {
    bool negative = false;
    u32 current_char = 0;

    if (string[0] == '-') {
        negative = true;
        current_char = 1;
    }

    s32 num = 0;
    while (string[current_char]) {
        if (string[current_char] < '0' || string[current_char] > '9') {
                // Return an error here.. but how?
                if(ok) { *ok = 0; }
        }

        num *= 10;
        num += string[current_char] - '0';
        current_char++;
    }

    if (negative) {
        num = -num;
    }
    if(ok) { *ok = 1; }
    return num;
}

int ok;
s32 val = intval("123a", &ok);
if(ok) {
    printf("conversion successful\n");
}

Ответ 4

Также популярна глобальная переменная errno os-стиля. Используйте errno.h.

Если errno не равно нулю, что-то пошло не так.

Здесь ссылка на справочную страницу для errno.

Ответ 5

Посмотрите, как стандартная библиотека справляется с этой проблемой:

long  strtol(const  char  *str,  char **restrict endptr, int base);

Здесь после вызова endptr указывает на первый символ, который не может быть проанализирован. Если endptr == str, то никакие символы не были преобразованы, и это проблема.

Ответ 6

В целом я предпочитаю, как предложил Джон Скит, т.е. возвращая bool (int или uint) об успехе и сохраняя результат в переданном адресе. Но ваша функция очень похожа на strtol, поэтому я считаю, что неплохо использовать тот же (или похожий) API для вашей функции. Если вы дадите ему такое же имя, как my_strtos32, это упростит понимание того, что делает функция без чтения документации.

EDIT: поскольку ваша функция явно 10, my_strtos32_base10 - лучшее имя. Пока ваша функция не является бутылочной горловиной, вы можете пропустить свою реализацию. И просто оберните вокруг strtol:


s32
my_strtos32_base10(const char *nptr, char **endptr)
{
    long ret;
    ret = strtol(nptr, endptr, 10);
    return ret;
}

Если вы позже осознаете это как узкое место, вы все равно можете оптимизировать его для своих нужд.

Ответ 7

Вы можете либо вернуть экземпляр класса, в котором свойство будет интересоваться значением, другое свойство будет каким-то статусом. Или, передайте экземпляр класса результата..

Pseudo code
  MyErrStatEnum = (myUndefined, myOK, myNegativeVal, myWhatever)

ResultClass
  Value:Integer;
  ErrorStatus:MyErrStatEnum

Пример 1:

result := yourMethod(inputString)

if Result.ErrorStatus = myOK then 
   use Result.Value
else
  do something with Result.ErrorStatus

free result

Пример 2

create result
yourMethod(inputString, result)

if Result.ErrorStatus = myOK then 
   use Result.Value
else
  do something with Result.ErrorStatus

free result

Преимущество этого подхода заключается в том, что вы можете в любое время расширить информацию, возвращаясь, добавив дополнительные свойства в класс результатов.

Чтобы расширить это понятие, оно также применяется к вызовам методов с несколькими входными параметрами. Например, вместо CallYourMethod (val1, val2, val3, bool1, bool2, string1) вместо этого имеет класс со свойствами, соответствующими значениям val1, val2, val3, bool1, bool2, string1 и использовать их как один входной параметр. Он очищает вызовы метода и делает код более легко модифицированным в будущем. Я уверен, что вы видели, что вызовы методов с более чем несколькими параметрами гораздо труднее использовать /debug. (7 - это самое большее, что я бы сказал.)