const char * const versus const char *?

110

Przeglądam kilka przykładowych programów, aby ponownie zapoznać się z C ++ i napotkałem następujące pytanie. Po pierwsze, oto przykładowy kod:

void print_string(const char * the_string)
{
    cout << the_string << endl;
}

int main () {
    print_string("What's up?");
}

W powyższym kodzie parametr print_string mógłby być zamiast tego const char * const the_string. Który byłby bardziej odpowiedni do tego?

Rozumiem, że różnica polega na tym, że jeden jest wskaźnikiem do stałego znaku, a drugi jest stałym wskaźnikiem do stałego znaku. Ale dlaczego oba te rozwiązania działają? Kiedy miałoby to znaczenie?

fot
źródło

Odpowiedzi:

244

To ostatnie uniemożliwia modyfikowanie the_stringwnętrza print_string. Właściwie byłoby to właściwe, ale być może gadatliwość zniechęciła programistę.

char* the_string: Mogę zmienić, chardo których the_stringpunktów, i mogę zmodyfikować, charna które wskazuje.

const char* the_string: Mogę zmienić, na charktóre the_stringpunkty, ale nie mogę zmodyfikować, charna które wskazuje.

char* const the_string: Nie mogę zmienić punktów, charna które the_stringwskazuje, ale mogę zmodyfikować, charna które wskazuje.

const char* const the_string: Nie mogę zmienić punktów, charna które the_stringwskazuje, ani zmodyfikować, charna które wskazuje.

Kent Boogaart
źródło
11
+1 za ostatnie zdanie. Stała poprawność jest gadatliwa, ale warto.
mskfisher
6
@Xeo: Twoja forma jest jeszcze bardziej zagmatwana, ponieważ dzieli ją jedna transpozycja od całkowitej zmiany jej znaczenia. const char *jest znacznie lepszy, ponieważ constznajduje się po zupełnie przeciwnej stronie.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
7
@R ..: Cóż, przynajmniej dla mnie tak nie jest. Czytając od prawej do lewej, otrzymuję „wskaźnik do znaku stałego”. Po prostu czuję się lepiej w ten sposób.
Xeo,
6
Cóż, oszukujesz siebie, ponieważ typy C są czytane od wewnątrz na zewnątrz, a nie od lewej do prawej. :-)
R .. GitHub STOP HELPING ICE
11
Jestem trochę zawstydzony, najwyraźniej jestem jedyną osobą, która tego nie rozumie ... ale jaka jest różnica między „znakiem, na który wskazuje” a „znakiem, na który wskazuje”?
Brak
138
  1. Zmienny wskaźnik do zmiennego znaku

    char *p;
  2. Zmienny wskaźnik do stałego znaku

    const char *p;
  3. Stały wskaźnik do zmiennego znaku

    char * const p; 
  4. Stały wskaźnik do stałego znaku

    const char * const p;
James Michael Hare
źródło
Czy nie powinno tak być: const char* p; --> constant pointer to mutable characteri char *const p; --> mutable pointer to constant character
PnotNP
2
@NulledPointer No. Deklaracje C ++ są tworzone od prawej do lewej. Możesz więc czytać const char * pjako: „p jest wskaźnikiem do stałej znakowej” lub zmiennym wskaźnikiem do stałego znaku, jak poprawnie stwierdza James. to samo z drugim :).
Samidamaru,
28

const char * constśrodki wyżeł, jak również dane wskazywały na wskaźnik, są zarówno const!

const char *oznacza, że tylko dane wskazywane przez wskaźnik to const. Jednak sam wskaźnik nie jest stałą.

Przykład.

const char *p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //okay, changing the non-const pointer. 

const char * const p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //error, changing the const pointer. 
Nawaz
źródło
20

(Wiem, że to jest stare, ale i tak chciałem się nimi podzielić.)

Chciałem tylko rozwinąć odpowiedź Thomasa Matthewsa. Reguła Right-Left Rule deklaracji typu C prawie mówi: kiedy czytasz deklarację typu C, zacznij od identyfikatora i idź w prawo, kiedy możesz, i w lewo, kiedy nie możesz.

Najlepiej wyjaśnić to na kilku przykładach:

Przykład 1

  • Zacznij od identyfikatora, nie możemy iść w prawo, więc idziemy w lewo

    const char* const foo
                ^^^^^

    foo jest stałą ...

  • Kontynuuj w lewo

    const char* const foo
              ^

    foo jest stałym wskaźnikiem do ...

  • Kontynuuj w lewo

    const char* const foo
          ^^^^

    foo jest stałym wskaźnikiem do char ...

  • Kontynuuj w lewo

    const char* const foo
    ^^^^^

    foo jest stałym wskaźnikiem do stałej char (Complete!)

