Szukaj…


Wprowadzenie

Wskaźnik to adres, który odnosi się do lokalizacji w pamięci. Są one powszechnie używane, aby umożliwić funkcjom lub strukturom danych rozpoznawanie i modyfikowanie pamięci bez konieczności kopiowania pamięci, o której mowa. Wskaźniki są użyteczne zarówno dla typów pierwotnych (wbudowanych), jak i zdefiniowanych przez użytkownika.

Wskaźniki skorzystać z „dereference” * , „adres” & , i „strzałka” -> operatorzy. Operatory „*” i „->” służą do uzyskania dostępu do wskazanej pamięci, a operator & służy do uzyskania adresu w pamięci.

Składnia

  • <Typ danych> * <Nazwa zmiennej>;
  • <Typ danych> * <Nazwa zmiennej> = & <Nazwa zmiennej tego samego typu danych>;
  • <Typ danych> * <Nazwa zmiennej> = <Wartość tego samego typu danych>;
  • int * foo; // Wskaźnik wskazujący wartość całkowitą
  • int * bar = & myIntVar;
  • długi * słupek [2];
  • long * bar [] = {& myLongVar1, & myLongVar2}; // Równa się: długi * pasek [2]

Uwagi

Należy pamiętać o problemach przy deklarowaniu wielu wskaźników na tej samej linii.

int* a, b, c; //Only a is a pointer, the others are regular ints.

int* a, *b, *c; //These are three pointers!

int *foo[2]; //Both *foo[0] and *foo[1] are pointers.

Podstawy wskaźnika

C ++ 11

Uwaga: we wszystkich poniższych nullptr zakłada się istnienie stałej nullptr C ++ 11. We wcześniejszych wersjach zamień nullptr NULL , stałą, która kiedyś odgrywała podobną rolę.

Tworzenie zmiennej wskaźnikowej

Zmienna wskaźnika może zostać utworzona przy użyciu określonej * składni, np. int *pointer_to_int; .
Gdy zmienna ma typ wskaźnika ( int * ), zawiera tylko adres pamięci. Adres pamięci to miejsce, w którym przechowywane są dane typu bazowego ( int ).

Różnica jest wyraźna przy porównywaniu wielkości zmiennej z rozmiarem wskaźnika do tego samego typu:

// Declare a struct type `big_struct` that contains
// three long long ints.
typedef struct {
    long long int foo1;
    long long int foo2;
    long long int foo3;
} big_struct;

// Create a variable `bar` of type `big_struct`
big_struct bar;
// Create a variable `p_bar` of type `pointer to big_struct`.
// Initialize it to `nullptr` (a null pointer).
big_struct *p_bar0 = nullptr;

// Print the size of `bar`
std::cout << "sizeof(bar) = " << sizeof(bar) << std::endl;
// Print the size of `p_bar`.
std::cout << "sizeof(p_bar0) = " << sizeof(p_bar0) << std::endl;

/* Produces:
    sizeof(bar) = 24
    sizeof(p_bar0) = 8
*/

Biorąc adres innej zmiennej

Wskaźniki można przypisywać między sobą tak jak normalne zmienne; w tym przypadku to adres pamięci jest kopiowany z jednego wskaźnika do drugiego, a nie rzeczywiste dane wskazywane przez wskaźnik.
Ponadto mogą przyjmować wartość nullptr która reprezentuje pustą lokalizację pamięci. Wskaźnik równy nullptr zawiera niepoprawne miejsce w pamięci, a zatem nie odnosi się do prawidłowych danych.

Możesz uzyskać adres pamięci zmiennej danego typu, poprzedzając ją adresem operatora & . Wartość zwracana przez & jest wskaźnikiem do typu bazowego, który zawiera adres pamięci zmiennej (która jest prawidłowymi danymi, o ile zmienna nie wykracza poza zakres ).

// Copy `p_bar0` into `p_bar_1`.
big_struct *p_bar1 = p_bar0;

// Take the address of `bar` into `p_bar_2`
big_struct *p_bar2 = &bar;

// p_bar1 is now nullptr, p_bar2 is &bar.

p_bar0 = p_bar2;

// p_bar0 is now &bar.

p_bar2 = nullptr;

// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr

W przeciwieństwie do referencji:

  • przypisanie dwóch wskaźników nie zastępuje pamięci, do której odnosi się przypisany wskaźnik;
  • wskaźniki mogą być zerowe.
  • adres operatora jest jawnie wymagany.

Dostęp do zawartości wskaźnika

Ponieważ pobranie adresu wymaga & , a także dostęp do treści wymaga użycia operatora dereferencyjnego * jako prefiksu. Po odsunięciu wskaźnika staje się on zmienną typu bazowego (w rzeczywistości jest do niego odniesieniem). Można go odczytać i zmodyfikować, jeśli nie const .

(*p_bar0).foo1 = 5;

// `p_bar0` points to `bar`. This prints 5.
std::cout << "bar.foo1 = " << bar.foo1 << std::endl;

// Assign the value pointed to by `p_bar0` to `baz`.
big_struct baz;
baz = *p_bar0;

// Now `baz` contains a copy of the data pointed to by `p_bar0`.
// Indeed, it contains a copy of `bar`.

// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;

Połączenie * i operatora . jest skracany przez -> :

