C/Wskaźniki - więcej: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Lethern (dyskusja | edycje)
mNie podano opisu zmian
Lethern (dyskusja | edycje)
przeniesiono z C/Wskaźniki
Linia 222:
<ref name="little endian">Ponownie przyjmując, że bajt ma 8 bitów, int dwa bajty i liczby zapisywane są w formacie little endian</ref>
}}
 
 
== Tablice wielowymiarowe ==
[[Grafika:Array of array storage.svg|thumb|180px|tablica dwuwymiarowa &mdash; w rzeczywistości tablica ze wskaźnikami do tablic]]
 
W rozdziale [[../Tablice/]] pokazaliśmy, jak tworzyć tablice wielowymiarowe, gdy ich rozmiar jest znany w czasie kompilacji. Teraz zaprezentujemy, jak to wykonać za pomocą wskaźników i to w sytuacji, gdy rozmiar może się zmieniać. Załóżmy, że chcemy stworzyć tabliczkę mnożenia:
<source lang="C">
int ROZMIAR;
printf("Podaj rozmiar tabliczki mnozenia: ");
int **tabliczka = malloc(ROZMIAR * sizeof *tabliczka); /* 1 */
*tabliczka = malloc(ROZMIAR * ROZMIAR * sizeof **tabliczka); /* 2 */
/* tutaj można pętlą/funkcjami operującymi na pamięci zainicjować tabliczkę mnożenia */
free(*tabliczka); /* 5 */
free(tabliczka); /* 6 */
tabliczka = NULL; /* 7 */
</source>
 
Najpierw musimy przydzielić pamięć - najpierw dla "tablicy tablic" (1) a potem dla wszystkich elementów tablicę (2). Ponieważ tablica jest typu int* to nasza tablica tablic będzie wskaźnikiem na int* czyli int**. Tablicę zwalniamy najpierw zwalniając wskaźnik na elementy tablic, a potem całą tablicę:
 
<source lang="C">
free(*tabliczka); /* 5 */
free(tabliczka); /* 6 */
</source>
 
Należy nie pomylić kolejności. Gdybyśmy najpierw pozbyli się wskaźnika na wskaźniki, a dopiero potem pozbyli się poszczególnych wskaźników na tablice, program będzie próbował odwołać się do pamięci, która nie była mu przydzielona.
 
Jeśli chodzi o linie:
<source lang="c">
int **tabliczka = malloc(ROZMIAR * sizeof *tabliczka); /* 1 */
*tabliczka = malloc(ROZMIAR * ROZMIAR * sizeof **tabliczka); /* 2 */
</source>
to linie te alokują pamięć. Najczęstszą konstrukcją są dwa alokowania pamięci: najpierw do wskaźnika <code>int **</code> przypisujemy bajty zaalokowane w pamięci o ilości <code>ROZMIAR * sizeof **tabliczka</code>. Jest to już ci znane, znacznie ciekawsza jest druga alokacja pamięci. Różni się ona od pierwszej tym, że przekazujemy jako ilość elementów '''kwadrat''' rozmiaru tablicy: <code>ROZMIAR * ROZMIAR</code>.
 
Zauważmy, że w ten sposób możemy uzyskać nie tylko normalną, "kwadratową" tablicę (dla dwóch wymiarów). Możliwe jest np. uzyskanie tablicy trójkątnej:
0123
012
01
0
lub tablicy o dowolnym innym rozkładzie długości wierszy, np.:
 
<source lang="C">
const size_t wymiary[] = { 2, 4, 6, 8, 1, 3, 5, 7, 9 };
int i;
int **tablica = malloc((sizeof wymiary / sizeof *wymiary) * sizeof *tablica);
for (i = 0; i<10; ++i) {
tablica[i] = malloc(wymiary[i] * sizeof **tablica);
}
</source>
 
Gdy nabierzesz wprawy w używaniu wskaźników oraz innych funkcji malloc i realloc nauczysz się wykonywać różne inne operacje takie jak dodawanie kolejnych wierszy, usuwanie wierszy, zmiana rozmiaru wierszy, zamiana wierszy miejscami itp.
 
== Wskaźniki na funkcje ==
Dotychczas zajmowaliśmy się sytuacją, gdy wskaźnik wskazywał na jakąś zmienną. Jednak nie tylko zmienna ma swój adres w pamięci. Oprócz zmiennej także i funkcja musi mieć swoje określone miejsce w pamięci. A ponieważ funkcja ma swój adres{{r|addr}}, to nie ma przeszkód, aby i na nią wskazywał jakiś wskaźnik.
 
=== Deklaracja wskaźnika na funkcję ===
Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji.
Wskaźnik na funkcję różni się od innych rodzajów wskaźników. Jedną z głównych różnic jest jego deklaracja. Zwykle wygląda ona tak:
 
typ_zwracanej_wartości (*nazwa_wskaźnika)(typ1 argument1, typ2 argument2);
 
