Serwis Edukacyjny
Nauczycieli
w I-LO w Tarnowie

obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Uaktualniono: 31.07.2022

©2024 mgr Jerzy Wałaszek
I LO w Tarnowie

Typy danych

SPIS TREŚCI
Podrozdziały

Typy całkowite bez znaku

Programując grafikę będziesz operował na poziomie bitów. Dlatego musisz znać i rozumieć sposób kodowania danych graficznych za pomocą bitów.

Bit jest najmniejszą jednostką informacji, którą przetwarza komputer. Nie wnikając w skomplikowane teorie informatyczne, możemy powiedzieć, że bit jest "takim czymś", co może występować tylko w dwóch postaciach. Co to jest "to coś"? Otóż "to coś" zależy od sposobu realizacji bitu w danym systemie. Współczesne komputery w swoich wnętrzach przedstawiają bity w postaci napięć (o napięciu elektrycznym powinieneś się uczyć na fizyce, jeśli nie, to kup sobie baterię paluszkową, a otrzymasz wzorzec napięcia elektrycznego o wartości około 1,5V). Napięcia są wygodną postacią bitów dla układów elektronicznych. Dlatego taką reprezentację bitów wybrano. W przyszłości w komputerach kwantowych bity będą zapewne kodowane stanami kwantowymi atomów, ale to jeszcze daleka przyszłość.

Aby bit kodować napięciem, musimy przyjąć dwie wartości tego napięcia, ponieważ bit może znajdować się w dwóch stanach. I tak:

W sumie jest to sprawa techniczna i nas mało obchodzi. W informatyce bity zwykle oznacza się cyframi: 0 – stan niski i 1 – stan wysoki. Sama nazwa bit pochodzi od angielskiego terminu binary digit, czyli cyfra dwójkowa, tzn. cyfra 0 lub 1.

Jeśli bity potraktujemy jak cyfry, to za ich pomocą można zapisywać liczby.

Setki lat temu pewien hinduski mędrzec wymyślił stosowany przez do dzisiaj system liczbowy zwany pozycyjnym systemem dziesiętnym. Takie systemy wymyślały już wcześniejsze cywilizacje, ale hinduski jest z nich najbardziej zaawansowany.

Dowolną liczbę zapisuje się skończoną ilością znaków, czyli cyfr. W systemie dziesiętnym tych znaków jest 10:

0 1 2 3 4 5 6 7 8 9

Znasz je dobrze i są dla ciebie zupełnie naturalne. Cechą charakterystyczną systemu pozycyjnego jest to, iż cyfry reprezentują różne wartości w zależności od swojej pozycji w zapisie liczby. Na przykład: liczba 555 składa się z trzech takich samych cyfr 555. Ale pierwsza cyfra 5 oznacza pięć setek, druga cyfra 5 oznacza pięć dziesiątek, a ostatnia oznacza pięć jednostek. Te setki, dziesiątki i jednostki to są tzw. wagi pozycji. Każda pozycja w liczbie posiada swoją wagę, która jest potęgą liczby 10, a liczba 10 jest tzw. podstawą systemu:

wagi 1000
103
100
102
10
101
1
100
liczba 5 5 5 5
pozycje 3 2 1 0

Pozycje numerujemy od 0 od strony prawej ku lewej. Zwróć uwagę, że waga pozycji to podstawa podniesiona do potęgi o wykładniku równym numerowi pozycji. Taki sposób zapisu liczb umożliwia zapis dowolnej wartości. Potrzebujemy większej liczby, dodajemy kolejną pozycję o wadze 10 razy większej, itd.

Wartość liczby uzyskasz mnożąc cyfry przez wagi pozycji, na których stoją, a następnie sumując te iloczyny. Dla liczby dziesiętnej nie odkryjemy nic nowego, ale zapiszmy:

wagi 1000
103
100
102
10
101
1
100
 
liczba 2 6 4 3 = 2 x 103 + 6 x 102 + 4 x 101 + 1 x 100
pozycje 3 2 1 0  

