do koszyka |
do koszyka |
do koszyka |
do koszyka |
do koszyka |
do koszyka |
do koszyka |
do koszyka |
do koszyka |
KURS JĘZYKA C++ PODSTAWY
00. Informacje ogólne - wstęp 01. Praca z konsolą - początki - cout 02. Zmienne (int, float, double) 03. Konsola cd. - cin 04. Zmienne tekstowe - (char, string) 05. Operatory 06. Instrukcje warunkowe (if - else, switch - case) 07. Pętle (for, while) 08. Instrukcje warunkowe cd. 09. Break, Continue 10. Funkcje 11. Zakresy ważności 12. Tablice zmiennych 13. Mała powtórka :))) 14. Struct - struktury 15. String, Vector - wstęp do STL 16. Wskaźniki, referencje 16a. Wskaźnik do typów prostych 16b. Wskaźnik do funkcji 16c. Operatory new i delete 16d. Referencje 17. Pliki źródłowe i nagłówkowe Zadania powtórzeniowe 18. Class - klasy 19. Konstruktor , destruktor 20. Argumenty domyślne, przeciążanie (przeładowanie) funkcji i operatorów C++ FAQ: 01. Operacje na plikach, odczyt/zapis02. Nie działa mi polecenie gotoxy() co robić ? 03. Jaki podręcznik do nauki C++ ? ostatnia aktualizacja: 29.08.2005 ODCZYT - ZAPIS DANYCH, OPERACJE NA PLIKACH
Zaczniemy od omówienia zapisu danych na dysk, najpierw sobie coś zapiszemy a następnie to odczytamy aby sprawdzić
czy wszystko wykonaliśmy poprawnie :).
Zapis danych tekstowych. Aby zapisać dane na dysku najpierw musimy wiedzieć jakie dane chcemy zapisać (typ), następnie ile tych danych ma być, ostatnim krokiem jest określenie ścieżki dostępu oraz wybranie nazwy pliku. Zacznijmy od zapisania na dysk danych tekstowych. 1 - dodajemy bibliotekę fstream, odpowiada ona za operacje zapisu danych na dysku 2 - tworzymy sobie tablicę z przykładowymi danymi do zapisania 3 - tworzymy obiekt fstream o nazwie plik_zapis, dzięki któremu nawiążemy "łączność" z plikiem 4 - rozpoczynamy komunikację z plikiem, jako pierwszy parametr podajemy nazwę pliku wraz z pełną ścieżką dostępu, jeśli podamy bez ścieżki plik zostanie utworzony w katalogu bieżącym programu drugi parametr (ios::out) informuje że otwieramy plik do zapisu, jeśli plik o podanej nazwie istnieje, zostanie skasowany i wpisana zostanie do niego nowa zawartość, jeśli pliku nie ma - zostanie on utworzony 5 - zapis do pliku, pierwszy parametr to źródło danych (ich adres w pamięci) , drugi ilość danych do zapisu 6 - kończymy pracę z plikiem, zamykamy dostęp do niego jeśli nie zamkniemy dostępu do pliku to do momentu zakończenia naszego programu dostęp do niego z innych aplikacji nie będzie możliwy Zapis danych tekstowych mamy wstępnie przedstawiony, jest to najprostsza możliwość gdyż w bardzo prosty sposób możemy określić ilość danych która ma zostać zapisana na dysk, niestety w przypadku innych typów danych (innych niż char) mogą pojawić się pewne drobne problemy. Zapis danych dowolnego typu. Została tu wprowadzona pewna modyfikacja otóż do drugiego parametru przy otwieraniu pliku został dodany identyfikator ios::binary informujący o tym, że zapisywane dane mogą być danymi dowolnego typu i nie wolno ich modyfikować. Może wyjaśnijmy o co w tym chodzi. Otóż w przypadku "zwykłego" trybu zapisu dane traktowane są jako tekst i niektóre białe znaki ulegają modyfikacji, sposób ich modyfikacji jest zależny od systemu operacyjnego np. w systemie Windows znak końca linii o kodzie 0x0a jest zamieniany na parę znaków 0x0d oraz 0x0a co w przypadku tekstu nie ma specjalnego znaczenia natomiast w przypadku innego typu danych ma zazwyczaj katastrofalne skutki. Ja osobiście zazwyczaj stosuje opcje zapisu z ios::binary nawet w przypadku danych tekstowych, w niczym to nie przeszkadza a w przyszłości można uniknąć przykrych niespodzianek. Jak określić ilość danych do zapisu w przypadku danych tekstowych już wiemy, co jednak w przypadku gdy chcemy zapisać na dysk np. tablicę danych typu int? Tu z pomocą przychodzi nam operator sizeof, oczywiście nie jest możliwe jego zastosowanie w taki sposób: Uruchom powyższy przykład i zobacz jaka jest długość zapisanego pliku, w systemie Windows są to 4 bajty co oczywiście nas nie satysfakcjonuje gdyż chcieliśmy zapisać całą tablicę int'ów. Co się stało ? Operator sizeof zwrócił nam nie rozmiar tablicy gdyż rozmiar ten nie jest mu znany lecz rozmiar wskaźnika (ile on zajmuje bajtów w pamięci) i tyle też zostało zapisane. W przypadku dynamicznie przydzielanej pamięci (operatorem new) musimy już sami wyliczyć ile danych chcemy zapisać. Tym razem operator sizeof zwraca nam informację o tym ile w pamięci zajmuje jedna zmienna typu int, następnie ta "informacja" (liczba) jest wymnażana przez ilość zmiennych które chcemy zapisać. W ten sposób mamy dokładnie określony rozmiar danych do zapisu. Aby nasz przykład uczynić jeszcze bardziej uniwersalnym radzę wprowadzić poniższe modyfikacje Właśnie z takiej metody radzę korzystać, ilość przydzielanej/zapisywanej pamięci jest przechowywana w zmiennej i to zawartość tej zmiennej "wszystko kontroluje". Tym sposobem dużo łatwiej można zapanować nad tym co robi nasz program. Warto zerknąć teraz do pliku który zapisaliśmy, co tak naprawdę w nim się znalazło, jako że przydzieliliśmy sobie pamięć nic do niej nie wpisując tylko ją od razu zapisaliśmy na dysk. Czasem mogą się tam znaleźć całkiem ciekawe dane z innych programów lecz zazwyczaj są to jakieś "śmieci". Może teraz kilka wyjaśnień jaka właściwie jest różnica między zmiennymi, po co korzystać z sizeof() ? Otóż jak już kiedyś wspomniałem zmienna char to dokładnie 1 bajt, więc tablica 10 zmiennych typu char to dokładnie 10 bajtów natomiast zmienna np. typu int nie zawsze ma dokładnie taki sam rozmiar, np w systemie Windows zmienna int zajmuje w pamięci 4 bajty więc tablica 10 zmienych typu int to dokładnie 40 bajtów. Z tego względu w naszym przykładzie jeśli chcemy zapisać np 20 zmiennych typu int nie powinniśmy stosować zapisu 20*4 lub jeszcze prościej 80, ale właśnie sizeof(int)*20 , po prostu ta postać zapisu jest bardziej uniwersalna i przenośna, twój kod źródłowy tak zapisany poprawnie skompiluje się zarówno w systemie: Windows, pod Dos'em lub na Linux'ie. Odczyt danych tekstowych. 1 - do odczytu raczej wszystkie typy plików powinno się otwierać w trybie ios::binary 2 - przed wczytaniem pliku musimy najpierw sprawdzić jego długość (żeby wiedzieć ile bajtów danych mamy wczytać) funkcja seekg(0,ios::end); ustawia nam znacznik odczytu na końcu pliku 3 - tellg(); podaje nam na której pozycji w pliku jesteśmy, jako że ustawiliśmy znacznik na koniec pliku to wartość zwrócona (pozycja) jest równocześnie długością pliku, długość (w bajtach) jest zapisywana do zmiennej i_DlugoscPliku 4 - przydzielamy pamięć na dane które zostaną wczytane z pliku przydzielamy o 1 bajt więcej (i_DlugoscPliku+1) gdyż w pliku mamy zapisany tylko czysty tekst a jak pamiętamy każdy ciąg tekstowy musi być zakończony znacznikiem końca tekstu - ten jeden dodatkowy bajt rezerwujemy sobie właśnie na ten znacznik (oczywiście można to było uwzględnic przy zapisywaniu pliku i zapisać tekst razem ze znacznikiem, no cóż wam pozostawiam eksperymenty :) ) 5 - powracamy na początek pliku (bo od początku będziemy wczytywać dane), dzięki seekg możemy rozpocząć odczyt w dowolnym miejscu pliku, czasem się to przydaje np. w sytuacji gdy plik jest bardzo duży i chcemy wczytać tylko jakiś jego wybrany fragment 6 - odczyt pliku, podajemy gdzie dane mają być wczytane i ile należy ich wczytać 7 - kończymy pracę z plikiem 8 - uzupełniamy znacznik końca tekstu (można tą linię pominąć żeby wybadać co się stanie :) ) 9 - wyświetlamy wczytane dane 10 - zwalniamy wcześniej przydzieloną pamięć Odczyt danych dowolnego typu. W przypadku danych innego typu niż tekstowe powyższy przykład też zadziała, niestety pojawić się może pewien problem z "dostaniem się" do tak wczytanych danych. Jak wiemy dane niezależnie od swego ułożenia/postaci w pamięci na dysk zapisywane są jako ciągi bajtów i jako ciągi bajtów zostają potem wczytane do pamięci zazwyczaj do tablicy typu char, to od nas zależy dalsza ich interpretacja. 1 - tutaj drobna zmiana, nie odczytujemy już danych tekstowych więc nie potrzeba dodawać jednego bajtu na znak końca tekstu 2 - ta linijka wymaga dłuższego komentarza, zastosowałem tutaj tzw rzutowanie wskaźników, wcześniej sprawa nie omawiana ale tutaj już bez tego się nie obejdziemy. Rzutowanie to nic innego jak zamiana jednego typu wskaźnika na wskaźnik innego typu. W tym przypadku wczytaliśmy dane do tablicy typu char (dane 1 bajtowe) ale my WIEMY że to co zostało wczytane są to dane typu int, bezpośrednio przez wskaźnik typu char nie możemy ich odczytać więc tworzymy sobie dodatkowy wskaźnik do danych typu int i ładujemy do niego adres naszej tablicy. Dokładniej mówiąc linia: int *pi_Dane = (int*)pc_Dane; oznacza: utwórz wskaźnik typu int o nazwie pi_Dane i przypisz do niego adres tablicy pc_Dane , przed przypisaniem dokonaj konwersji (rzutowania) wskaźnika z typu char do typu int - zapis (int*). 3 - odczytujemy pierwszy element naszej wczytanej tablicy, już przy pomocy wskaźnika int Oczywiście we wszystkich przykładach w których korzystałem z operatora new pominąłem sprawdzanie czy przydzielana pamięć została faktycznie przydzielona, w normalnym programie raczej nie powinno się z tego rezygnować, ja zrezygnowałem ze sprawdzania TYLKO dla zwiększenia czytelności kodu. Jeszcze jedna uwaga, każdy obszar przydzielonej pamięci operatorem new mógł być wcześniej używany przez inny program, więc zanim zaczniemy z niego korzystać należy go wcześniej wyczyścić (wypełnić zerami). Ogólnie warto wrócić do lekcji 16c aby sobie trochę temat operatorów new i delete przypomnieć. |
|