Jaka jest różnica między const int *, const int * const i int const *?

1355

I zawsze bałagan, jak używać const int*, const int * consti int const *słusznie. Czy istnieje zestaw reguł określających, co możesz, a czego nie możesz zrobić?

Chcę poznać wszystkie nakazy i zakazy dotyczące zadań, przekazywania funkcji itp.

MD XF
źródło
175
Możesz użyć „Reguły zgodnej z ruchem wskazówek zegara / spirali” do odszyfrowania większości deklaracji C i C ++.
James McNellis,
52
cdecl.org to świetna strona internetowa, która automatycznie tłumaczy dla Ciebie deklaracje C.
Dave Gallagher,
6
@Calmarius: zacznij od nazwy typu / powinna być, przejdź w prawo, kiedy możesz, w lewo, kiedy musisz . int *(*)(char const * const). Zacząć z prawej strony w nawiasach *to musimy przejść w lewo: pointer. Poza parens możemy przenieść rację pointer to function of .... Następnie musimy przesunąć w lewo: pointer to function of ... that returns pointer to int. Powtórz, aby rozwinąć ten parametr ( ...) pointer to function of (constant pointer to constant char) that returns pointer to int. Jaka byłaby równoważna deklaracja jednowierszowa w łatwym do czytania języku, takim jak Pascal?
Mark K Cowan,
1
@MarkKCowan W Pascal byłoby to coś w rodzaju function(x:^char):^int. Typy funkcji sugerują wskaźnik do funkcji, więc nie trzeba jej określać, a Pascal nie wymusza poprawności const. Można go odczytać od lewej do prawej.
Calmarius
5
Pierwszą rzeczą po lewej stronie „const” jest to, co stałe. Jeśli „const” jest najdalej na lewo, to pierwszą rzeczą na prawo jest stała.
Cupcake

Odpowiedzi:

2208

Przeczytaj to wstecz ( zgodnie z regułą zegara / zasadą spirali ):

  • int* - wskaźnik na int
  • int const * - wskaźnik na const int
  • int * const - const wskaźnik do int
  • int const * const - const wskaźnik do const int

Teraz pierwszy constmoże być po dowolnej stronie tego typu, więc:

  • const int * == int const *
  • const int * const == int const * const

Jeśli chcesz naprawdę zwariować, możesz robić takie rzeczy:

  • int ** - wskaźnik do wskaźnika do int
  • int ** const - const wskaźnik do wskaźnika do int
  • int * const * - wskaźnik do stałej wskaźnik do wartości int
  • int const ** - wskaźnik do wskaźnika do const int
  • int * const * const - const wskaźnik do const wskaźnik do int
  • ...

Aby upewnić się, że jasno rozumiemy znaczenie const:

int a = 5, b = 10, c = 15;

const int* foo;     // pointer to constant int.
foo = &a;           // assignment to where foo points to.

/* dummy statement*/
*foo = 6;           // the value of a can´t get changed through the pointer.

foo = &b;           // the pointer foo can be changed.



int *const bar = &c;  // constant pointer to int 
                      // note, you actually need to set the pointer 
                      // here because you can't change it later ;)

*bar = 16;            // the value of c can be changed through the pointer.    

/* dummy statement*/
bar = &a;             // not possible because bar is a constant pointer.           

foojest zmiennym wskaźnikiem do stałej liczby całkowitej. Pozwala to zmienić to, na co wskazujesz, ale nie wartość, którą wskazujesz. Najczęściej jest to widoczne w przypadku ciągów w stylu C, w których masz wskaźnik do znaku const char. Możesz zmienić wskazany ciąg, ale nie możesz zmienić zawartości tych ciągów. Jest to ważne, gdy sam ciąg znaków znajduje się w segmencie danych programu i nie należy go zmieniać.

barjest stałym lub stałym wskaźnikiem do wartości, którą można zmienić. To jest jak odniesienie bez dodatkowego cukru syntaktycznego. Z tego powodu zwykle używasz referencji, w której używasz T* constwskaźnika, chyba że musisz zezwolić na NULLwskaźniki.

