Eu implementei a função atoi()
! Aqui está meu código:
int my_atoi(char* pointer) { int result = 0; char* pointer1; multiplier = 1; char sign = 1; if(*pointer == "-") sign =- 1; pointer1 = pointer; while(*pointer != "\0") { if(*pointer >= "0" && *pointer <= "9") multiplier = multiplier * 10; pointer = pointer + 1; } pointer = pointer1; while(*pointer != "\0") { if(*pointer >= "0" && *pointer <= "9") { result = result + ( (*pointer%48) * multiplier); multiplier = multiplier / 10; } pointer = pointer+1; } return (result * sign) / 10; }
Eu me pergunto se há alguma maneira de melhorar minha função. Eu sei que há um problema com minha função. E se o usuário quiser converter char*
em int
esta string: “232-19”. O que devo fazer então? Qualquer conselho seria realmente útil!
Comentários
- como está o problema ” string para int: 232-19 ” conectado com o código em mãos?
- Bem, e se eu quiser converter de string para int o número -255 e por acidente eu digitar ” 8-255 “. Então, de acordo com meu algoritmo, o número 8255 será retornado. Eu sei que ‘ é muito estúpido se preocupar com essas coisas, mas e se o usuário for extremamente burro? Além disso, eu sei que é muito difícil para alguém digitar 8-255 em vez de -255, mas nunca se sabe, pode acontecer!
- gerar um erro. o formato de entrada está com defeito. você não deve ‘ adivinhar o que o usuário queria, mas fazê-lo deixar sua intenção inequivocamente clara;)
- Você só precisa de uma passagem da string (não duas) .
- Por favor, não edite seu código depois de ter sido revisado para que possa tornar qualquer revisão irrelevante.
Resposta
Coisas que você pode melhorar
Variáveis / Inicialização
-
Onde você declara
multiplier
? Presumo que, uma vez que não é declarado no método, é declarado como uma variável global. Tente evitar variáveis globais.O problema com variáveis globais é que, uma vez que todas as funções têm acesso a elas, fica cada vez mais difícil descobrir quais funções realmente leem e escrevem essas variáveis.
Para entender como o aplicativo funciona, você deve levar em consideração todas as funções que modificam o estado global. Isso pode ser feito, mas conforme o aplicativo cresce, fica mais difícil a ponto de ser virtualmente impossível (ou pelo menos uma completa perda de tempo).
Se você não depende de variáveis globais, você pode passar o estado entre funções diferentes, conforme necessário. Dessa forma, você tem uma chance muito melhor de entender o que cada função faz, já que não precisa levar em conta o estado global.
Então, em vez de usar variáveis globais, inicialize as variáveis em
main()
e passe-as como argumentos para funções, se necessário. Nesse caso, não vejo necessidade demultiplier
ser usado fora da função, então simplesmente mantenha-o declarado dentro da função. -
sign
deve ser umint
, e não umchar
.
Algoritmo
-
Neste momento, você está implementando um método complicado e difícil de seguir para converter um caractere em um número. A maneira fácil é fazer com que
isdigit()
faça o trabalho árduo para você. Isso também ajudará você a implementar o Princípio DRY .while(*pointer != "\0") { if(*pointer >= "0" && *pointer <= "9") multiplier = multiplier * 10; pointer = pointer + 1; } pointer = pointer1; while(*pointer != "\0") { if(*pointer >= "0" && *pointer <= "9") { result = result + ( (*pointer%48) * multiplier); multiplier = multiplier / 10; } pointer = pointer+1; }
Vê como você tem dois loops fazendo coisas quase idênticas? Veja como simplifiquei tudo isso usando
isdigit()
.while (isdigit(*c)) { value *= 10; value += (int) (*c - "0"); c++; }
Você percorre os caracteres na string, desde que sejam dígitos. Para cada um, adicione ao contador você está mantendo – o valor a adicionar é o valor inteiro do caractere. Isso é feito subtraindo o valor ASCII de
"0"
do valor ascii do dígito em questão. -
Observe que este código não “para lidar com o estouro. Se você transmitir” 89384798719061231 “(que não” caberá em um
int
), o resultado será indefinido. A correção é bastante simples, basta usar umlong long int
para mitigar isso. Ainda teremos problemas para números extremamente longos, mas consertar isso para que a função funcione conforme o esperado é um pouco mais complicado.
Documentação
-
Para onde foram todos os seus comentários? Um desenvolvedor mais recente simplesmente olharia boquiaberto para parte do seu código.
result = result + ( (*pointer%48) * multiplier);
Os comentários podem realmente ajudar outras pessoas a entender seu código. Não exagere, porém, você terá que equilibrar quanto de para colocá-los em seu programa.
Sintaxe / estilo
-
Parece um erro de digitação.
if(*pointer == "-") sign =- 1;
Adicione um espaço para maior clareza.
if(*pointer == "-") sign = -1;
-
Você deve não modifique o
char*
que você aceita como um parâmetro na função. Portanto, declare o parâmetro como constante.int my_atoi(const char* pointer)
-
Use mais operadores abreviados.
pointer++; // same as pointer = pointer+1; multiplier /= 10; // same as multiplier = multiplier / 10; multiplier *= 10; // same as multiplier = multiplier * 10;
Código final
#include <stdio.h> #include <assert.h> #include <ctype.h> long long int my_atoi(const char *c) { long long int value = 0; int sign = 1; if( *c == "+" || *c == "-" ) { if( *c == "-" ) sign = -1; c++; } while (isdigit(*c)) { value *= 10; value += (int) (*c-"0"); c++; } return (value * sign); } int main(void) { assert(5 == my_atoi("5")); assert(-2 == my_atoi("-2")); assert(-1098273980709871235 == my_atoi("-1098273980709871235")); puts("All good."); // I reach this statement on my system }
Comentários
- Você não deve ‘ alterar os tipos de retorno arbitrariamente.
atoi()
tradicionalmente retorna umint
, entãomy_atoi()
também deveria. Se você deseja analisar umlong long
, emulestrtoll()
. -
isdigit(*c)
não é definido para*c
valores menores que 0 (diferente de EOF). Melhor parawhile (isdigit((unsigned char) (*c) ))
- Canto perdido: quando
my_atoi()
o resultado deve serLLONG_MIN
,value += (int) (*c-'0');
é um estouro de número inteiro assinado (UB) ao tentar formarLLONG_MAX + 1
. - Usando
isdigit
está totalmente errado, uma vez que não ‘ tem uma função relacionadanumeric_value
. Portanto, se o seu conjunto de caracteres tem dois intervalos de dígitos (0 a 9 e ٠ a ٩), os números índicos serão analisados de forma errada. Basta seguir'0' <= c && c <= '9'
para estar seguro. Isso também evita o comportamento indefinido de usar a função ctype incorretamente. - Você perdeu um ponto importante quando escreveu ” valor ASCII de ‘ 0 ‘ ” : há ‘ s nada que diga que o conjunto de caracteres do host precisa ser ASCII (apenas que 0..9 são contíguos). É ‘ por isso que você escreve
'0'
em vez de um número de ponto de código específico da codificação.
Resposta
[Editar]
Exceto pelo comportamento em caso de erro, atoi()
é equivalente para (int)strtol(nptr, (char **)NULL, 10)
. strtol()
aceita espaços em branco à esquerda. OP “s my_atoi(char* pointer)
não. Para remediar:
int my_atoi(const char* pointer) { while (isspace((unsigned char) *pointer)) { pointer++; } ...
O que se segue descreve uma boa forma de lidar com INT_MIN
.
OTOH, entregar valores fora de [INT_MIN...INT_MAX]
não é definido pela especificação C, portanto, algumas simplificações podem ser teve. Veja mais abaixo.
Quando uma string representa INT_MIN
, (vamos supor que 32 bits int
) como "-2147483648"
, o código é executado em int
estouro tentando calcular 2147483648
. Uma maneira simples de resolver isso é, em vez de encontrar o valor positivo e negá-lo, abraçar o lado negativo das coisas. Ao fazer a maior parte da matemática na faixa de INT_MIN
a 0
, evitamos o UB. Lado negativo: alguns acham esta abordagem mais desafiadora de seguir.
Indo para um número inteiro mais amplo ou unsigned
nem sempre é possível, pois o tamanho inteiro de “text- -> inteiro “a rotina pode ter o tamanho máximo. Estritamente falando, unsigned
nem sempre tem um intervalo positivo mais amplo do que int
. Em qualquer caso, toda a matemática pode ser tratada no tamanho inteiro assinado desejado sem recorrer a outros tipos.
#include <ctype.h> #include <limits.h> int my_atoi(const char* pointer) { // good idea to make the `const` int result = 0; while (isspace((unsigned char) *pointer)) { pointer++; } char sign = *pointer; if (*pointer == "-" || *pointer == "+") { // text could lead with a "+" pointer++; } int ch; // isdigit() expects an unsigned char or EOF, not char while ((ch = (unsigned char)(*pointer)) != 0) { if (!isdigit(ch)) break; ch -= "0"; // Will overflow occur? if ((result < INT_MIN/10) || (result == INT_MIN/10 && ch > -(INT_MIN%10))) Handle_Overflow(); result *= 10; result -= ch; // - , not + pointer++; } if (sign != "-") { if (result < -INT_MAX) Handle_Overflow(); result = -result; } return result; }
Notas:
pointer%48
é confuso. O que há de especial em 48? Se você quer dizer "0"
, use pointer % "0"
.
“string:” 232-19 “. O que devo fazer então? ” Recomende interromper a conversão em “232” e retornar o valor 232. Pode definir errno
, mas o típico atoi()
função não faz muito tratamento de erros.
Em estouro, configuração errno
, pode acontecer, mas, novamente, típico atoi()
function não faz muito tratamento de erros. Sugerir o retorno simples de INT_MAX
ou INT_MIN
.
Se quiser um melhor tratamento de erros, mude para algo como o seguinte e definir um status de erro.
int my_atoi(const char *s, int *ErrorCode);
ou localização onde as coisas terminaram. Se isso for bom, eles terminaram em "\0"
.
int my_atoi(const char *s, const char **endptr);
[Editar] Simplificado: Removido detecção fora do intervalo, conforme a especificação C permite. “Se o valor do resultado não puder ser representado, o comportamento é indefinido.
int my_atoi(const char* pointer) { int result = 0; while (isspace((unsigned char) *pointer)) { pointer++; } char sign = *pointer; if (*pointer == "-" || *pointer == "+") { pointer++; } while (isdigit((unsigned char)*pointer)) { result = result*10 - (*pointer++ - "0"); } if (sign != "-") { result = -result; } return result; }
Comentários
-
INT_MIN/10
eINT_MIN%10
exigem comportamento C99.
Resposta
char sign = *pointer; if (*pointer == "-" || *pointer == "+") { pointer++; }
Por que remover a referência de “ponteiro” três vezes? Uma vez é o suficiente:
char sign = *pointer; if (sign == "-" || sign == "+") { pointer++; }
Comentários
- Bem-vindo à revisão do código, sua primeira resposta parece boa , Aproveite sua estadia! Embora eu me pergunte se isso faz alguma diferença no código gerado.
Resposta
se você está ok com a recursão então o código pode ser reduzido para um abaixo
#include <string.h> #include <math.h> #include <stdbool.h> int natural_number(const char* string) { int index = strlen(string) - 1; int number = pow(10, index) * (*string - "0"); return (index == 0) ? number : number + natural_number(string + 1); } int my_atoi(const char* string) { int sign = (*string == "-") ? -1 : 1; int offset = (*string == "-") ? 1 : 0; return sign * natural_number(string + offset); } /* test cases */ my_atoi("-100") == -100; my_atoi("0") == 0; my_atoi("100") == 100;
O esgotamento da pilha pode ser mitigado por -foptimize-sibling-calls
sinalizador do compilador, sendo compatível com os compiladores GCC e Clang.
Atualização:
Conforme observado pela implementação de Roland Illig não lida com entrada malformada. Se for desejado seguir de perto a atoi
semântica , o próximo código deve ser ótimo não se esqueça de definir Compile Options
como um nos comentários .
int digit(char symbol) { return symbol - "0"; } /* tail call optimized */ int natural_number_tc(const char* string, int number) { return !isdigit(*string) ? number : natural_number_tc(string + 1, 10 * number + digit(*string)); } int natural_number(const char* string) { return natural_number_tc(string, 0); } const char* left_trim_tc(const char* string, const char* symbol) { return !isspace(*string) ? symbol : left_trim_tc(string + 1, symbol + 1); } const char* left_trim(const char* string) { return left_trim_tc(string, string); } int my_atoi(const char* string) { const char* symbol = left_trim(string); int sign = (*symbol == "-") ? -1 : 1; size_t offset = (*symbol == "-" || *symbol == "+") ? 1 : 0; return sign * natural_number(symbol + offset); }
Este ainda é o código do chux “onde os loops são substituídos por recursão
int result = 0; while (isdigit((unsigned char)*pointer)) { result = 10 * result + (*pointer - "0"); pointer++; } // VS int loop(const char* pointer, int result) { return !isdigit((unsigned char)*pointer) ? result : loop(pointer + 1, 10 * result + (*pointer - "0")) }
Comentários
- Caso de teste:
buf = malloc(65536); buf[0] = '\0'; my_atoi(buf)
provavelmente travará. - Caso de teste:
bufsize = 1 << 20; buf = malloc(bufsize); memset(buf, '0', bufsize); buf[bufsize - 1] = '\0'; my_atoi(buf)
levará muito muito tempo.
Resposta
Para um exercício em leetcode , escreveu o seguinte impl: atoi cpp code
class Solution { private: bool checkMin(int a, int b=10, int c=0, int min_val=INT_MIN) { /* accepts a*b + c, min a>min; b>min; c>min check a*b+c > min or not b>0; a<0 -ive; c<0 a!=0 */ min_val = min_val -c; //std::cout<<"new min input: "<<a <<" , "<< c<<" iter: "<<b << " "<<min_val <<std::endl; //compare with a now if(a<min_val) return false; int cur_prod = 0; if(a==0) return true; for(;b>1;b--) { cur_prod += a; int curr_diff = min_val-cur_prod; /* subtraction possible because min_val<prod, min_val-prod<prod-prod min_val-prod<0 ---1 prod<0 -prod>0 min_val+(-prod )> min_val+0 [x+ (+ive quantity)>x ] min_val-prod>min_val --2 from 1, 2 min_val< min_val-prod < 0 ---3 from 3, min_val-prod can be expressed in integer check if curr_diff still can hold a deduction of a which means: curr_diff<a should hold, for a further a deduction in prod -5, -6 for ex of min_val = 59, a = -6 at b = 2 (9th iteration) prod = -54 you can"t add -6 now, since it will cross definable limit only b-1 iterations because at i-1 th iteration, ith product formation is checked */ //std::cout<<"check function for input: "<<a <<" , "<< c<<" iter: "<<b << " prod now = " //<< cur_prod << " diff = " <<curr_diff<<" is curr_dif<a "<<(curr_diff<a)<<std::endl; if(curr_diff>a) { //std::cout<<" not possible"<<std::endl; return false; } } return true; } bool checkMax(int a, int b=10, int c=0, int max_val=INT_MAX) { /* accepts a*b + c, min a<max; b<max; c<max check a*b+c < max or not b>0; a>0, c>0 */ max_val = max_val -c; //std::cout<<"new max input: "<<a <<" , "<< c<<" iter: "<<b << " "<<max_val <<std::endl; //compare with a now if(a>max_val) return false; int cur_prod = 0; if(a==0) return true; for(;b>1;b--) { cur_prod += a; int curr_diff = max_val-cur_prod; /* subtraction possible because max_val>prod, max_val-prod>prod-prod max_val-prod>0 ---1 prod>0 -prod<0 max_val+(-prod )< max_val+0 [x+ (-ive quantity)<x ] max_val-prod<max_val --2 from 1, 2 0< max_val-prod < max_val ---3 from 3, max_val-prod can be expressed in integer check if curr_diff still can hold a increment of a which means: curr_diff>a should hold, for a further a deduction in prod 5>6 fails for ex of max_val = 59, a = 6 at b = 2 (9th iteration) prod = 54 you can"t add 6 now, since it will cross definable limit only b-1 iterations because at i-1 th iteration, ith product formation is checked */ //std::cout<<"check function for input: "<<a <<" , "<< c<<" iter: "<<b << " prod now = " // << cur_prod << " diff = " <<curr_diff<<" is curr_dif<a "<<(curr_diff>a)<<std::endl; if(curr_diff<a) { //std::cout<<" not possible"<<std::endl; return false; } } return true; } public: int myAtoi(string str) { //code to trim string int i =0, end=str.length()-1; //std::cout<<i<<" "<<end<<std::endl; while(i<end && str.at(i)==" ") {i++;continue;} while(end>-1 && str.at(end)==" ") {end--;continue;} if(end<i) return 0; int sign=1; if(str.at(i)=="-") {sign = -1; i++;} else if(str.at(i)=="+") {i++;} string tr_str = str.substr(i, end-i+1); int num = 0; for(char& digit : tr_str) { if(digit<"0" || digit>"9") return num; // not convertable character - exit int c= digit-"0"; if(sign==-1) { //std::cout<<"Evaluating "<<c<<std::endl; //number cannot be lower than INT_MIN // do a check of num * 10 - c //num<0 already if(checkMin(num, 10, -c, INT_MIN)) num = num*10 -c; else { num = INT_MIN; break; } //std::cout<<"number is"<<num<<std::endl; } else { if(checkMax(num, 10, c, INT_MAX)) num = num*10 +c; else { num = INT_MAX; break; } //std::cout<<"number is"<<num<<std::endl; } } return num; } };
Comentários
- Bem-vindo à revisão do código! Você apresentou uma solução alternativa, mas não ‘ t revisou o código. Explique seu raciocínio (como sua solução funciona e por que é melhor do que o original) para que o autor e outros leitores possam aprender com seu processo de pensamento.
- o código usa um método, onde checkMin, onde não multiplicação direta é executada até que o resultado seja validado. ser maior que INT_MIN.