Jaki jest sens wskaźników const?

149

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?

R. Ruiz.
źródło
29
A co jeśli chcesz zagwarantować w czasie kompilacji, że wskaźnik nie może i nie powinien być modyfikowany tak, aby wskazywał na coś innego?
Platinum Azure
25
Dokładnie ten sam punkt, co każdy constparametr.
David Heffernan
2
@PlatinumAzure, mam kilka lat doświadczenia w programowaniu, ale brakuje moich praktyk oprogramowania. Cieszę się, że zadałem to pytanie, ponieważ nigdy nie czułem potrzeby zmuszania kompilatora do wskazywania moich własnych błędów logicznych. Moja pierwsza reakcja w momencie, gdy zadałem pytanie, brzmiała: „Dlaczego powinno mnie obchodzić, czy wskaźnik zostanie zmodyfikowany ?, nie wpłynie to na dzwoniącego”. Dopóki nie przeczytałem wszystkich odpowiedzi, zrozumiałem, że powinno mnie to obchodzić, ponieważ jeśli spróbuję to zmodyfikować, ja i moja logika kodowania są błędne (lub wystąpiła literówka, taka jak brak gwiazdki) i mogłem nie zdawać sobie sprawy, że kompilator tego nie zrobił powiedz mi.
R. Ruiz.
3
@ R.Ruiz. Bez wątpienia nawet najbardziej doświadczeni z nas mogliby skorzystać z dodatkowych gwarancji poprawności. Ponieważ inżynieria oprogramowania jest formą inżynierii, w której trochę większa swoboda może być akceptowalna (losowe HTTP 502, wolne połączenia, sporadyczne niepowodzenie ładowania obrazu nie są rzadkimi przypadkami, ale awaria silnika w samolocie jest niedopuszczalna i prawdopodobnie poważna), czasami ludzie programują z nadmiernym pośpiechem. Ten sam argument, który uzasadnia pisanie testów jednostkowych, będzie wyjaśniał użycie constgwarancji poprawności. To tylko daje nam większą pewność, że nasz kod jest niewątpliwie poprawny.
Platinum Azure,
3
Powiązane pytanie: stackoverflow.com/questions/219914/…
Raedwald

Odpowiedzi:

207

const jest narzędziem, którego powinieneś używać w dążeniu do bardzo ważnej koncepcji C ++:

Znajdź błędy w czasie kompilacji, a nie w czasie wykonywania, pobierając kompilator, aby egzekwował to, co masz na myśli.

Chociaż nie zmienia to funkcjonalności, dodawanie constgeneruje błąd kompilatora, gdy robisz rzeczy, których nie chciałeś zrobić. Wyobraź sobie następującą literówkę:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

Jeśli używasz int* const, spowoduje to wygenerowanie błędu kompilatora, ponieważ zmieniasz wartość na ptr. 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żywaniem const.

tenfour
źródło
8
Dziękuję, to jest odpowiedź, która mnie przekonuje. Umieszczasz const, aby kompilator ostrzegał cię o własnych błędach przypisania. Twój przykład doskonale ilustruje tę koncepcję, ponieważ jest to częsty błąd wskaźników. Niż Ty!
R. Ruiz.
25
„Pomóż kompilatorowi pomóc ci” to mantra, którą normalnie śpiewam.
Flexo
1
+1, ale tutaj jest interesujący przypadek, w którym można się spierać: stackoverflow.com/questions/6305906/ ...
Raedwald
3
więc to ta sama odpowiedź, której udzieliłbyś: „Po co ustawiać zmienne jako prywatne, skoro nie możesz ich używać poza klasą”.
Lee Louviere
to może być trochę nie na temat, ale gdzie ten wskaźnik stałej znajduje się w pamięci? Wiem, że wszystkie const znajdują się w globalnej części pamięci, ale nie jestem w 100% pewien co do const, które jest przekazywane jako func var. dzięki i przepraszam, jeśli to jest poza tematem
Tomer
77

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órych constkod 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 constparametrów wewnątrz funkcji.

Przykład:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

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.

