#1 04-01-2007 00:17:52

mieczyk

Member

3375619
Zarejestrowany: 03-08-2007
Posty: 24
Punktów :   
WWW

[Język C] Wskaźniki - wprowadzenie.

Wskaźniki to temat bardzo obszerny. Z tego powodu, postanowiłem umieszczać teksty o wskaźnikach stopniowo. Poniższy tekst stanowi wstęp do wskaźników w języku C. Tekst został napisany językiem prostym, zrozumiałym nawet dla laika. Jeżeli zauważycie jakiekolwiek błędy, proszę o ich sprostowanie. Życzę miłej lektury...

Definicja wskaźnika.

Wskaźnik jest to specjalny rodzaj zmiennej, w której jest zapisany adres w pamięci komputera. Nie służy on do przechowywania standardowych informacji, lecz wskazuje na adres pamięci, w którym te informacje się znajdują. Stąd ich nazwa.

----------------------------------------------------------------------------------------------------------------------------------

W języku C, do zmiennej możemy się odwołać na dwa sposoby:

- poprzez jej nazwę,
- poprzez jej adres w pamięci.

W pierwszym przypadku odnosimy się do wartości przechowywanej w pamięci, w drugim natomiast odwołujemy się do adresu w pamięci, gdzie jest przechowywana zmienna.
W celu intuicyjnego zrozumienia, czym jest adres zmiennej posłużę się uproszczonym rysunkiem.

Załóżmy, że przypisaliśmy do zmiennej dowolną wartość, np.

                                  zmienna = 35;

Ta wartość zostaje zapisana w jednej z komórek pamięci, co pokazuje ten rysunek:

                              +------+
                              |         |  1005
                              +------+ 
                              |         |  1004
                              +------+
                              |  35    |  1003         (zmienna)
                              +------+
                              |         |  1002
                              +------+         
                                  ...                 \
                       /                                \
                     /                                    \
             Komórki pamięci                   Adres komórki pamięci

Widzimy, że wartość zmiennej (czyli 35) została zapisana w pamięci pod adresem 1003.

(Adres jest tylko przykładowy, ponieważ adresowanie pamięci zależy od systemu.)

W języku C istnieje możliwość odczytania adresu naszej zmiennej za pomocą operatora pobrania adresu (&).

Popatrzmy na poniższy przykład:

Kod:

#include <stdio.h>
#include <stdlib.h>

int main()
{
     int x = 35;

     printf("Wartosc zmiennej x: %d\n", x);
     printf("Adres zmiennej x: %d\n", &x);          /* lub %x, czyli heksadecymalnie */ 

     getchar();
     return 0;
}

+ Na początku zadeklarowaliśmy zmienną x typu int i przypisaliśmy jej wartość 35.
+ Pierwszy "printf" najzwyczajniej w świecie wypisuje wartość naszej zmiennej. Drugi natomiast wypisze nam ADRES zmiennej x, gdyż umieściliśmy przed zmienną x operator pobrania adresu.

Widzimy, że jeśli chcemy pobrać adres dowolnej zmiennej, wystarczy dodać & przed jej nazwą.


----------------------------------------------------------------------------------------------------------------------------------
Uwaga! Operator pobrania adresu & może być używany tylko w wyrażeniu. Nie jest zmienną, ponieważ przedstawia tylko liczbę całkowitą. Dlatego instrukcja:

                                &x = 35;
NIE jest dozwolona.
----------------------------------------------------------------------------------------------------------------------------------

Wskaźnik nie jest niczym innym jak pewnym rodzajem zmiennej, która przechowuje adres innej zmiennej. Przykładowo, jeżeli x jest przechowywany pod adresem 21602, to wskaźnik do zmiennej x będzie zawierał wartość 21602.

Aby utworzyć zmienną wskaźnikową w języku C należy użyć następującej składni:

                                       typ *nazwa_zmiennej;

  typ - jest to typ zmiennej wskazywanej (jeżeli zmienna jest typu int, to jej wskaźnik też musi być typu int).
  * - gwiazdka oznacza zmienną wskaźnikową.
  nazwa_zmiennej - nazwa zmiennej wskaźnikowej.

Spójrzmy na kolejny przykład:

Kod:

#include <stdio.h>
#include <stdlib.h>

int main()
{
     int x = 35;
     int *wsk_x;

     wsk_x = &x; 

     printf("Wartosc zmiennej x: %d\n", x);
     printf("Adres zmiennej x: %d\n", wsk_x);      

     getchar();
     return 0;
}

+ Powyższy przykład nieznacznie różni się od poprzedniego.
+ Tutaj oprócz zmiennej x, deklarujemy zmienną wskaźnikową o nazwie wsk_x. Następnie przypisujemy do niej adres zmiennej x.
+ Jak widzimy, program wyświetli na początku wartość zmiennej, a potem wyświetli jej adres, który jest przechowywany w naszej zmiennej wskaźnikowej.


