Nie mówię o wskaźnikach do wartości const, ale o samych wskaźnikach const.
Uczę się C i C ++ poza bardzo podstawowymi rzeczami i do dziś zdałem sobie sprawę, że wskaźniki są przekazywane do funkcji według wartości, co ma sens. Oznacza to, że wewnątrz funkcji mogę ustawić skopiowany wskaźnik na inną wartość bez wpływu na oryginalny wskaźnik wywołującego.
Jaki jest więc sens posiadania nagłówka funkcji, który mówi:
void foo(int* const ptr);
Wewnątrz takiej funkcji nie możesz ustawić ptr na coś innego, ponieważ jest to stała i nie chcesz, aby była modyfikowana, ale funkcja taka:
void foo(int* ptr);
Działa równie dobrze! ponieważ wskaźnik jest mimo to kopiowany, a wskaźnik w obiekcie wywołującym nie ma wpływu, nawet jeśli zmodyfikujesz kopię. Więc jaka jest zaleta const?
const
parametr.const
gwarancji poprawności. To tylko daje nam większą pewność, że nasz kod jest niewątpliwie poprawny.Odpowiedzi:
const
jest narzędziem, którego powinieneś używać w dążeniu do bardzo ważnej koncepcji C ++:Chociaż nie zmienia to funkcjonalności, dodawanie
const
generuje błąd kompilatora, gdy robisz rzeczy, których nie chciałeś zrobić. Wyobraź sobie następującą literówkę:Jeśli używasz
int* const
, spowoduje to wygenerowanie błędu kompilatora, ponieważ zmieniasz wartość naptr
. Generalnie dodawanie ograniczeń za pomocą składni jest dobrą rzeczą. Po prostu nie idź za daleko - podany przykład to przypadek, w którym większość ludzi nie zawraca sobie głowy używaniemconst
.źródło
Staram się używać tylko
const
argumentów, ponieważ umożliwia to więcej sprawdzeń kompilatora: jeśli przypadkowo ponownie przypiszę wartość argumentu wewnątrz funkcji, kompilator mnie ugryzie.Rzadko używam zmiennych ponownie, czystsze jest tworzenie nowych zmiennych do przechowywania nowych wartości, więc zasadniczo wszystkie moje deklaracje zmiennych są
const
(z wyjątkiem niektórych przypadków, takich jak zmienne pętli, w którychconst
kod nie działa).Zauważ, że ma to sens tylko w definicji funkcji. Nie należy go do deklaracji , co widzi użytkownik. A użytkownika nie obchodzi, czy używam
const
parametrów wewnątrz funkcji.Przykład:
Zwróć uwagę, jak są zarówno argument, jak i zmienna lokalna
const
. Żadne z nich nie jest konieczne, ale dzięki funkcjom, które są nawet nieco większe, wielokrotnie uchroniło mnie to przed popełnieniem błędów.źródło
+1
od innegoconst
obsesyjnego fanatyka. Jednak wolę, aby moi kompilatorzy szczekali na mnie. Popełniam zbyt wiele błędów i bardzo cierpiałbym, gdyby ugryzły.const
chyba, że jest dobry powód”. Jest jednak kilka dobrych wyjątków, np. Kopiuj i zamieńconst
argument z argumentu i najlepiej skomentuj dlaczego. Ta niewielka dodatkowa praca nie usprawiedliwia oznaczania wszystkich argumentów jako innych niżconst
domyślne i otwierania się na wszystkie potencjalne błędy, które mogą powodować.Twoje pytanie dotyka czegoś bardziej ogólnego: czy argumenty funkcji powinny być stałymi?
Stałość argumentów wartości (takich jak wskaźnik) jest szczegółem implementacji i nie stanowi części deklaracji funkcji. Oznacza to, że Twoja funkcja jest zawsze taka:
Od osoby implementującej funkcję zależy, czy chce ona użyć zmiennej argumentu function-scope w sposób zmienny czy stały:
Dlatego postępuj zgodnie z prostą zasadą, aby nigdy nie umieszczać
const
deklaracji (nagłówka) i umieszczaj ją w definicji (implementacji), jeśli nie chcesz lub nie musisz modyfikować zmiennej.źródło
const
deklaracja i (część prototypowa) definicji będą generalnie identyczne.const
deklaracji. Dodanie kwalifikatora do implementacji zależy wyłącznie od Ciebie.const
deklaracji, ale nie definicji, ma sens. Wydaje się po prostu usterką w języku, że jest to jedyny przypadek, w którym deklaracja i definicja nie są identyczne mają sens. (Oczywiście nie jest to jedyna usterka C.)Kwalifikator const najwyższego poziomu jest odrzucany w deklaracjach, więc deklaracje w pytaniu deklarują dokładnie tę samą funkcję. Z drugiej strony, w definicji (implementacji) kompilator sprawdzi, czy jeśli oznaczysz wskaźnik jako const, to nie zostanie on zmodyfikowany w treści funkcji.
źródło
int* t ptr
jest błędem składniowym. Bez tego oba są identyczne ze względu na przeciążenie.Masz rację, dla dzwoniącego nie ma to absolutnie żadnego znaczenia. Ale dla autora funkcji może to być siatka bezpieczeństwa „w porządku, muszę się upewnić, że nie wskażę złej rzeczy”. Niezbyt przydatne, ale też nie bezużyteczne.
Zasadniczo jest to to samo, co posiadanie
int const the_answer = 42
w programie.źródło
const int
iint const
są równoważne, podczas gdyconst int*
iint* const
mają dwa różne znaczenia!int const
części; Od jakiegoś czasu stawiam ten typ przed const (brzmi nienaturalnie) i zdaję sobie sprawę z różnic. Ten artykuł może się przydać. Ja sam miałem nieco inne powody, aby przejść na ten styl.Słowo
const
kluczowe jest bardzo złożone, jest dość złożone. Ogólnie rzecz biorąc, dodanie dużej ilości const do programu jest uważane za dobrą praktykę programistyczną. Przeszukaj sieć pod kątem „poprawności const”, a znajdziesz mnóstwo informacji na ten temat.Słowo kluczowe const to tak zwany „kwalifikator typu”, inne to
volatile
irestrict
. Przynajmniej zmienna podlega tym samym (mylącym) regułom co const.Po pierwsze, słowo kluczowe const służy dwóm celom. Najbardziej oczywistym z nich jest ochrona danych (i wskaźników) przed celowym lub przypadkowym niewłaściwym wykorzystaniem poprzez ustawienie ich tylko do odczytu. Każda próba zmodyfikowania zmiennej const zostanie zauważona przez kompilator w czasie kompilacji.
Ale jest też inny cel w każdym systemie z pamięcią tylko do odczytu, a mianowicie zapewnienie, że pewna zmienna jest alokowana w takiej pamięci - może to być na przykład EEPROM lub flash. Są one znane jako pamięci nieulotne, NVM. Zmienna przydzielona w NVM będzie oczywiście nadal podlegać wszystkim regułom zmiennej const.
Istnieje kilka różnych sposobów użycia
const
słowa kluczowego:Zadeklaruj stałą zmienną.
Można to zrobić jako
Te dwie formy są całkowicie równoważne . Ten ostatni styl jest uważany za zły styl i nie powinien być używany.
Powodem, dla którego drugi wiersz jest uważany za zły styl, jest prawdopodobnie to, że „specyfikatory klasy pamięci”, takie jak static i extern, również mogą być zadeklarowane po rzeczywistym typie
int static
itp. Ale zrobienie tego dla specyfikatorów klasy pamięci jest oznaczone jako przestarzała funkcja przez komitet C (projekt ISO 9899 N1539 z 6.11.5). Dlatego też ze względu na spójność nie należy w ten sposób zapisywać kwalifikatorów typów. Nie służy to żadnemu innemu celowi, jak tylko zmylić czytelnika.Zadeklaruj wskaźnik do stałej zmiennej.
Oznacza to, że zawartość „X” nie może być modyfikowana. Jest to normalny sposób deklarowania wskaźników w ten sposób, głównie jako część parametrów funkcji dla „stałej poprawności”. Ponieważ „X” w rzeczywistości nie musi być deklarowany jako stała, może to być dowolna zmienna. Innymi słowy, zawsze możesz „uaktualnić” zmienną do stałej. Technicznie rzecz biorąc, C pozwala również na obniżenie wersji z const do zwykłej zmiennej przez jawne typy typów, ale jest to uważane za złe programowanie i kompilatory zwykle ostrzegają przed tym.
Zadeklaruj stały wskaźnik
Oznacza to, że sam wskaźnik jest stały. Możesz modyfikować to, na co wskazuje, ale nie możesz modyfikować samego wskaźnika. Nie ma to wielu zastosowań, jest kilka, takich jak zapewnienie, że adres wskazywany przez wskaźnik (wskaźnik do wskaźnika) nie zmienia się, gdy jest przekazywany jako parametr do funkcji. Będziesz musiał napisać coś niezbyt czytelnego w ten sposób:
Wątpię, aby wielu programistów C mogło uzyskać stałą i * właśnie tam. Wiem, że nie mogę - musiałem sprawdzić w GCC. Myślę, że właśnie dlatego rzadko widzisz tę składnię dla wskaźnika do wskaźnika, mimo że jest to uważane za dobrą praktykę programistyczną.
Stałe wskaźniki mogą być również używane, aby upewnić się, że sama zmienna wskaźnikowa jest zadeklarowana w pamięci tylko do odczytu, na przykład możesz chcieć zadeklarować jakąś tabelę wyszukiwania opartą na wskaźnikach i zaalokować ją w NVM.
I oczywiście, jak wskazują inne odpowiedzi, stałe wskaźniki mogą być również używane do wymuszania „stałej poprawności”.
Zadeklaruj stały wskaźnik do stałych danych
To jest połączenie dwóch typów wskaźników opisanych powyżej, ze wszystkimi atrybutami obu.
Zadeklaruj funkcję składową tylko do odczytu (C ++)
Ponieważ jest to oznaczone jako C ++, powinienem również wspomnieć, że można deklarować funkcje składowe klasy jako stałą. Oznacza to, że funkcja nie może modyfikować żadnego innego elementu członkowskiego klasy, gdy jest wywoływana, co zarówno zapobiega przypadkowym błędom programisty klasy, ale także informuje osobę wywołującą funkcję składową, że nic nie będzie zepsuć dzwoniąc do niego. Składnia jest następująca:
źródło
(imo) to naprawdę nie ma sensu jako domyślne. bardziej sensownym domyślnym jest przekazanie jako nieprzekazywalny wskaźnik (
int* const arg
). to znaczy wolałbym, aby wskaźniki przekazywane jako argumenty były niejawnie deklarowane jako const.Zaletą jest to, że jest to dość łatwe i czasami niejasne, kiedy modyfikujesz adres, na który wskazuje argument, tak że możesz wprowadzić błąd, gdy nie jest dość łatwo stały. zmiana adresu jest nietypowa. jaśniej jest utworzyć zmienną lokalną, jeśli zamierzasz zmodyfikować adres. jak również, manipulacja surowym wskaźnikiem jest łatwym sposobem na wprowadzenie błędów.
więc łatwiej jest przejść przez niezmienny adres i utworzyć kopię (w tych nietypowych przypadkach), gdy chcesz zmienić adres, na który wskazuje argument:
dodanie tego lokalnego jest praktycznie bezpłatne i zmniejsza ryzyko błędów, jednocześnie poprawiając czytelność.
na wyższym poziomie: jeśli manipulujesz argumentem jako tablicą, zwykle jest jaśniejsze i mniej podatne na błędy, aby klient zadeklarował argument jako kontener / kolekcję.
ogólnie rzecz biorąc, dodanie const do wartości, argumentów i adresów jest dobrym pomysłem, ponieważ nie zawsze zdajesz sobie sprawę z efektów ubocznych, które kompilator z radością wymusza. w związku z tym jest tak samo przydatna jak stała, jak używana w kilku innych przypadkach (np. pytanie jest podobne do „Dlaczego powinienem deklarować wartości stałe?”). na szczęście mamy również referencje, których nie można ponownie przypisać.
źródło
const
słowo kluczowe, powinno miećmutable
słowo kluczowe (cóż, ma, ale z niewłaściwą semantyką).Jeśli programujesz systemy wbudowane lub sterowniki urządzeń, w których masz urządzenia mapowane w pamięci, często używane są obie formy `` const '', jeden, aby zapobiec ponownemu przypisaniu wskaźnika (ponieważ wskazuje na stały adres sprzętowy), a jeśli urządzenie peryferyjne register, na który wskazuje, jest rejestrem sprzętowym tylko do odczytu, wtedy inna stała wykryje wiele błędów w czasie kompilacji, a nie w czasie wykonywania.
16-bitowy rejestr chipów peryferyjnych tylko do odczytu może wyglądać następująco:
static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;
Następnie możesz łatwo odczytać rejestr sprzętu bez konieczności uciekania się do języka asemblera:
input_word = *peripheral;
źródło
int iVal = 10; int * const ipPtr = & iVal;
Podobnie jak zwykła zmienna const, wskaźnik do stałej musi być zainicjowany wartością przy deklaracji, a jego wartości nie można zmienić.
Oznacza to, że wskaźnik do stałej będzie zawsze wskazywał tę samą wartość. W powyższym przypadku ipPtr zawsze będzie wskazywać na adres iVal. Jednakże, ponieważ wskazywana wartość nadal nie jest stała, możliwa jest zmiana wskazywanej wartości poprzez dereferencję wskaźnika:
* ipPtr = 6; // dozwolone, ponieważ pnPtr wskazuje na wartość inną niż stała int
źródło
To samo pytanie można zadać w przypadku każdego innego typu (nie tylko wskaźników):
źródło
Twoje pytanie bardziej dotyczy tego, dlaczego definiować dowolną zmienną jako stałą, a nie tylko parametr będący wskaźnikiem stałej funkcji. Obowiązują tu te same zasady, co w przypadku definiowania dowolnej zmiennej jako stałej, jeśli jest to parametr funkcji, zmiennej składowej lub zmiennej lokalnej.
W twoim konkretnym przypadku funkcjonalnie nie robi różnicy, jak w wielu innych przypadkach, gdy deklarujesz zmienną lokalną jako stałą, ale nakłada ograniczenie, że nie możesz modyfikować tej zmiennej.
źródło
Przekazywanie wskaźnika do funkcji stałej nie ma sensu, ponieważ i tak zostanie przekazana przez wartość. To tylko jedna z tych rzeczy, na które pozwala ogólny projekt języka. Zakazanie tego tylko dlatego, że nie ma sensu, spowodowałoby, że język byłby określony. większy.
Jeśli jesteś w funkcji, to oczywiście jest inny przypadek. Posiadanie wskaźnika, który nie może zmienić tego, na co wskazuje, jest stwierdzeniem, które sprawia, że kod jest bardziej przejrzysty.
źródło
Myślę, że zaletą byłoby to, że kompilator może wykonywać bardziej agresywne optymalizacje wewnątrz funkcji, wiedząc, że ten wskaźnik nie może się zmienić.
Unika też np. przekazanie tego wskaźnika do podfunkcji, która akceptuje odniesienie do wskaźnika innego niż stałe (i dlatego może zmienić wskaźnik podobnie jak
void f(int *&p)
), ale zgadzam się, że użyteczność jest w tym przypadku nieco ograniczona.źródło
W ten sposób można zademonstrować przykład, w którym wskaźnik do stałej ma duże zastosowanie. Załóżmy, że masz klasę z dynamiczną tablicą w środku i chcesz przekazać użytkownikowi dostęp do tablicy, ale bez przyznawania im praw do zmiany wskaźnika. Rozważać:
Który produkuje:
Ale jeśli spróbujemy tego:
Otrzymujemy:
Więc oczywiście możemy zmodyfikować zawartość tablicy, ale nie wskaźnik tablicy. Dobrze, jeśli chcesz mieć pewność, że wskaźnik ma spójny stan podczas przekazywania go z powrotem do użytkownika. Jest jednak jeden haczyk:
Nadal możemy usunąć odwołanie do pamięci wskaźnika, nawet jeśli nie możemy zmodyfikować samego wskaźnika.
Więc jeśli chcesz, aby odwołanie do pamięci zawsze wskazywało na coś (IE nigdy nie może być modyfikowane, podobnie jak obecnie działa odniesienie), to jest wysoce przydatne. Jeśli chcesz, aby użytkownik miał pełny dostęp i mógł go modyfikować, to opcja non-const jest dla Ciebie.
Edytować:
Po zauważeniu komentarza okorz001 o niemożności przypisania ze względu na to, że GetArray () jest operandem o właściwej wartości, jego komentarz jest całkowicie poprawny, ale powyższe nadal ma zastosowanie, jeśli miałbyś zwrócić odniesienie do wskaźnika (przypuszczam, że założyłem, że GetArray jest odwołując się do referencji), na przykład:
Zwróci w pierwszym, powodując błąd:
Ale druga nastąpi wesoło, pomimo potencjalnych konsekwencji pod spodem.
Oczywiście pojawi się pytanie „dlaczego miałbyś chcieć zwrócić odniesienie do wskaźnika”? Istnieją rzadkie przypadki, w których trzeba przypisać pamięć (lub dane) bezpośrednio do oryginalnego wskaźnika, o którym mowa (na przykład, zbudować własny interfejs malloc / free lub new / free), ale w tych przypadkach jest to odniesienie inne niż const . Odniesienie do wskaźnika const Nie spotkałem się z sytuacją, która by to uzasadniała (chyba że jako zadeklarowane zmienne referencyjne const, a nie typy zwracane?).
Zastanów się, czy mamy funkcję, która przyjmuje wskaźnik do stałej (w przeciwieństwie do takiej, która tego nie robi):
Błąd w stałej daje w ten sposób komunikat:
Co jest dobre, ponieważ prawdopodobnie nie chcemy tego robić, chyba że chcemy spowodować problemy opisane w komentarzach. Jeśli wyedytujemy dekrementację w funkcji const, wystąpią następujące zdarzenia:
Oczywiście, mimo że A to „Dane [1]”, jest traktowane jako „Dane [0]”, ponieważ wskaźnik NonConst zezwalał na operację zmniejszania. Po zaimplementowaniu const, jak pisze inna osoba, wychwytujemy potencjalny błąd, zanim się pojawi.
Inną ważną kwestią jest to, że wskaźnik do stałej może być użyty jako pseudo-odniesienie, ponieważ rzecz, na którą wskazuje odniesienie, nie może zostać zmieniona (można się zastanawiać, czy być może tak został zaimplementowany). Rozważać:
Podczas próby kompilacji powoduje następujący błąd:
Co jest prawdopodobnie złą rzeczą, jeśli chodziło o ciągłe odniesienie do A. Jeśli
B = NULL
zostanie zakomentowany, kompilator z radością pozwoli nam zmodyfikować,*B
a zatem A. Może to nie wydawać się przydatne w przypadku ints, ale zastanów się, czy miałeś jedną postawę aplikacji graficznej, w której chciałbyś niemodyfikowalnego wskaźnika, który odnosi się do niej, który mógłbyś przekazać na około.Jego użycie jest zmienne (przepraszam za niezamierzony kalambur), ale używane poprawnie, jest to kolejne narzędzie w pudełku, które pomaga w programowaniu.
źródło
Temp.GetArray() = NULL
kończy się niepowodzeniem, ponieważTemp.GetArray()
jest wartością r, a nie dlatego, że ma wartość stałą. Ponadto uważam, że kwalifikator const jest usuwany ze wszystkich zwracanych typów.Nie ma nic specjalnego we wskaźnikach, w których nigdy nie chciałbyś, aby były stałe. Tak jak możesz mieć stałe
int
składowe klasy , możesz również mieć stałe wskaźniki z podobnych powodów: Chcesz się upewnić, że nikt nigdy nie zmieni tego, na co wskazuje. Odnośniki w C ++ w pewnym stopniu rozwiązują ten problem, ale zachowanie wskaźnika jest dziedziczone z C.źródło
Uważam, że zapobiegłoby to zwiększaniu lub zmniejszaniu wskaźnika w treści funkcji przez kod.
źródło
Rodzaje deklarowania dowolnych zmiennych, np. -
(1) Deklarowanie stałej zmiennej.
DataType const varibleName;
const dataType* PointerVaribleName=&X;
dataType* const PointerVaribleName=&X;
źródło