18. Operacje wejścia - wyjścia cz.1

18. Operacje wejścia - wyjścia cz.1

Autor: Paweł Kruczkowski

Opublikowano: 1/23/2007, 12:00 AM

Liczba odsłon: 92573

Tematem niniejszego artykułu są standardowe operacje wejścia – wyjścia. Już wielokrotnie spotkaliśmy się z podstawowymi operacjami zarówno wejściowymi jak i wyjściowymi na łamach portalu CentrumXP, niemniej jednak dzisiejszy tekst będzie utrwaleniem oraz poszerzeniem zdobytej już wiedzy na temat sposobów komunikowania się z użytkownikiem za pomocą języka C# 2.0.

Jak wiemy wykonanie jakiegokolwiek programu przez komputer polega na wprowadzaniu odpowiednich danych do pamięci operacyjnej oraz wykonaniu odpowiednich instrukcji, które te dane  będą w odpowiedni sposób  przetwarzać. W końcu, wykonanie programu to wyprowadzenie przez komputer (wyświetlenie) uzyskanych wyników. Do wprowadzania i wyprowadzania danych służą między innymi: klawiatura, myszka, dyski, ekran czy drukarka. W niniejszym artykule posłużą nam jedynie klawiatura (do wprowadzania danych) oraz ekran (na którym wyświetlane będą wyniki, jakie otrzymamy w wyniku działania naszych programów).

Pierwszym punktem naszego artykułu będzie zapoznanie się z operacjami konsolowymi wejścia – wyjścia, jakich  dostarcza nam język C# 2.0.

Warto zapamiętać: język C# obsługuje operacje wejścia – wyjścia za pomocą strumieni. Kiedy programista chce użyć jakiejś zmiennej bądź obiektu, to korzysta z jego nazwy i gotowe. Jednak, gdy chce zapisać dane do np. pliku lub odczytać dane z tego pliku poprzez np. internet, musi  je umieścić we wspomnianym właśnie strumieniu.

Napiszmy przykład, który prezentuje sposób używania operacji konsolowych w języku C# 2.0:

using System;
 
namespace CentrumXP_18
{
    class MojaKlasa
    {
        static void Main()
        {
            int mojaLiczba;
            string mojaLiterka;
            string mojZnak;
 
            Console.WriteLine("Podaj dowolną liczbę:");
            mojaLiczba = int.Parse(Console.ReadLine());
 
            Console.WriteLine("Podaj dowolną literkę:");
            mojaLiterka = Console.ReadLine();
 
            Console.WriteLine("Podaj dowolny znak:");
            mojZnak = Console.ReadLine();
 
            Console.WriteLine("Oto moje wyniki:");
            Console.WriteLine("Moja liczba to: {0}.", mojaLiczba);
            Console.WriteLine("Moja litera to: {0}.", mojaLiterka);
            Console.WriteLine("Moje znaki to: {0}.", mojZnak);
        }
    }
}

W powyższym przykładzie godną uwagi jest metoda ReadLine(), dzięki której możliwe było odczytanie kolejno wprowadzonej przez użytkownika liczby, litery oraz dowolnego znaku.

Wyjaśnienia wymaga również następujący fragment kodu:

mojaLiczba = int.Parse(Console.ReadLine());
 

W powyższej linii użyliśmy metody Parse(), która udostępnia sposób zamiany wartości numerycznej (odczytanej jako łańcuch wprowadzony przez użytkownika przy pomocy klawiatury) na wewnętrzny format (w naszym przypadku tym formatem jest intiger). A więc po odczytywaniu łańcucha znaku wpisanego przez użytkownika i użyciu metody ReadLine() skorzystano następnie z metody Parse(), dzięki której dokonano zamiany łańcucha na liczbę całkowitą.

Po uruchomieniu powyższego przykałdu otrzymamy na ekranie naszego monitora następujące wyniki:

Powyższy przykład jest bardzo przydatny, aby zapamiętać 3 ważne informacje:

  1. w języku C# parametr wejściowy musi być typu łańcuchowego. Do jego wczytania służy metoda ReadLine().
  2. metoda Parse() umożliwia zamianę wartości numerycznej (odczytanej jako łańcuch np. z klawiatury) na wewnętrzny format
  3. do wyprowadzenia zmiennych (wyświetlenia ich) na ekranie komputera służy metoda WriteLine().