Spróbujmy teraz dokonać pewnej zmiany w ostatnim przykładzie. Dopiszmy linijkę:

               printf( "Wartosc zmiennej x: %d\n", *wsk_x);

Widzimy, że powyższa linijka spowoduje wyświetlenie rzeczywistej wartości zmiennej x, czyli 35. Stało się tak, ponieważ przed zmienną wskaźnikową umieściliśmy znak *.  Jest to operator dereferencji, nazywany też operatorem wyłuskania, ponieważ powoduje "wydobycie" wartości znajdującej się pod adresem przechowywanym w zmiennej wskaźnikowej.


                      int x = 35;
                      int *wsk_x;
                      wsk_x = &x;

                              +------+
                              |         |  1005
                              +------+ 
                              |         |  1004
                              +------+
                              |  35    |  1003        (x)
                              +------+
                              |         |  1002
                              +------+       

             
                      INSTRUKCJA       |   CO WYŚWIETLI
          --------------------------------------------------------------
            printf("%d", x);             |         35
          --------------------------------------------------------------
            printf("%d", &x);           |        1003       
          --------------------------------------------------------------
            printf("%d", wsk_x);      |        1003
          --------------------------------------------------------------
            printf("%d", *wsk_x);    |         35
          --------------------------------------------------------------

Należy również pamiętać, że zmienna wskaźnikowa też jest przechowywana w pamięci pod pewnym adresem. Spójrzmy na poniższy rysunek:

                              +------+
                              | 1003 |  1005         (wsk_x)                                int *wsk_x;
                              +------+                                                             int x = 35;
                              |         |  1004                                                     wsk_x = &x;
                              +------+
                              |  35    |  1003        (x)
                              +------+
                              |         |  1002
                              +------+       

Adres zmiennej wskaźnikowej możemy wyświetlić:

                                printf("%d", &wsk_x);

Zerknijmy na kolejny przykład:

Kod:

#include <stdio.h>
#include <stdlib.h>

int main()
{
     int x = 35;
     int *wsk_x;
     int **wsk_wsk_x;

     wsk_x = &x;
     wsk_wsk_x = &wsk_x;

     printf("Wartosc zmiennej x: %d\n", **wsk_wsk_x);
     printf("Adres zmiennej x: %d\n", *wsk_wsk_x);
     printf("Adres zmiennej wskaznikowej wsk_x: %d\n", wsk_wsk_x);

    getchar();
    return 0;
}

+ Tym razem, oprócz wskaźnika na zmienną x, chcemy użyć drugiego wskaźnika, w tym przypadku na zmienną wsk_x, która też jest wskaźnikiem. Stąd te dwie gwiazdki przed zmienną wsk_wsk_x. (Uwaga! Gdybyśmy chcieli zrobić drugi wskaźnik, ale na zmienną niewskaźnikową, to używany tylko jednej gwiazdki).
+ Spójrzmy na ten fragment kodu:

                 wsk_x = &x;
                 wsk_wsk_x = &wsk_x;

Najpierw przypisujemy wskaźnikowi wsk_x adres zmiennej x, a następnie wskaźnikowi wsk_wsk_x przypisujemy adres zmiennej wskaźnikowej wsk_x.
+ Zerknijmy teraz na instrukcje "printf". Pierwszy "printf" wyświetli nam wartość zmiennej x, ponieważ użyliśmy dwóch gwiazdek (operator wyłuskania). Oznaczo to, że najpierw "wydobyliśmy" wartość zmiennej wskaźnikowej wsk_x (czyli inaczej adres zmiennej x), a następnie wydobyliśmy wartość zmiennej x. Następne instrukcje są chyba jasne. Drugi "printf" wyświetla adres zmiennej x (czyli to co przechowuje zmienna wskaźnikowa wsk_x), a ostatni "printf" wyświetla to, co przechowuje zmienna wsk_wsk_x (czyli adres zmiennej wsk_x).


Poniższy rysunek powinien pomóc w zrozumieniu czym jest wskaźnik do innego wskaźnika. Załóżmy, że kwadracik to nasza zmienna:


                                          *wsk_wsk_x                              **wsk_wsk_x
                              ,----------------------------->             ,--------------------------->       
                  +--------+                                      +--------+                                   +-------+
                  |   1005  |                                      |  1003   |                                   |   35    |
                  +--------+                                      +--------+                                   +-------+
                  nazwa: wsk_wsk_x  --------             nazwa: wsk_x -----------               nazwa: x
                  adres: 1007                       '------>  adres: 1005                  '-------->  adres: 1003


 
                            INSTRUKCJA   |    INSTRUKCJA RÓWNOZNACZNA
   -----------------------------------------------------------------------------------------------
      printf("%d", wsk_wsk_x);       |        printf("%d", &wsk_x);
   -----------------------------------------------------------------------------------------------
      printf("%d", *wsk_wsk_x);     |        printf("%d", wsk_x);
                                                 |        printf("%d", &x);
   -----------------------------------------------------------------------------------------------
      printf("%d", **wsk_wsk_x);   |       printf("%d", *wsk_x);
                                                 |       printf("%d", x);
   -----------------------------------------------------------------------------------------------


