Po co deklarować zmienną w jednym wierszu, a przypisywać do niej w następnym?

101

Często widzę w kodach C i C ++ następującą konwencję:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

zamiast

some_type val = something;
some_type *ptr = &something_else;

Początkowo założyłem, że był to nawyk, który pozostał z czasów, kiedy trzeba było zadeklarować wszystkie zmienne lokalne na początku zakresu. Ale nauczyłem się nie odrzucać tak szybko nawyków doświadczonych programistów. Czy jest więc dobry powód, aby zadeklarować w jednym wierszu, a potem przypisać?

Jonathan Sterling
źródło
12
+1 za „Nauczyłem się nie odrzucać tak szybko nawyków doświadczonych programistów”. To mądra lekcja do nauczenia się.
Wildcard

Odpowiedzi:

92

do

W C89 wszystkie deklaracje musiały znajdować się na początku zakresu ( { ... }), ale wymaganie to zostało szybko usunięte (najpierw z rozszerzeniami kompilatora, a później ze standardem).

C ++

Te przykłady nie są takie same. some_type val = something;wywołuje konstruktor kopiowania, podczas gdy val = something;wywołuje konstruktora domyślnego, a następnie operator=funkcję. Ta różnica jest często krytyczna.

Zwyczaje

Niektóre osoby wolą najpierw zadeklarować zmienne, a później je zdefiniować, w przypadku, gdy formatują kod później z deklaracjami w jednym miejscu, a definicją w innym.

Jeśli chodzi o wskaźniki, niektórzy ludzie mają zwyczaj inicjować każdy wskaźnik do NULLlub nullptrbez względu na to, co z nim robią.

orlp
źródło
1
Wielkie wyróżnienie dla C ++, dzięki. Co powiesz na zwykły C?
Jonathan Sterling
13
Fakt, że MSVC nadal nie obsługuje deklaracji, z wyjątkiem początku bloku, gdy kompiluje się w trybie C, jest dla mnie źródłem niekończących się podrażnień.
Michael Burr,
5
@Michael Burr: To dlatego, że MSVC w ogóle nie obsługuje C99.
lub
3
„some_type val = coś; wywołuje konstruktor kopiowania”: może wywoływać konstruktor kopiowania, ale Standard pozwala kompilatorowi na pominięcie domyślnej konstrukcji tempory, kopiowanie konstrukcji val oraz zniszczenie tymczasowej i bezpośrednie konstruowanie val za pomocą some_typekonstruktor somethingjako jedyny argument. Jest to bardzo interesujący i niezwykły przypadek w C ++ ... oznacza, że ​​istnieje domniemanie dotyczące semantycznego znaczenia tych operacji.
2
@Aerovistae: dla typów wbudowanych są one takie same, ale nie zawsze można powiedzieć to samo dla typów zdefiniowanych przez użytkownika.
orlp
27

Oznaczyłeś jednocześnie swoje pytanie C i C ++, podczas gdy odpowiedź jest znacząco różna w tych językach.

Po pierwsze, brzmienie tytułu pytania jest niepoprawne (a ściślej nie ma znaczenia dla samego pytania). W obu przykładach zmienna jest zadeklarowana i zdefiniowana jednocześnie, w jednym wierszu. Różnica między twoimi przykładami polega na tym, że w pierwszym zmienne są albo niezainicjowane, albo zainicjowane wartością zastępczą, a następnie przypisuje się im znaczącą wartość później. W drugim przykładzie zmienne są inicjowane od razu.

Po drugie, w języku C ++, jak zauważył @nightcracker w swojej odpowiedzi, te dwie konstrukcje są semantycznie różne. Pierwszy polega na inicjalizacji, a drugi na przypisaniu. W C ++ operacje te są przeciążalne i dlatego mogą potencjalnie prowadzić do różnych wyników (chociaż można zauważyć, że wytwarzanie nie równoważnych przeciążeń inicjalizacji i przypisywania nie jest dobrym pomysłem).

W oryginalnym standardowym języku C (C89 / 90) deklarowanie zmiennych w środku bloku jest nielegalne, dlatego na początku bloku można zobaczyć zmienne zadeklarowane jako niezainicjowane (lub zainicjowane wartościami zastępczymi), a następnie przypisane znaczącym wartości później, gdy te znaczące wartości staną się dostępne.

