Wykład 4
Struktury, unie, pola bitowe

Struktury

Struktura jest obiektem złożonym z jednej lub kilku zmiennych (mogą one być różnych typów) zgrupowanych dla wygody pod jedną nazwą. Struktura pozwala traktować grupę zmiennych, związanych ze sobą w jakiś logiczny sposób, jak jeden obiekt (jedną zmienną). Ogólna postać deklaracji struktury została przedstawiona poniżej

struct nazwaStruktury
{
  typ1 skladowa1;
  
  ...
  
  typN skladowaN;
};
Mówiąc o typie strukturalnym będziemy mieć na myśli nazwę oraz typy i porządek składowych pewnej struktury.

Jednym z najczęściej spotykanych przykładów struktury, jest struktura reprezentująca punkt w przestrzeni dwuwymiarowej
struct punkt
{
  float x;
  float y;
};
Zmienne strukturalne mogą być deklarowane równocześnie z deklaracją typu strukturalnego
struct punkt
{
  float x;
  float y;
} myPunkt;
Jeśli do tego typu nie będzi później odwołań (czyli w naszym przypadku nie będzie potrzeby użycia w programie nazwy punkt to deklaracja typu nie musi zawierać nazwyStruktury
struct
{
  float x;
  float y;
} myPunkt;
Nazwa strutury i nazwa zmiennej strukturalnej mogą być takie same
struct punkt
{
  float x;
  float y;
} punkt;
Zmienne strukturalne mogą być inicjalizowane za pomocą listy inicjalizatorów zawierającej wartości wszystkich składowych struktury
struct punkt punkt2d={1.0, 2.7};
Dostęp do konkretnej składowej struktury uzyskujemy za pomocą operatora . (kropka), którego argumentami jest zmienna strukturalna oraz składowa
zmiennaStrukturalna.skladowa
na przykład
punkt2d.x=1.75;
Dostęp do składowych struktury można również uzyskać za pomocą wskaźnika i operatora -> Po lewej stronie tego operatora znajduje się wskaźnik do struktury po prawej zaś nazwa pola
struct punkt *wsk;
wsk=&punkt2d;
wsk->x=1.75;


#include <stdio.h>

struct punkt
{
  float wspX;
  float wspY;
};

float dist(struct punkt p1, struct punkt p2)
{
  return sqrt((p1.wspX-p2.wspX)*(p1.wspX-p2.wspX)+(p1.wspY-p2.wspY)*(p1.wspY-p2.wspY));
}

int main(void)
{
  struct punkt pkt1={0,0},pkt2={5,0},pkt3;
  
  pkt3.wspX=0;
  pkt3.wspY=3;
  
  printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
    pkt1.wspX,pkt1.wspY,pkt2.wspX,pkt2.wspY,dist(pkt1,pkt2));
  printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
    pkt1.wspX,pkt1.wspY,pkt3.wspX,pkt3.wspY,dist(pkt1,pkt3));
    
  return 0;
}
Listing 4.1

Odleglosc punktu (0.00,0.00) od (5.00,0.00) wynosi 5.00
Odleglosc punktu (0.00,0.00) od (0.00,3.00) wynosi 3.00
Output 4.1 Efekt działania programu z listingu 4.1

#include <stdio.h>

struct osoba
{
 char *imie;
 char wiek;
};

int main(void)
{
  struct osoba o1={"Piotr",26},
               o2={"Michal",24};
  struct osoba *wsk;               
               
  wsk=&o1;
  printf("%s ma lat %d\n",(*wsk).imie,(*wsk).wiek);
  // mozna taz inaczej i tak robimy to najczesciej               
  wsk=&o2;
  printf("%s ma lat %d\n",wsk->imie,wsk->wiek);
  
  return 0;
}
Listing 4.2

Piotr ma lat 26
Michal ma lat 24
Output 4.2 Efekt działania programu z listingu 4.2

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

struct wezel
{
  int wartosc;
  struct wezel *l;
  struct wezel *r;
};

void WypiszDrzewo(struct wezel *wezel)
{
  if(wezel->l!=NULL)
    WypiszDrzewo(wezel->l);
  printf("%d ",wezel->wartosc);
  if(wezel->r!=NULL)
    WypiszDrzewo(wezel->r);
}

int main(void)
{
/* 
         1
        / \
      /    \
     2     5
    / \   / \
  3    4 6   7
            / \
           8   9
*/           
  struct wezel w1={1,NULL,NULL},
               w2={2,NULL,NULL},
               w3={3,NULL,NULL},
               w4={4,NULL,NULL},
               w5={5,NULL,NULL},
               w6={6,NULL,NULL},
               w7={7,NULL,NULL},
               w8={8,NULL,NULL},
               w9={9,NULL,NULL};
  struct wezel *korzen=&w1;
    
  w1.l=&w2;w1.r=&w5;
  w2.l=&w3;w2.r=&w4;
  w5.l=&w6;w5.r=&w7;
  w7.l=&w8;w7.r=&w9;

  WypiszDrzewo(korzen);
  
  return 0;
}
Listing 4.3