std::cout << "bar.foo1 = " << (*p_bar0).foo1 << std::endl; // Prints 5
std::cout << "bar.foo1 = " <<  p_bar0->foo1  << std::endl; // Prints 5

Dereferencje nieprawidłowych wskaźników

Podczas wyłuskiwania wskaźnika należy upewnić się, że wskazuje on prawidłowe dane. Odwołanie do nieprawidłowego wskaźnika (lub wskaźnika zerowego) może prowadzić do naruszenia dostępu do pamięci lub do odczytu lub zapisu danych śmieciowych.

big_struct *never_do_this() {
   // This is a local variable. Outside `never_do_this` it doesn't exist.
   big_struct retval;
   retval.foo1 = 11;
   // This returns the address of `retval`.
   return &retval;
   // `retval` is destroyed and any code using the value returned
   // by `never_do_this` has a pointer to a memory location that
   // contains garbage data (or is inaccessible).
}

W takim scenariuszu g++ i clang++ poprawnie wydają ostrzeżenia:

(Clang) warning: address of stack memory associated with local variable 'retval' returned [-Wreturn-stack-address]
(Gcc)   warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]

Dlatego należy zachować ostrożność, gdy wskaźniki są argumentami funkcji, ponieważ mogą być zerowe:

void naive_code(big_struct *ptr_big_struct) {
    // ... some code which doesn't check if `ptr_big_struct` is valid.
    ptr_big_struct->foo1 = 12;
}

// Segmentation fault.
naive_code(nullptr);

Operacje wskaźnika

Istnieją dwa operatory wskaźników: Adres operatora (&): Zwraca adres pamięci jego operandu. Contents-of (Dereference) operator (*): Zwraca wartość zmiennej znajdującej się pod adresem podanym przez jej operatora.

int var = 20;
int *ptr;
ptr = &var;

cout << var << endl;
//Outputs 20 (The value of var)

cout << ptr << endl;
//Outputs 0x234f119 (var's memory location)

cout << *ptr << endl;
//Outputs 20(The value of the variable stored in the pointer ptr

Gwiazdka (*) służy do deklarowania wskaźnika w prostym celu wskazania, że jest wskaźnikiem. Nie należy mylić tego z operatorem dereferencyjnym , który służy do uzyskania wartości znajdującej się pod określonym adresem. Są to po prostu dwie różne rzeczy reprezentowane tym samym znakiem.

Wskaźnik arytmetyczny

Przyrost / spadek

Wskaźnik może być zwiększany lub zmniejszany (przedrostek i postfiks). Zwiększenie wskaźnika przesuwa wartość wskaźnika do elementu w tablicy jeden element za aktualnie wskazywanym elementem. Zmniejszenie wskaźnika przenosi go do poprzedniego elementu w tablicy.

Arytmetyka wskaźnika jest niedozwolona, jeśli typ wskazany przez wskaźnik nie jest kompletny. void jest zawsze niepełnym typem.

char* str = new char[10]; // str = 0x010
++str;                    // str = 0x011  in this case sizeof(char) = 1 byte

int* arr = new int[10];   // arr = 0x00100
++arr;                    // arr = 0x00104 if sizeof(int) = 4 bytes

void* ptr = (void*)new char[10];
++ptr;    // void is incomplete.

Jeśli wskaźnik do elementu końcowego zostanie zwiększony, wówczas wskaźnik wskazuje jeden element za końcem tablicy. Taki wskaźnik nie może być wyłuskany, ale można go zmniejszyć.

Zwiększenie wskaźnika do elementu „jeden za końcem” w tablicy lub zmniejszenie wskaźnika do pierwszego elementu w tablicy daje niezdefiniowane zachowanie.

Wskaźnik do obiektu innego niż tablica może być traktowany do celów arytmetyki wskaźnika, tak jakby był tablicą o rozmiarze 1.

Dodawanie odejmowanie

Do wskaźników można dodawać wartości całkowite; działają one jako inkrementacja, ale o konkretną liczbę, a nie o 1. Wartości całkowite można również odjąć od wskaźników, działając jak zmniejszanie wskaźnika. Podobnie jak w przypadku inkrementacji / dekrementacji, wskaźnik musi wskazywać pełny typ.

char* str = new char[10];  // str = 0x010
str += 2;                  // str = 0x010 + 2 * sizeof(char) = 0x012

int* arr = new int[10];    // arr = 0x100
arr += 2;                  // arr = 0x100 + 2 * sizeof(int) = 0x108, assuming sizeof(int) == 4.

Różnice w wskaźnikach

Można obliczyć różnicę między dwoma wskaźnikami dla tego samego typu. Dwa wskaźniki muszą znajdować się w tym samym obiekcie tablicy; w przeciwnym razie niezdefiniowane wyniki zachowania.

Biorąc pod uwagę dwa wskaźniki P i Q w tej samej tablicy, jeśli P jest i tym elementem w tablicy, a Q jest j tym elementem, wówczas P - Q będzie i - j . Typ wyniku to std::ptrdiff_t , z <cstddef> .

char* start = new char[10];  // str = 0x010
char* test = &start[5];
std::ptrdiff_t diff = test - start; //Equal to 5.
std::ptrdiff_t diff = start - test; //Equal to -5; ptrdiff_t is signed.


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow