Wprowadzenie do typów danych
W języku C# istnieje zestaw typów wbudowanych (zwanych też czasem pierwotnymi lub prymitywnymi). Są one zdefiniowane przez sam język i dostępne bez dodatkowych importów. Każdy z tych typów ma:
- Określony rozmiar – ilość pamięci, którą zajmuje w systemie
- Zakres wartości – minimalne i maksymalne wartości, które może przechowywać
- Zestaw operacji – jakie działania można na nim wykonywać
- Wartość domyślną – co otrzymujemy przy braku inicjalizacji
Oprócz typów wbudowanych, C# pozwala na tworzenie typów zdefiniowanych przez użytkownika (np. klasy, struktury), ale w tej lekcji skupiamy się głównie na podstawowych typach predefiniowanych.
Podział typów: wartościowe vs referencyjne
Typy wartościowe (Value Types)
Przechowują swoje dane bezpośrednio w pamięci stosu (ang. stack).
Charakterystyki:
- Dane przechowywane bezpośrednio w zmiennej
- Szybki dostęp do danych
- Automatyczne zarządzanie pamięcią
- Kopiowanie wartości przy przypisaniu
int a = 10;
int b = a; // b otrzymuje KOPIĘ wartości z a
a = 20; // zmiana a nie wpływa na b
Console.WriteLine($"a = {a}, b = {b}"); // a = 20, b = 10Przykłady typów wartościowych: bool, int, float, double, decimal, char, byte, short, struktury
Typy referencyjne (Reference Types)
Przechowują w zmiennej jedynie odwołanie (adres) do obiektu, który faktycznie znajduje się w pamięci zarządzanej na stercie (ang. heap).
Charakterystyki:
- Zmienna przechowuje wskaźnik do obiektu
- Rzeczywiste dane na stercie (heap)
- Zarządzanie przez Garbage Collector
- Współdzielenie obiektu między zmiennymi
string tekst1 = "Hello";
string tekst2 = tekst1; // obie zmienne wskazują na ten sam obiekt
// Jednak string jest specjalny - jest niemutowalnyPrzykłady typów referencyjnych: object, string, klasy, tablice, interfejsy
Praktyczne różnice:
// Typy wartościowe - kopiowanie
int liczba1 = 5;
int liczba2 = liczba1; // kopia wartości
liczba1 = 10;
Console.WriteLine($"liczba1: {liczba1}, liczba2: {liczba2}"); // 10, 5
// Typy referencyjne - współdzielenie (z wyjątkiem string)
int[] tablica1 = {1, 2, 3};
int[] tablica2 = tablica1; // obie wskazują na tę samą tablicę
tablica1[0] = 99;
Console.WriteLine($"tablica2[0]: {tablica2[0]}"); // 99 - zmiana widoczna!Szczegółowe omówienie typów numerycznych
Tabela porównawcza typów:
| Typ | Rozmiar (bity) | Zakres wartości | Kategoria | Wartość domyślna | Sufiks literału |
|---|---|---|---|---|---|
bool | ~8* | true lub false | Wartościowy | false | — |
byte | 8 | 0 do 255 | Wartościowy | 0 | — |
sbyte | 8 | -128 do 127 | Wartościowy | 0 | — |
short | 16 | -32,768 do 32,767 | Wartościowy | 0 | — |
ushort | 16 | 0 do 65,535 | Wartościowy | 0 | — |
int | 32 | -2,147,483,648 do 2,147,483,647 | Wartościowy | 0 | — |
uint | 32 | 0 do 4,294,967,295 | Wartościowy | 0 | u / U |
long | 64 | -9,223,372,036,854,775,808 do 9,223,372,036,854,775,807 | Wartościowy | 0 | l / L |
ulong | 64 | 0 do 18,446,744,073,709,551,615 | Wartościowy | 0 | ul / UL |
float | 32 | ±1.5×10⁻⁴⁵ do ±3.4×10³⁸ | Wartościowy | 0.0f | f / F |
double | 64 | ±5.0×10⁻³²⁴ do ±1.7×10³⁰⁸ | Wartościowy | 0.0d | d / D |
decimal | 128 | ±1.0×10⁻²⁸ do ±7.9×10²⁸ | Wartościowy | 0.0m | m / M |
char | 16 | U+0000 do U+FFFF | Wartościowy | '\0' | — |
string | zmienny | ograniczony pamięcią | Referencyjny | null | — |
object | zmienny | wszystkie typy | Referencyjny | null | — |
* bool ma rozmiar 1 bajta w pamięci, ale używa tylko 1 bitu informacji
Typy liczbowe całkowite
Podstawowe typy całkowite:
// Najczęściej używane
int standardowaLiczba = 42; // -2 miliarda do +2 miliarda
long duzaLiczba = 9876543210L; // bardzo duży zakres
// Mniejsze typy - oszczędność pamięci
byte malaBezznakowa = 255; // 0-255
sbyte malaZeznakiem = -128; // -128 do 127
short srednia = 30000; // -32k do +32k
ushort sredniaBezznak = 65000; // 0 do 65k
// Większe typy bez znaku
uint duzaBezznak = 4000000000U; // 0 do ~4 miliardy
ulong ogromna = 18000000000000000000UL; // ogromne liczby dodatnie
// Sprawdzenie granic
Console.WriteLine($"int.MinValue = {int.MinValue}");
Console.WriteLine($"int.MaxValue = {int.MaxValue}");
Console.WriteLine($"byte.MaxValue = {byte.MaxValue}")Typy liczbowe zmiennoprzecinkowe
Porównanie float, double, decimal:
// float - pojedyncza precyzja (32-bit)
float pojedyncza = 3.14159f; // ~7 cyfr precyzji
float naukowa = 1.23e-4f; // notacja naukowa
Console.WriteLine($"float: {pojedyncza}");
// double - podwójna precyzja (64-bit) - DOMYŚLNY
double podwojna = 3.14159265358979; // ~15-17 cyfr precyzji
double takzedouble = 1.23e-10; // bez sufiksu = double
Console.WriteLine($"double: {podwojna}");
// decimal - najwyższa precyzja dla finansów (128-bit)
decimal finansowa = 3.14159265358979323846m; // ~28-29 cyfr precyzji
decimal cena = 29.99m; // idealne dla pieniędzy
Console.WriteLine($"decimal: {finansowa}");Problemy precyzji i rozwiązania:
// Problem z float/double w obliczeniach finansowych
double zle = 0.1 + 0.2;
Console.WriteLine($"0.1 + 0.2 = {zle}"); // 0.30000000000000004
// Rozwiązanie - decimal dla finansów
decimal dobrze = 0.1m + 0.2m;
Console.WriteLine($"0.1m + 0.2m = {dobrze}"); // 0.3
// Porównywanie liczb zmiennoprzecinkowych
double a = 0.1 + 0.2;
double b = 0.3;
bool rowne = Math.Abs(a - b) < 1e-10; // Tolerancja błędu
Console.WriteLine($"Czy równe z tolerancją: {rowne}");
// Sprawdzanie specjalnych wartości
double dzieleniePrzezZero = 1.0 / 0.0; // Infinity
double niePoprawne = 0.0 / 0.0; // NaN
Console.WriteLine($"1/0 = {dzieleniePrzezZero}");
Console.WriteLine($"0/0 = {niePoprawne}");
Console.WriteLine($"Czy NaN: {double.IsNaN(niePoprawne)}");Typ znakowy (char)
// Podstawowe użycie
char litera = 'A';
char cyfra = '5';
char spacja = ' ';
// Znaki specjalne (escape sequences)
char nowaLinia = '\n'; // nowa linia
char tab = '\t'; // tabulator
char ukosnik = '\\'; // ukośnik
char apostorf = '\''; // apostrof
char cudzyslow = '\"'; // cudzysłów
// Kody Unicode
char serce = '\u2665'; // ♥
char euro = '\u20AC'; // €
Console.WriteLine($"{serce} {euro}");
// Konwersje char
char literaA = 'A';
int kodASCII = literaA; // 65
char zKodu = (char)66; // 'B'
Console.WriteLine($"'A' = {kodASCII}, kod 66 = '{zKodu}'");
// Sprawdzanie właściwości znaków
char testZnak = 'a';
Console.WriteLine($"Czy litera: {char.IsLetter(testZnak)}");
Console.WriteLine($"Czy cyfra: {char.IsDigit(testZnak)}");
Console.WriteLine($"Wielka litera: {char.ToUpper(testZnak)}");Typ logiczny (bool)
// Podstawowe wartości
bool prawda = true;
bool falsz = false;
// Z porównań
int a = 5, b = 10;
bool wieksza = a > b; // false
bool rowne = a == b; // false
bool nierowne = a != b; // true
// Operacje logiczne
bool wynik1 = true && false; // false (AND)
bool wynik2 = true || false; // true (OR)
bool wynik3 = !true; // false (NOT)
// Short-circuit evaluation
bool test = (a != 0) && (b / a > 2); // Bezpieczne - jeśli a=0, b/a nie będzie wykonane
// Konwersje
string tekstLogiczny = prawda.ToString(); // "True"
bool zTekstu = bool.Parse("false"); // false
bool bezpiecznaKonwersja;
bool udaloSie = bool.TryParse("maybe", out bezpiecznaKonwersja); // false, udaloSie = false
Console.WriteLine($"Wynik: {wynik1}, Tekst: {tekstLogiczny}");Typ string – specjalny przypadek
String jest typem referencyjnym, ale zachowuje się podobnie do typu wartościowego ze względu na niemutowalność.
// Podstawowe operacje na string
string powitanie = "Witaj";
string nazwa = "świecie";
string pelnePowitanie = powitanie + " " + nazwa + "!";
// Niemutowalność string
string oryginal = "Hello";
string zmieniony = oryginal + " World";
Console.WriteLine(oryginal); // nadal "Hello"
Console.WriteLine(zmieniony); // "Hello World"
// Interpolacja stringów (C# 6.0+)
string imie = "Jan";
int wiek = 25;
string opis = $"Nazywam się {imie} i mam {wiek} lat";
Console.WriteLine(opis);
// Formatowanie
string sformatowany = string.Format("Pi = {0:F2}", Math.PI);
Console.WriteLine(sformatowany); // Pi = 3.14
// Porównywanie stringów
string s1 = "hello";
string s2 = "HELLO";
bool rowne1 = s1 == s2; // false
bool rowne2 = s1.Equals(s2, StringComparison.OrdinalIgnoreCase); // true
// Przydatne metody
string tekst = " Hello World ";
Console.WriteLine($"Długość: {tekst.Length}");
Console.WriteLine($"Wielkie: '{tekst.ToUpper()}'");
Console.WriteLine($"Obcięte: '{tekst.Trim()}'");
Console.WriteLine($"Zawiera 'World': {tekst.Contains("World")}");Typ object – uniwersalny typ bazowy
// Boxing - typ wartościowy → object
int liczba = 42;
object obiekt1 = liczba; // boxing
Console.WriteLine(obiekt1); // 42
// Unboxing - object → typ wartościowy
int z_powrotem = (int)obiekt1; // unboxing
Console.WriteLine(z_powrotem); // 42
// Różne typy w object
object[] rozne = {
42, // int
"tekst", // string
3.14, // double
true, // bool
'A' // char
};
foreach (object item in rozne)
{
Console.WriteLine($"Typ: {item.GetType().Name}, Wartość: {item}");
}
// Sprawdzanie typu
object nieznany = "Hello";
if (nieznany is string s)
{
Console.WriteLine($"To string o długości {s.Length}");
}
// Pattern matching (C# 7.0+)
string OpisObiektu(object obj) => obj switch
{
int i => $"Liczba całkowita: {i}",
string s => $"Tekst: {s}",
bool b => $"Logiczna: {b}",
_ => "Nieznany typ"
};