Cena matowa
źródło
481
Chciałbym dołączyć ogólną regułę, która może pomóc zapamiętać, jak odkryć, czy „const” ma zastosowanie do wskaźnika lub do wskazanych danych: podziel instrukcję na znak gwiazdki, a następnie, jeśli słowo kluczowe const pojawi się w lewej części (jak w „const int * foo”) - należy do wskazanych danych, jeśli znajduje się we właściwej części („int * const bar”) - dotyczy wskaźnika.
Michael
14
@Michael: Uznanie dla Michaela za tak prostą zasadę zapamiętywania / rozumienia zasady const.
sivabudh
10
@Jeffrey: przeczytaj wstecz działa dobrze, o ile nie ma nawiasów. Więc ... no cóż ... użyj typedefs
Mooing Duck
12
+1, chociaż lepszym podsumowaniem byłoby: przeczytaj deklaracje wskaźnika do tyłu , co oznacza, że ​​w pobliżu wypowiedzi @ Michaela: zatrzymaj normalne czytanie od lewej do prawej przy pierwszej gwiazdce.
Wolf
3
@gedamial działa, działa dobrze, ale musisz przypisać go w tym samym czasie, gdy go deklarujesz (ponieważ nie możesz ponownie przypisać „wskaźnika stałego”). const int x = 0; const int *const px = &x; const int *const *const p = &px;działa dobrze.
RastaJedi
356

Dla tych, którzy nie wiedzą o Regule zgodnej z ruchem wskazówek zegara / Spirali: Zacznij od nazwy zmiennej, przejdź zgodnie z ruchem wskazówek zegara (w tym przypadku cofnij się) do następnego wskaźnika lub typu . Powtarzaj, aż wyrażenie się skończy.

Oto demo:

wskaźnik do int

const wskaźnik do int const

wskaźnik do int const

wskaźnik do const int

const wskaźnik do int

Shijing Lv
źródło
8
@ Jan link do złożonego przykładu nie ma uprawnień. możesz opublikować go bezpośrednio tutaj lub usunąć ograniczenia oglądania?
R71
8
@Rog miał kiedyś wszystkie otwarte uprawnienia dostępu ... Nie napisałem tego artykułu i niestety nie mam uprawnień dostępu. Oto zarchiwizowana wersja tego artykułu, która nadal działa: archive.is/SsfMX
Jan Rüegg
8
Złożony przykład jest wciąż od prawej do lewej, ale obejmuje rozwiązywanie nawiasów w normalny sposób. Cała spirala zgodna z ruchem wskazówek zegara nie ułatwia tego.
Matthew Read
4
Ostateczny przykład: void (*signal(int, void (*fp)(int)))(int);z archive.is/SsfMX
naXa
3
Nie polegaj na tej zasadzie. To nie jest uniwersalne. W niektórych przypadkach zawodzi.
haccks
150

Myślę, że na wszystko już tu jest odpowiedź, ale chcę tylko dodać, że powinieneś wystrzegać się typedefs! NIE są to tylko zamienniki tekstu.

Na przykład:

typedef char *ASTRING;
const ASTRING astring;

Typ astringjest char * const, nie const char *. Jest to jeden z powodów, dla których zawsze staram constsię na prawo od tego typu i nigdy na początku.