W języku C99 można zadeklarować zmienne w środku bloku (podobnie jak w C ++), co oznacza, że ​​pierwsze podejście jest potrzebne tylko w niektórych szczególnych sytuacjach, gdy inicjator nie jest znany w punkcie deklaracji. (Dotyczy to również C ++).

Mrówka
źródło
2
@Jonathan Sterling: Przeczytałem twoje przykłady. Prawdopodobnie musisz odświeżyć standardową terminologię języków C i C ++. W szczególności na warunkach deklaracji i definicji , które mają określone znaczenie w tych językach. Powtórzę to jeszcze raz: w obu twoich przykładach zmienne są zadeklarowane i zdefiniowane w jednym wierszu. W C / C ++ linia some_type val;natychmiast deklaruje i definiuje zmienną val. To właśnie miałem na myśli w mojej odpowiedzi.
1
Rozumiem, co masz na myśli. Zdecydowanie masz rację, deklarując i definiując sposób, w jaki ich używałem, raczej bez znaczenia. Mam nadzieję, że przyjmiecie moje przeprosiny za złe sformułowania i źle przemyślany komentarz.
Jonathan Sterling
1
Tak więc, jeśli konsensus jest taki, że „zadeklarować” jest niewłaściwym słowem, sugerowałbym, aby ktoś z lepszą znajomością standardu niż ja edytował stronę Wikibooks.
Jonathan Sterling
2
W każdym innym kontekście deklaracja byłaby właściwym słowem, ale ponieważ deklaracja jest dobrze zdefiniowaną koncepcją , z konsekwencjami, w C i C ++ nie można używać jej tak luźno, jak w innych kontekstach.
lub
2
@ybungalobill: Mylisz się. Deklaracja i definicja w C / C ++ nie wykluczają się wzajemnie. W rzeczywistości definicja jest tylko specyficzną formą deklaracji . Każda definicja jest deklaracją w tym samym czasie (z kilkoma wyjątkami). Istnieją deklaracje definiujące (tj. Definicje) i deklaracje niezdefiniowane. Co więcej, zwykle deklaracja therm jest używana przez cały czas (nawet jeśli jest to definicja), z wyjątkiem kontekstów, w których rozróżnienie między tymi dwoma jest kluczowe.
13

Myślę, że to stary nawyk, pozostałość po czasach „deklaracji lokalnej”. A zatem jako odpowiedź na twoje pytanie: Nie, nie sądzę, żeby był dobry powód. Nigdy tego nie robię.


źródło
4

Powiedziałem coś na ten temat w mojej odpowiedzi na pytanie Helium3 .

Zasadniczo mówię, że to wizualna pomoc, aby łatwo zobaczyć, co się zmieniło.

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

i

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}
pmg
źródło
4

Inne odpowiedzi są całkiem dobre. W C. jest trochę historii. W C ++ istnieje różnica między konstruktorem a operatorem przypisania.

Dziwię się, że nikt nie wspomina o dodatkowej kwestii: oddzielenie deklaracji od użycia zmiennej może być czasem znacznie bardziej czytelne.

Mówiąc wizualnie, podczas czytania kodu, bardziej przyziemne artefakty, takie jak typy i nazwy zmiennych, nie są tym, co na ciebie wyskakuje. Są to stwierdzenia , które zazwyczaj najbardziej Cię interesują, spędzasz najwięcej czasu, wpatrując się w nie, a więc masz tendencję do zerkania na resztę.

Jeśli mam kilka typów, nazw i przydziałów, wszystko dzieje się w tej samej ciasnej przestrzeni, to trochę przeciążam informacje. Co więcej, oznacza to, że dzieje się coś ważnego w przestrzeni, na którą zwykle spoglądam.

Może to wydawać się nieco sprzeczne z intuicją, ale jest to jeden przypadek, w którym zwiększenie źródła zajmuje więcej miejsca w pionie. Uważam, że jest to podobne do tego, dlaczego nie powinieneś pisać wypełnionych dżemem linii, które wykonują szalone ilości arytmetyki wskaźnika i zadania w ciasnej pionowej przestrzeni - tylko dlatego, że język pozwala ci uciec od takich rzeczy, nie oznacza, że ​​powinieneś to cały czas. :-)