Prześledźmy jeszcze inny prosty przykład prezentujący sposób używania operacji wejścia – wyjścia:

using System;
 
namespace CentrumXP_18
{
    class MojaKlasa
    {
        public enum Oceny
        {
            Jedynka = 1,
            Dwojka = 2,
            Trojka = 3,
            Czworka = 4,
            Piatka = 5,
            Szostka = 6
        }
       
        static void Main()
        {
            int mojaOcena;
 
            Console.WriteLine("Podaj ocenę jaką dzisiaj dostałeś w szkole:");
            mojaOcena = int.Parse(Console.ReadLine());
 
            switch (mojaOcena)
            {
                case ((int)Oceny.Jedynka):
                    Console.WriteLine("Niestety dostałeś dzisiaj jedynke. Popraw się!");
                    break;
                case ((int)Oceny.Dwojka):
                    Console.WriteLine("Niestety dostałeś dzisiaj słabą ocenę. Popraw się!");
                    break;
                case ((int)Oceny.Trojka):
                    Console.WriteLine("Niestety trójka jest słabą oceną. Postaraj sie o wyżej!");
                    break;
                case ((int)Oceny.Czworka):
                    Console.WriteLine("Czwórka jest dobrą oceną...");
                    break;
                case ((int)Oceny.Piatka):
                    Console.WriteLine("Gratuluję! Pięć to bardzo dobry stopień.");
                    break;
                case ((int)Oceny.Szostka):
                    Console.WriteLine("Gratuluję! Szóstka to wspaniała ocena! Wiecej ich.");
                    break;
                default:
                    Console.WriteLine("Niestety podałeś nieprawidłowy stopień:-(");
                    break;
            }
        }
    }
}

W powyższym przykładzie, obok użycia metod ReadLine() oraz WriteLine() godną uwagi jest instrukcja switch. Jest ona ulubioną instrukcją programistów stanowiącą alternatywę dla zagnieżdżonych instrukcji if. Dlatego też warto napisać parę słów na jej temat na łamach portalu CentrumXP. Jak większość z nas się domyśla, zagnieżdżone instrukcje if są mało czytelne i w czasie ich pisanie nietrudno o pomyłkę. Stąd twórcy języka C# utowrzyli alternatywę dla pętli if, którą to definiujemy w następujący sposób:

switch (wyrażenie)
            {
                case (stałe_wyrażenie):
                    instrukcja
                    instrukcja skoku
                [default: instrukcja]
            }

Wyrażenie warunkowe znajduje się w nawiasach w nagłówku instrukcji switch (podobnie jak w instrukcji if). Każda instrukcja case wymaga stałego wyrażenia (np. wyliczenia, które użyliśmy w powyższym przykładzie). Jeśli dana instrukcja case pasuje do wyrażenia, to zostaną wykonane instrukcje z nim związane, a następnie – instrukcja skoku. Zwykle instrukcją skoku jest break, który powoduje wyjście programu z instrukcji switch.

W powyższym przykładzie zdefiniowaliśmy sobie wyliczenie przechowujące stopnie, jakie każdy uczeń może dostać w szkole. Następnie za pomocą metody ReadLine() program wczytuje ocenę, jaką wpisał za pomocą klawiatury uczeń. Kolejnym krokiem jest wykonanie się instrukcji switch, która w zależności od podanej oceny będzie wykonywać odpowiednią instrukcją wraz z odpowiadającą mu metodą WriteLine().

Po uruchomieniu powyższego przykładu otrzymamy następujące wyniki:

A co będzie, jak uczeń wpisze inną cyfrę niż z zakresu: 1-6? Nasz program jest na to przygotowany - a mianowicie instrukcja switch, która zawiera instrukcję: default. Instrukcja ta jest wykonywana w momencie, gdy żadna z instrukcji case nie pasuje do danego wyrażenia (podanej przez użytkownika oceny) znajdującego się w nawiasach w nagłówku switch. Innymi słowy, jeśli uczeń w powyższym programie poda inną liczbę niż z przedziału: 1-6, wówczas żadna z instrukcji case nie będzie na to „przygotowana” i wykona się instrukcja default wraz z metodą WriteLine(), dzięki której na ekranie otrzymamy napis informujący o tym, że podano błędną ocenę:

Drugim punktem niniejszego artykułu są operacje wejścia – wyjścia na plikach. Jak wiemy, do tej pory wykonywaliśmy operacje na danych, które były przechowywane wyłącznie w pamięci operacyjnej komputera. Jednak język C# umożliwia nam zapis i odczyt do/z pamięci zewnętrznej. Przechowywanie uporządkowanych danych w pamięci zewnętrznej komputera odbywa się w plikach. W zależności od dostępu do pliku mamy do czynienia z dwoma rodzajami klas plików:

- pliki o dostępie sekwencyjnym – dostęp do danego elementu wymaga przejrzenia wszystkich elementów poprzedzających go w pliku
- pliki o dostępie swobodnym – dostęp do danego elementu nie zależy od innych elementów w pliku.

Jak każdy język obiektowy, tak i C# 2.0 dostarcza nam szereg klas, które pozwalają nam na zapis danych do pliku oraz ich odczyt z pliku. Plik otwieramy za pośrednictwem połączenia go ze strumieniem. Język C# definiuje zarówno klasy strumieni bajtowych jak i znakowych. Wszystkie klasy strumieni są zdefiniowane wewnątrz przestrzeni nazw System.IO.

Strumienie znakowe umożliwiają zapis do plików tekstowych i ich odczyt. Do przeprowadzenia takich operacji możemy wykorzystać strumień FileStream oraz klasy StreamReader (do odczytu) oraz StreamWriter (do zapisu). Klasy te automatycznie konwertują strumień bajtów na strumień znaków (i oczywiście na odwrót).

Aby zdefiniować strumień bajtów wraz z plikiem musimy zbudować obiekt typu FileStream, który będzie między innymi miał konstruktor przyjmujący 2 parametry (nazwa pliku, który chcemy otworzyć oraz tryb otwarcia tegoż pliku):

FileStream fs = new FileStream("C:\\moj_plik.txt", FileMode.CreateNew);

W powyższej linii kodu zdefiniowaliśmy obiekt fs klasy FileStream, który definiuje konstruktor przyjmujący wspomniane dwa parametry: plik: mój_plik.txt, który chcemy otworzyć oraz tryb określający tryb otwarcia pliku (w naszym przypadku tworzymy nowy plik). Poniżej prezentujemy wszystkie rodzaje trybów otwarcia pliku:

Tryb
Opis
FileMode.Append
Dopisywanie danych do końca pliku
FileMode.CreateNew
Utworzenie nowego pliku
FileMode.Open
Otwarcie pliku, który już istnieje
FileMode.OpenOrCreate
Otwarcie pliku, któru już istnieje lub utworzenie pliku, który nie istniał
FileMode.Truncate
Otwarcie pliku, który już istnieje, redukując jego wartość do zera

Aby utworzyć wyjściowy strumień znakowy, musimy umieścić obiekt typu Stream (np. FileStream) wewnątrz klasy StreamWriter. Klasa ta oferuje kilka konstruktorów, z których najczęściej używanym jest:

StreamWriter(Stream nazwa_otwartego_strumienia);

Poniżej napiszemy prosty program, który zapisuje do pliku (mojPlik.txt) znajdującego się na dycku C wszystkie dane odczytane z klawiatury:

using System;
using System.IO;
 
namespace CentrumXP_18
{
    class MojaKlasa
    {
        static void Main(string[] args)
        {
            //zmienne
            string mojeDaneZapis;
            FileStream fsZapis;
           
            //tworzymy obiekt typu FileStream
            fsZapis = new FileStream("C://mojPlik.txt", FileMode.CreateNew);
            //tworzymy strumien typu StreamWriter
            StreamWriter sw = new StreamWriter(fsZapis);
           
            //pobieramy dane od uzytkownika
            Console.WriteLine("Podaj dane do pliku.");
            Console.WriteLine("Podaj imię i nazwisko:");
            mojeDaneZapis = Console.ReadLine();
            //zapisujemy lancuch tekstu do pliku
            sw.WriteLine(mojeDaneZapis);
            //pobieramy dane
            Console.WriteLine("Podaj szczęśliwą liczbę:");
            mojeDaneZapis = Console.ReadLine();
            //zapisujemy lancuch tekstu do pliku
            sw.WriteLine(mojeDaneZapis);
            //zamykamy zapis
            sw.Close();
        }
    }
}