Konrad Rudolph
źródło
17
+1od innego constobsesyjnego fanatyka. Jednak wolę, aby moi kompilatorzy szczekali na mnie. Popełniam zbyt wiele błędów i bardzo cierpiałbym, gdyby ugryzły.
sbi
2
+1, bardzo polecam strategię „ constchyba, że ​​jest dobry powód”. Jest jednak kilka dobrych wyjątków, np. Kopiuj i zamień
Flexo
2
Gdybym projektował nowy język, zadeklarowane obiekty byłyby domyślnie tylko do odczytu („const”). Potrzebowałbyś jakiejś specjalnej składni, na przykład słowa kluczowego „var”, aby obiekt był zapisywalny.
Keith Thompson,
@Keith Kusi mnie, by powiedzieć „oczywiście”. W tej chwili wszystko inne jest głupie. Ale niestety większość projektantów języków wydaje się nie zgadzać z nami… Właściwie już to opublikowałem (w usuniętym już pytaniu „Jaka jest Twoja najbardziej kontrowersyjna opinia programistyczna?”).
Konrad Rudolph
1
@KubaOber Nie rozumiem, jak to w ogóle jest pesymizacja. Jeśli później stwierdzisz, że musisz zmodyfikować to, co zostało przekazane w treści funkcji, po prostu usuń constargument z argumentu i najlepiej skomentuj dlaczego. Ta niewielka dodatkowa praca nie usprawiedliwia oznaczania wszystkich argumentów jako innych niż constdomyślne i otwierania się na wszystkie potencjalne błędy, które mogą powodować.
podkreślenie_d
20

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:

void foo(T);

Od osoby implementującej funkcję zależy, czy chce ona użyć zmiennej argumentu function-scope w sposób zmienny czy stały:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

Dlatego postępuj zgodnie z prostą zasadą, aby nigdy nie umieszczać constdeklaracji (nagłówka) i umieszczaj ją w definicji (implementacji), jeśli nie chcesz lub nie musisz modyfikować zmiennej.

Kerrek SB
źródło
5
Zgadzam się z tym - ale jest mi trochę niewygodnie, jeśli chodzi o zmianę deklaracji i definicji. W sprawach innych niż constdeklaracja i (część prototypowa) definicji będą generalnie identyczne.
Keith Thompson,
@KeithThompson: Cóż, nie musisz tego robić, jeśli nie chcesz. Po prostu nie składaj constdeklaracji. Dodanie kwalifikatora do implementacji zależy wyłącznie od Ciebie.
Kerrek SB,
1
Jak powiedziałem, zgadzam się, że umieszczenie constdeklaracji, 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.)
Keith Thompson,
16

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.

David Rodríguez - dribeas
źródło
Nie wiedziałem tego. Więc jeśli spróbuję przeciążać void foo (int * const ptr) z void foo (int * t ptr), otrzymam błąd kompilatora. Dzięki!
R. Ruiz.
@ R.Ruiz. Jeśli twoje spowolnienie to void foo (int * t ptr), a twoja definicja to void foo (int * const ptr), NIE otrzymasz błędu kompilatora.
Trevor Hickey
1
@TrevorHickey Będą ... ale nie z powodu, dla którego myślą. int* t ptrjest błędem składniowym. Bez tego oba są identyczne ze względu na przeciążenie.
underscore_d
14

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 = 42w programie.

cnicutar
źródło
1
Jakie to ma znaczenie, gdzie go wskażą, czy to wskaźnik przydzielony na stosie? Ta funkcja nie może tego zepsuć. O wiele bardziej sensowne jest używanie wskaźników tylko do odczytu, gdy mamy do czynienia ze wskaźnikami do wskaźników. To nie to samo, co int const! Głosuję przeciw temu tylko za to mylące stwierdzenie. const inti int constsą równoważne, podczas gdy const int*i int* constmają dwa różne znaczenia!
Lundin
1
@lundin Załóżmy, że funkcja jest duża i zawiera inne elementy. Przypadkowo można wskazać na coś innego (na stosie funkcji). Nie jest to problematyczne dla dzwoniącego, ale z pewnością może być dla dzwoniącego. Nie widzę w moich stwierdzeniach nic mylącego: „ dla autora funkcji może to być siatka bezpieczeństwa”.
cnicutar
1
@Lundin O int constczęś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.
cnicutar
1
W porządku. Właśnie napisałem własną rozwlekłą odpowiedź na wypadek, gdyby ktoś inny niż ty i ja czytaliśmy ten post. Podałem również uzasadnienie, dlaczego int const to zły styl, a const int to dobry styl.
Lundin
Lundin, ja też czytam! Zgadzam się, że const int to lepszy styl. @cnicutar, Twoja pierwsza odpowiedź do Lundin jest tym, czego szukałem, kiedy zadawałem to pytanie. Więc problem nie leży w dzwoniącym (jak bezmyślnie myślałem), ale const jest gwarancją dla wywołującego. Podoba mi się ten pomysł, dziękuję.
R. Ruiz.
14