Oczywiście argumentów może być więcej (albo też w ogóle może ich nie być). Oto przykład wykorzystania wskaźnika na funkcję:
<source lang="C">
#include <stdio.h>
int suma (int lhs, int rhs)
{
return lhs+rhs;
}
int main ()
{
int (*wsk_suma)(int a, int b);
wsk_suma = suma;
printf("4+5=%d\n", wsk_suma(4,5));
return 0;
}
</source>
 
Zwróćmy uwagę na dwie rzeczy:
# przypisując nazwę funkcji bez nawiasów do wskaźnika automatycznie informujemy kompilator, że chodzi nam o '''adres''' funkcji
# wskaźnika używamy tak, jak normalnej funkcji, na którą on wskazuje
 
=== Do czego można użyć wskaźników na funkcje? ===
Język C jest językiem strukturalnym, jednak dzięki wskaźnikom istnieje w nim możliwość "zaszczepienia" pewnych obiektowych właściwości. Wskaźnik na funkcję może być np. elementem struktury - wtedy mamy bardzo prymitywną namiastkę [[C++/Czym jest obiekt|klasy]], którą dobrze znają programiści, piszący w języku [[C++]]. Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrotnej<ref>Funkcje zwrotne znalazły zastosowanie głównie w programowaniu [[w:GUI|GUI]]</ref>. Dobrym przykładem może być np. tworzenie sterowników, gdzie musimy poinformować różne podsystemy, jakie funkcje w naszym kodzie służą do wykonywania określonych czynności. Przykład:
<source lang="C">
struct urzadzenie {
int (*otworz)(void);
void (*zamknij)(void);
int (*rejestruj)(void);
};
typedef struct urzadzenie urzadzenie;
int moje_urzadzenie_otworz (void)
{
/* kod...*/
}
void moje_urzadzenie_zamknij (void)
{
/* kod... */
}
int rejestruj_urzadzenie(void) {
/* kod... */
}
urzadzenie create (void)
{
urzadzenie moje_urzadzenie;
moje_urzadzenie.otworz = moje_urzadzenie_otworz;
moje_urzadzenie.zamknij = moje_urzadzenie_zamknij;
moje_urzadzenie.rejestruj = rejestruj_urzadzenie;
moje_urzadzenie.rejestruj();
return moje_urzadzenie;
}
</source>
 
W ten sposób w pamięci każda ''klasa'' musi przechowywać wszystkie wskaźniki do wszystkich ''metod''. Innym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i wówczas w strukturze będzie przechowywany jedynie wskaźnik do tej struktury, np.:
<source lang="C">
struct urzadzenie_metody {
int (*otworz)(void);
void (*zamknij)(void);
int (*rejestruj)(void);
};
struct urzadzenie {
const struct urzadzenie_metody *m;
};
typedef struct urzadzenie urzadzenie;
int moje_urzadzenie_otworz (void)
{
/* kod...*/
}
void moje_urzadzenie_zamknij (void)
{
/* kod... */
}
static const struct urzadzenie_metody
moje_urzadzenie_metody = {
moje_urzadzenie_otworz,
moje_urzadzenie_zamknij,
rejestruj_urzadzenie
};
int rejestruj_urzadzenie(void) {
/* kod... */
}
urzadzenie create (void)
{
urzadzenie moje_urzadzenie;
moje_urzadzenie.m = &moje_urzadzenie_metody;
moje_urzadzenie.rejestruj();
return moje_urzadzenie;
}
</source>
 
== Ciekawostki ==
* w rozdziale [[Programowanie:C:Zmienne#Stałe|Zmienne]] pisaliśmy o stałych. Normalnie nie mamy możliwości zmiany ich wartości, ale z użyciem wskaźników staje się to możliwe:
 
<source lang="C">
const int CONST = 0;
int *c = &CONST;
*c = 1;
printf("%i\n", CONST); /* wypisuje 1 */
</source>
 
Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd - wtedy może pomóc jawne rzutowanie z <code>const int*</code> na <code>int*</code>.
 
=== C++ ===
* język [[C++]] oferuje mechanizm podobny do wskaźników, ale nieco wygodniejszy &ndash; [[C++/Referencje|referencje]]
* język [[C++]] dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci - przez operatory [[C++/Zarządzanie pamięcią|new i delete]]
* język [[C++]] oferuje mechanizm podobny do dynamicznej alokacji i zwalniania pamięci, ale nieco wygodniejszy - [[C++/String|klasa string]] i [[C++/Vector|klasa vector]]
* w rozdziale [[C/Typy złożone#Studium przypadku - implementacja listy wskaźnikowej|Typy złożone]] znajduje się opis implementacji listy za pomocą wskaźników. Przykład ten może być bardzo przydatny przy zrozumieniu, po co istnieją wskaźniki, jak się nimi posługiwać oraz jak dobrze zarządzać pamięcią.