Wskaźniki

Deklaracja wskaźnika, alokacja pamięci

Wskaźnik jest to adres pod którym przechowywana jest zmienna określonego typu. Deklaracja ma postać:
  typ *nazwa;
np. int *wsk;. Aby można było z tym wskaźnikiem zrobić coś sensownego można przypisać mu adres istniejącej zmiennej. Wykorzystuje się do tego operator pobrania adresu &. Aby otrzymać lub zmienić wartość na którą wskazuje dany wskaźnik należy użyć operatora *. Przykładowo:
  int z;
  int *wsk=&z;

  *wsk=1; // zmienna z będzie miała wartość 1 

Jeśli wsk jest wskaźnikiem danego typu, to można na nim wykonywać operacje arytmetyczne. I tak wsk+1 będzie wskazywał na kolejną zmienną danego typu w pamięci itd.

W języku C++ jak i w C istnieje ścisła zależność pomiędzy tablicami a wskaźnikami. Można powiedzieć, że tablica jest wskaźnikiem stałym (nie prawidłowa jest na przykład instrukcja T++, gdzie T jest tablicą). Przykładowo:

  int T[3]={1,2,3};
  int *wsk=T;
  *wsk=3;   // równoważnie T[0]=3
  wsk++;
  *wsk=2;   // T[1]=2
  wsk++;
  *wsk=1; // T[2]=1

Wskaźników można używać jako tak zwanych iteratorów, co może zwiększać efektywność operacji tablicowych. Przykładowo funkcja kopiująca łańcuchy może wyglądać tak:

char *strcpy(char *dst, const char *src)
{
  char *cp = dst;
  while (*cp++ = *src++);
  return dst;
}

Wskaźnikowi można także dynamicznie (tzn. w trakcie działania programu) przydzielić pamięć. Wykorzystujemy w tym celu operator new:

  int *wsk=new int;  // alokacja nowej zmiennej typu int
  double *wd=new double[10];  // alokacja tablicy
Aby zwolnić wcześniej przydzieloną pamięć wykorzystujemy operator delete:
  delete wsk;
  delete []wd;

Tablica wskaźników a wskaźnik na tablice

  int T1[10][10];
  int (*W1)[10];
  int **W2;
  int *T2[10];

Po takich deklaracjach T1 jest tablicą dwuwymiarową 10x10, W1 wskaźnikiem na tablicę 10-elementową, W2 wskaźnikiem na wskaźnik, zaś T2 tablicą 10 wskaźników. Możliwe są więc przypisania:

  W2=T2;  // T2 jest tablicą wskaźników, a więc wskaźnikiem na wskaźnik
  W1=T1;  // T1 jest tablicą tablic, a więc wskaźnikiem na tablicę
Ale nie możliwe są przypisania W2=T1 lub W1=T2.

Porównaj dwa programy:

Tablice wielowymiarowe

W języku C tablice wielowymiarowe można tworzyć na kilka sposobów. Należy rozróżnić pomiędzy tak zwanymi tablicami prostokątnymi, które swoją strukturą w pamięci nie różnią się od tablic jednowymiarowych, np:
 int T[12][5];
Od tablic tablic, czyli tablic wskaźników (ang. jagged arrays) np:
  int *T2[10];
Tablica T z pierwszego przykładu zajmuje w pamięci zwarty obszar. W drugim przypadku zwarty obszar w pamięci zajmuje jedynie tablica wskaźników do tablic jednowymiarowych (które po tej deklaracji jeszcze nie istnieją). Proszę zwrócić uwagę, że tablice wskazywane przez T2 nie muszą mieć tego samego rozmiaru.

Pewną trudność może sprawiać elastyczne przekazywanie tablic wielowymiarowych do funkcji, gdyż albo, stosując rzutowanie typów, rezygnujemy z określania typów przekazywanych parametrów, albo jesteśmy skazani na wpisanie na stałe wszystkich rozmiarów tablicy z wyjątkiem ostatniego. Ilustrację dla tego problemu może stanowić przykładowe rozwiązanie zadania 2Dfun: frame rotate.

Przykładowe zadania

Napisz własną funkcję int my_strcat(char cel[], const char *s), która dopisuje łańcuch znaków s na koniec łańcucha cel. Użyj wskaźników jak iteratorów.

Napisz funkcję char* strcpy(const char* source), która tworzy kopię łańcucha source, o rozmiarze dokładnie takim, jak potrzeba i zwraca wskaźnik do utworzonej kopii.

Napisz funkcję int* realloc(int* source, int n), która tworzy kopię n elementowej tablicy source, o rozmiarze 2 razy większym niż oryginał, zwraca wskaźnik do utworzonej kopii oraz zwalnia pamięć zajmowaną przez source.

Użyj poprzedniej funkcji do zapamiętywania dowolnie długich ciągów liczb wczytywanych ze standardowego wejścia.

Napisz funkcję int* glue(int **T1, int l1, int k), która sklei wszystkie elementy T1, zawierającej l1 tablic k1 liczbowych w jedną tablicę l1k1 liczbową.

Wróć