Kaz Dragon
źródło
20
I dla mnie to jest powód, aby nigdy nie pisać na maszynie wskaźników. Nie widzę korzyści w takich rzeczach typedef int* PINT(zakładam, że pochodzi to z praktyk w C i wielu programistów wciąż to robi). Świetnie, zamieniłem to *na P, nie przyspiesza to pisania, a także wprowadza wspomniany problem.
Mephane 28.01.11
1
@Mephane - Widzę to. Jednak wydaje mi się, że w pewnym sensie unikam miłej funkcji językowej, aby nadal używać wyjątkowej reguły składniowej (o rozmieszczeniu „const”), zamiast unikać wyjątkowej reguły składniowej, aby móc bezpiecznie korzystać z tej funkcji językowej .
TED,
6
@Mephane PINTjest rzeczywiście dość głupim użyciem typedef, zwłaszcza że sprawia, że ​​myślę, że system przechowuje piwo jako pamięć. Typedef są jednak bardzo przydatne w przypadku wskaźników do funkcji.
ApproachingDarknessFish
5
@KazDragon THANKS! Bez tego pomieszałbym wszystkie te typefile PVOID, LPTSTRrzeczy w interfejsie Win32!
David Lee
2
@Mephane: Musiałem użyć pSomething kilka razy, gdy korzystałem z niektórych starszych makr, które zostały napisane w celu akceptacji typu, ale rozpadłyby się, gdyby typ nie był pojedynczym identyfikatorem alfanumerycznym. :)
Groo
56

Jak prawie wszyscy podkreślali:

Jaka jest różnica między const X* p, X* const pa const X* const p?

Musisz przeczytać deklaracje wskaźnika od prawej do lewej.

  • const X* p oznacza „p wskazuje na X, który jest stały”: obiektu X nie można zmienić za pomocą p.

  • X* const p oznacza „p jest stałym wskaźnikiem na X, który nie jest stały”: nie możesz zmienić samego wskaźnika p, ale możesz zmienić obiekt X za pomocą p.

  • const X* const p oznacza „p jest stałym wskaźnikiem na X, który jest stały”: nie można zmienić samego wskaźnika p, ani nie można zmienić obiektu X za pomocą p.

Łukasz
źródło
3
Nie zapominaj, że const X* p;== X const * p;jak w"p points to an X that is const": the X object can't be changed via p.
Jesse Chisholm
proste i ładne wyjaśnienie!
Edison Lo
50
  1. Stałe odniesienie:

    Odwołanie do zmiennej (tutaj int), która jest stała. Zmiennie przekazujemy zmienną jako referencję, ponieważ referencje mają mniejszy rozmiar niż rzeczywista wartość, ale występuje efekt uboczny, a to dlatego, że jest jak alias rzeczywistej zmiennej. Możemy przypadkowo zmienić główną zmienną poprzez nasz pełny dostęp do aliasu, więc ustawiamy ją na stałą, aby zapobiec temu efektowi ubocznemu.

    int var0 = 0;
    const int &ptr1 = var0;
    ptr1 = 8; // Error
    var0 = 6; // OK
  2. Stałe wskaźniki

    Gdy stały wskaźnik wskazuje zmienną, nie może wskazywać żadnej innej zmiennej.

    int var1 = 1;
    int var2 = 0;
    
    int *const ptr2 = &var1;
    ptr2 = &var2; // Error
  3. Wskaźnik do stałej

    Wskaźnik, przez który nie można zmienić wartości zmiennej, którą wskazuje, jest znany jako wskaźnik na stały.

    int const * ptr3 = &var2;
    *ptr3 = 4; // Error
  4. Stały wskaźnik do stałej

    Stały wskaźnik do stałej to wskaźnik, który nie może ani zmienić adresu, na który wskazuje, ani nie może zmienić wartości przechowywanej pod tym adresem.

    int var3 = 0;
    int var4 = 0;
    const int * const ptr4 = &var3;
    *ptr4 = 1;     // Error
     ptr4 = &var4; // Error
Behrooz Tabesh
źródło
20

Ogólna zasada jest taka, że constsłowo kluczowe stosuje się do tego, co poprzedza go natychmiast. Wyjątek: początek constma zastosowanie do poniższych.

  • const int*jest taki sam jak int const*i oznacza „wskaźnik do stałej int” .
  • const int* constjest taki sam jak int const* consti oznacza „stały wskaźnik do stałej int” .

Edycja: Dla Dos i zakazów, jeśli ta odpowiedź nie wystarczy, czy możesz być bardziej precyzyjny w kwestii tego, czego chcesz?

AProgrammer
źródło
19

To pytanie pokazuje dokładnie, dlaczego lubię robić rzeczy w sposób, w jaki wspomniałem w moim pytaniu, czy po typ id jest akceptowalny?

Krótko mówiąc, uważam, że najłatwiejszym sposobem zapamiętania tej reguły jest to, że „const” idzie za rzeczą, której dotyczy. Zatem w twoim pytaniu „int const *” oznacza, że ​​int jest stały, podczas gdy „int * const” oznacza, że ​​wskaźnik jest stały.

Jeśli ktoś zdecyduje się umieścić go na samym początku (np .: „const int *”), jako specjalny wyjątek w tym przypadku dotyczy to rzeczy po nim.

Wiele osób lubi używać tego wyjątku, ponieważ uważają, że wygląda to ładniej. Nie podoba mi się to, ponieważ jest to wyjątek i dlatego myli rzeczy.

PRZETRZĄSAĆ
źródło
2
Jestem rozdarty w tej sprawie. Logicznie ma to sens. Jednak większość programistów c ++ napisałaby const T*i stało się to bardziej naturalne. Jak często i tak używasz T* const, zwykle wystarczy odniesienie. Uraziło mnie to raz, gdy chciałem, boost::shared_ptr<const T>a zamiast tego napisałem const boost::shared_ptr<T>. Ten sam problem w nieco innym kontekście.
Matt Price
Właściwie używam stałych wskaźników częściej niż stałych. Musisz także pomyśleć o tym, jak zareagujesz w obecności wskaźników na wskaźniki (itp.) Wprawdzie są one rzadsze, ale fajnie byłoby pomyśleć o rzeczach, w których można poradzić sobie z tymi sytuacjami za pomocą applomb.
TED
1
Kolejną miłą zaletą umieszczenia stałej po prawej stronie tego typu jest to, że teraz wszystko po lewej stronie dowolnego typu constjest tym, co jest stałe, a wszystko po jego prawej stronie jest tym, co jest rzeczywiście stałe. Weź int const * const * p;jako przykład. Nie, zwykle nie piszę tak, to tylko przykład. Po pierwsze const: wpisz int, a int, który jest const, jest zawartością wskaźnika const, czyli zawartością p. Druga const: typ jest wskaźnikiem na constint, const oblect jest zawartościąp
dgnuff
18

Proste użycie const.

Najprostszym zastosowaniem jest zadeklarowanie nazwanej stałej. Aby to zrobić, deklaruje się stałą, jakby była zmienną, ale dodaje się constprzed nią. Należy go natychmiast zainicjować w konstruktorze, ponieważ oczywiście nie można później ustawić wartości, ponieważ to by ją zmieniło. Na przykład:

const int Constant1=96; 

utworzy stałą całkowitą, niewyobrażalnie nazywaną Constant1, o wartości 96.

Takie stałe są przydatne w przypadku parametrów używanych w programie, ale nie trzeba ich zmieniać po skompilowaniu programu. Ma to przewagę dla programistów nad #definekomendą preprocesora C , ponieważ jest zrozumiałe i wykorzystywane przez sam kompilator, a nie tylko podstawiane w tekście programu przez preprocesora przed dotarciem do głównego kompilatora, więc komunikaty o błędach są znacznie bardziej pomocne.

Działa również ze wskaźnikami, ale należy uważać, constaby ustalić, czy wskaźnik lub wskazywany przez niego wskaźnik jest stały, czy oba. Na przykład:

const int * Constant2 

deklaruje, że Constant2jest zmiennym wskaźnikiem do stałej liczby całkowitej i:

int const * Constant2

jest alternatywną składnią, która robi to samo, podczas gdy

int * const Constant3

deklaruje, że Constant3jest stałym wskaźnikiem do zmiennej liczby całkowitej i

int const * const Constant4

deklaruje, że Constant4jest stałym wskaźnikiem do stałej liczby całkowitej. Zasadniczo „const” stosuje się do wszystkiego, co znajduje się po jego bezpośredniej lewej stronie (poza tym, że nic tam nie ma, w takim przypadku stosuje się do tego, co jest jego bezpośrednim prawem).

ref: http://duramecho.com/ComputerInformation/WhyHowCppConst.html

ufukgun
źródło
9

Miałem te same wątpliwości, co ty, dopóki nie natknąłem się na tę książkę autorstwa G ++ Scotta Meyersa z C ++. Odwołaj się do trzeciej pozycji w tej książce, w której mówi on szczegółowo o używaniu const.

Postępuj zgodnie z tą radą

  1. Jeśli słowo constpojawia się po lewej stronie gwiazdki, wskazuje to na stałe
  2. Jeśli słowo constpojawia się po prawej stronie gwiazdki, sam wskaźnik jest stały
  3. Jeśli constpojawia się po obu stronach, obie są stałe
rgk
źródło
7

To proste, ale trudne. Należy pamiętać, że możemy zamienić constkwalifikator z dowolnego typu danych ( int, char, float, itd.).

Zobaczmy poniższe przykłady.


const int *p==> *pjest tylko do odczytu [ pjest wskaźnikiem do stałej liczby całkowitej]

int const *p==> *pjest tylko do odczytu [ pjest wskaźnikiem do stałej liczby całkowitej]


int *p const==> Błędne oświadczenie. Kompilator zgłasza błąd składniowy.

int *const p==> pjest tylko do odczytu [ pjest stałym wskaźnikiem do liczby całkowitej]. Ponieważ wskaźnik ptutaj jest tylko do odczytu, deklaracja i definicja powinny znajdować się w tym samym miejscu.


const int *p const ==> Błędne oświadczenie. Kompilator zgłasza błąd składniowy.

const int const *p ==> *pjest tylko do odczytu

const int *const p1 ==> *pi psą tylko do odczytu [ pjest stałym wskaźnikiem do stałej liczby całkowitej]. Ponieważ wskaźnik ptutaj jest tylko do odczytu, deklaracja i definicja powinny znajdować się w tym samym miejscu.


int const *p const ==> Błędne oświadczenie. Kompilator zgłasza błąd składniowy.

int const int *p ==> Błędne oświadczenie. Kompilator zgłasza błąd składniowy.

int const const *p ==> *pjest tylko do odczytu i jest równoważne zint const *p

int const *const p ==> *pi psą tylko do odczytu [ pjest stałym wskaźnikiem do stałej liczby całkowitej]. Ponieważ wskaźnik ptutaj jest tylko do odczytu, deklaracja i definicja powinny znajdować się w tym samym miejscu.

Abhijit Sahu
źródło
6

Istnieje wiele innych subtelnych punktów otaczających poprawność const w C ++. Przypuszczam, że pytanie dotyczyło po prostu C, ale podam kilka powiązanych przykładów, ponieważ tag to C ++:

  • Często przekazujesz duże argumenty, takie jak łańcuchy, TYPE const &co uniemożliwia modyfikowanie lub kopiowanie obiektu. Przykład:

    TYPE& TYPE::operator=(const TYPE &rhs) { ... return *this; }

    Jest TYPE & constto jednak bez znaczenia, ponieważ odniesienia są zawsze stałe.

  • Powinieneś zawsze oznaczać metody klas, które nie modyfikują klasy jako const, w przeciwnym razie nie możesz wywołać metody z TYPE const &referencji. Przykład:

    bool TYPE::operator==(const TYPE &rhs) const { ... }

  • Są częste sytuacje, w których zarówno wartość zwracana, jak i metoda powinny być const. Przykład:

    const TYPE TYPE::operator+(const TYPE &rhs) const { ... }

    W rzeczywistości metody const nie mogą zwracać danych klasy wewnętrznej jako odwołania do non-const.

  • W rezultacie często trzeba utworzyć zarówno metodę const, jak i metodę non-const przy użyciu przeciążenia const. Na przykład, jeśli zdefiniujesz T const& operator[] (unsigned i) const;, to prawdopodobnie będziesz również chciał wersji nie-stałej podanej przez:

    inline T& operator[] (unsigned i) { return const_cast<char&>( static_cast<const TYPE&>(*this)[](i) ); }