asveikau
źródło
2

W C była to standardowa praktyka, ponieważ zmienne musiały zostać zadeklarowane na początku funkcji, w przeciwieństwie do C ++, gdzie można je zadeklarować w dowolnym miejscu w ciele funkcji do późniejszego użycia. Wskaźniki zostały ustawione na 0 lub NULL, ponieważ po prostu upewniło się, że wskaźnik nie wskazuje śmieci. W przeciwnym razie nie mogę wymyślić żadnej znaczącej korzyści, która zmusiłaby każdego do takiego działania.

Vite Falcon
źródło
2

Zalety lokalizacji definicji zmiennych i ich znaczącej inicjalizacji:

  • jeśli zmiennym zwykle przypisuje się znaczącą wartość, gdy pojawiają się po raz pierwszy w kodzie (inna perspektywa na to samo: opóźniasz ich pojawienie się, dopóki znacząca wartość nie będzie dostępna), wówczas nie ma szans na przypadkowe użycie ich z wartością bez znaczenia lub niezainicjowaną ( co może się łatwo zdarzyć, jeśli inicjalizacja zostanie przypadkowo pominięta z powodu instrukcji warunkowych, oceny zwarcia, wyjątków itp.)

  • może być bardziej wydajny

    • pozwala uniknąć kosztów ustawiania wartości początkowej (domyślna konstrukcja lub inicjalizacja do pewnej wartości wartownika, np. NULL)
    • operator= czasami może być mniej wydajny i wymagać tymczasowego obiektu
    • czasami (szczególnie w przypadku funkcji wbudowanych) optymalizator może usunąć niektóre / wszystkie nieefektywności

  • minimalizacja zakresu zmiennych z kolei minimalizuje średnią liczbę zmiennych w zakresie : to

    • Dzięki temu łatwiej psychicznie śledzić zmienne w zakresie przepływy wykonanie i wypowiedzi, które mogłyby wpłynąć na te zmienne, a import ich wartości
    • przynajmniej w przypadku niektórych złożonych i nieprzezroczystych obiektów zmniejsza to zużycie zasobów (sterty, wątków, pamięci współużytkowanej, deskryptorów) programu
  • czasami bardziej zwięzłe, ponieważ nie powtarzasz nazwy zmiennej w definicji, niż w początkowym znaczącym przypisaniu

  • niezbędne w przypadku niektórych typów, takich jak odwołania i gdy chcesz, aby obiekt był const

Argumenty do grupowania definicji zmiennych:

  • czasami wygodne i / lub zwięzłe jest rozróżnienie rodzaju wielu zmiennych:

    the_same_type v1, v2, v3;

    (jeśli powodem jest to, że nazwa typu jest zbyt długa lub złożona, typedefczasem może być lepsza)

  • czasami pożądane jest grupowanie zmiennych niezależnie od ich użycia, aby podkreślić zestaw zmiennych (i typów) zaangażowanych w niektóre operacje:

    type v1;
    type v2; type v3;

    Podkreśla to powszechność typów i ułatwia ich zmianę, przy jednoczesnym trzymaniu się zmiennej w wierszu, która ułatwia kopiowanie-wklejanie, //komentowanie itp.

Jak to często bywa w programowaniu, podczas gdy jedna praktyka może przynieść wyraźną korzyść empiryczną w większości sytuacji, druga praktyka naprawdę może być zdecydowanie lepsza w kilku przypadkach.

Tony
źródło
Chciałbym, aby więcej języków rozróżniało przypadek, w którym kod deklaruje i ustawia wartość zmiennej, która nigdy nie zostałaby zapisana w innym miejscu, chociaż nowe zmienne mogłyby używać tej samej nazwy [tzn. Gdzie zachowanie byłoby takie samo, bez względu na to, czy późniejsze instrukcje użyłyby tej samej zmiennej lub inny], od tych, w których kod tworzy zmienną, która musi być zapisywalna w wielu miejscach. Podczas gdy oba przypadki użycia będą działać w ten sam sposób, wiedza o tym, kiedy zmienne mogą się zmienić, jest bardzo pomocna podczas próby wyśledzenia błędów.
supercat