Funkcje w C# – dziel i rządź
Nauczysz się tworzyć własne funkcje, przekazywać parametry i zwracać wartości. Zrozumiesz, dlaczego funkcje to podstawa dobrego programowania.
Czym jest funkcja i po co nam?
Funkcja to wydzielony fragment kodu wykonujący konkretne zadanie. Można ją wywołać wielokrotnie z różnymi danymi.
Wyobraź sobie, że piszesz program, który 10 razy wyświetla powitanie. Bez funkcji musiałbyś skopiować ten sam kod 10 razy. Z funkcją – piszesz raz, wywołujesz 10 razy.
Zalety używania funkcji
| Zasada | Co oznacza? | Przykład |
|---|---|---|
| DRY | Don’t Repeat Yourself – nie powtarzaj kodu | Zamiast 10× ten sam kod → 1 funkcja wywoływana 10× |
| Czytelność | Kod podzielony na małe kawałki | ObliczPodatek() mówi więcej niż 20 linii obliczeń |
| Testowanie | Łatwiej sprawdzić małą funkcję | Testujesz Dodaj(2,3) → oczekujesz 5 |
| Reużywalność | Raz napisane, używane wszędzie | Funkcja SprawdzEmail() w wielu miejscach |
Struktura funkcji
Każda funkcja składa się z kilku elementów. Poznaj schemat, który będziesz stosować zawsze.
// Schemat ogólny: [modyfikator] [typ zwracany] [NazwaFunkcji]([parametry]) { // ciało funkcji - kod do wykonania return [wartość]; // jeśli typ != void } // Przykład konkretny: static int Dodaj(int a, int b) { int wynik = a + b; return wynik; }
Wyjaśnienie elementów
| Element | Co to? | Przykłady |
|---|---|---|
static | Modyfikator – funkcja należy do klasy, nie do obiektu | static, public, private |
| Typ zwracany | Co funkcja „oddaje” po zakończeniu | int, string, bool, void |
| Nazwa | Jak wywołujemy funkcję (PascalCase!) | ObliczSume, SprawdzWiek |
| Parametry | Dane wejściowe w nawiasach | (int a, int b), (string imie) |
return | Zwraca wartość i kończy funkcję | return wynik; |
Na tym etapie nauki wszystkie funkcje piszemy jako static. Dlaczego? Bo wywołujemy je z Main(), która też jest static. Bez static musielibyśmy tworzyć obiekt klasy – to temat na później (programowanie obiektowe).
Nazwy funkcji piszemy PascalCase – każde słowo z wielkiej litery, bez spacji:
✅ ObliczPodatek, SprawdzCzyPelnoletni, WypiszWynik
❌ obliczpodatek, oblicz_podatek, obliczPodatek
Funkcje void (bez wartości zwracanej)
Funkcje void wykonują akcję, ale nic nie zwracają. Służą do wyświetlania, zapisywania, modyfikowania – ale nie „oddają” żadnej wartości.
Przykład 1: Bez parametrów
// Definicja funkcji static void PowitajUzytkownika() { Console.WriteLine("Witaj w programie!"); Console.WriteLine("Miłego dnia!"); } // Wywołanie w Main(): PowitajUzytkownika(); // Wyświetli dwie linie PowitajUzytkownika(); // Można wywołać wielokrotnie!
Przykład 2: Z parametrem
// Funkcja przyjmuje imię jako parametr static void PowitajOsobe(string imie) { Console.WriteLine($"Witaj {imie}!"); } // Wywołania z różnymi argumentami: PowitajOsobe("Anna"); // Witaj Anna! PowitajOsobe("Marek"); // Witaj Marek! PowitajOsobe("Ola"); // Witaj Ola!
Parametr – zmienna w definicji funkcji: string imie
Argument – konkretna wartość przy wywołaniu: "Anna"
Funkcje zwracające wartość
Te funkcje wykonują obliczenia i „oddają” wynik za pomocą słowa return. Wynik można zapisać do zmiennej lub użyć dalej.
Przykład 1: Funkcja matematyczna
// Funkcja zwraca int – typ przed nazwą! static int Dodaj(int a, int b) { int wynik = a + b; return wynik; // Zwraca wartość } // Wywołanie – wynik zapisujemy do zmiennej: int suma = Dodaj(5, 3); Console.WriteLine($"Suma: {suma}"); // Suma: 8 // Można też użyć bezpośrednio: Console.WriteLine(Dodaj(10, 20)); // 30
Przykład 2: Funkcja zwracająca bool
// Funkcja sprawdzająca warunek – zwraca true/false static bool CzyPelnoletni(int wiek) { return wiek >= 18; // Zwraca wynik porównania } // Użycie w warunku if: if (CzyPelnoletni(20)) { Console.WriteLine("Osoba jest pełnoletnia"); } // Lub zapisanie do zmiennej: bool pelnoletni = CzyPelnoletni(16); // false
void – tylko działa
WypiszWynik(5);
Wyświetla coś, ale nic nie zwraca.
Nie można zapisać do zmiennej.
return – oddaje wartość
int x = Oblicz(5);
Zwraca wynik, który można zapisać, użyć w if, przekazać dalej.
Funkcja z typem int MUSI zwrócić int. Kompilator nie pozwoli skompilować funkcji bez return (lub z return złego typu).
Parametry funkcji
Funkcja może przyjmować wiele parametrów różnych typów. Poznasz też parametry domyślne i przekazywanie przez referencję.
Wiele parametrów różnych typów
static void WypiszDane(string imie, int wiek, double wzrost) { Console.WriteLine($"Imię: {imie}"); Console.WriteLine($"Wiek: {wiek}"); Console.WriteLine($"Wzrost: {wzrost}m"); } // Wywołanie – kolejność argumentów musi się zgadzać! WypiszDane("Anna", 17, 1.65);
Parametry z wartością domyślną
Parametr domyślny ma przypisaną wartość już w definicji. Jeśli nie podasz argumentu, użyta zostanie wartość domyślna.
// "powitanie" ma wartość domyślną "Cześć" static void WypiszPowitanie(string imie, string powitanie = "Cześć") { Console.WriteLine($"{powitanie}, {imie}!"); } // Różne sposoby wywołania: WypiszPowitanie("Anna"); // Cześć, Anna! WypiszPowitanie("Anna", "Witaj"); // Witaj, Anna! WypiszPowitanie("Marek", "Dzień dobry"); // Dzień dobry, Marek!
Praktyczny przykład: Kalkulator ceny
// Rabat domyślnie 0%, VAT domyślnie 23% static double ObliczCene(double cenaBazowa, double rabat = 0, double vat = 23) { double cenaPoRabacie = cenaBazowa - (cenaBazowa * rabat / 100); double cenaZVat = cenaPoRabacie + (cenaPoRabacie * vat / 100); return cenaZVat; } // Wywołania: double cena1 = ObliczCene(100); // 100zł, 0% rabatu, 23% VAT = 123zł double cena2 = ObliczCene(100, 10); // 100zł, 10% rabatu, 23% VAT = 110.7zł double cena3 = ObliczCene(100, 10, 8); // 100zł, 10% rabatu, 8% VAT = 97.2zł
Parametry domyślne MUSZĄ być na końcu listy parametrów!
✅ Poprawnie
void F(int a, int b, string c = "x")
Parametr domyślny na końcu
❌ Błąd kompilacji
void F(int a, string c = "x", int b)
Parametr bez domyślnej po parametrze z domyślną
Parametr ref – przekazywanie przez referencję
Normalnie funkcja otrzymuje kopię wartości. Zmiany wewnątrz funkcji nie wpływają na oryginalną zmienną. Słowo ref pozwala funkcji modyfikować oryginalną zmienną.
Przykład: Porównanie z ref i bez ref
// BEZ ref – zmienia tylko kopię static void ZwiekszBezRef(int liczba) { liczba++; // Zmienia KOPIĘ } // Z ref – zmienia oryginał static void ZwiekszZRef(ref int liczba) { liczba++; // Zmienia ORYGINAŁ } // Test: int a = 5; ZwiekszBezRef(a); Console.WriteLine(a); // 5 – BEZ ZMIAN! int b = 5; ZwiekszZRef(ref b); // UWAGA: "ref" też przy wywołaniu! Console.WriteLine(b); // 6 – ZMIENIONE!
Klasyczny przykład: Zamiana wartości
static void ZamienWartosci(ref int a, ref int b) { int temp = a; a = b; b = temp; } int x = 5, y = 10; Console.WriteLine($"Przed: x={x}, y={y}"); // x=5, y=10 ZamienWartosci(ref x, ref y); Console.WriteLine($"Po: x={x}, y={y}"); // x=10, y=5
Słowo ref musi być zarówno w definicji funkcji, jak i przy jej wywołaniu. To celowe – programista widzi, że zmienna może zostać zmodyfikowana.
Zakres zmiennych (scope)
Zmienne mają ograniczony „zasięg”. Zmienna lokalna istnieje tylko wewnątrz funkcji, w której została utworzona.
class Program { // Zmienna na poziomie klasy – dostępna wszędzie w klasie static int liczbaGlobalna = 10; static void PrzykladZakresu() { // Zmienna lokalna – dostępna tylko w tej funkcji int liczbaLokalna = 5; Console.WriteLine(liczbaGlobalna); // ✅ OK Console.WriteLine(liczbaLokalna); // ✅ OK } static void Main() { Console.WriteLine(liczbaGlobalna); // ✅ OK // Console.WriteLine(liczbaLokalna); // ❌ BŁĄD! Nie istnieje tutaj } }
Staraj się używać zmiennych lokalnych. Zmienne „globalne” (na poziomie klasy) utrudniają śledzenie, kto i kiedy je modyfikuje.
Przeciążanie funkcji (overloading)
Możesz mieć kilka funkcji o tej samej nazwie, ale z różnymi parametrami. Kompilator wybierze właściwą na podstawie argumentów.
// Ta sama nazwa "Policz", różne parametry: // Wersja dla dwóch int static int Policz(int a, int b) { return a + b; } // Wersja dla dwóch double static double Policz(double a, double b) { return a + b; } // Wersja dla trzech int static int Policz(int a, int b, int c) { return a + b + c; } // Wywołania – kompilator wybiera właściwą wersję: int w1 = Policz(5, 3); // Wersja int, int double w2 = Policz(5.5, 3.2); // Wersja double, double int w3 = Policz(1, 2, 3); // Wersja int, int, int
Przeciążanie jest przydatne, gdy ta sama operacja logiczna może działać na różnych typach danych. Zamiast DodajInt, DodajDouble, DodajTrzy – masz jedno Dodaj.
Funkcje muszą różnić się parametrami (liczbą lub typami). Nie możesz mieć dwóch funkcji różniących się tylko typem zwracanym!
Zadanie dla uczniów
Kalkulator ocen
Napisz program z następującymi funkcjami:
static void WypiszMenu()– wyświetla menu programustatic double ObliczSrednia(int o1, int o2, int o3)– oblicza średnią z 3 ocenstatic string DajOpis(double srednia)– zwraca opis słowny:- ≥ 4.5 → „bardzo dobry”
- ≥ 3.5 → „dobry”
- ≥ 2.5 → „dostateczny”
- < 2.5 → „niedostateczny”
static void WypiszWynik(string imie, double srednia, string opis)– wyświetla podsumowanie
Przykładowy wynik:
=== KALKULATOR OCEN === Podaj imię: Anna Podaj ocenę 1: 4 Podaj ocenę 2: 5 Podaj ocenę 3: 4 ----------------------- Uczeń: Anna Średnia: 4.33 Wynik: dobry
⭐ Bonus 1: Dodaj przeciążoną wersję ObliczSrednia dla 4 i 5 ocen
⭐⭐ Bonus 2: Dodaj funkcję z parametrem ref, która zamienia dwie najgorsze oceny miejscami (do wyświetlenia posortowanych)