„const int” kontra „int const” jako parametry funkcji w C ++ i C.

116

Rozważać:

int testfunc1 (const int a)
{
  return a;
}

int testfunc2 (int const a)
{
  return a;
}

Czy te dwie funkcje są takie same pod każdym względem, czy też istnieje różnica?

Interesuje mnie odpowiedź na język C, ale jeśli jest coś ciekawego w języku C ++, to też chciałbym wiedzieć.

Nils Pipenbrinck
źródło
Czy jest teraz słowo kluczowe const w języku C? Kiedyś go nie było, ale nie znam standardu C 99.
Onorio Catenacci
8
Nie musisz. C90 wystarczy. Nie było go jednak w oryginalnym K&R C.
Mark Baker
3
Jest to słowo kluczowe w C89 i ANSI. Nie wiem, czy było to słowo kluczowe w czasach Kerningham i Richie.
Nils Pipenbrinck
7
Ta strona tłumaczy „C gibberish
Motti
5
Powiedziałbym „C bełkot do angielskiego bełkotu”, ale nadal miło :)
Kos

Odpowiedzi:

175

const Ti T constsą identyczne. W przypadku typów wskaźników staje się to bardziej skomplikowane:

  1. const char* jest wskaźnikiem do stałej char
  2. char const* jest wskaźnikiem do stałej char
  3. char* const jest stałym wskaźnikiem do (zmiennego) char

Innymi słowy, (1) i (2) są identyczne. Jedynym sposobem utworzenia wskaźnika (a nie pointee) constjest użycie przyrostka- const.

Dlatego wiele osób woli zawsze umieszczać czcionkę constpo prawej stronie (styl „wschodnia stała”): sprawia, że ​​jej położenie względem typu jest spójne i łatwe do zapamiętania (wydaje się również, że anegdotycznie ułatwia to nauczanie początkujących ).

Konrad Rudolph
źródło
2
C ma stałą, podaną: static const char foo [] = "foo"; lepiej nie zmieniać foo.
James Antill
4
K&R C nie miał const; C90 (i C99) tak. Jest nieco ograniczony w porównaniu do C ++, ale jest przydatny.
Mark Baker
czy dotyczy to również referencji?
Ken,
1
@Ken Tak, to to samo.
Konrad Rudolph
1
@ étale-cohomology Słuszna uwaga, dodano. Powinienem tam być przez cały czas.
Konrad Rudolph
339

Sztuczka polega na przeczytaniu deklaracji od tyłu (od prawej do lewej):

const int a = 1; // read as "a is an integer which is constant"
int const a = 1; // read as "a is a constant integer"

Obie są tym samym. W związku z tym:

a = 2; // Can't do because a is constant

Sztuczka z czytaniem wstecz jest szczególnie przydatna, gdy masz do czynienia z bardziej złożonymi deklaracjami, takimi jak:

const char *s;      // read as "s is a pointer to a char that is constant"
char c;
char *const t = &c; // read as "t is a constant pointer to a char"

*s = 'A'; // Can't do because the char is constant
s++;      // Can do because the pointer isn't constant
*t = 'A'; // Can do because the char isn't constant
t++;      // Can't do because the pointer is constant
Ates Goral
źródło
5
a co z "char const * u"? Czy to brzmi „wskaźnik do stałego znaku” czy „wskaźnik, który jest stały do ​​znaku”? Wydaje się niejednoznaczne. Norma mówi o tym pierwszym, ale aby się tego dowiedzieć, należy wziąć pod uwagę zasady pierwszeństwa i asocjatywności.
Panayiotis Karabassis
5
@PanayiotisKarabassis Wszystko należy traktować jako łańcuch przymiotników, bez zamiany miejscami. char const *, czytane od lewej do prawej to: „wskaźnik, stała, znak”. To wskaźnik do const char. Kiedy mówisz „wskaźnik, który jest stały”, przymiotnik „stały” znajduje się na wskaźniku. Tak więc w tym przypadku Twoja lista przymiotników powinna wyglądać tak: „const, pointer, char”. Ale masz rację, ta sztuczka jest niejednoznaczna. To naprawdę „sztuczka”, więcej niż ostateczna „zasada”.
Ates Goral
5
Kiedy deklarujesz dziką kombinację tablicy, funkcji, wskaźnika i wskaźnika funkcji, czytanie wstecz już nie działa (niestety). Możesz jednak czytać te niechlujne deklaracje w spiralny wzór . Inni byli tak sfrustrowani nimi, że wymyślili Go.
Martin JH
@Martin JH: Czy nie można ich rozbić za pomocą czcionek? I / lub używanie odniesień w celu wyeliminowania pośredników?
Peter Mortensen
14

Nie ma różnicy. Obaj deklarują „a” jako liczbę całkowitą, której nie można zmienić.

Miejscem, w którym zaczynają się pojawiać różnice, jest użycie wskaźników.