Afaik, nie ma funkcji const w C, funkcje inne niż same nie mogą być const w C ++, metody const mogą mieć skutki uboczne, a kompilator nie może używać funkcji const w celu uniknięcia powielania wywołań funkcji. W rzeczywistości nawet proste int const &odniesienie może świadczyć o zmianie wartości, do której się odnosi, w innym miejscu.

Jeff Burdges
źródło
6

Oryginalni twórcy wielokrotnie opisywali składnię deklaracji C i C ++ jako nieudany eksperyment.

Zamiast tego, niech wymienić typ „wskaźnik Type”; Nazwie to Ptr_:

template< class Type >
using Ptr_ = Type*;

Teraz Ptr_<char>jest wskaźnik do char.

Ptr_<const char>jest wskaźnikiem do const char.

I const Ptr_<const char>jest constwskaźnikiem do const char.

Tam.

wprowadź opis zdjęcia tutaj

Pozdrawiam i hth. - Alf
źródło
3
masz cytat na pierwsze zdanie?
sp2danny,
@ sp2danny: Googling „Eksperyment nieudany w składni C” wykasuje tylko kilka wywiadów z Bjarne Stroustrup, gdzie wyraża swoją opinię w tym kierunku, np. „Uważam, że składnia deklaratora C jest eksperymentem, który się nie udał” w wywiadzie Slashdot. Nie mam więc odniesienia do twierdzeń na temat poglądów oryginalnych projektantów C. Wydaje mi się, że można je znaleźć poprzez wystarczająco silny wysiłek badawczy, a może po prostu obalić, pytając ich, ale myślę, że tak jest lepiej. z tą częścią roszczenia, wciąż niezdecydowana i prawdopodobnie prawdziwa :)
Pozdrowienia i hth. - Alf
1
„Oryginalni składnicy deklaracji C i C ++ wielokrotnie opisywali jako nieudany eksperyment”. źle dla C zmień zdanie na temat C lub podaj cytaty.
Stargateur
3
@Stargateur: Najwyraźniej przeczytałeś poprzednie komentarze i znalazłeś coś, co możesz wykorzystać w pedanterii. Powodzenia w życiu. W każdym razie, dawni ludzie tacy jak ja pamiętają dużo, czego nie możemy udowodnić bez angażowania się w bardzo czasochłonne badania. Możesz po prostu uwierzyć mi na słowo.
Pozdrawiam i hth. - Alf
1
@Stargateur „Sethi (...) zauważył, że wiele zagnieżdżonych deklaracji i wyrażeń stałoby się prostszych, gdyby operator pośredni został przyjęty jako operator postfiksowy zamiast prefiksu, ale do tego czasu było już za późno na zmianę”. pochodzi z DMR. Oczywiście DMR nie wynalazł stałych i niestabilnych słów kluczowych, pochodzą one z C ++ / X3J11, jak wykazano na tej stronie.
Antti Haapala
6

Dla mnie pozycja consttj. Czy wydaje się LEWA czy PRAWA, czy zarówno LEWA, jak i PRAWA w stosunku do *pomaga mi ustalić rzeczywiste znaczenie.

  1. A constdo LEWEJ *oznacza, że ​​obiekt wskazany wskaźnikiem jest constobiektem.

  2. Od A constdo PRAWEJ *oznacza, że ​​wskaźnik jest constwskaźnikiem.

Poniższa tabela pochodzi ze Stanford CS106L Standard C ++ Programming Laboratory Course Reader.

wprowadź opis zdjęcia tutaj

sri
źródło
3

Dotyczy to głównie drugiego wiersza: najlepszych praktyk, zadań, parametrów funkcji itp.

Ogólna praktyka. Spróbuj zrobić wszystko const, co możesz. Innymi słowy: zrób wszystko constod samego początku, a następnie usuń dokładnie minimalny zestaw constniezbędny do działania programu. Będzie to bardzo pomocne w uzyskaniu poprawności stałej i pomoże uniknąć subtelnych błędów, gdy ludzie będą próbować przypisać rzeczy, których nie powinni modyfikować.