Typ void* oraz rzutowanie typów.

Należy pamiętać, że przy deklarowaniu zmiennej wskaźnikowej określamy również jej typ, który powinien być taki sam jak typ zmiennej, na którą wskazuje zmienna wskaźnikowa. Jest to wymagane, ponieważ różne typy zmiennych w C zajmują w pamięci różną wielkość.

Czasami zdarza się, że nie wiemy na jaki typ ma wskazywać dany wskaźnik. W takich przypadkach stosuje się typ void*. Taki wskaźnik możemy potem odnieść do konkretnego typu danych (Uwaga! W języku C++ wymagane jest rzutowanie typów).

Rzutowanie typów wskaźnikowych przypomina zwykłe rzutowanie typów zmiennych. Robi się to następująco:

                   zmienna_wskaznikowa = (nowy_typ*)&zmienna;

Oto przykładowy programik, który wyjaśni ten mechanizm:

Kod:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int x = 5;
    void *wsk_x;

    wsk_x = (int*)&x;

    return 0;
}

Powyższy program nie robi nic poza przypisaniem wskaźnikowi adresu zmiennej x. Różnica polega na tym, że rzutowaliśmy typ void* na int x. Co prawda w język C konwersja typu void* na inny nie jest wymagane, lecz w C++ jest konieczna. Mimo to zostało pokazane jak dokonywać rzutowania typów.

Wskaźniki jako argumenty funkcji.

Zerknijmy na poniższy kod źródłowy:

Kod:

#include <stdio.h>
#include <stdlib.h>

int zmien(int x)
{
     return x+5;
}

int main()
{
     int x = 5;

     printf("Zmienna przed wywolaniem funkcji: %d\n", x);
     printf("Funkcja zwraca wartosc: %d\n", zmien(x));
     printf("Zmienna po wywolaniu funkcji: %d\n", x);

     getchar();
     return 0:
}

W programie napisaliśmy funkcję zmień typu int, która pobiera argument typu int i zwraca wartość argumentu powiększoną o 5.

Powyższy kod jest przykładem przekazania parametru do funkcji poprzez wartość. Oznacza to, że wartość argumentu umieszczonego w wywołaniu jest kopiowana i umieszczana w oddzielnym miejscu w pamięci. Oznacza to, że operacje wykonywane wewnątrz tej funkcji nie zmienią wartości zmiennej lokalnek zadeklarowanej w funkcji głównej. Pokazują to wyświetlane wartości.

Dzięki wskaźnikom możemy zmodyfikować wartość zmiennej przekazywanej do funkcji, ponieważ jeśli przekazujemy argument za pomocą jego adresu, korzystając ze zmiennej wskaźnikowej, to nie powstaje jego kopia. Zamiast tego tworzona jest druga zmienna, wskazującą to samo położenie w pamięci. Jeżeli więc zmienimy wartość zmiennej wskaźnikowej, zmieni się także wartość argumentu umieszczonego w wywołaniu.

Prezentuje to poniższy przykład:

Kod:

#include <stdio.h>
#include <stdlib.h>

void zmien(int *wsk_x)
{
    *wsk_x = 10;
}

int main()
{
   int x=5;
   
   printf("Wartosc zmiennej przed wywolaniem funkcji: %d\n", x);
   zmien(&x);
   printf("Wartosc zmiennej po wywołaniu funkcji: %d\n", x);
   
   getchar();
   return 0; 
}

Tym razem funkcja zmien przyjmuje wskaźnik jako parametr, a następnie zmienia wartość zmiennej, na którą wskazuje wskaźnik wsk_x. Widać, że po wywołaniu funkcji zmieniła się wartość zmiennej lokalnej x.

Należ pamiętać, że argumentem przekazywanym do funkcji pobierającej wskaźnik, musi być adres zmiennej. Dlatego przy wywołaniu funkji przed zmienną x daliśmy operator pobrania adresu.


-----------------------------------------------------------------------------------------------------------------------------

To tyle jeżeli chodzi o wstęp. Starałem się pisać w sposób przystępny (prościej to już chyba nie można ), dlatego wstęp ten jest skierowany do osób, które nie rozumieją podstaw wskaźników.

Na dniach wrzucę kolejną część artykułu. Będzie ona traktowała o dynamicznej alokacji pamięci oraz o tym jak używać tablic ze wskaźnikami.

Ostatnio edytowany przez mieczyk (04-03-2007 20:40:01)

Offline

 

Stopka forum

RSS
Powered by PunBB 1.2.23
© Copyright 2002–2008 PunBB
Polityka cookies - Wersja Lo-Fi


Darmowe Forum | Ciekawe Fora | Darmowe Fora
GotLink.pl