Pięknym jest to, że system pozycyjny wcale nie musi opierać się na podstawie 10. Można wybrać inną liczbę. Na przykład weźmy za podstawę systemu liczbę 4. W każdym systemie pozycyjnym cyfr jest tyle, ile wynosi jego podstawa. Dla systemu czwórkowego mamy tylko 4 cyfry: 0 1 2 3. Wagi pozycji są teraz potęgami liczby 4, a nie dziesięć. To się zmienia. Ale zasada wyliczania wartości pozostaje taka sama, jak w systemie dziesiętnym:

wagi 64
43
16
42
4
41
1
40
 
liczba 2 3 2 1 = 2 x 43 + 3 x 42 + 2 x 41 + 1 x 40 = 2 x 64 + 3 x 16 + 2 x 4 + 1 x 1 = 249  
pozycje 3 2 1 0  

Jeśli za podstawę przyjmiemy liczbę 2, to powstanie system dwójkowy. W systemie dwójkowym są dwie cyfry: 0 i 1. Wagi pozycji są potęgami liczby 2. Zasada obliczania wartości wciąż pozostaje taka sama:

wagi 8
23
4
22
2
21
1
20
 
liczba 1 1 0 1 = 1 × 8 + 1 × 4 + 0 × 2 + 1 × 1 = 13
pozycje 3 2 1 0  

Doszliśmy do systemu zapisu liczb, w którym na cyfry można użyć bitów, czyli za pomocą bitów możemy zapisać dowolne liczby, bo to jest wspólna cecha wszystkich systemów pozycyjnych. Wadą z punktu widzenia człowieka jest to, że liczby większe mają długi zapis i łatwo się pomylić: 11110101010100111110102. Ale system dwójkowy nie jest przeznaczony dla dla ludzi, tylko dla komputerów, a im pasuje doskonale.

Tak zdefiniowany system dwójkowy pozwala zapisywać liczby całkowite nieujemne. W informatyce nazywa się on Naturalnym Kodem Dwójkowym (ang. NBC = Natural Binary Code).

Pamięć komputerowa, która przechowuje dane, jest zorganizowana w komórki przechowujące po 8 bitów. To rozwiązanie przyjęto z powodów ekonomicznych. 8 bitów to tzw. bajt (ang. byte).

Jeden bajt może przyjmować wartości od 000000002 do 111111112, czyli od 0 do 255 w systemie dziesiętnym:

111111112 = 1 × 27 + 1 × 26 + 1 × 25 + 1 × 24 + 1 × 23 + 1 × 22 + 1 × 21 + 1 × 20
111111112 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
111111112 = 255

W języku C++ daną jednobajtową reprezentuje się typem unsigned char. Od wersji 11 C++ pojawił się wydzielony typ uint8_t (ang. uint = unsigned integer).


Uruchom CodeBlocks, utwórz projekt konsoli, wpisz poniższy program i wciśnij F9, aby go skompilować i uruchomić:

C++
// Typy 1-bajtowe
//---------------

#include <inttypes.h>
#include <iostream>

using namespace std;

int main()
{
    cout << sizeof(unsigned char) << endl
         << sizeof(uint8_t) << endl;
    return 0;
}

Program wyświetla rozmiar danych w bajtach. Otrzymasz 2 jedynki. Typ unsigned char jest używany standardowo do reprezentacji znaków o kodach ASCII od 0 do 255. Typ uint8_t używa się do liczb o wartościach od 0 do 255. W sumie to to samo.


Uruchom kolejny program:

C++
// Typy 1-bajtowe
//---------------

#include <inttypes.h>
#include <iostream>

using namespace std;

int main()
{
    unsigned char a = 65;
    uint8_t       b = 65;

    cout << a << endl
         << b << endl;
    return 0;
}

W programie tworzymy dwie zmienne. Zmienna a jest typu unsigned char. Zmienna b jest typu uint8_t. Obu zmiennym nadajemy tę samą wartość 65. W wyniku otrzymujemy dwie litery A, ponieważ liczba 65 jest kodem ASCII właśnie litery A. Oznacza to, iż z punktu widzenia kompilatora oba typy unsigned char i uint8_t to ten sam typ znakowy. Oczywiście w niczym to nie przeszkadza, bo znak to tak naprawdę kod, czyli liczba całkowita.

Gdy programujesz z biblioteką SDL2, to otrzymujesz dodatkową definicję tego typu: Uint8.


