Proste Typy Danych w C#

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 = 10

Przykł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 niemutowalny

Przykł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:

TypRozmiar (bity)Zakres wartościKategoriaWartość domyślnaSufiks literału
bool~8*true lub falseWartościowyfalse
byte80 do 255Wartościowy0
sbyte8-128 do 127Wartościowy0
short16-32,768 do 32,767Wartościowy0
ushort160 do 65,535Wartościowy0
int32-2,147,483,648 do 2,147,483,647Wartościowy0
uint320 do 4,294,967,295Wartościowy0u / U
long64-9,223,372,036,854,775,808 do 9,223,372,036,854,775,807Wartościowy0l / L
ulong640 do 18,446,744,073,709,551,615Wartościowy0ul / UL
float32±1.5×10⁻⁴⁵ do ±3.4×10³⁸Wartościowy0.0ff / F
double64±5.0×10⁻³²⁴ do ±1.7×10³⁰⁸Wartościowy0.0dd / D
decimal128±1.0×10⁻²⁸ do ±7.9×10²⁸Wartościowy0.0mm / M
char16U+0000 do U+FFFFWartościowy'\0'
stringzmiennyograniczony pamięciąReferencyjnynull
objectzmiennywszystkie typyReferencyjnynull

* 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"
};