3 2 4 1 6 5 8 7 9 
Output 4.3 Efekt działania programu z listingu 4.3


typedef

W języku C wprowadzono mechanizm pozwalający na tworzenie nowych nazw typów danych. Na przykład deklaracja

typedef int calkowite;

tworzy dla typu int synonim calkowite. Z nazwy calkowite można korzystać w deklaracjach, rzutowaniach itp; ogólnie mówiąc, wszędzie tam, gdzie możemy korzystać z typu int

calkowite iloscDanych, iloscKonczyn;

Należy wyraźnie zaznaczyć, że deklaracja typedef w żadnym razie nie tworzy nowego typu. Nadaje ona po prostu nową nazwę dla typu już istniejącego. Powracając do przykładu ze strukturą reprezentującą punktu, możemy teraz napisać

typedef struct
{
  float x;
  float y;
} punkt;

int main(void)
{
  punkt p1,p2;
  ...
  p1.x=p2.y;
  ...
  return 0;
}

#include <stdio.h>
#include 

struct pkt1
{
  float x;
  float y;
};

typedef struct
{
  float x;
  float y;
} pkt2;

float dist1(struct pkt1 p1, struct pkt1 p2)
{
  return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

float dist2(pkt2 p1, pkt2 p2)
{
  return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

int main(void)
{
  struct pkt1 p11={0,0},p12={3,0};
  pkt2 p21={0,0},p22={3,0};
  
  printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
    p11.x,p11.y,p12.x,p12.y,dist1(p11,p12));
  printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
    p21.x,p21.y,p22.x,p22.y,dist2(p21,p22));
    
  return 0;
}
Listing 4.4

Odleglosc punktu (0.00,0.00) od (3.00,0.00) wynosi 3.00
Odleglosc punktu (0.00,0.00) od (3.00,0.00) wynosi 3.00
Output 4.4 Efekt działania programu z listingu 4.4


Unie

Unie pozwalają na odwoływanie się do tego samego obszaru pamięci za pośrednictwem różnych typów. Deklaracja unii różni się od deklaracji struktury wyłącznie słowem kluczowym; piszemy union zamiast struct

union unijka
{
  char x;
  int y;
  float z;
};
W odróżnieniu jednak od struktury, wszystkie składowe unii rozpoczynają się pod tym samym adresem. Tak więc wielkość unii jest rozmiarem jej największej składowej. W praktyce oznacza to, że w danym fragmencie kodu możemy operować tylko jedną składową

#include <stdio.h>

union _u
{
  int xi;
  float xf;
} u;

int main(void)
{
  u.xi=10;
  printf("%d %f\n",u.xi,u.xf);
  u.xf=10;
  printf("%d %f\n",u.xi,u.xf);
  
  return 0;
}
Listing 4.5

10 0.000000
1092616192 10.000000
Output 4.5 Efekt działania programu z listingu 4.5


Pola bitowe

Pola bitowe są stałymi składającymi się z określonej liczby bitów; wchodzą one w skład struktur lub unii. Deklaracja pola bitowego ma następującą postać

typ opcjonalnyIdentyfikator: dlugosc;

gdzie typ jest typem unsigned int lub signed int a dlugosc liczbą bitów zajmowanych przez pole bitowe w pamięci. Długość pola nie może być większa niż długość słowa maszynowego (zazwyczaj sizeof(int)). Jeżeli niewielkie pola bitowe pozostawiają dostatecznie dużo miejsca dla kolejnych pól to mogą one zostać umieszczone w tej samej jednostce pamięci. Pole bitowe o zerowej długości wskazuje, że następne pole bitowe powinno zostać umieszczone w nowej jednostce pamięci bez względu na to czy w bieżącej jednostce pamięci pozostało jeszcze miejsce. Pola bitowe typu unsigned int interpretowane są jako liczby bez znaku, natomiast signed int mogą przyjmować wartości ujemne i są kodowane w systemie uzupełnieniowym do dwóch. W stosunku do pól bitowych nie można użyć operatora adresu (można natomiast zrobić to w stosunku do zmiennej struktralne takie pole zawierającej). Należy mieć na uwadze, że niektóre sposoby wykorzystania pól bitowych mogą powodować kłopoty z przenośnością jako że interpretacja bitów tworzących słowo może zależeć od rodzaju używanego sprzętu.

struct
{
  
  unsigned int pole_1: 2;
  unsigned int pole_2: 4;
  signed int pole_3: 3;
} s;

/*
s.pole_1 : pole 2-bitowe mogace przechowac liczby z przedzialu od 0 do 3
s.pole_2 : pole 4-bitowe mogace przechowac liczby z przedzialu od 0 do 15
s.pole_3 : pole 3-bitowe mogace przechowac liczby z przedzialu od -4 do 3
*/