logo

Wskaźniki C++

Wskaźniki są symboliczną reprezentacją adresów. Umożliwiają programom symulację call-by-reference, a także tworzenie i manipulowanie dynamicznymi strukturami danych. Iteracja po elementach tablic lub innych struktur danych jest jednym z głównych zastosowań wskaźników.

Adres zmiennej, z którą pracujesz, jest przypisywany do zmiennej wskaźnikowej, która wskazuje na ten sam typ danych (taki jak int lub ciąg).



TCP vs udp

Składnia:

datatype *var_name; int *ptr; // ptr can point to an address which holds int data>
Jak działa wskaźnik w C++

Jak używać wskaźnika?

  • Zdefiniuj zmienną wskaźnikową
  • Przypisanie adresu zmiennej do wskaźnika za pomocą operatora jednoargumentowego (&), który zwraca adres tej zmiennej.
  • Dostęp do wartości przechowywanej w adresie za pomocą operatora jednoargumentowego (*), który zwraca wartość zmiennej znajdującej się pod adresem określonym przez jej operand.

Powodem, dla którego kojarzymy typ danych ze wskaźnikiem, jest: że wie, w ilu bajtach przechowywane są dane . Kiedy zwiększamy wskaźnik, zwiększamy go o rozmiar typu danych, na który wskazuje.

Wskaźniki w C++C++
// C++ program to illustrate Pointers #include  using namespace std; void geeks() {  int var = 20;  // declare pointer variable  int* ptr;  // note that data type of ptr and var must be same  ptr = &var;  // assign the address of a variable to a pointer  cout << 'Value at ptr = ' << ptr << '
';  cout << 'Value at var = ' << var << '
';  cout << 'Value at *ptr = ' << *ptr << '
'; } // Driver program int main()  {   geeks();   return 0; }>

Wyjście
Value at ptr = 0x7ffe454c08cc Value at var = 20 Value at *ptr = 20>

Referencje i wskazówki

Istnieją 3 sposoby przekazywania argumentów C++ do funkcji:



  • Wywołanie według wartości
  • Call-By-Reference z argumentem wskaźnikowym
  • Call-By-Reference z argumentem referencyjnym
C++
// C++ program to illustrate call-by-methods #include  using namespace std; // Pass-by-Value int square1(int n) {  // Address of n in square1() is not the same as n1 in  // main()  cout << 'address of n1 in square1(): ' << &n << '
';  // clone modified inside the function  n *= n;  return n; } // Pass-by-Reference with Pointer Arguments void square2(int* n) {  // Address of n in square2() is the same as n2 in main()  cout << 'address of n2 in square2(): ' << n << '
';  // Explicit de-referencing to get the value pointed-to  *n *= *n; } // Pass-by-Reference with Reference Arguments void square3(int& n) {  // Address of n in square3() is the same as n3 in main()  cout << 'address of n3 in square3(): ' << &n << '
';  // Implicit de-referencing (without '*')  n *= n; } void geeks() {  // Call-by-Value  int n1 = 8;  cout << 'address of n1 in main(): ' << &n1 << '
';  cout << 'Square of n1: ' << square1(n1) << '
';  cout << 'No change in n1: ' << n1 << '
';  // Call-by-Reference with Pointer Arguments  int n2 = 8;  cout << 'address of n2 in main(): ' << &n2 << '
';  square2(&n2);  cout << 'Square of n2: ' << n2 << '
';  cout << 'Change reflected in n2: ' << n2 << '
';  // Call-by-Reference with Reference Arguments  int n3 = 8;  cout << 'address of n3 in main(): ' << &n3 << '
';  square3(n3);  cout << 'Square of n3: ' << n3 << '
';  cout << 'Change reflected in n3: ' << n3 << '
'; } // Driver program int main() { geeks(); }>

Wyjście
address of n1 in main(): 0x7fffa7e2de64 address of n1 in square1(): 0x7fffa7e2de4c Square of n1: 64 No change in n1: 8 address of n2 in main(): 0x7fffa7e2de68 address of n2 in square2(): 0x7fffa7e2de68 Square of n2: 64 Change reflected in n2: 64 address of n3 in main(): 0x7fffa7e2de6c address of n3 in square3(): 0x7fffa7e2de6c Square of n3: 64 Change reflected in n3: 64>