W powyższym przykładzie utworzyliśmy obiekt fsZapis typu FileStream wewnątrz klasy StreamWriter:

StreamWriter sw = new StreamWriter(fsZapis);

Następnie zbieramy wszystkie dane, jakie zostały wpisane z klawiatury i za pomocą metody WriteLine() wywołanej na obiekcie sw (obiekt klasy StreamWriter) zapisujemy je do pliku mojPlik.txt, który został wcześniej utworzony dzięki klasie FileStream (i trybowi FileMode.CreateNew).

Przy zapisywaniu danych do pliku i używaniu przy tym obiektów klasy StreamWriter nie możemy zapomnieć o metodzie Close() (metoda wywoływana właśnie na tych obiektach), dzięki której zamykamy możliwość dalszego zapisywania łańcuchów znaków do naszego pliku tekstowego.

Po skompilowaniu i uruchomieniu powyższego przykładu otrzymamy następujące wyniki, które następnie w odpowiedni sposób są zapisane do pliku mojPlik.txt stworzonego na dysku C:

Umiemy już zapisywać dane do pliku. A więc czas na przykład prezentujący sposób odczytu tych danych z pliku: mojPlik.txt:

using System;
using System.IO;
 
namespace CentrumXP_18
{
    class MojaKlasa
    {
        static void Main(string[] args)
        {
            //zmienne
            string mojeDaneOdczyt;
            FileStream fsOdczyt;
           
            //tworzymy obiekt typu FileStream
            fsOdczyt = new FileStream("C://mojPlik.txt", FileMode.Open);
            //tworzymy strumien typu StreamReader
            StreamReader sr = new StreamReader(fsOdczyt);
           
            //odczyt danych z pliku: mojPlik.txt
            Console.WriteLine("Odczytujemy dane z pliku.");
 
            while ((mojeDaneOdczyt = sr.ReadLine()) != null)
            {
                Console.WriteLine(mojeDaneOdczyt);
            }
            //zamykamy plik
            sr.Close();
            Console.ReadLine();           
        }
    }
}
 

Aby odczytać dane z pliku musimy utworzyć strumień wejściowy (w naszym przykładzie obiekt fsOdczyt klasy FileStream) bazujący na znakach, który musi znajdować się wewnątrz klasy StreamReader: StreamReader sr = new StreamReader(fsOdczyt);

Wartym podkreślenia jest następujący fragment kodu:

while ((mojeDaneOdczyt = sr.ReadLine()) != null

Powyższa linijka kodu umożliwia odczyt danych z pliku, aż zostanie osiągnięty koniec tego pliku. Wszystkie te dane zostaną następnie w odpowiedni sposób wyświetlone na ekranie. Podobnie jak przy zapisywaniu, tak i przy odczycie ważną metodą jest metoda Close(), która musi zostać wywołana na obiekcie typu StreamReader, aby dany plik został w odpowiedni sposób zamknięty.

Jeśli nasz program zostanie uruchominy, to na ekranie otrzymamy następujące wyniki:

Tematem niniejszego artykułu było przedstawienie podstawowych operacji wejścia-wyjścia, jakie oferuje nam język C# 2.0. Opowiedzieliśmy sobie o podstawowych metodach służących do prawidłowej komunikacji użytkownika z komputerem (poznaliśmy znaczenie metod WriteLine() oraz ReadLine()). Nauczylismy się również zapisywać dane do pliku tekstowego i z niego je odczytywać. Przy okazji jednego z przykładów tego artykułu zdefiniowaliśmy instrukcję switch, której znaczenie jest z punktu widzenia programistów bardzo ważne.

Za tydzień będziemy kontynuować ten temat. Powiemy sobie między innymi szerzej o sposobach zapisu i odczytu danych, a także o sposobach tworzenia plików i katalogów na naszym dysku.

Jak wykorzystać Copilot w codziennej pracy? Kurs w przedsprzedaży
Jak wykorzystać Copilot w codziennej pracy? Kurs w przedsprzedaży

Wydarzenia