Programowanie strukturalne
- Wykład 11

Tablice wielowymiarowe

Skomplikujmy to jeszcze bardziej

Tak!

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

int main()
{
    int tab[3][2] = {{2,-3},{5,4},{8,7}};
    //wskaznik do tablicy dwóch wartosci int
    //operator [] ma wyzszy priorytet niz *
    int (*wsk)[2];
    wsk=tab;
    printf("%d %p %p\n",**wsk,*wsk,wsk);
    printf("%d %p %p\n",**(wsk+1),*(wsk+1),wsk+1);
    printf("%d %p\n",*(wsk[0]),wsk[0]);
    printf("%d %p\n",*(wsk[1]),wsk[1]);
    printf("%d %p\n",*(wsk[2]),wsk[2]);
    printf("%d %p\n",*(wsk[1]+1),wsk[1]+1);
    printf("%d %p\n",*(*wsk+1),*wsk+1);
    printf("%d\n",*wsk[1]);
    printf("%d\n",**wsk+1);
    printf("%d\n",wsk[2][1]);
    printf("%d\n",*(*(wsk+2)+1));
    return 0;
}
#include <stdlib.h>
#include <stdio.h>

int main()
{
    //tablica dwoch wskazników do int
    int *wsk[2]= {
    (int[]) {3,4,5},
    (int[]) {-2,3,-4},
    };
    printf("%d %p\n",wsk[0][0],&wsk[0][0]);
    printf("%d %p\n",wsk[0][1],&wsk[0][1]);
    printf("%d %p\n",wsk[0][2],&wsk[0][2]);
    printf("%d %p\n",wsk[0][3],&wsk[0][3]);
    printf("%d %p\n",wsk[1][0],&wsk[1][0]);
    printf("%d %p\n",*(*(wsk+1)+2),*(wsk+1)+2);
    return 0;
}

Tablice postrzępione

Tablica postrzępiona jest to taka dwuwymiarowa tablica, która posiada różną liczbę kolumn w każdym rzędzie.

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

int main()
{
    int (*(arr2[])) = {
        (int[]) {0, 1, 4, 3},
        (int[]) {4, -1},
        (int[]) {6, -2, 8}
        };
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int **tab = (int**) malloc(sizeof(int*)*2);
    tab[0]=(int*) malloc(sizeof(int)*4);
    tab[1]=(int*) malloc(sizeof(int)*3);
    return 0;
}

Więcej wymiarów?

int *** alokuj(int n, int m, int k)
{
    int *** tab = malloc(n*sizeof(int **));
    for(int i=0;i<n;i++)
    {
        tab[i] = malloc(m*sizeof(int*));
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            tab[i][j] = malloc(k*sizeof(int));
        }
    }
    return tab;
}

void zwolnij(int *** tab, int n, int m, int k)
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            free(tab[i][j]);
        }
    }
    for(int i=0;i<n;i++)
    {
        free(tab[i]);
    }
    free(tab);
}

int main()
{
    int *** tab = alokuj(2,3,4);
    zwolnij(tab,2,3,4);
    return 0;
}

Złożone typy danych

Struktury

Struktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej. Od tablic jednakże różni się tym, iż te wartości mogą być różnych typów.

Deklaracja struktury:

struct Struktura {
  int pole1;
  int pole2;
  char pole3;
};

Deklaracja zmiennej strukturalnej:

struct Struktura zmiennaS;

Dostęp do pól - “kropka” - operator wyboru składnika:

zmiennaS.pole1 = 60;   /* przypisanie liczb do pól */
zmiennaS.pole2 = 2;
zmiennaS.pole3 = 'a';  /* a teraz znaku */
#include <stdio.h>
#include <stdlib.h>

 struct Struktura {
   int pole1;
   int pole2;
   char pole3;
 };

int main()
{
    struct Struktura zmiennaS = {60, 2, 'a'};
}
struct moja_struct {
    int a;
    char b;
    } moja = {1,'c'};
#include <stdio.h>
#include <stdlib.h>

struct Struktura{
   int pole;
 } abc;