Zamknij bieżący projekt i utwórz nowy projekt sdl2.

Wpisz poniższy program, skompiluj go i uruchom :

C++
// Typy 1-bajtowe
//----------------

#include <SDL.h>
#include <iostream>

using namespace std;

int main(int argc, char* args[])
{
    Uint8 a = 65;

    cout << sizeof(a) << endl
         << a << endl;
    return 0;
}

Program tworzy zmienną a typu Uint8 i nadaje jej wartość 65. Następnie wyświetlamy rozmiar w bajtach zmiennej a oraz jej zawartość. Otrzymasz 1 i A. Wynika z tego, że typ Uint8 jest po prostu kolejnym synonimem typu unsigned char.

Po co nam nowy typ, skoro mamy już unsigned char? Może dlatego, że krócej się pisze Uint8 i jest bardziej czytelne (Unsigned integer 8 bits)? Nie, chodzi tutaj o kompatybilność i ujednolicenie typów. SDL2 może być zainstalowane na różnych platformach sprzętowych, nie tylko na komputerach PC. Stosowanie typu Uint8 daje nam pewność, że zawsze będzie to 8 bitów w naturalnym kodzie dwójkowym.

W SDL2 dostępne są jeszcze typy Uint16 (16 bitów), Uint32 (32 bity), Uint64 (64 bity).

Podsumujmy:

Standard C++ C++ 11 SDL2 Zakres
unsigned char uint8_t Uint8 0...255
unsigned short int uint16_t Uint16 0...65535
unsigned int uint32_t Uint32 0...4294967295
unsigned long long int uint64_t Uint64 0...18446744073709551615

Zakres dowolnego typu w kodzie NBC o n bitach obliczysz ze wzoru: 0...2n-1.


W projekcie SDL2 podmień program na poniższy:

C++
// Typy całkowite
//----------------

#include <SDL.h>
#include <iostream>

using namespace std;

int main(int argc, char* args[])
{
    cout << "TYPY DANYCH W SDL2\n"
            "------------------\n\n"
         << "Uint8  ROZMIAR: " << sizeof(Uint8) << endl
         << "Uint16 ROZMIAR: " << sizeof(Uint16) << endl
         << "Uint32 ROZMIAR: " << sizeof(Uint32) << endl
         << "Uint64 ROZMIAR: " << sizeof(Uint64) << endl;
    return 0;
}

Na początek:  podrozdziału   strony 

Typy całkowite ze znakiem

Naturalny kod dwójkowy umożliwia zapis tylko wartości nieujemnych. Jeśli w zmiennej typu bezznakowego (tzn. nie przyjmującej wartości ujemnych, tylko dodatnie i zero) spróbujesz umieścić liczbę ujemną, to wynik może być zaskakujący:
C++
// Typy całkowite
//----------------

#include <SDL.h>
#include <iostream>

using namespace std;

int main(int argc, char* args[])
{
    Uint16 a = -5;

    cout << a << endl;
    return 0;
}

Powyższy program daje wynik 65531. Dlaczego tak się dzieje, zobaczysz za chwilę.

