Implementoval jsem funkci atoi()
! Tady je můj kód:
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; }
Zajímalo by mě, jestli existuje nějaký způsob, jak mohu zlepšit svou funkci. Vím, že je problém s mou funkcí. Co když chce uživatel převést z char*
na int
tento řetězec: „232-19“. Co mám potom dělat? Jakákoli rada by byla opravdu užitečná!
Komentáře
- jak je problém “ řetězec na int: 232-19 “ spojené s kódem po ruce?
- Co když chci převést z řetězce na int číslo -255 a náhodou napíšu “ 8-255 „. Potom podle mého algoritmu bude vráceno číslo 8255. Znám to ‚ Je dost hloupé se o tyto věci starat, ale co když je uživatel extrémně hloupý? Dále vím, že je pro něho opravdu těžké psát 8-255 místo -255, ale nikdy nevíte, může se to stát!
- vyvolat chybu. vstupní formát je vadný. neměli byste ‚ neuhádnout, co uživatel chtěl, ale přimět ho, aby nezaměnitelně objasnil svůj záměr;)
- Potřebujete pouze jeden průchod řetězce (ne dva) .
- Po kontrole prosím neupravujte svůj kód, aby mohly být jakékoli recenze irelevantní.
Odpovědět
Věci, které byste mohli vylepšit
Proměnné / inicializace
-
Kde deklarujete
multiplier
? Předpokládám, že protože není deklarován v rámci metody, je deklarován jako globální proměnná. Snažte se vyhnout globálním proměnným.Problém s globálními proměnnými spočívá v tom, že protože k nim má přístup každá funkce, je stále obtížnější zjistit, které funkce tyto proměnné skutečně čtou a zapisují.
Abyste pochopili, jak aplikace funguje, musíte do značné míry zohlednit každou funkci, která mění globální stav. Toho lze dosáhnout, ale jak aplikace roste, bude to těžší až do bodu, kdy bude prakticky nemožné (nebo alespoň úplná ztráta času).
Pokud se nespoléháte na globální proměnné, můžete může podle potřeby předávat stav mezi různými funkcemi. Tímto způsobem máte mnohem větší šanci pochopit, co jednotlivé funkce dělají, protože nemusíte brát v úvahu globální stav.
Takže místo použití globální proměnné, inicializujte proměnné v
main()
a v případě potřeby je předejte jako argumenty funkcím. V takovém případě nevidím potřebu, abymultiplier
bylo vůbec použito mimo tuto funkci, takže to prostě nechávám deklarované ve funkci. -
sign
by měl býtint
, nikolichar
.
Algoritmus
-
Právě teď implementujete komplikovanou a těžko sledovatelnou metodu převodu znaku na číslo. Snadný způsob je nechat
isdigit()
vykonat tvrdou práci za vás. To vám také pomůže implementovat SUCHÝ princip .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; }
Podívejte se, jak máte dvě smyčky, které dělají téměř identické věci? Takto jsem to všechno zjednodušil pomocí
isdigit()
.while (isdigit(*c)) { value *= 10; value += (int) (*c - "0"); c++; }
Procházíte znaky v řetězci, pokud jsou číslice. U každého přidejte do počítadla zachováte – přidaná hodnota je celočíselná hodnota znaku. To se provádí odečtením hodnoty ASCII
"0"
od hodnoty ascii příslušné číslice. -
Upozorňujeme, že tento kód není „t zpracovat přetečení. Pokud předáte“ 89384798719061231 „(který se nevejde do
int
), výsledek není definován. Oprava je dostatečně jednoduchá, ke zmírnění stačí použítlong long int
. Stále budeme mít problémy s extrémně dlouhými čísly, ale napravit to tak, aby funkce fungovala tak, jak bylo zamýšleno, je trochu komplikovanější.
Dokumentace
-
Kam se dostaly všechny vaše komentáře? Novější vývojář se jednoduše podíval na část vašeho kódu.
result = result + ( (*pointer%48) * multiplier);
Komentáře mohou opravdu pomoct jiným lidem porozumět vašemu kódu. Nepřemýšlejte s nimi přes palubu, ale budete muset vyvážit, kolik z do vašeho programu.
Syntaxe / Styling
-
Vypadá to jako překlep.
if(*pointer == "-") sign =- 1;
Přidejte prostor pro přehlednost.
if(*pointer == "-") sign = -1;
-
Měli byste ne upravte své
char*
, které přijmete jako parametr funkce. Proto deklarujte parametr jako konstantní.int my_atoi(const char* pointer)
-
Použijte více operátorů zkratky.
pointer++; // same as pointer = pointer+1; multiplier /= 10; // same as multiplier = multiplier / 10; multiplier *= 10; // same as multiplier = multiplier * 10;
Konečný kód
#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 }
Komentáře
- Neměli byste ‚ libovolně měnit typy návratů.
atoi()
tradičně vracíint
, takžemy_atoi()
by také měl. Pokud chcete analyzovatlong long
, napodobtestrtoll()
. -
isdigit(*c)
není definován pro*c
hodnoty menší než 0 (jiné než EOF). Lepší nežwhile (isdigit((unsigned char) (*c) ))
- zmeškaný roh: Když
my_atoi()
výsledek by měl býtLLONG_MIN
,value += (int) (*c-'0');
je podepsané celočíselné přetečení (UB) při pokusu o vytvořeníLLONG_MAX + 1
. - Použití
isdigit
je vůbec špatně, protože ‚ nemá související funkcinumeric_value
. Pokud má tedy vaše znaková sada dva rozsahy číslic (0 až 9 a ٠ až ٩), budou indická čísla analyzována nesprávně. Stačí se držet'0' <= c && c <= '9'
, abyste byli v bezpečí. Tím se také zabrání tomu, aby nedefinované chování nesprávně používalo funkci ctype. - Při psaní “ hodnoty ASCII ‚ 0 ‚ “ : tam ‚ nic, co říká, že znaková sada hostitele musí být ASCII (pouze 0..9 na sebe navazují). Proto ‚ proto píšete
'0'
místo čísla kódového bodu specifického pro kódování.
Odpověď
[Upravit]
S výjimkou chování při chybě je atoi()
ekvivalentní do (int)strtol(nptr, (char **)NULL, 10)
. strtol()
přijímá úvodní prázdné znaky. OP „s my_atoi(char* pointer)
není. Náprava:
int my_atoi(const char* pointer) { while (isspace((unsigned char) *pointer)) { pointer++; } ...
Níže je popsán dobrý způsob řešení INT_MIN
.
OTOH, předávání hodnot mimo [INT_MIN...INT_MAX]
není definováno specifikací C, takže lze provést některá zjednodušení měl. Viz dále.
Když řetězec představuje INT_MIN
, (předpokládejme 32bitový int
), například "-2147483648"
, kód běží do int
přetečení při pokusu o výpočet 2147483648
. Jednoduchý způsob, jak to vyřešit, je spíše než najít kladnou hodnotu a poté ji vyvrátit. Přijměte negativní stránku věcí. Provedením lvího podílu matematiky v rozsahu INT_MIN
až 0
se vyhneme UB. Nevýhoda: některým se tento přístup zdá náročnější.
Přechod na širší celé číslo nebo unsigned
to není vždy možné, protože celočíselná velikost textu -> integer „rutina může být maximální velikost. Přísně vzato unsigned
nemusí vždy mít širší kladný rozsah než int
. V každém případě lze veškerou matematiku zvládnout v požadované celé velikosti se znaménkem, aniž bychom se uchýlili k jiným typům.
#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; }
Poznámky:
pointer%48
je matoucí. Co je zvláštního na 48? Pokud máte na mysli "0"
, použijte pointer % "0"
.
„string:“ 232-19 „. Co mám udělat udělat? “ Doporučte zastavit převod na „232“ a vrátit hodnotu 232. Mohlo nastavit errno
, ale typické atoi()
funkce nedělá příliš mnoho zpracování chyb.
Při přetečení se může stát nastavení errno
, ale opět typické atoi()
nedělá příliš mnoho zpracování chyb. Navrhněte jednoduché vrácení INT_MAX
nebo INT_MIN
.
Pokud chcete lepší zpracování chyb, přejděte na něco jako následující a nastavit chybový stav.
int my_atoi(const char *s, int *ErrorCode);
nebo umístění kde věci skončily. Pokud je to dobré, skončily na "\0"
.
int my_atoi(const char *s, const char **endptr);
[Upravit] Zjednodušeno: Odstraněno detekce mimo dosah, jak to C spec umožňuje. „Pokud hodnotu výsledku nelze vyjádřit, chování není definováno.
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; }
Komentáře
-
INT_MIN/10
aINT_MIN%10
vyžadují chování C99.
Odpověď
char sign = *pointer; if (*pointer == "-" || *pointer == "+") { pointer++; }
Proč zrušit odkazování na „ukazatel“ třikrát? Stačí jednou:
char sign = *pointer; if (sign == "-" || sign == "+") { pointer++; }
Komentáře
- Vítejte v Code Review, vaše první odpověď vypadá dobře , Užijte si pobyt! I když jsem zvědavý, jestli to má vliv na generovaný kód.
Odpověď
pokud jste v pořádku s rekurzí pak lze kód zkrátit na jeden níže
#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;
Vyčerpání zásobníku lze zmírnit -foptimize-sibling-calls
příznakem kompilátoru, což je podporováno překladači GCC i Clang.
Aktualizace:
Jak je uvedeno implementace Roland Illig nezpracovává chybně zadaný vstup. Pokud je to žádoucí, řiďte se atoi
sémantikou , další kód by měl být v pořádku nezapomeňte nastavit Compile Options
na jednu v komentářích .
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); }
Toto je stále chux kód, kde byly smyčky nahrazeny rekurzí
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")) }
Komentáře
- Testovací případ:
buf = malloc(65536); buf[0] = '\0'; my_atoi(buf)
pravděpodobně selže. - Testovací případ:
bufsize = 1 << 20; buf = malloc(bufsize); memset(buf, '0', bufsize); buf[bufsize - 1] = '\0'; my_atoi(buf)
bude trvat velmi dlouho.
Odpovědět
Pro cvičení v leetcode , napsal následující impl: atoi cpp kód
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; } };
Komentáře
- Vítejte v Code Review! Představili jste alternativní řešení, ale kód ‚ nebyl zkontrolován. Vysvětlete prosím své úvahy (jak vaše řešení funguje a proč je lepší než originál), aby se autor a ostatní čtenáři mohli poučit z vašeho myšlenkového procesu.
- kód používá metodu, kde checkMin, kde ne přímé množení se provádí až do ověření výsledku. být větší než INT_MIN.