Unikaj const_cast <> jak ognia. Istnieje jeden lub dwa uzasadnione przypadki użycia, ale są one bardzo nieliczne i dalekie. Jeśli próbujesz zmienić constobiekt, znacznie lepiej będzie znaleźć tego, kto zadeklarował go constw pierwszym tempie i porozmawiać z nim o tej sprawie, aby osiągnąć konsensus co do tego, co powinno się stać.

Co bardzo starannie prowadzi do zadań. Możesz przypisać do czegoś tylko wtedy, gdy jest to non-const. Jeśli chcesz przypisać do czegoś, co jest const, patrz wyżej. Pamiętaj, że w deklaracjach int const *foo;i int * const bar;innych sprawach są const- inne odpowiedzi tutaj wspaniale obejmowały tę kwestię, więc nie będę w nią wchodził.

Parametry funkcji:

Przekaż wartość: np. void func(int param)Nie obchodzi Cię to w ten czy inny sposób w witrynie wywołującej. Można argumentować, że istnieją przypadki użycia do deklarowania funkcji jako, void func(int const param)ale nie ma to wpływu na program wywołujący, tylko na samą funkcję, ponieważ żadna przekazywana wartość nie może zostać zmieniona przez funkcję podczas wywołania.

Przekaż przez odniesienie: np. void func(int &param)Teraz robi różnicę. Jak właśnie zadeklarowano, funcmoże ulec zmianie param, a każda strona wywołująca powinna być przygotowana na konsekwencje. Zmiana deklaracji na void func(int const &param)zmianę umowy oraz gwarancje, funcktórych teraz nie można zmienić param, co oznacza, że ​​to, co przekazane, powróci. Jak zauważyli inni, jest to bardzo przydatne do taniego przepuszczania dużego obiektu, którego nie chcesz zmieniać. Przekazanie referencji jest znacznie tańsze niż przekazanie dużego obiektu pod względem wartości.

Przechodzą przez wskaźnik: przykład void func(int *param)i void func(int const *param)te dwa są prawie równoznaczne z ich odpowiednikami referencyjnych, z zastrzeżeniem, że funkcja nazywa teraz musi sprawdzić nullptrchyba niektórych innych zapewnia gwarancji umownej func, że nigdy nie otrzyma nullptrw param.

Opinia na ten temat. Udowodnienie poprawności w takim przypadku jest piekielnie trudne, po prostu cholernie łatwo popełnić błąd. Więc nie ryzykuj i zawsze sprawdzaj parametry wskaźnika dla nullptr. Zaoszczędzisz sobie bólu i cierpienia oraz trudnych do znalezienia błędów w perspektywie długoterminowej. A jeśli chodzi o koszt testu, jest on tani jak brud, aw przypadkach, gdy analiza statyczna wbudowana w kompilator może to zarządzać, optymalizator i tak go obejmie. Włącz Generowanie kodu czasu łącza dla MSVC lub WOPR (myślę) dla GCC, a uzyskasz szeroki program, tzn. Nawet w wywołaniach funkcji przekraczających granicę modułu kodu źródłowego.

Na koniec dnia wszystkie powyższe stanowią bardzo solidny argument, aby zawsze preferować odniesienia do wskaźników. Są po prostu bezpieczniejsze.

dgnuff
źródło
3

Stała z int po obu stronach sprawi, że wskaźnik do stałej int :

const int *ptr=&i;

lub:

int const *ptr=&i;

constpo *spowoduje stały wskaźnik do int :

int *const ptr=&i;

W tym przypadku wszystkie są wskaźnikiem do stałej liczby całkowitej , ale żaden z nich nie jest stałym wskaźnikiem:

 const int *ptr1=&i, *ptr2=&j;

W tym przypadku wszystkie są wskaźnikami na stałą liczbą całkowitą, a ptr2 jest stałym wskaźnikiem na stałą liczbą całkowitą . Ale ptr1 nie jest stałym wskaźnikiem:

