Czy mogę użyć if (wskaźnik) zamiast if (wskaźnik! = NULL)?

171

Czy bezpiecznie jest sprawdzić wskaźnik, że nie jest NULL, pisząc po prostu, if(pointer)czy muszę go używać if(pointer != NULL)?

danijar
źródło
13
Prawda jest taka, że ​​jeśli zamierzasz użyć jawnego sprawdzenia, równie skuteczne - i często preferowane - jest testowanie przeciwko 0lub nullptr. ( NULLjest C'ismem i wymaga dołączenia pliku nagłówkowego.)
cHao
5
@danijar Możesz użyć nullptr w nowoczesnym C ++.
SurvivalMachine,
9
@cHao Jaki jest sens w „dążeniu do zgodności z C”?
qdii,
5
@danijar: Tak, nie powinieneś używać NULLw C ++ od tego momentu, ponieważ NULLjest to makro zależne od implementacji, które może powodować niejednoznaczne zachowania.
Alok Zapisz
3
Chociaż nie jest to przypadek „if”, obejrzyj tę demonstrację na żywo ideone, aby dowiedzieć się, dlaczego należy unikać „NULL” i „0” dla wskaźników w C ++: ideone.com/tbvXNs
kfsone

Odpowiedzi:

197

Możesz; wskaźnik zerowy jest niejawnie konwertowany na wartość logiczną fałsz, podczas gdy wskaźniki inne niż null są konwertowane na wartość true. Ze standardu C ++ 11, sekcja dotycząca konwersji logicznych:

Wartość pr wartości arytmetycznej, wyliczenia bez zakresu, wskaźnika lub wskaźnika do typu elementu członkowskiego można przekonwertować na wartość prvalue typu bool. Wartość zerowa, pusta wartość wskaźnika lub pusta wartość wskaźnika elementu członkowskiego jest konwertowana na false; każda inna wartość jest konwertowana na true . Prvalue type std::nullptr_t może być konwertowane na prvalue type bool ; wynikowa wartość to false .

Joni
źródło
42

Tak, mogłeś.

  • Wskaźnik null jest niejawnie konwertowany na false
  • wskaźnik niezerowy jest konwertowany na wartość true.

Jest to część standardowej konwersji C ++, która mieści się w klauzuli konwersji Boolean :

§ 4.12 Konwersje boolowskie

Wartość pr arytmetycznego, wyliczenia bez zakresu, wskaźnika lub wskaźnika do typu elementu członkowskiego można przekonwertować na prvalue typu bool. Wartość zerowa, pusta wartość wskaźnika lub pusta wartość wskaźnika elementu członkowskiego jest konwertowana na fałsz; każda inna wartość jest konwertowana na prawdę. Wartość pr typu std :: nullptr_t można przekonwertować na wartość pr typu bool; wynikowa wartość to fałsz.

billz
źródło
29

Tak, możesz. W rzeczywistości wolę używać, if(pointer)ponieważ łatwiej jest czytać i pisać, gdy się do tego przyzwyczaisz.

Zwróć również uwagę, że wprowadzono C ++ 11, nullptrktóry jest preferowany w stosunku do NULL.

Yu Hao
źródło
10
Wskaźnik nie jest wyrażeniem logicznym. Jest konwertowany niejawnie. Jeśli lepiej jest przeczytać, kiedy musisz pamiętać o tej konwersji, aby zrozumieć, to Twoja opinia. To tylko jeden rodzaj stylu kodowania.
harfiarz
7
@harper Można powiedzieć, że to styl kodowania. Ale możesz zastosować tę samą logikę do if(som_integer)vs, if(some_integer != 0)ponieważ liczby całkowite również nie są wartościami logicznymi, prawda? Wolę unikać 0lub NULLw oświadczeniu „jeśli”.
Yu Hao,
13
Zgodziłbym się, że to po prostu kwestia stylu kodowania. Zacząłem preferować if (pointer)siebie, ale if (ptr != nullptr)wydaje mi się to całkowicie uzasadnione. Z drugiej strony, gdybym zobaczył kogoś w moim zespole, który napisał if (some_integer), kazałbym mu to zmienić if (some_integer != 0). Nie będę jednak udawać, że to nie jest względnie arbitralna preferencja z mojej strony - po prostu wolę nie traktować tak samo wskaźników i liczb całkowitych.
Joel,
1
@YuHao A ponieważ jest to styl kodu, nie powiedziałbym „to preferowane”, ale „wolę”.
harfiarz
5
@ franji1 A co powiesz na to if(isReady) if(filePtr) if(serviceNo)? Celowe tworzenie złych nazw zmiennych nie ma w tym przypadku większego znaczenia. W każdym razie zrozumiałem już twój punkt widzenia i zrozumiałem, ale mogę nalegać na używanie mojego własnego stylu kodowania, OK?
Yu Hao,
13

Odpowiedź na pytanie, ale chciałbym dodać swoje punkty.

Zawsze będę wolał if(pointer)zamiast if(pointer != NULL)i if(!pointer)zamiast if(pointer == NULL):

  • To jest proste, małe
  • Mniejsze szanse na napisanie błędnego kodu, przypuśćmy, że jeśli błędnie wpisałem operator sprawdzania równości, ==z którym =
    if(pointer == NULL)można się pomylić, if(pointer = NULL)więc będę tego unikać, najlepiej po prostu if(pointer).
    ( W jednej odpowiedzi zasugerowałem również stan Yody , ale to inna sprawa)

  • Podobnie za while (node != NULL && node->data == key)to po prostu napiszę, while (node && node->data == key)że jest to dla mnie bardziej oczywiste (pokazuje to za pomocą zwarcia).

  • (może to być głupi powód) Ponieważ NULL jest makrem, załóżmy, że ktoś przez pomyłkę przedefiniuje inną wartość.
Grijesh Chauhan
źródło
6
Użycie = zamiast == prawie zawsze generuje ostrzeżenie kompilatora, w dniach, kiedy tak nie było, ludzie
użyliby
@paulm, że właśnie dodałem ten punkt, nazywa się stan Yoda, niektórzy ludzie nie lubią tego, ponieważ jest mniej czytelny.
Grijesh Chauhan
2
(boolean expression)? true : falsejest całkowicie bezcelowe. Wyrażenie zwraca albo do truealbo do false; to, co mówisz, to „jeśli to prawda, daj mi prawdę, jeśli to fałsz, daj mi fałsz”. Krótko mówiąc: jest to całkowicie równoważne z samym wyrażeniem logicznym. Zauważ, że node == NULLjest to wyrażenie boolowskie. Przy okazji, twoje dwie implementacje zwracają dokładnie przeciwne do siebie. Albo chcesz !=w pierwszym, albo tylko !w drugim.
celtschk,
BTW, jedną z możliwych zabezpieczeń przed =zamiast nich ==jest tworzenie zmiennych, constgdy tylko jest to możliwe. Na przykład, możesz zdefiniować swoją funkcję jako isEmnpy(node* const head) { ... }, a wtedy kompilator odmówi jej skompilowania, jeśli przypadkowo napiszesz node = NULLzamiast node == NULL. Oczywiście działa to tylko w przypadku zmiennych, których naprawdę nie trzeba zmieniać.
celtschk
2
Ponieważ klasy inteligentnego wskaźnika mają T* get() constzamiast tego operator T*() constunikać niejawnych konwersji. Mają jednak plik operator bool() const.
StellarVortex
13

Jawne sprawdzenie wartości NULL może dać kompilatorowi wskazówkę dotyczącą tego, co próbujesz zrobić, ergo prowadząc do mniejszej podatności na błędy.

wprowadź opis obrazu tutaj

Minqi Pan
źródło
8

Tak, możesz. Możliwość niejawnego porównywania wartości z zerami została odziedziczona z języka C i jest dostępna we wszystkich wersjach C ++. Możesz również użyć if (!pointer)do sprawdzenia wskaźników dla NULL.

dasblinkenlight
źródło
2

Odpowiednie przypadki użycia dla wskaźników o wartości null to

  • Przekierowanie do czegoś w rodzaju głębszego węzła drzewa, który może nie istnieć lub nie został jeszcze połączony. To jest coś, co należy zawsze ściśle zamknąć w dedykowanej klasie, więc czytelność lub zwięzłość nie stanowi tutaj większego problemu.
  • Dynamiczne rzuty. Rzutowanie wskaźnika klasy bazowej na konkretną klasę pochodną (coś, czego należy ponownie spróbować uniknąć, ale czasami może to okazać się konieczne) zawsze kończy się sukcesem, ale powoduje wyświetlenie wskaźnika o wartości null, jeśli klasa pochodna nie pasuje. Jednym ze sposobów sprawdzenia tego jest

    Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
    if(derived_ptr != nullptr) { ... }

    (lub najlepiej auto derived_ptr = ...). To jest złe, ponieważ pozostawia (prawdopodobnie nieprawidłowy, tj. Zerowy) wskaźnik pochodny poza ifzasięgiem bloku zabezpieczającego . Nie jest to konieczne, ponieważ C ++ pozwala na wprowadzenie zmiennych binarnych konwertowalnych wewnątrz if-condition :

    if(auto derived_ptr = dynamic_cast<Derived*>(base_ptr)) { ... }

    który jest nie tylko krótszy i bezpieczny w zakresie, ale jest też znacznie bardziej przejrzysty w swoim celu: kiedy sprawdzasz wartość null w oddzielnym warunku if, czytelnik zastanawia się, „ok, więc derived_ptrnie może być tutaj zero… cóż, dlaczego to jest zerowe? ” Podczas gdy wersja jednowierszowa mówi bardzo wyraźnie: „jeśli możesz bezpiecznie przesyłać base_ptrdo Derived*, użyj go do ...”.

    To samo działa równie dobrze w przypadku każdej innej operacji, która może spowodować niepowodzenie, która zwraca wskaźnik, chociaż IMO generalnie powinno się tego unikać: lepiej jest użyć czegoś takiego boost::optionaljak „kontener” dla wyników operacji, które mogą zakończyć się niepowodzeniem, zamiast wskaźników.

Tak więc, jeśli główny przypadek użycia wskaźników zerowych powinien być zawsze zapisywany w odmianie stylu implicit-cast, powiedziałbym, że ze względu na spójność dobrze jest zawsze używać tego stylu, tj. Byłbym zwolennikiem if(ptr)ponad if(ptr!=nullptr).


Obawiam się, że muszę zakończyć reklamą: if(auto bla = ...)składnia jest właściwie tylko nieco kłopotliwym przybliżeniem rzeczywistego rozwiązania takich problemów: dopasowywania wzorców . Dlaczego miałbyś najpierw wymusić jakąś akcję (na przykład rzucenie wskaźnika), a następnie rozważyć, że może wystąpić awaria ... To znaczy, to śmieszne, prawda? To tak, jakbyś miał coś do jedzenia i chciał zrobić zupę. Podajesz go asystentowi, który ma wycisnąć sok, jeśli jest to miękkie warzywo. Nie patrzysz na to najpierw. Kiedy masz ziemniaka, nadal dajesz go swojemu asystentowi, ale oni uderzają go z powrotem w twarz z notatką o niepowodzeniu. Ach, konieczne programowanie!

Znacznie lepiej: rozważ od razu wszystkie przypadki, które możesz napotkać. Następnie działaj odpowiednio. Haskell:

makeSoupOf :: Foodstuff -> Liquid
makeSoupOf p@(Potato{..}) = mash (boil p) <> water
makeSoupOf vegetable
 | isSoft vegetable  = squeeze vegetable <> salt
makeSoupOf stuff  = boil (throwIn (water<>salt) stuff)

Haskell ma również specjalne narzędzia, gdy istnieje naprawdę poważna możliwość awarii (a także do całej masy innych rzeczy): monady. Ale to nie jest miejsce na ich wyjaśnianie.

⟨/ogłoszenie⟩

leftaroundabout
źródło
1
W tym niekończącym się stole widzę tylko jedno zdanie, które faktycznie odpowiada na pytanie.
Markiz Lorne,
@EJP: jeśli potraktujesz pytanie dosłownie („ czy mogę użyć”), to w ogóle nie ma na nie wyraźnej odpowiedzi (odpowiedź brzmi po prostu „tak”). Starałem się podać właściwe powody, dla których PO powinien w rzeczywistości if(ptr)raczej używać niż if(ptr != nullptr), na co można powiedzieć dużo więcej.
leftaround około
1

tak oczywiście! w rzeczywistości pisanie if (wskaźnik) jest wygodniejszym sposobem pisania niż if (wskaźnik! = NULL), ponieważ: 1. jest łatwe do debugowania 2. łatwe do zrozumienia 3. jeśli przypadkowo zdefiniowana jest wartość NULL, wtedy również kod nie ulegnie awarii

Palak Jain
źródło
0

Tak. Właściwie powinieneś. Jeśli zastanawiasz się, czy powoduje to błąd segmentacji , tak nie jest.

darshandzend
źródło
29
Dlaczego powinieneś?
qdii,
0

Ponieważ inni odpowiedzieli już dobrze, oba są wymienne.

Niemniej jednak warto wspomnieć, że może zaistnieć przypadek, w którym możesz chcieć użyć wyraźnego oświadczenia, tj pointer != NULL.

Zobacz też https://stackoverflow.com/a/60891279/2463963

z3moon
źródło
-1

Tak, zawsze możesz to zrobić, ponieważ warunek „JEŻELI” ocenia się tylko wtedy, gdy warunek wewnątrz niego spełnia się. C nie ma logicznego typu zwracanego i dlatego zwraca wartość różną od zera, gdy warunek jest prawdziwy, a zwraca 0, gdy warunek w „JEŻELI” okaże się fałszywy. Wartością niezerową zwracaną domyślnie jest 1. Tak więc oba sposoby pisania kodu są poprawne, podczas gdy ja zawsze będę preferował drugi.

user2280507
źródło
1
Wartość niezerowa domyślnie jest niezdefiniowana, jeśli dobrze pamiętam.
Markiz Lorne,
-1

Zasadniczo myślę, że jeśli wyrażenie „if” można ponownie zapisać jako

const bool local_predicate = *if-expression*;
if (local_predicate) ...

taki, że nie powoduje ŻADNYCH OSTRZEŻEŃ, TO powinien być preferowanym stylem dla wyrażenia if . (Wiem, że otrzymuję ostrzeżenia, gdy przypiszę starą C BOOL( #define BOOL int) do C ++ bool, nie mówiąc już o wskaźnikach.)

franji1
źródło
-1

„Czy to jest bezpieczne…?” to pytanie o standard językowy i generowany kod.

„Czy to dobra praktyka?” jest pytaniem o to, jak dobrze zdanie jest rozumiane przez dowolnego człowieka, który je odczytuje. Jeśli zadajesz to pytanie, sugeruje to, że „bezpieczna” wersja jest mniej jasna dla przyszłych czytelników i pisarzy.

Fred Mitchell
źródło
Chciałem zapytać, czy to jest bezpieczne. Dlatego użyłem tego sformułowania. Jednak to, co tu napisałeś, nie jest odpowiedzią na pytanie. Zamiast tego powinien to być komentarz pod pytaniem. Możesz wtedy usunąć odpowiedź i dodać komentarz pod pytaniem.
danijar
@danijar Nie pamiętasz, kiedy byłeś nowy w StackOverflow i szukałeś sekcji „Komentarz” bez powodzenia? Ktoś z reputacją 7 nie może tego zrobić.
Broxzier,
@JimBalter Co jest bardzo mylące, ponieważ widać, że inni to robią. Kiedy byłem nowy w SO, ktoś obwiniał mnie za to.
Broxzier
@JimBalter Nie morduję i nie kradnę. Mówiłem danijarowi, że Fred Mitchell jest nowym użytkownikiem i nie może dodawać komentarzy.
Broxzier
@JimBalter Który zacząłeś dzisiaj. Ty też nie rozumiesz. Ten komentarz tylko popiera zagmatwanie tego.
Broxzier