W C++ domyślnie argumenty przekazywane są przez wartość, a zmiany dokonane w wywoływanej funkcji nie będą odzwierciedlane w przekazywanej zmiennej. Zmiany są wprowadzane w klon wykonany przez wywołaną funkcję. Jeśli chcesz bezpośrednio zmodyfikować oryginalną kopię (szczególnie przekazując ogromny obiekt lub tablicę) i/lub uniknąć narzutu związanego z klonowaniem, używamy przekazywania przez odwołanie. Przekazywanie referencji z argumentami referencyjnymi nie wymaga żadnej niezdarnej składni do odwoływania się i dereferencji.

Nazwa tablicy jako wskaźniki

Jakiś szyk nazwa zawiera adres pierwszego elementu tablicy, który działa jak stały wskaźnik. Oznacza to, że adresu zapisanego w nazwie tablicy nie można zmienić. Na przykład, jeśli mamy tablicę o nazwie val, to wal I &wart[0] można używać zamiennie.

C++
// C++ program to illustrate Array Name as Pointers #include  using namespace std; void geeks() {  // Declare an array  int val[3] = { 5, 10, 20 };  // declare pointer variable  int* ptr;  // Assign the address of val[0] to ptr  // We can use ptr=&val[0];(both are same)  ptr = val;  cout << 'Elements of the array are: ';  cout << ptr[0] << ' ' << ptr[1] << ' ' << ptr[2]; } // Driver program int main() { geeks(); }>

Wyjście
Elements of the array are: 5 10 20>
Reprezentacja danych w pamięci

Jeśli wskaźnik ptr zostanie wysłany do funkcji jako argument, dostęp do tablicy val można uzyskać w podobny sposób. Wskaźnik kontra tablica



Wyrażenia wskaźnikowe i arytmetyka wskaźników

Limitowany zestaw arytmetyka Na wskaźnikach można wykonywać operacje:

liczba całkowita do podwójnego Java
  • zwiększone ( ++ )
  • zmniejszone (-)
  • do wskaźnika można dodać liczbę całkowitą ( + lub += )
  • liczbę całkowitą można odjąć od wskaźnika ( – lub -=)
  • różnica między dwoma wskaźnikami (p1-p2)

( Notatka: Arytmetyka wskaźników jest bez znaczenia, jeśli nie jest wykonywana na tablicy.)

C++
// C++ program to illustrate Pointer Arithmetic #include  using namespace std; void geeks() {  // Declare an array  int v[3] = { 10, 100, 200 };  // declare pointer variable  int* ptr;  // Assign the address of v[0] to ptr  ptr = v;  for (int i = 0; i < 3; i++) {  cout << 'Value at ptr = ' << ptr << '
';  cout << 'Value at *ptr = ' << *ptr << '
';  // Increment pointer ptr by 1  ptr++;  } } // Driver program int main() { geeks(); }>

Wyjście
Value at ptr = 0x7ffe5a2d8060 Value at *ptr = 10 Value at ptr = 0x7ffe5a2d8064 Value at *ptr = 100 Value at ptr = 0x7ffe5a2d8068 Value at *ptr = 200>
Reprezentacja danych w pamięci

Zaawansowana notacja wskaźnikowa

Rozważmy notację wskaźnikową dla dwuwymiarowych tablic numerycznych. rozważ następującą deklarację

int nums[2][3] = { { 16, 18, 20 }, { 25, 26, 27 } };>

Ogólnie rzecz biorąc, nums[ i ][ j ] jest równoważne *(*(nums+i)+j)

Notacja wskaźników w C++

Wskaźniki i literały łańcuchowe

Literały łańcuchowe to tablice zawierające sekwencje znaków zakończone znakiem null. Literały łańcuchowe to tablice typu znak plus kończący znak null, przy czym każdy z elementów jest typu const char (ponieważ znaków ciągu nie można modyfikować).

>

To deklaruje tablicę z dosłowną reprezentacją maniaka, a następnie wskaźnik do jej pierwszego elementu jest przypisywany do ptr. Jeśli wyobrazimy sobie, że geek jest przechowywany w komórkach pamięci zaczynających się od adresu 1800, możemy przedstawić poprzednią deklarację jako:

Wskaźniki i literały łańcuchowe

