Rozważ następujący fragment kodu:
int main()
{
int x { 5 };
int* ptr { &x }; // ptr is a normal (non-const) pointer
int y { 6 };
ptr = &y; // we can point at another value
*ptr = 7; // we can change the value at the address being held
return 0;
}W przypadku normalnych (innych niż stałe) wskaźników możemy zmienić zarówno to, na co wskazuje wskaźnik (poprzez przypisanie wskaźnikowi nowego adresu do przechowywania), jak i zmienić wartość pod przechowywanym adresem (przypisując nową wartość do wyłuskanego wskaźnika).
Co się jednak stanie, jeśli wartość, na którą chcemy wskazać, to const?
int main()
{
const int x { 5 }; // x is now const
int* ptr { &x }; // compile error: cannot convert from const int* to int*
return 0;
}Powyższy fragment nie zostanie skompilowany — nie możemy ustawić normalnego wskaźnika tak, aby wskazywał na zmienną const. Ma to sens: zmienna const to taka, której wartości nie można zmienić. Zezwolenie programiście na ustawienie wskaźnika innego niż stały na wartość stałą umożliwiłoby programiście wyłuskanie wskaźnika i zmianę wartości. Naruszyłoby to stałość zmiennej.
Wskaźnik na wartość stałą
A wskaźnik do wartości stałej (czasami nazywany pointer to const w skrócie) jest (niestałym) wskaźnikiem, który wskazuje na wartość stałą.
Aby zadeklarować wskaźnik na wartość stałą, użyj słowa kluczowego const przed typem danych wskaźnika:
int main()
{
const int x{ 5 };
const int* ptr { &x }; // okay: ptr is pointing to a "const int"
*ptr = 6; // not allowed: we can't change a const value
return 0;
}W powyższym przykładzie ptr wskazuje na const int. Ponieważ wskazywanym typem danych jest const, nie można zmienić wskazywanej wartości.
Jednakże ponieważ wskaźnik do const sam w sobie nie jest const (po prostu wskazuje na wartość const), możemy zmienić to, na co wskazuje wskaźnik, przypisując mu nowy adres:
int main()
{
const int x{ 5 };
const int* ptr { &x }; // ptr points to const int x
const int y{ 6 };
ptr = &y; // okay: ptr now points at const int y
return 0;
}Podobnie jak odwołanie do const, wskaźnik do const może również wskazywać na zmienne inne niż const. Wskaźnik do const traktuje wskazywaną wartość jako stałą, niezależnie od tego, czy obiekt pod tym adresem był początkowo zdefiniowany jako const, czy nie:
int main()
{
int x{ 5 }; // non-const
const int* ptr { &x }; // ptr points to a "const int"
*ptr = 6; // not allowed: ptr points to a "const int" so we can't change the value through ptr
x = 6; // allowed: the value is still non-const when accessed through non-const identifier x
return 0;
}Wskaźniki const
Możemy również ustawić sam wskaźnik jako stały. A const wskaźnik jest wskaźnikiem, którego adresu nie można zmienić po inicjalizacji.
Aby zadeklarować wskaźnik const, użyj const słowa kluczowego po gwiazdce w deklaracji wskaźnika:
int main()
{
int x{ 5 };
int* const ptr { &x }; // const after the asterisk means this is a const pointer
return 0;
}W powyższym przypadku ptr jest stałym wskaźnikiem do (innej niż stała) wartości int.
Podobnie jak zwykła zmienna const, wskaźnik const musi zostać zainicjowany przy definicji i tej wartości nie można zmienić za pomocą przypisanie:
int main()
{
int x{ 5 };
int y{ 6 };
int* const ptr { &x }; // okay: the const pointer is initialized to the address of x
ptr = &y; // error: once initialized, a const pointer can not be changed.
return 0;
}Jednakże, ponieważ wskazywana wartości nie jest stała, możliwa jest zmiana wskazywanej wartości poprzez wyłuskanie wskaźnika const:
int main()
{
int x{ 5 };
int* const ptr { &x }; // ptr will always point to x
*ptr = 6; // okay: the value being pointed to is non-const
return 0;
}Wskaźnik const na wartość stałą
Na koniec możliwe jest zadeklarowanie const wskaźnik do wartości stałej używając const słowa kluczowego zarówno przed typem, jak i po gwiazdka:
int main()
{
int value { 5 };
const int* const ptr { &value }; // a const pointer to a const value
return 0;
}Nie można zmienić adresu stałego wskaźnika na wartość stałą, ani też wartości, którą wskazuje, nie można zmienić za pomocą wskaźnika. Można go wyłuskać jedynie w celu uzyskania wartości, na którą wskazuje.
Podsumowanie wskaźników i stałych
Podsumowując, wystarczy zapamiętać tylko 4 reguły, a są one całkiem logiczne:
- Wskaźnikowi niebędącemu stałym (np.
int* ptr) można przypisać inny adres, aby zmienić to, na co wskazuje. - A wskaźnik const (np.
int* const ptr) zawsze wskazuje na ten sam adres i adresu tego nie można zmienić.
- Wskaźnik na wartość inną niż stała (np.
int* ptr) może zmienić wartość, na którą wskazuje. Nie mogą one wskazywać na wartość stałą. - Wskaźnik na wartość stałą (np.
const int* ptr) traktuje wartość jako stałą, gdy jest dostępny za pośrednictwem wskaźnika i dlatego nie może zmienić wartości, na którą wskazuje. Można je wskazać na stałe lub niestałe wartości l (ale nie na wartości r, które nie mają adresu).
Utrzymanie prostej składni deklaracji może być pewnym wyzwaniem:
- A
constzanim gwiazdka (np.const int* ptr) zostanie powiązana ze wskazywanym typem. Jest to zatem wskaźnik do wartości stałej i wartości tej nie można modyfikować poprzez wskaźnik. - A
constpo gwiazdce (np.int* const ptr) jest ona powiązana z samym wskaźnikiem. Dlatego temu wskaźnikowi nie można przypisać nowego adresu.
int main()
{
int v{ 5 };
int* ptr0 { &v }; // points to an "int" but is not const itself. We can modify the value or the address.
const int* ptr1 { &v }; // points to a "const int" but is not const itself. We can only modify the address.
int* const ptr2 { &v }; // points to an "int" and is const itself. We can only modify the value.
const int* const ptr3 { &v }; // points to a "const int" and is const itself. We can't modify the value nor the address.
// if the const is on the left side of the *, the const belongs to the value
// if the const is on the right side of the *, the const belongs to the pointer
return 0;
}