Przykład 2

  • Zacznij od identyfikatora, nie możemy iść w prawo, więc idziemy w lewo

    char* const foo
          ^^^^^

    foo jest stałą ...

  • Kontynuuj w lewo

    char* const foo
        ^

    foo jest stałym wskaźnikiem do ...

  • Kontynuuj w lewo

    char* const foo
    ^^^^

    foo jest stałym wskaźnikiem do char (Complete!)

Przykład 1337

  • Zacznij od identyfikatora, ale teraz możemy iść w prawo!

    const char* const* (*foo[8])()
                            ^^^

    foo to tablica 8 ...

  • Naciśnij nawias, więc nie możesz już iść w prawo, idź w lewo

    const char* const* (*foo[8])()
                        ^

    foo jest tablicą zawierającą 8 wskaźników do ...

  • Skończone w nawiasach, można teraz iść w prawo

    const char* const* (*foo[8])()
                                ^^

    foo to tablica 8 wskaźników do funkcji, która zwraca ...

  • Nic więcej w prawo, idź w lewo

    const char* const* (*foo[8])()
                     ^

    foo to tablica 8 wskaźników do funkcji, która zwraca wskaźnik do ...

  • Kontynuuj w lewo

    const char* const* (*foo[8])()
                ^^^^^

    foo to tablica 8 wskaźników do funkcji, która zwraca wskaźnik do stałej ...

  • Kontynuuj w lewo

    const char* const* (*foo[8])()
              ^

    foo to tablica 8 wskaźników do funkcji, która zwraca wskaźnik do stałego wskaźnika do ...

  • Kontynuuj w lewo

    const char* const* (*foo[8])()
          ^^^^

    foo to tablica 8 wskaźników do funkcji, która zwraca wskaźnik do stałego wskaźnika do znaku char ...

  • Kontynuuj w lewo

    const char* const* (*foo[8])()
    ^^^^^

    foo to tablica 8 wskaźników do funkcji, która zwraca wskaźnik do stałego wskaźnika do stałej char (Complete!)

Dalsze wyjaśnienie: http://www.unixwiz.net/techtips/reading-cdecl.html

Garrett
źródło
CMIIW, const char * const foo powinno być równoważne z char const * const foo?
luochenhuan
@luochenhuan Tak, rzeczywiście są.
Garrett
12

Wiele osób sugeruje czytanie specyfikacji typu od prawej do lewej.

const char * // Pointer to a `char` that is constant, it can't be changed.
const char * const // A const pointer to const data.

W obu formach wskaźnik wskazuje na dane stałe lub tylko do odczytu.

W drugiej formie nie można zmienić wskaźnika; wskaźnik będzie zawsze wskazywał to samo miejsce.

Thomas Matthews
źródło
3

Różnica polega na tym, że bez tego dodatkowego constprogramista mógłby zmienić wewnątrz metody, gdzie wskazuje wskaźnik; na przykład:

 void print_string(const char * the_string)
 {
    cout << the_string << endl;
    //....
    the_string = another_string();
    //....

 }

Byłoby to zamiast tego nielegalne, gdyby podpis był void print_string(const char * const the_string)

Wielu programistów uważa, że ​​dodatkowe constsłowo kluczowe jest zbyt rozwlekłe (w większości scenariuszy) i pomija je, nawet jeśli byłoby to poprawne semantycznie.

leonbloy
źródło
2

W tym drugim gwarantujesz nie modyfikowanie zarówno wskaźnika jak i znaku, w pierwszym gwarantujesz tylko, że zawartość się nie zmieni ale możesz przesuwać wskaźnik

Jesus Ramos
źródło
Ach, więc bez końcowej stałej mógłbym tak ustawić wskaźnik, aby wskazywał na zupełnie inny ciąg?
fot.
Tak, bez tej końcowej stałej możesz użyć wskaźnika parametru do wykonania pewnych iteracji za pomocą arytmetyki wskaźników, gdzie jeśli była taka stała, musiałbyś stworzyć swój własny wskaźnik, który jest kopią tego parametru.
Jesus Ramos,
2

Nie ma powodu, dla którego żaden z nich nie działałby. Wszystko co print_string()robi to wypisanie wartości. Nie próbuje go modyfikować.

Dobrym pomysłem jest utworzenie funkcji, która nie modyfikuje argumentów oznaczania jako stałych. Zaletą jest to, że zmienne, których nie można zmienić (lub których nie chcesz zmieniać), mogą być przekazywane do tych funkcji bez błędów.