Ponieważ wskaźniki i tablice zachowują się w wyrażeniach w ten sam sposób, ptr może służyć do uzyskiwania dostępu do znaków literału łańcuchowego. Na przykład:

char ptr = 0; char x = *(ptr+3); char y = ptr[3];>

Tutaj zarówno x, jak i y zawierają k zapisane w 1803 (1800+3).

szybkie sortowanie

Wskaźniki do wskaźników

W C++ możemy utworzyć wskaźnik do wskaźnika, który z kolei może wskazywać na dane lub inny wskaźnik. Składnia wymaga po prostu operatora jednoargumentowego (*) dla każdego poziomu pośredniego podczas deklarowania wskaźnika.

char a; char *b; char ** c; a = ’g’; b = &a; c = &b;>

Tutaj b wskazuje na znak, który przechowuje „g”, a c wskazuje na wskaźnik b.

Wskaźniki pustki

Jest to specjalny typ wskaźnika dostępny w C++, który reprezentuje brak typu. Puste wskaźniki są wskaźnikami wskazującymi na wartość, która nie ma typu (a zatem także o nieokreślonej długości i nieokreślonych właściwościach dereferencji). Oznacza to, że wskaźniki void mają dużą elastyczność, ponieważ mogą wskazywać na dowolny typ danych. Ta elastyczność się opłaca. Do tych wskaźników nie można bezpośrednio odwoływać się. Należy je najpierw przekształcić w inny typ wskaźnika, który wskazuje konkretny typ danych, zanim zostaną wyłuskane.

prawidłowe identyfikatory Java
C++
// C++ program to illustrate Void Pointer #include  using namespace std; void increase(void* data, int ptrsize) {  if (ptrsize == sizeof(char)) {  char* ptrchar;  // Typecast data to a char pointer  ptrchar = (char*)data;  // Increase the char stored at *ptrchar by 1  (*ptrchar)++;  cout << '*data points to a char'  << '
';  }  else if (ptrsize == sizeof(int)) {  int* ptrint;  // Typecast data to a int pointer  ptrint = (int*)data;  // Increase the int stored at *ptrchar by 1  (*ptrint)++;  cout << '*data points to an int'  << '
';  } } void geek() {  // Declare a character  char c = 'x';  // Declare an integer  int i = 10;  // Call increase function using a char and int address  // respectively  increase(&c, sizeof(c));  cout << 'The new value of c is: ' << c << '
';  increase(&i, sizeof(i));  cout << 'The new value of i is: ' << i << '
'; } // Driver program int main() { geek(); }>

Wyjście
*data points to a char The new value of c is: y *data points to an int The new value of i is: 11>

Nieprawidłowe wskaźniki

Wskaźnik powinien wskazywać prawidłowy adres, ale niekoniecznie prawidłowe elementy (jak w przypadku tablic). Są to tak zwane nieprawidłowe wskaźniki. Niezainicjowane wskaźniki są również nieprawidłowymi wskaźnikami.

int *ptr1; int arr[10]; int *ptr2 = arr+20;>

W tym przypadku ptr1 nie został zainicjowany, więc staje się nieprawidłowym wskaźnikiem, a ptr2 wykracza poza zakres arr, więc również staje się nieprawidłowym wskaźnikiem. (Uwaga: nieprawidłowe wskaźniki niekoniecznie powodują błędy kompilacji)

Wskaźniki NULL

A wskaźnik zerowy jest wskaźnikiem, który wskazuje donikąd i nie jest tylko nieprawidłowym adresem. Poniżej przedstawiono 2 metody przypisania wskaźnikowi wartości NULL;

int *ptr1 = 0; int *ptr2 = NULL;>

Zalety wskaźników

  • Wskaźniki redukują kod i poprawiają wydajność. Służą do pobierania ciągów znaków, drzew, tablic, struktur i funkcji.
  • Wskaźniki pozwalają nam zwracać wiele wartości z funkcji.
  • Oprócz tego wskaźniki umożliwiają nam dostęp do miejsca w pamięci komputera.

Powiązane artykuły:

  • Nieprzezroczysty wskaźnik
  • Blisko, daleko i ogromne wskaźniki

Quizy:

  • Podstawy wskaźników
  • Zaawansowany wskaźnik