int main()
{
    abc.pole=4;
    printf("%d",abc.pole);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

 struct Struktura {
   int pole1;
   int pole2;
   char pole3;
 };

int main()
{
    struct Struktura zmiennaS =
        { .pole1=60, .pole2=2, .pole3='a'};
}
#include <stdio.h>
#include <stdlib.h>

 struct Struktura {
   int pole1;
   int pole2;
   char pole3;
 };

int main()
{
    struct Struktura zmiennaS =
        { .pole1=60, .pole2=0.2, .pole3='a'};
    printf("%d\n",zmiennaS);
    printf("%p\n",&zmiennaS);
    printf("%p\n",&zmiennaS.pole1);
    printf("%p\n",&zmiennaS.pole2);
    printf("%p\n",&zmiennaS.pole3);
}

Co znaczy nazwa struktury?

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

struct Test{
    int p1;
    char p2;
    //double p3; //porównac po odkomentowaniu poczatku linijki
};

int main()
{
    struct Test a={22,'w'};
    printf("%d\n",a); //unexptected
    printf("%p\n",&a); // adres struktury/pierwszego pola
    printf("%p\n",&a.p1); //adres pierwszego pola
    //printf("%p\n",(&a).p1); //blad kompilacji
    printf("%p\n",&a.p2); //adres drugiego pola
    //printf("%d\n",*a); //brak kompilacji
    printf("%d\n",*(&a)); //unexptected
    printf("%d\n",a.p1); //wartosc pierwszego pola
    printf("%c\n",a.p2); //wartosc drugiego pola pola
    return 0;
}
#include <stdio.h>
#include <math.h>

struct Punkt2D {
    float x;
    float y;
};


int main() {
    struct Punkt2D p1 = {3.0, 4.0};
    struct Punkt2D p2 = {6.0, 8.0};
    return 0;
}

“structure padding”

#include <stdio.h>

struct Przyklad {
    char x;
    int y;
};

int main() {
    printf("Rozmiar struktury: %Iu bajty\n", sizeof(struct Przyklad));
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

 struct Struktura {
   int pole1;
   double pole2;
   char pole3;
 };

int main()
{
    struct Struktura zmiennaS =
        { .pole1=60, .pole2=0.2, .pole3='a'};
    printf("%p\n",&zmiennaS.pole1);
    printf("%p\n",&zmiennaS.pole2);
    printf("%p\n",&zmiennaS.pole3);
}
#include <stdio.h>
#include <stdlib.h>

#pragma pack ( 1 )
 struct Struktura {
   int pole1;
   double pole2;
   char pole3;
 };

int main()
{
    struct Struktura zmiennaS =
        { .pole1=60, .pole2=0.2, .pole3='a'};
    printf("%p\n",&zmiennaS.pole1);
    printf("%p\n",&zmiennaS.pole2);
    printf("%p\n",&zmiennaS.pole3);
}

Parę zasad dla GCC:

  1. Pola są wyrównane do swojego naturalnego granicznika: Typy danych o wielkości 1 bajta (np. char) są wyrównane do granicy 1 bajta, typy danych o wielkości 2 bajty (np. short) są wyrównane do granicy 2 bajtów, a typy danych o wielkości 4 bajty (np. int, float) są wyrównane do granicy 4 bajtów, itd. Oznacza to, że adres w pamięci dla danego pola jest zawsze podzielny przez wielkość tego pola.

  2. Całkowity rozmiar struktury jest wyrównany do największego granicznika używanego przez jakiekolwiek z jej pól: Na przykład, jeśli struktura ma największe pole typu int, to cały rozmiar struktury będzie wielokrotnością wielkości int, czyli 4 bajtów.

  3. Kolejność pól ma znaczenie: Pisanie pól w strukturze od największego do najmniejszego rozmiaru może zminimalizować ilość paddingu.

Wskaźnik do struktury

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

// Definicja struktury
struct Punkt2D {
    float x;
    float y;
};

int main() {
    // Alokacja pamięci dla struktury Punkt2D
    struct Punkt2D *punkt = (struct Punkt2D *)malloc(sizeof(struct Punkt2D));

    // Przypisanie wartości do pól struktury poprzez wskaźnik
    punkt->x = 3.0;
    punkt->y = 4.0;

    printf("Punkt przed przesunieciem: (%.1f, %.1f)\n", punkt->x, punkt->y);

    // Przesunięcie punktu o wektor (2.0, 3.0)
    punkt->x += 2.0;
    punkt->y += 3.0;

    printf("Punkt po przesunieciu: (%.1f, %.1f)\n", punkt->x, punkt->y);

    // Zwolnienie zaalokowanej pamięci
    free(punkt);

    return 0;
}

Tablice struktur

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

struct Struktura {
  int pole1;
  double pole2;
  char pole3;
};

int main()
{
    struct Struktura tabS[5];
    struct Struktura zm;
    tabS[1] = zm;
}
#include <stdio.h>

struct ksiazka {
    char tytul[50];
    char autor[50];
    int liczba_stron;
    float ocena;
};

void wyswietlKsiazki(struct ksiazka* tablica, int rozmiar) {
    for (int i = 0; i < rozmiar; i++) {
        printf("Tytul: %s\n", tablica[i].tytul);
        printf("Autor: %s\n", tablica[i].autor);
        printf("Liczba stron: %d\n", tablica[i].liczba_stron);
        printf("Ocena: %.2f\n", tablica[i].ocena);
        printf("\n");
    }
}

int main() {
    struct ksiazka biblioteka[2] = {
        {"Wiedzmin", "Andrzej Sapkowski", 320, 8.7},
        {"1984", "George Orwell", 328, 8.5}
    };

    wyswietlKsiazki(biblioteka, 2);

    return 0;
}
#include <stdio.h>

struct ksiazka {
    char tytul[50];
    char autor[50];
    int liczba_stron;
    float ocena;
};

struct ksiazka znajdzKsiazkeZNajwiekszaLiczbaStron(struct ksiazka* tablica, int rozmiar) {
    struct ksiazka najwieksza = tablica[0];
    
    for (int i = 1; i < rozmiar; i++) {
        if (tablica[i].liczba_stron > najwieksza.liczba_stron) {
            najwieksza = tablica[i];
        }
    }
    
    return najwieksza;
}

int main() {
    struct ksiazka biblioteka[3] = {
        {"Wiedzmin", "Andrzej Sapkowski", 320, 8.7},
        {"1984", "George Orwell", 328, 8.5},
        {"War and Peace", "Leo Tolstoy", 1225, 9.0}
    };

    struct ksiazka najwieksza = znajdzKsiazkeZNajwiekszaLiczbaStron(biblioteka, 3);

    printf("Ksiazka z najwieksza liczba stron to: %s\n", najwieksza.tytul);
    printf("Liczba stron: %d\n", najwieksza.liczba_stron);

    return 0;
}

typedef

typedef to słowo kluczowe w języku C, które pozwala na definiowanie aliasów dla typów danych. W kontekście struktur, typedef jest często używane w celu uproszczenia deklaracji zmiennych strukturalnych i funkcji.

Wariant 1 - Użycie typedef podczas definiowania struktury:

#include <stdio.h>

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

int main() {
    Punkt2D punkt = {3.0, 4.0};
    printf("Punkt: (%.1f, %.1f)\n", punkt.x, punkt.y);
    return 0;
}

Wariant 2 - Użycie typedef po definiowaniu struktury:

#include <stdio.h>

struct Punkt {
    float x;
    float y;
};

typedef struct Punkt Punkt2D;

int main() {
    Punkt2D punkt = {3.0, 4.0};
    printf("Punkt: (%.1f, %.1f)\n", punkt.x, punkt.y);
    return 0;
}

Wariant 3 - Użycie typedef w kombinacji ze wskaźnikami na strukturę:

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

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

int main() {
    Punkt2D *punkt = (Punkt2D *)malloc(sizeof(Punkt2D));
    punkt->x = 3.0;
    punkt->y = 4.0;
    printf("Punkt: (%.1f, %.1f)\n", punkt->x, punkt->y);
    free(punkt);
    return 0;
}

Wariant 4 - Użycie typedef w funkcjach przyjmujących struktury jako argumenty:

#include <stdio.h>

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

void wypisz_punkt(Punkt2D p) {
    printf("Punkt: (%.1f, %.1f)\n", p.x, p.y);
}

int main() {
    Punkt2D punkt = {3.0, 4.0};
    wypisz_punkt(punkt);
    return 0;
}

Przekazywanie struktur do funkcji

  1. Przekazywanie struktury przez wartość:
#include <stdio.h>

struct Punkt2D {
    float x;
    float y;
};

void wypisz_punkt(struct Punkt2D p) {
    printf("Punkt: (%.1f, %.1f)\n", p.x, p.y);
}

int main() {
    struct Punkt2D punkt = {3.0, 4.0};
    wypisz_punkt(punkt); // przekazanie struktury przez wartość
    return 0;
}
  1. Przekazywanie struktury przez wskaźnik:
#include <stdio.h>

struct Punkt2D {
    float x;
    float y;
};

void przesun_punkt(struct Punkt2D *p, float dx, float dy) {
    p->x += dx;
    p->y += dy;
}

int main() {
    struct Punkt2D punkt = {3.0, 4.0};
    printf("Punkt przed przesunieciem: (%.1f, %.1f)\n", punkt.x, punkt.y);
    przesun_punkt(&punkt, 2.0, 3.0); // przekazanie struktury przez wskaźnik
    printf("Punkt po przesunieciu: (%.1f, %.1f)\n", punkt.x, punkt.y);
    return 0;
}

Zwracanie przez funkcję struktury

Przykład zwracania struktury jako zwykłej zmiennej:

#include <stdio.h>

struct Point {
  int x;
  int y;
};

struct Point add_points(struct Point p1, struct Point p2) {
  struct Point result;
  result.x = p1.x + p2.x;
  result.y = p1.y + p2.y;
  return result;
}

int main() {
  struct Point p1 = {1, 2};
  struct Point p2 = {3, 4};
  struct Point sum = add_points(p1, p2);
  printf("sum: (%d, %d)\n", sum.x, sum.y);
  return 0;
}

Przykład zwracania struktury przez wskaźnik przekazany jako argument:

#include <stdio.h>

struct Point {
  int x;
  int y;
};

void add_points(struct Point p1, struct Point p2, struct Point *result) {
  result->x = p1.x + p2.x;
  result->y = p1.y + p2.y;
}

int main() {
  struct Point p1 = {1, 2};
  struct Point p2 = {3, 4};
  struct Point sum;
  add_points(p1, p2, &sum);
  printf("sum: (%d, %d)\n", sum.x, sum.y);
  return 0;
}

Przykład zwracania struktury przez wskaźnik przekazany (bezpośrednio):

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

struct Person {
    char *name;
    int age;
};

struct Person *create_person(char *name, int age) {
    struct Person *new_person = malloc(sizeof(struct Person));
    new_person->name = name;
    new_person->age = age;
    return new_person;
}

int main() {
    struct Person *person1 = create_person("John Doe", 30);
    printf("Name: %s, Age: %d\n", person1->name, person1->age);
    free(person1);
    return 0;
}

Unie

Unia (ang. union) jest typem, który pozwala przechowywać różne rodzaje danych w tym samym obszarze pamięci (jednak nie równocześnie).

 union Nazwa {
   typ1 nazwa1;
   typ2 nazwa2;
   /* ... */
 };
#include <stdio.h>
#include <stdlib.h>

union Unia {
   int pole1;
   char pole2;
 };

int main()
{
    union Unia zm;
    zm.pole1=67;
    printf("%c\n",zm.pole1);
    printf("%d\n",zm.pole1);
    printf("%c\n",zm.pole2);
    printf("%d\n",zm.pole2);
    return 0;
}
#include <stdio.h>

union Number {
    int i;
    float f;
    double d;
};

int main() {
    union Number num;
    num.i = 10;
    printf("int: %d\n", num.i);
    num.f = 3.14;
    printf("float: %f\n", num.f);
    num.d = 2.718;
    printf("double: %lf\n", num.d);
    printf("int: %d\n", num.i);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
union Liczba
{
    int a;
    float b;
};
struct Dane
{
    int tp;
    union Liczba zaw;
};
struct Dane wczytaj()
{
    struct Dane temp;
    printf("Jesli chcesz wpisac liczbe calk to wpisz 0,a jesli wymierna to wpisz 1\n");
    scanf("%d", &temp.tp);
    if (temp.tp ==0)
    {
        scanf("%d", &temp.zaw.a);
    }
    else
    {
        scanf("%f", &temp.zaw.b);
    }
    return temp;
};
void wyswietl(struct Dane arg)
{
    if (arg.tp ==0)
    {
        printf("%d\n", arg.zaw.a);
    }
    else
    {
        printf("%f\n", arg.zaw.b);
    }
}
int main()
{
    union Liczba zm;
    zm.a =5;
    printf("%d\n", zm.a);
    printf("%f\n", zm.b);
    zm.b=3.4;
    printf("%d\n", zm.a);
    printf("%f\n", zm.b);
    struct Dane dane1;
    dane1= wczytaj();
    wyswietl(dane1);
    return 0;
}

Typ wyliczeniowy

Służy do tworzenia zmiennych, które mogą przyjmować tylko pewne z góry ustalone wartości:

enum Nazwa {WARTOSC_1, WARTOSC_2, WARTOSC_N };
#include <stdio.h>
#include <stdlib.h>

enum miasta {OLSZTYN, GDANSK, KRAKOW, WARSZAWA, BYDGOSZCZ};

int main()
{
    enum miasta m1 = OLSZTYN;
    printf("%s\n",m1);
    printf("%d\n",m1);
    printf("%u\n",m1);
    return 0;
}

Struktury - prekursor do obiektowości?

#include <stdio.h>
#include <string.h>

struct Laptop {
    char model[30];
    float cena;
};

struct Laptop initLaptop(char* model, float cena) {
    struct Laptop nowyLaptop;
    strncpy(nowyLaptop.model, model, sizeof(nowyLaptop.model) - 1);
    nowyLaptop.model[sizeof(nowyLaptop.model) - 1] = '\0';
    nowyLaptop.cena = cena;
    return nowyLaptop;
}

void pokazLaptop(struct Laptop laptop) {
    printf("Model: %s\n", laptop.model);
    printf("Cena: %.2f\n", laptop.cena);
}

void zmniejszCene(struct Laptop* laptop) {
    laptop->cena *= 0.95;
}

int main() {
    struct Laptop mojLaptop = initLaptop("Dell XPS 15", 5000.0);
    pokazLaptop(mojLaptop);
    zmniejszCene(&mojLaptop);
    printf("Po obnizce ceny:\n");
    pokazLaptop(mojLaptop);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Ksiazka {
    char tytul[50];
    int liczba_stron;
} Ksiazka;

Ksiazka* initKsiazka(const char *tytul, int liczba_stron) {
    if(strlen(tytul) < 5 || liczba_stron <= 50)
        return NULL;

    Ksiazka* nowa_ksiazka = (Ksiazka*)malloc(sizeof(Ksiazka));
    strcpy(nowa_ksiazka->tytul, tytul);
    nowa_ksiazka->liczba_stron = liczba_stron;

    return nowa_ksiazka;
}

void pokazKsiazka(Ksiazka ksiazka) {
    printf("Tytul: %s, Liczba stron: %d\n", ksiazka.tytul, ksiazka.liczba_stron);
}

void dodajStrony(Ksiazka *ksiazka) {
    ksiazka->liczba_stron += 10;
}

int main() {
    Ksiazka* ksiazka1 = initKsiazka("Wojna i pokoj", 1200);

    if(ksiazka1 != NULL) {
        pokazKsiazka(*ksiazka1);
        dodajStrony(ksiazka1);
        pokazKsiazka(*ksiazka1);
    } else {
        printf("Nie udalo sie utworzyc ksiazki.\n");
    }

    free(ksiazka1);

    return 0;
}

Bibliografia