Jeśli chodzi o dokładną składnię, chcesz wskazać, które typy argumentów są „bezpieczne” przy przekazywaniu do funkcji.

Jonathan Wood
źródło
2

Myślę, że rzadko jest to istotne, ponieważ funkcja nie jest wywoływana z argumentami takimi jak & * the_string lub ** the_string. Sam wskaźnik jest argumentem typu wartości, więc nawet jeśli go zmodyfikujesz, nie zmienisz kopii użytej do wywołania funkcji. Wersja, którą pokazujesz, gwarantuje, że ciąg się nie zmieni i myślę, że w tym przypadku jest to wystarczające.

Eglin
źródło
2

const char *oznacza, że ​​nie możesz użyć wskaźnika do zmiany wskazywanego elementu. Możesz jednak zmienić wskaźnik, aby wskazywał coś innego.

Rozważać:

const char * promptTextWithDefault(const char * text)
{
    if ((text == NULL) || (*text == '\0'))
        text = "C>";
    return text;
}

Parametr jest niebędącym const char *stałym wskaźnikiem do const char, więc można go zmienić na inną wartość (jak stały ciąg). Gdybyśmy jednak omyłkowo napisali *text = '\0', wystąpiłby błąd kompilacji.

Prawdopodobnie, jeśli nie zamierzasz zmieniać tego, na co wskazuje parametr, możesz wprowadzić parametr const char * const text, ale nie jest to powszechne. Zwykle zezwalamy funkcjom na zmianę wartości przekazywanych do parametrów (ponieważ przekazujemy parametry według wartości, żadna zmiana nie wpływa na wywołującego).

Przy okazji: dobrą praktyką jest unikanie, char const *ponieważ często jest błędnie const char *odczytywany - oznacza to samo co , ale zbyt wiele osób czyta to jako sens char * const.

TonyR
źródło
Łał! Próbowałem znaleźć różnicę między moim const char *a podpisem char const *- sposób, w jaki sformułowałeś swoją BTW, naprawdę pomógł!
mędrzec
1

Prawie wszystkie inne odpowiedzi są poprawne, ale constpomijają jeden aspekt tego: kiedy użyjesz extra parametru w deklaracji funkcji, kompilator zasadniczo je zignoruje. Przez chwilę zignorujmy złożoność twojego przykładu będącego wskaźnikiem i po prostu użyj int.

void foo(const int x);

deklaruje tę samą funkcję co

void foo(int x);

Tylko w definicji funkcji ma dodatkowe constznaczenie:

void foo(const int x) {
    // do something with x here, but you cannot change it
}

Ta definicja jest zgodna z którąkolwiek z powyższych deklaracji. Dzwoniącego to nie obchodzix to const- to jest szczegół implementacji, który nie ma znaczenia w miejscu rozmowy.

Jeśli masz constwskaźnik do constdanych, obowiązują te same zasady:

// these declarations are equivalent
void print_string(const char * const the_string);
void print_string(const char * the_string);

// In this definition, you cannot change the value of the pointer within the
// body of the function.  It's essentially a const local variable.
void print_string(const char * const the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // COMPILER ERROR HERE
}

// In this definition, you can change the value of the pointer (but you 
// still can't change the data it's pointed to).  And even if you change
// the_string, that has no effect outside this function.
void print_string(const char * the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // OK, but not observable outside this func
}

Niewielu programistów C ++ zadaje sobie trud tworzenia parametrów const, nawet jeśli mogą, niezależnie od tego, czy te parametry są wskaźnikami.

Adrian McCarthy
źródło
„kompilator zasadniczo to zignoruje” nie zawsze jest prawdą, program Visual C ++ 2015 wygeneruje ostrzeżenie, jeśli dodasz to dodatkowe constdo parametru funkcji w definicji, ale nie w deklaracji.
raymai97
@ raymai97: Myślę, że to błąd w kompilatorze 2015, ale nie mam 2015 pod ręką do przetestowania. Pracuję tak, jak opisałem w 2017 roku, co według moich rozmów z niektórymi znawcami norm jest oczekiwanym zachowaniem.
Adrian McCarthy
-1

Różnica między nimi polega na tym, że znak * może wskazywać na dowolny dowolny wskaźnik. Const char * natomiast wskazuje na stałe zdefiniowane w sekcji DATA pliku wykonywalnego. W związku z tym nie można modyfikować wartości znaków w łańcuchu const char *.

Maz
źródło
Nie pytam o różnicę między char * i const char *. Pytam między const char * i const char * const
pict
Ta odpowiedź jest nieprawidłowa. const char*Może wskazać nigdzie się podoba.
Sześcienny