int const *ptr1=&i, *const ptr2=&j;
Łowca
źródło
3
  • jeśli constjest na lewo od *, to odnosi się do wartości (to nie jest kwestia tego, czy jest to const intalbo int const)
  • jeśli constjest na prawo od *, to odnosi się do samego wskaźnika
  • oba mogą być jednocześnie

Ważna uwaga: const int *p nie oznacza, że ​​wartość, o której mówisz, jest stała !! . Oznacza to, że nie można tego zmienić za pomocą tego wskaźnika (co oznacza, że ​​nie można przypisać $ * p = ... `). Sama wartość może zostać zmieniona na inne sposoby. Na przykład

int x = 5;
const int *p = &x;
x = 6; //legal
printf("%d", *p) // prints 6
*p = 7; //error 

Ma to być stosowane głównie w sygnaturach funkcji, aby zagwarantować, że funkcja nie może przypadkowo zmienić przekazanych argumentów.

niebieska notatka
źródło
2

Tylko ze względu na kompletność dla C zgodnie z innymi wyjaśnieniami, nie jestem pewien dla C ++.

  • pp - wskaźnik do wskaźnika
  • p - wskaźnik
  • dane - wskazane w przykładach x
  • pogrubienie - zmienna tylko do odczytu

Wskaźnik

  • dane p - int *p;
  • dane p -int const *p;
  • dane p -int * const p;
  • dane p -int const * const p;

Wskaźnik do wskaźnika

  1. dane pp p - int **pp;
  2. dane pp p -int ** const pp;
  3. dane pp p -int * const *pp;
  4. dane pp p -int const **pp;
  5. dane pp p -int * const * const pp;
  6. dane pp p -int const ** const pp;
  7. dane pp p -int const * const *pp;
  8. dane pp p -int const * const * const pp;
// Example 1
int x;
x = 10;
int *p = NULL;
p = &x;
int **pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 2
int x;
x = 10;
int *p = NULL;
p = &x;
int ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

// Example 3
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 4
int const x = 10; // Definition must happen during declaration
int const * p = NULL;
p = &x;
int const **pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 5
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

// Example 6
int const x = 10; // Definition must happen during declaration
int const *p = NULL;
p = &x;
int const ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

// Example 7
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 8
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

N-poziomy Dereferencji

Po prostu idź dalej, ale niech ludzkość cię ekskomunikuje.

int x = 10;
int *p = &x;
int **pp = &p;
int ***ppp = &pp;
int ****pppp = &ppp;

printf("%d \n", ****pppp);
Niezdefiniowane zachowanie
źródło
0
  1. const int*- wskaźnik do stałego intobiektu.

Możesz zmienić wartość wskaźnika; nie można zmienić wartości intobiektu, na który wskazuje wskaźnik.


  1. const int * const- stały wskaźnik do stałego intobiektu.

Nie można zmienić wartości wskaźnika ani wartości intobiektu, na który wskazuje wskaźnik.


  1. int const *- wskaźnik do stałego intobiektu.

Ta instrukcja jest równoważna 1. const int*- Możesz zmienić wartość wskaźnika, ale nie możesz zmienić wartości intobiektu, na który wskazuje wskaźnik.


W rzeczywistości istnieje czwarta opcja:

  1. int * const- stały wskaźnik do intobiektu.

Możesz zmienić wartość obiektu, na który wskazuje wskaźnik, ale nie możesz zmienić wartości samego wskaźnika. Wskaźnik zawsze wskazuje ten sam intobiekt, ale tę wartość tego intobiektu można zmienić.


Jeśli chcesz określić określony typ konstrukcji C lub C ++, możesz użyć Reguły zgodnej z ruchem wskazówek zegara / Spirali stworzonej przez Davida Andersona; ale nie mylić z Regułą Andersona autorstwa Rossa J. Andersona, co jest czymś zupełnie odrębnym.

RobertS obsługuje Monikę Cellio
źródło