Powstaje problem: jak kodować liczby ujemne. W systemie dziesiętnym po prostu wymyślono znak minus, który dopisujemy przed liczbą: np. -45. Ale znak minus to dodatkowy symbol. Komputery przetwarzają wyłącznie bity, czyli cyfry 0 i 1. Dlatego wymyślono kilka sposobów kodowania liczb ujemnych. Najpopularniejszy jest system uzupełnień do 2, zwany krótko U2 (ang. Two's Complement). Musimy tutaj wprowadzić umowę, że liczba U2 posiada ustaloną z góry liczbę bitów. To ważne, ponieważ najstarszy bit ma wagę ujemną. Weźmy dla przykładu 4-bitowe liczby U2:

wagi (-8)
(-23)
4
22
2
21
1
20
 
liczba 1 1 0 1 = 1 × (-8) + 1 × 4 + 0 × 2 + 1 × 1 = -3
pozycje 3 2 1 0  

Wartość liczby U2 obliczamy tak samo jak dla liczb NBC: mnożymy cyfry przez wagi ich pozycji i sumujemy te iloczyny. Wagi pozycji są potęgami podstawy 2. Waga najstarszej pozycji jest ujemna. Najstarszy bit zwany jest bitem znaku (ang. sign bit). Jeśli ma wartość 1, to liczba jest ujemna, jeśli ma wartość 0, to liczba jest nieujemna. W poniższej tabelce są wszystkie wartości 4-bitowych liczb U2:

(-8)
(-23)
4
22
2
21
1
20
 
0 0 0 0 = 0
0 0 0 1 = 1
0 0 1 0 = 2
0 0 1 1 = 3 (2 + 1)
0 1 0 0 = 4
0 1 0 1 = 5 (4 + 1)
0 1 1 0 = 6 (4 + 2)
0 1 1 1 = 7 (4 + 2 + 1)
1 0 0 0 = -8
1 0 0 1 = -7 (-8 + 1)
1 0 1 0 = -6 (-8 + 2)
1 0 1 1 = -5 (-8 + 2 + 1)
1 1 0 0 = -4 (-8 + 4)
1 1 0 1 = -3 (-8 + 4 + 1)
1 1 1 0 = -2 (-8 + 4 + 2)
1 1 1 1 = -1 (-8 + 4 + 2 + 1)

Zwróć uwagę, że wartości ujemnych jest o 1 więcej od wartości dodatnich (zero nie ma znaku).

Zakres n-bitowych liczb U2 liczymy wzorem: -2n-1...2n-1-1.

Na przykład 8 bitowe liczby U2 mają zakres: od -28-1 do 28-1-1: -27... 27-1: -128 ... 127.

Dlaczego stosuje się taki dziwny kod U2? Powodem jest to, iż obliczenia na liczbach U2 można wykonywać w tych samych układach elektronicznych co obliczenia na liczbach NBC. Zatem procesor nie musi zawierać osobnych obwodów dla liczb ze znakiem i bez znaku. W efekcie mamy oszczędność.

Daną grupę bitów można interpretować zarówno jako liczbę ze znakiem jak i bez znaku. Jednak wartości mogą wtedy być inne:

Kod Wartość
NBC U2
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 -8
1001 9 -7
1010 10 -6
1011 11 -5
1100 12 -4
1101 13 -3
1110 14 -2
1111 15 -1

Jeśli bit znaku ma wartość 0 (czyli liczba jest nieujemna, dodatnia), to oba kody przedstawiają tę samą liczbę. Gdy bit znaku przyjmuje wartość 1 (liczba staje się ujemna), liczby NBC i U2 różnią się między sobą. Zwróć jednak uwagę, że występuje wtedy pomiędzy nimi prosty związek:

Liczba NBC = 16 + Liczba U2

Dla n bitowych liczb otrzymamy:

Liczba NBC = 2n + Liczba U2

W programie przedstawionym na początku tego podrozdziału w zmiennej bezznakowej umieściliśmy wartość U2 równą -5. Zmienna miała długość 16 bitów, zatem:

Wartość NBC = 216 - 5 = 65536 - 5 = 65531

Po prostu komputer umieścił liczbę U2 w zmiennej NBC, a później zinterpretował tę liczbę jako liczbę NBC i wyszedł mu wynik 65531. Czy teraz jest to dla ciebie jasne? To jest prosta konsekwencja podwójnej interpretacji liczb. Musisz na takie rzeczy uważać w swoich programach, bo czasem prowadzą do trudno wykrywalnych błędów.

Mamy następujące typy zmiennych całkowitych ze znakiem (ang. Sint = Signed integer):

Standard C++ C++ 11 SDL2 Zakres
signed char sint8_t Sint8 -128...127
signed short int sint16_t Sint16 -32768...32767
signed int sint32_t Sint32 -2147483648...2147483647
signed long long int sint64_t Sint64 -9223372036854775808...9223372036854775807

W projekcie SDL2 uruchom poniższy program:

C++
// Typy całkowite ze znakiem
//--------------------------

#include <SDL.h>
#include <iostream>

using namespace std;

int main(int argc, char* args[])
{
    cout << "TYPY DANYCH W SDL2\n"
            "------------------\n\n"
         << "Sint8  ROZMIAR: " << sizeof(Sint8)  << endl
         << "Sint16 ROZMIAR: " << sizeof(Sint16) << endl
         << "Sint32 ROZMIAR: " << sizeof(Sint32) << endl
         << "Sint64 ROZMIAR: " << sizeof(Sint64) << endl;
    return 0;
}

Jeśli cię zainteresował temat typów danych, to przeczytaj nasz artykuł o binarnym kodowaniu liczb, który dokładniej opisuje to zagadnienie.


Na początek:  podrozdziału   strony 

Przydatne stałe

W SDL2 możesz korzystać z następujących stałych związanych z typami całkowitymi:
Stała Wartość Uwagi
SDL_MAX_SINT8 127 Graniczne wartości liczb 8-bitowych ze znakiem
SDL_MIN_SINT8 -128
SDL_MAX_UINT8 255 Graniczne wartości liczb 8-bitowych bez znaku
SDL_MIN_UINT8 0
SDL_MAX_SINT16 32767 Graniczne wartości liczb 16-bitowych ze znakiem
SDL_MIN_SINT16 -32768
SDL_MAX_UINT16 65535 Graniczne wartości liczb 16-bitowych bez znaku
SDL_MIN_UINT16 0
SDL_MAX_SINT32 2147483647 Graniczne wartości liczb 32-bitowych ze znakiem
SDL_MIN_SINT32 -2147483648
SDL_MAX_UINT32 4294967295 Graniczne wartości liczb 32-bitowych bez znaku
SDL_MIN_UINT32 0
SDL_MAX_SINT64 9223372036854775807 Graniczne wartości liczb 64-bitowych ze znakiem
SDL_MIN_SINT64 -9223372036854775808
SDL_MAX_UINT64 18446744073709551615 Graniczne wartości liczb 64-bitowych bez znaku
SDL_MIN_UINT64 0

W projekcie SDL2 wpisz poniższy program, skompiluj go i uruchom:

C++
// Typy całkowite
//---------------

#include <SDL.h>
#include <iostream>

using namespace std;

int main(int argc, char* args[])
{
    cout << "ZAKRESY DANYCH W SDL2\n"
            "---------------------\n\n"
         << "Sint8  : " << (int)SDL_MIN_SINT8 << " ... " << (int)SDL_MAX_SINT8 << endl
         << "Uint8  : " << (int)SDL_MIN_UINT8 << " ... " << (int)SDL_MAX_UINT8 << endl
         << "Sint16 : " << SDL_MIN_SINT16 << " ... " << SDL_MAX_SINT16 << endl
         << "Uint16 : " << SDL_MIN_UINT16 << " ... " << SDL_MAX_UINT16 << endl
         << "Sint32 : " << SDL_MIN_SINT32 << " ... " << SDL_MAX_SINT32 << endl
         << "Uint32 : " << SDL_MIN_UINT32 << " ... " << SDL_MAX_UINT32 << endl
         << "Sint64 : " << SDL_MIN_SINT64 << " ... " << SDL_MAX_SINT64 << endl
         << "Uint64 : " << SDL_MIN_UINT64 << " ... " << SDL_MAX_UINT64 << endl;
    return 0;
}

Na początek:  podrozdziału   strony 

Kolejność bajtów

Komputer przechowuje zmieniające się dane w pamięci RAM (ang. Random Access Memory). Pamięć zbudowana jest z komórek, z których każda przechowuje 8 bitów informacji. Komórki pamięci są odpowiednio ponumerowane. Numer komórki nazywamy adresem. Jeśli procesor chce uzyskać dostęp do odpowiedniej komórki (coś w niej zapisać lub coś z niej odczytać), to przesyła do pamięci jej adres, po czym następuje przesłanie danych z pamięci do procesora lub z procesora do pamięci:

W pojedynczej komórce pamięci można umieścić daną typu char, Uint8 lub Sint8. Dane o długości większej niż 8 bitów wymagają więcej niż jednej komórki pamięci. Na przykład w CodeBlocks mamy:

Typ Liczba komórek
Uint16 2
Uint32 4
Uint64 8

Porcja 8 bitów nazywana jest bajtem (ang. byte). Jeśli dane o długości większej niż 8 bitów podzielimy na bajty, to w każdym z nich otrzymamy różne bity danej. Na przykład dla Uint16 mamy:

bity b15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0
bajty b15 ... b8 b7 ... b0
oznaczenia starszy bajt MSB młodszy bajt LSB

Bajt zawierający starsze bity danej nazywamy starszym lub bardziej znaczącym bajtem MSB (ang. More Significant Byte). Bajt zawierający młodsze bity danej nazywamy młodszym lub mniej znaczącym bajtem LSB (ang. Less Significant Byte). Kolejność tych bajtów w pamięci zależy od rodzaju mikroprocesora komputera. Stosowane są dwa rozwiązania:

Big Endian - bajt MSB jest pierwszy, procesory Motorola 68k (komputery Macintosh).

Little Endian - bajt LSB jest pierwszy, procesory Intel Pentium i kompatybilne (komputery IBM PC).

Na przykład dana Uint16 (dwójkowo) o wartości 11111111000000002 zostanie umieszczona w pamięci jako:

Big Endian   Little Endian
Pamięć
Adres Zawartość komórki
... ...
... ...
a 11111111
a+1 00000000
... ...
... ...
 
Pamięć
Adres Zawartość komórki
... ...
... ...
a 00000000
a+1 11111111
... ...
... ...

Jak widzisz, kolejność bajtów jest odwrotna. To samo dotyczy danych o większej liczbie bajtów: w trybie Big Endian zapis rozpoczyna się od najstarszych bajtów, a w trybie Little Endian od najmłodszych.


Jak sprawdzić, który system jest stosowany na twoim komputerze? Bardzo prosto. Utwórz w CodeBlocks projekt sdl2, przekopiuj poniższy program, skompiluj go i uruchom:

C++
// Little/Big Endian
//------------------

#include <SDL.h>
#include <iostream>

using namespace std;

int main(int argc, char* args[])
{
    Uint16 a = 1;
    Uint8 * p = (Uint8 *) & a;

    if( * p == 1) cout << "Little Endian" << endl;
    else          cout << "Big Endian" << endl;
    return 0;
}

Program tworzy zmienną a typu Uint16, czyli zajmującą w pamięci RAM 2 bajty. Następnie tworzy wskaźnik p do danych jednobajtowych typu Uint8. We wskaźniku p umieszcza adres pierwszego bajtu zmiennej a. Zwróć uwagę, iż należy tu zastosować rzutowanie, czyli informujemy kompilator, aby potraktował adres zmiennej Uint16 tak, jakby to był adres zmiennej Uint8. W ten sposób uzyskujemy dostęp do pierwszego bajtu zmiennej a. Instrukcja warunkowa if sprawdza, czy w pierwszym bajcie znajduje się wartość 1. Jeśli tak, to jest to młodszy bajt LSB zmiennej, a zatem mamy do czynienia z trybem Little Endian. W przeciwnym razie pierwszym bajtem jest MSB zmiennej, czyli mamy do czynienia z trybem Big Endian.


Zwykle tryby te nie muszą zaprzątać twojej uwagi, o ile nie musisz operować w pamięci na poszczególnych bajtach danej. Jednak czasami występuje konieczność konwersji, jeśli musimy wczytać do naszego programu dane utworzone i zapisane w systemie Big Endian, a zatem odwrotnym do tego, który stosują komputery IBM PC.

Napiszmy prostą funkcję, która odwraca kolejność bajtów w zmiennej. Utwórz w CodeBlocks projekt sdl2, przekopiuj poniższy program, skompiluj go i uruchom:

C++
// Little/Big Endian
//------------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Funkcja odwraca kolejność bajtów
// p - wskaźnik zmiennej
// n - liczba bajtów
void ReverseBytes(void * p, int n)
{
    Uint8 * p1, * p2, x;
    p1 = (Uint8 *) p;
    p2 = p1 + n - 1;
    while(p1 < p2)
    {
        x = * p1;
        * p1 = * p2;
        * p2 = x;
        p1++; p2 --;
    }
}
int main(int argc, char* args[])
{
    Uint64 a = 0x0102030405060708L;

    cout << "Przed: 0" << hex << a << endl;
    ReverseBytes(&a,8);
    cout << "Po:    0" << hex << a << endl;

    return 0;
}

Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2024 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.