Obie z nich:

const int *a
int const *a

zadeklarować „a” jako wskaźnik do liczby całkowitej, która się nie zmienia. Można przypisać „a”, ale „* a” nie można.

int * const a

deklaruje „a” jako stały wskaźnik do liczby całkowitej. „* a” można przypisać do, ale „a” nie można.

const int * const a

deklaruje „a” jako stały wskaźnik do stałej liczby całkowitej. Nie można przypisać ani „a”, ani „* a”.

static int one = 1;

int testfunc3 (const int *a)
{
  *a = 1; /* Error */
  a = &one;
  return *a;
}

int testfunc4 (int * const a)
{
  *a = 1;
  a = &one; /* Error */
  return *a;
}

int testfunc5 (const int * const a)
{
  *a = 1;   /* Error */
  a = &one; /* Error */
  return *a;
}
Andru Luvisi
źródło
Ostatni przykład to najprostsze wyjaśnienie, świetnie!
exru
7

Prakash ma rację, twierdząc, że deklaracje są takie same, chociaż może być potrzebne trochę więcej wyjaśnienia przypadku wskaźnika.

„const int * p” jest wskaźnikiem do int, który nie pozwala na zmianę int przez ten wskaźnik. „int * const p” jest wskaźnikiem do int, którego nie można zmienić, aby wskazywało na inną int.

Zobacz https://isocpp.org/wiki/faq/const-correctness#const-ptr-vs-ptr-const .

Fred Larson
źródło
Kotwica („faq-18.5.) Jest zepsuta. Do której z nich należy się odnosić (jest kilka z„ const ”i„ * ”)?
Peter Mortensen
@PeterMortensen: Tak, rotacja linków. Dzięki. Zaktualizowałem link.
Fred Larson
5

const intjest identyczny int const, jak jest prawdziwy w przypadku wszystkich typów skalarnych w C. Ogólnie, deklarowanie parametru funkcji skalarnej jako constniepotrzebne, ponieważ semantyka wywołania przez wartość w C oznacza, że ​​wszelkie zmiany w zmiennej są lokalne dla jej funkcji otaczającej.

Emerick Rogul
źródło
4

To nie jest bezpośrednia odpowiedź, ale powiązana wskazówka. Aby wszystko było proste, zawsze używam konwekcji „ constna zewnątrz”, gdzie przez „na zewnątrz” mam na myśli skrajną lewą lub skrajną prawą. W ten sposób nie ma zamieszania - stała odnosi się do najbliższej rzeczy (albo typu, albo *). Na przykład,



int * const foo = ...; // Pointer cannot change, pointed to value can change
const int * bar = ...; // Pointer can change, pointed to value cannot change
int * baz = ...; // Pointer can change, pointed to value can change
const int * const qux = ...; // Pointer cannot change, pointed to value cannot change
Pat Notz
źródło
6
Być może lepiej będzie, jeśli użyjesz reguły „const tworzy stałą wszystko, co z niej zostanie”. Np. „Int * const foo” sprawia, że ​​wskaźnik jest „const”, ponieważ pozostaje na nim wskaźnik. Jednakże, możesz napisać drugą linię „int const * bar”, tworząc int const, ponieważ jest pozostawiona temu samemu. „int const * const * qux” powoduje, że zarówno int, jak i wskaźnik stają się stałymi, ponieważ jeden z nich zostaje mu raz pozostawiony.
Mecki
4

Są takie same, ale w C ++ jest dobry powód, aby zawsze używać const po prawej stronie. Będziesz spójny wszędzie, ponieważ funkcje składowe const muszą być deklarowane w ten sposób:

int getInt() const;

Zmienia thiswskaźnik funkcji z Foo * constna Foo const * const. Spójrz tutaj.

Nick Westgate
źródło
3
To jest zupełnie inny rodzaj const.
Justin Meiners
1
Dlaczego jest zupełnie inaczej? Wystarczająco różne, by zdobyć głos przeciw.
Nick Westgate
1
Tak, chodzi o różnicę między „const int” a „int const” Twoja odpowiedź nie ma z tym nic wspólnego.
Justin Meiners
1
Powiedziałem, że są takie same. A jednak zaakceptowane i najwyżej ocenione odpowiedzi również dają dodatkowe informacje na temat typów wskaźników. Czy je też odrzuciłeś?
Nick Westgate
3

Tak, są takie same tylko int

i inne dla int*

prakash
źródło
5
(const int *) i (int const *) są takie same, różnią się tylko od (int * const).
James Antill
3

Myślę, że w tym przypadku są takie same, ale oto przykład, w którym porządek ma znaczenie:

const int* cantChangeTheData;
int* const cantChangeTheAddress;
user7545
źródło
2
Rzeczywiście, ale int const * jest tym samym, co pierwszy, więc kolejność int i const nie ma znaczenia, to tylko kolejność * i const.
Mark Baker