Słowo constkluczowe 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 volatilei restrict. 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 constsłowa kluczowego:

Zadeklaruj stałą zmienną.

Można to zrobić jako

const int X=1; or
int const X=1;

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 staticitp. 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.

const int* ptr = &X;

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

int* const ptr = &X;

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:

void func (int*const* ptrptr)

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

const int* const ptr=&X;

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:

void MyClass::func (void) const;
Lundin
źródło
8

... dzisiaj zdałem sobie sprawę, że wskaźniki są przekazywane przez wartości do funkcji, co ma sens.

(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.

Więc jaka jest zaleta 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:

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

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ć.

Justin
źródło
4
+1 za to, że jest to ustawienie domyślne. C ++, podobnie jak większość języków, ma to odwrotnie. Zamiast mieć constsłowo kluczowe, powinno mieć mutablesłowo kluczowe (cóż, ma, ale z niewłaściwą semantyką).
Konrad Rudolph
2
Ciekawy pomysł z domyślną wartością const. Dziękuję za odpowiedź!
R. Ruiz.
6

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;

Dan Haynes
źródło
5

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

jusathr
źródło
5

To samo pytanie można zadać w przypadku każdego innego typu (nie tylko wskaźników):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}
pmg
źródło
LOL, masz rację. Przypadek wskaźników przyszedł mi do głowy jako pierwszy, ponieważ myślałem, że są wyjątkowe, ale są jak każda inna zmienna przekazywana przez wartość.
R. Ruiz.
5

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.

zar
źródło
Twój komentarz uświadamia, jak daremne jest moje pytanie, ponieważ masz rację: to to samo, co posiadanie dowolnego innego parametru jak const. Może się to wydawać bezużyteczne, ale ma pomóc programiście mieć to ograniczenie i uniknąć błędów
R. Ruiz.
4

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.

Anders Abel
źródło
4

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.

MartinStettner
źródło
+1 za optymalizacje. Przynajmniej książki mówią, że to prawda. Chciałbym dokładnie zrozumieć, czym są te optymalizacje
R. Ruiz.
4

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ć:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

Który produkuje:

Dane wejściowe
wprowadzają dane

Ale jeśli spróbujemy tego:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

Otrzymujemy:

błąd: wartość l wymagana jako lewy operand przypisania // Znowu udaremniono Drat!

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:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

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:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

Zwróci w pierwszym, powodując błąd:

błąd: przypisanie lokalizacji tylko do odczytu „Temp.TestA :: GetArray ()”

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):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

Błąd w stałej daje w ten sposób komunikat:

błąd: zmniejszenie wartości parametru tylko do odczytu „Dane”

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:

NonConst: A
Const: B

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ć:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

Podczas próby kompilacji powoduje następujący błąd:

błąd: przypisanie zmiennej tylko do odczytu „B”

Co jest prawdopodobnie złą rzeczą, jeśli chodziło o ciągłe odniesienie do A. Jeśli B = NULLzostanie zakomentowany, kompilator z radością pozwoli nam zmodyfikować, *Ba 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.

Sight3
źródło
2
Temp.GetArray() = NULLkoń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.
Oscar Korz
@ okorz001: Po testach rzeczywiście masz rację. Jednak powyższe ma zastosowanie, jeśli zwrócisz odwołanie do samego wskaźnika. Odpowiednio zmodyfikuję swój post.
Sight3
3

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 intskł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.

Mark B.
źródło
3

Uważam, że zapobiegłoby to zwiększaniu lub zmniejszaniu wskaźnika w treści funkcji przez kod.

Mike Christensen
źródło
3

Rodzaje deklarowania dowolnych zmiennych, np. -
(1) Deklarowanie stałej zmiennej.
DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2) Zadeklaruj wskaźnik do stałej zmiennej
const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified

Pyadav
źródło
Dostarczamy zmienną wskaźnikową jako stałą do funkcji jako argument, gdy nie chcemy zmienić jej rzeczywistej wartości
Pyadav