profil

C++ - wykład 1/4

poleca 85% 103 głosów

Treść
Grafika
Filmy
Komentarze



INŻYNIERIA OPROGRAMOWANIA

PROGRAMOWANIE W JĘZYKU C/C++



Semestr II





Wykład 1 - 2 godz.

Zakres tematyczny

1. Dynamiczna alokacja obiektów

2. Operacje we/wy - obsługa zbiorów

3. Obsługa zbiorów - operacje strumieniowe - podejście języka C



1. Dynamiczna alokacja obiektów

W poprzednim semestrze omawialiśmy zagadnienia dynamicznej alokacji dla standardowych typów zmiennych, w podejściu standardowym i języka C++. Po omówieniu podstawowych informacji o obiektach, pora wspomnieć o tym zagadnieniu w odniesieniu do typów zdefiniowanych przez użytkownika.

Przypomnijmy, że dla dużych obiektów (zmiennych, tablic, struktur) deklarowanie ich "na sztywno" w programie może prowadzić do problemów z umieszczeniem ich w pamięci. Jedynym rozsądnym wyjściem jest wtedy czasowe pobranie (zarezerwowanie) pewnego obszaru pamięci w którym chwilowo przechowywane będą dane , a po ich użyciu zwolnienie. W języku C obszar ten nazwano heap'em, C++ free store. Różnica między nimi polegała jedynie na funkcjach używanych do dostępu do tej pamięci.

W języku C, do dynamicznej alokacji pamięci używaliśmy funkcji typu malloc, alloc, calloc, oraz free do zwalniania pamięci.. Musieliśmy jednak pamiętać z jakim modelem pamięci pracujemy. Dla innego niż small, tiny musieliśmy używać funkcji farmalloc, faralloc, farfree. Przypomnijmy jak to robiliśmy na przykładzie struktury zdefiniowanej na poprzednim wykładzie:

struct Date *dateptr;

dateptr = (struct date *) malloc(sizeof(struct date));

W języku C++ uniezależniamy się od modelu pamięci poprzez operowanie operatorami new i jego uzupełnieniem - operatorem delete.

O ile w stosunku do zmiennych podstawowych (także struktur i tablic) można wymiennie stosować oba podejścia, tak do dynamicznej alokacji obiektów (przykładów klas zdefiniowanych przez użytkownika ) możliwe jest jedynie użycie operatorów new i delete. Dlaczego? Otóż gdybyśmy chcieli użyć funkcji malloc do tworzenia takiego nowego (dynamicznego co prawda, ale jednak) obiektu, wówczas nie uruchamiany by był konstruktor tworzący egzemplarze klas. Wówczas funkcja malloc zwracała by wskaźnik do niezainicjowanego bloku pamięci. Moglibyśmy co prawda używać wtedy funkcji składowych klasy, ale odnosiły by się one do nieprawidłowo skonstruowanego obiektu, co najprawdopodobniej spowodowało by sytuacje błędna. Np.

Date *dateptr;

int i;

datepte = (Date *) malloc(sizeof(Date));

i = dateptr->getmonth(); //zwraca niezdefiniowana wartość miesiąca.

W takim przypadku tracimy wszystkie możliwości jakie przewidzieliśmy dla konstruktora. Lepszym wyjściem jest tu użycie operatora new. Np.

Date *firstptr, secondptr;

int i;

firstptr = new Date; //wywołanie konstruktora

i = firstptr->getmonth(); //zwraca 1, bo tak było przewidziane w konstruktorze



secondptr = new Date(3,15,1996); //wywołanie konstruktora z zainicjowaniem //pamięci

i = secondptr->getmonth();

Operator new wywołuje konstruktor niezależnie od tego, czy użyliśmy argumentów czy nie, co gwarantuje, że każdy alokowany obiekt jest prawidłowo skonstruowany!!!

Zwalnianie pamięci zarezerwowanej operatorem new wykonuje operator delete. Dla obiektów alokowanych w ostatnim przykładzie zwolnienie pamięci odbywa się w następujący sposób:

delete firstptr;

delete secondptr;

Operator ten automatycznie wywołuje destruktor dla obiektu zanim alokuje go z pamięci.

Jak widać użycie obu operatorów dla obiektów jest typowe i podobne jak dla zmiennych podstawowych. Pamiętajmy, że użycie tego operatora do zwalniania pamięci nie przydzielonej operatorem new lub powtórne skasowanie już skasowanego wskaźnika powoduje, że program będzie zachowywał się dziwnie, lub po prostu zawiesi się.!!!



Operator new i wskaźniki składowe klas - klasy ze wskaźnikami

Przypuśćmy, że chcemy zapisać klasę o nazwie String, gdzie każdy obiekt zawiera ciąg znaków. Przechowywanie stringów w tablicach nie jest odpowiednie, ponieważ nie wiemy jak długie będa te stringi. Zamiast tego każdemu obiektowi możemy włączyć wskaźnik do char jako daną składową i dynamicznie alokować odpowiednią pamięć dla każdego obiektu. Prześledźmy to na przykładzie.

#include

#include

class String

{

public:

String();

String(const char *s);

String(char c, int n);

void set(int index, chat newchar);

char get(int index) const;

int getlength()const {return length;}

void display()const {cout<
~String();

private:

int length;

char *buf;

}

String::String() { buf = 0; length = 0; }

String::String(const char *s) { length = strlen(s); buf = new char[length+1]; strcpy(buf,s); }

String::String(char c, int n) { length=n; buf=newchar[length+1]; memset(buf,c,length);

buf[length]=''; }

void String::set(int index, char newchar)

{

if((index>0) && (index<=length)) buf[index-1]=newchar;

}

char String::get(int index)const

{

if((index>0) &&(inex<=length)) return buf[index-1];

else return 0;

}

String::~String() { delete buf;} //pracuje nawet dla pustego stringu. Kasowanie wskaźnika //NULL jest bezpieczne;

main()

{

string mystring("oto mój string");

mystring.set(1,'O'); //zmienia znak na odpowiedniej pozycji

}

Konstruktor, który pobiera wskaźnik do char ,używa operatora new do alokowania bufora zawierającego string. Potem kopiuje zawartość stringu do bufora. W rezultacie obiekt typu string nie jest ciągłym blokiem pamięci, jak to się ma w przypadku struktury. Każdy obiekt składa się z dwóch bloków pamięci. Jeden zawiera length i buf, drugi zawiera wprowadzone znaki.

Wywołanie operatora sizeof() dla obiektu klasy string poda jedynie rozmiar pierwszego bloku. Jednak różne obiekty tej samej klasy mogą mieć bufory na znaki różnej długości.



PROBLEMY!!!

Przypóśćmy, że mamy funkcję main, która ma postać:

main()

{

string yourstring("oto twój string");

string mystring("oto mój string");

yourstring = mystring;

}

Jest oczywistym co robi taki program. Tworzy dwa obiekty yourstring i mystring i przypisuje yourstring zawartość obiektu mystring. Wygląda to nieszkodliwie, ale...

Kiedy przypisujemy jeden obiekt drugiemu, kompilator wykonuje przypisanie wszystkich składowych:

yourstring.length = mystring.length;

yourstring.buf = mystring.buf;

Pierwsze przypisanie nie jest problemem. Ale buf jest wsakżnikiem. Przypisanie ich oznacza, że mystring.buf wskazuje na ten sam obszar co yourstring.buf. Dwa obiekty operują na tym samym buforze:







Oznacza to, że modyfikacja jednego obiektu pociąga za sobą modyfikację drugiego, co nie zawsze jest zjawiskiem pożądanym.

Jednak poważniejszy problem powstaje, gdy alokowany obiekt ma być likwidowany. Wówczas blok pierwszy pamięci zawierający length i buf są delokowane automatycznie, jednak bufor znaków alokowany był przy pomocy operatora new, a więc musi być usuwany operatorem delete. Dlatego też obiekty takich klas jak string, zawierajace wskaźniki jako dane składowe muszą posiadać destruktory, likwidujące alokowane obszary. Jeśli klasa nie miałaby zdefiniowanego destruktora, zajęty obszar pamięci nie byłby zwalniany i program mógłby się zakończyć z komunikatem"out of memory".



Dynamiczna alokacja tablic obiektów

Podobnie jak tablice typów wbudowanych możemy tworzyć tablice obiektów:

Date birthday[10];

Podczas deklarowania tablicy obiektów konstruktor wywoływany jest dla każdego elementu tablicy.

Gdy deklarujemy tablice bez inicjacji konstruktor wywoływany jest bez argumentów (efault constructor).

Gdy chcemy inicjować każdy element tablicy osobno, wywołujemy konstruktor z innymi argumentami:

Date birthday[10] = { Date(1,12,1986), Date(11,10,1986),

Date(1,1,1986)};

W powyższym przykładzie brakujące obiekty utworzone zostaną domyślnym konstruktorem (bez argumentów).

Tablice obiektów alokujemy taj jak dla innych typów:

String *text;

text = new string[5];

W tym przypadku nie możemy inicjować obiektów, wołany jest jedynie konstruktor default.

Przy delokacji tablicy obiektów opeartorem delete konieczne jest stosowanie pustych nawiasów wskazujących na to, że jest to tablica obiektów. Gdybyśmy je pomineli delokowany byłby jedynie obszar wskazany przez wskaźnik np. text, czyli zerowy element tablicy) tylko obiekt pierwszy .

delete []text;

przy takim zapisie destruktor wywoływany jest dla każdego obiektu osobno. Wtedy kompilator delokuje obszar pamięci wskazany przez text.

Przy użyciu klas bez destruktorów np. Date można kasować tablice bez []:

Date *mydate;

mydate=new Date[5];

// użycie klasy

delete mydate;

W tym przypadku kompilator rozpozna, że ma do czynienia z klasa bez destruktora i automatycznie od razu zwolni obszar wskazany przez mydate. Ponieważ obiekt nie ma alokowanych buforów nie ma problemu związanego z brakiem destruktorów.

Jednak dobrym zwyczajem jest trzymanie się konsekwentnie zapisu z []. Zawsze bowiem klasa może być reimplementowana tak, że będzie mogła wykonywać dynamiczna alokacje pamięci i wymagać destruktora. Wówczas nie będziemy musieli poprawiać całego programu.



2.Operacje we/wy - obsługa zbiorów

Procedury we/wy ze standardowej biblioteki C pozwalają zapisać dane i je odczytać ze zbiorów i urządzeń we/wy.

W języku C nie ma predefiniowanej struktury wewnętrznej zbiorów jak np w Clipperze, gdzie dane zapisywane są w porcjach zwanych rekordami, i gdzie dostęp do danych odbywa się poprzez licznik rekordów.

W C wszystkie dane traktowane sa jako sekwencja bajtów. Wszystkie funkcje we/wy można zasadniczo podzielić na 3 grupy (które pokrótce omówimy na wykładzie):

- strumieniowe operacje we/wy

- operacje we/wy niskiego poziomu

- operacje we/wydotyczące konsolo i portów

Funkcje strumieniowe traktuja zbiór danych i elementy zbioru danych jako ciąg pojedynczych znaków ( nie jako jakieś określone struktury danych), które mogą być później grupowane poprzez formatowanie, w formatowanych operacjach we/wy. Poprzezwybranie spośród wielu funkcji strumieniowych można przetwarzać dane w różnych rozmiarach i formatach, od pojedynczego znaku do dużych struktur danych.

Aby mieć możliwość operowania na danych zapisanych w zbiorach, w pierwszej kolejności dany zbiór otworzyć (lub stworzyć jeśli nie istnieje). Kiedy otwieramy zbiór przy pomocy funkcji strumieniowych, otwarty zbiór jest kojarzony ze strukturą typu FILE(zdefiniowana w stdio.h) zawierającą podstawowe informacje dotyczące otwieranego zbioru. Po otwarciu zbioru wskaźnik do tej struktury jest zwracany przez funkcje otwierającą zbiór. Pozostałe funkcje operujące na zbiorze używają tego wskaźnika do komunikowania się ze zbiorem.

Strumieniowe operacje we/wy dostarczają: buforowanych, formatowanych i nieformatowanych operacji we/wy.

Podczas buforowania strumienia, dane czytane ze strumienia lub do niego zapisywane są przechowywane w pewnym obszarze pamięci zwanej buforem. Przy zapisie - zawartość bufora wyjściowego jest przepisywana do miejsca przeznaczenia po zapełnieniu się bufora lub w momencie zamykania strumienia lub w czasie normalnego zakończenia programu (nie na skutek błędu wykonania). Potem bufor jest opróżniany. Przy czytaniu, blok danych umieszczany jest w buforze wejściowym, a dane czytane są z bufora. Kiedy bufor wejściowy jest pusty, następny blok danych przesyłany jest do bufora.

Operacje buforowane są operacjami bardzo efektywnymi ponieważ system przesyła duże bloki danych w jednej operacji, a nie jak przy niebuforowanych, gdy wykonuje się zapis i odczyt pojedynczych elementów danych. Jednak w przypadku błędu wykonania i nienormalnego zakończenia programu podczas opróżniania bufora dane są bezpowrotnie tracony.



Opreacje we/wy niskiego poziomu nie buforują ani nie formatują danych. Wykorzystują bezpośrednio możliwości operacji we/wy systemu operacyjnego. Przez te funkcje mamy dostęp do zbiorów i urządzeń zewnętrznych na niższym poziomie niż funkcje strumieniowe. Funkcje wykonują się szybciej bo omijany jest jakgdyby interfejspomiędzy systemem operacyjnym a użytkownikiem. Jednak posługiwanie nimi jest dużo trudniejsze (przynajmniej na początku). W momencie otwarcia zbioru na poziomie niższym , ze zbiorem kojarzona jest zmienna typu integer - tzw. "file handle". Poprzez ta daną podobnie jak poprzez wskaźnik FILE, funkcje odwołują się do konkretnego zbioru.



Operacje we/wy na konsoli i portach mogą być traktowane jako rozszerzenie (rozwinięcie) funkcji strumieniowych. Pozwalają czytać lub pisać na konsolę lub porty we/wy (np. port drukarki, RS). Operacje czytają dane w bajtach. Można operacjom tym nadawać różne opcje np. wyświetlanie danych przy czytaniu itp.

UWAGA!!

Funkcje niskiego poziomu i strumieniowe są generalnie niezgodne, tak więc na jednym zbiorze muszą być wykonywane operacje jednego typu. Ponieważ strumieniowe operacje we/wy są buforowane, a niskiego poziomu nie, próba dostępu do zbioru przez dwie różne metody może spowodować utratę danych w buforze.



3. Operacje we/wy - podejście języka C - funkcje strumieniowe

Aby używać funkcji strumieniowych musimy w zbiorze źródłowym dołączyć zbiór . W zbiorze tym zdefiniowane są stałe, typy i struktury (np. FILE) używane w funkcjach strumieniowych, zawiera deklaracje funkcji i makr do obsługi strumieni. Ze stałych, które mogą się przydać zdefiniowane są:

EOF - zdefiniownaj jako stała zwracające koniec zbioru

NULL - wskaźnik zerowy

BUFSIZE - bieżący rozmiar bufora strumieniowego w bajtach



OTWARCIE STRUMIENIA

Strumień przed jego użyciem musi być otwarty. Do tego celu służy jedna z funkcji: fdopen, fopen lub freopen. Po otwarciu może on być ustawiony w trybie: do odczytu, do zapisu, lub do aktualizacji, jako strumień tekstowy lub binarny. Wszystkie funkcje otwierające zwracają wskaźnik do struktury FILE, który służy do komunikowania się funkcji z konkretnym zbiorem danych

fp = fopen("name","mode")

name - nazwa zbioru

mode - tryb ustawienia zbioru

r - do czytania. Zbiór musi istnieć

w - otwiera pusty zbiór do zapisu, jeśli plik istnieje kasuje jego zawartość

a - do zapisu na końcu zbioru. Jeśli zbiór nie istnieje tworzy nowy



r+ - otwiera istniejący zbiór do zapisu i odczytu

w+ - otwiera pusty zbiór do zapisu i odczytu. Istniejący zbiór kasuje

a+ - otwiera do odczytu i zapisu na końcu. Nie istniejący zbiór tworzy

a i a+ - zapisuje zawsze na końcu mimo użycia funkcji pozycjonującej fseek

w i w+ - zawsze kasuje istniejący zbiór

b - otwiera zbiór w trybie binarnym

Funkcja zwraca wskaźnik fp lub NULL - przy błędzie otwarcia.



fp = fdopen(handle, "name")

Funkcja łączy strumień wejściowy ze zbiorem identyfikowanym przez stałą (file handle) -handle. Umożliwia wykonywanie operacji buforownych i formatowanych dla zbiorów otwartych w trybie "niskiego poziomu". Najpierw musi być otwarty taki zbiór, a potem użyte fdopen:

FILE *fp;

int fh = open("data",O_RDONLY);

fp = fdopen(fh,"r");

//operacje na zbiorze



fp = freopen(path,type,stream)

Funkcja zamyka sbiór stowarzyszony z file pointerem - stream i stowarzysza go ze zbiorem path. Jest zwykle wykorzystywany do przełączania standardowych we/wy: stdin, stdout, stdoux, stdprn na zbiór wyszczególniony przez programistę.

Zwraca wskaźnik do nowo otwartego zbioru. Jesli powstał błąd, to zbiór jest zamykany i zwracany NULL

stdin, stdout- standardowe pliki otwierane w momencie rozpoczęcia programu

stdoux - port COM1

stdprn- drukarka

Poniższy przykład przypisuje stdout zbiorowi data2 i zapisuje linie do tego zbioru:

FILE *stream, *errstream;

main()

{

stream=freopen("data2", "w", stdout);

if(stream == NULL)

fprintf("error on freopen
");

else

{

frpintf(stream, "To będzie zapoisane do zbioru data2");

system("type data2");

}

}

STEROWANIE BUFOROWANIEM

Zbiór otwarty w/w funkcjami jest z założenia buforowany. Predefiniowane stderr i stdoux nie są buforowane z założenia, chyba że używane są w rodzinie funkcji printf, scanf, gdzie przypisane są do tymczasowych buforów. Strumienie stdin, stdout, stdprn są buforowane z założenia.



void setbuf(fp, buf)

fp - wskaźnik do otwartego zbioru

buf- tablica -char buf[BUFSIZE] dla operacji buforowanych

NULL - dla operacji niebuforowanych

Funkcja pozwala kontrolować buforowanie strumienia





int setvbuf(fp,buf,type,size)

fp i buf -jak w poprzedniej funkcji

size - rozmiar bufora

type - _IONBF bez buforowania: size i buf są ignorowane

_IOFBF pełne buforowanie

_IOLBFbuforowanie wierszy pliku tekstowego

Funkcja kontroluje buforowanie i rozmiar bufora dla strumienia

char buf[100];

size = sizeof(buf);

Zwraca 0 jeśli OK, != 0 jeśli ustalony nieprawidłowy rozmiar bufora lub tryb buforowania



ZAPIS I ODCZYT DANYCH

Funkcje strumieniowe jak już wspomnieliśmy wcześniej pozwalają przesyłać dane na różne sposoby. Można czytać lub pisać binarnie, znak po znaku liniami, lub w skomplikowanych formatach.

Operacje odczytu i zapisu odbywaja się od bieżącej pozycji strumienia "file pointer" nie mylić z "FILE pointerem". File pointer zmienia pozycję pliku po zakończeniu zapisu lub odczytu. Np. jeśli czytamy jeden znak ze strumienia to file pointer rośnie o jeden i wskazuje na pierwszy nieprzeczytany znak .



FUNKCJE ODCZYTU I ZAPISU

| |

FORMATOWANE WE/WY BEZPOŚREDNI ZAPIS/ODCZYT



Najprostszymi funkcjami są funkcje realizujące we/wy znakowe. Są nimi:



FORMATOWANE WE/WY



CZYTANIE ZNAKU ZE STRUMIENIA

int fgetc(fp);

int fgetchar(void);

Fgetc czyta znak ze strumienia jako unsigned char i przekształca do int . Fgetchar jest ekwiwalentem funkcji fgetc(stdin) - czyli czyta ze stdin

Zwraca znak czytany lub EOF jeśli powstał błąd lub napotkano EOF.



ZAPIS ZNAKU DO STRUMIENIA

int fputc(c,fp);

int fputchar(c);

Fputc zapisuje pojedynczy znak do zbioru. Fputchar jest ekwiwalentem fputc(c, stdout).

Zwraca pisany znak lub EOF (jak wyżej).



CZYTANIE STRINGU ZE STRUMIENIA

char *fgets(string, n, fp);

Odczytuje ze strumienia fp ciąg znaków aż do:

- pierwszego znaku '
'

- do końca strumienia

- lub gdy przeczytano n-1 znaków

i wstawia go do stringu dołączając na końcu ''

Zwraca string lub NULL(jak wyżej).



ZAPIS STRINGU DO STRUMIENIA

int fputs(string ,fp);

Kopiuje string do zbioru fp, bez końcowego znaku ''.

Zwraca 0 jeśli OK, != 0jesli powstał błąd.



Przykład - dodaj zawartość zbioru 1 do zbioru 2.

#include

void fwriteln(FILE *fp, char *string)

{

while(*string != EOL && *string != NULL)

fputc(*string, fp);

fputc(NEW_LINE,fp);

}

#define MAX_STRING 100

void main()

{

FILE *if, *of;

CHAR STRING{MAX_STRING];



if((if = fopen("data1","r")) == NULL)

{

printf("Blad otwarcia zbioru we");

exit(1);

}

else

if((of=fopen("data2","a")) == NULL)

{

printf("Blad otwarcia zbioru wy");

exit(1);

}

else

{

while(fgets(string,MAX_STRING,if))

fwiteln(of,string);

}

fcloseall();

}



FORMATOWANY ZAPIS DO ZBIORU

int fprintf(fp, format,argument);



Funkcja formatuje i wpisuje do zbioru dane. Jest podobna do standardowej funkcji printf ma jedyni dodany fp identyfikyjący zbiór.

Zwraca liczbę zapisanych znaków lub -1 gdy wystąpił błąd.



FORMATOWANY ODCZYT ZE ZBIORU

int fscanf(fp, format,argument)

Funkcja czyta dane ze zbioru od bieżącej pozycji, do argumentu w podanym formacie.

Zwraca liczbę przepisanych danych wejściowych, Eof po napotkaniu końca zbioru lub gdy wystąpi błąd.



BEZPOŚREDNIE WE/WY



ODCZYT ZE ZBIORU

size_t fread(buffer, size, count,fp)

Wczytuje do bufora conajwyżej count obiektów o rozmiarze size.

Zwraca liczbę przeczytanych obiektów ( anie bajtów), chyba że wystąpił błąd lub EOF.



ZAPIS DO ZBIORU

size_t fwrite(buffer, size,count,fp);

Zapisuje z bufora do pliku co najwyżej count obiektów o długości size.

Zwraca liczbę wpisanych obiektów, chyba że wystąpił błąd.



File pointer w przypadku powodzenia w obu operacjach przesuwa się o liczbę bajtów przeczytanych/zapisanych ze/do zbioru.



FUNKCJE DODATKOWE



int fseek(fp, offset, origin)

Funkcja przesuwa file pointer do nowego miejsca to jest o "offset" (typu long) bajtów od origin.

Origin:

SEEK_SET - początek pliku

SEEK_CUR - bieżąca pozycja

SEEK_END koniec zbioru

Zwraca 0 jeśli OK, lub != 0 jeśli błąd.

Dla plików tekstowych fseek ma ograniczone użycie, bo zamiana CR i LF na jeden znak może spowodować , ze fseek da nieoczekiwane rezultaty.





long ftell(fp);

Podaje bieżącą pozycję pliku, liczona jako przesunięcie względem początku zbioru.

Zwraca -1L jeśli błąd



void rewind(fp);

Ustawia fp, na początek zbioru. Kasuje wskaźnik końca zbioru i błędów. Może być zastąpiona odpowiednio ustawioną funkcja fseek która jednak nie kasuje wskazań błędów i EOF



int fgetpos(fp, pos)

Zapamiętuje w pos bieżącą pozycję file pointera.

Zwraca 0 lub != 0 gdy błąd.



int fsetpos(fp, pos)

Ustawia w pos bieżącą pozycję pliku.

Zwraca 0 lub != 0 gdy błąd.



Obie ostatnie funkcje ustawiaja stałą errno na:

EINVAL - nieprawidłowe FILE * (nie wskazuje na strukture)

EBADF - wskazania na błędna strukturę lub zbiór niedostępny



ZAMYKANIE ZBIORU

PO ZAKOŃCZENIU PRACY ZAMYKAMY STRUMIENIE!!! Z wyjątkiem predefiniowanych które automatycznie zamykane są po zakończeniu programu. Tak tez się dzieje z innymi, ale należy pamiętać, iż ilość otwartych w tym samym czasie strumieni jest ograniczona.

int fclose(fp);

int fcloseall();



OBSŁUGA BŁĘDÓW

Wiele funkcji ustawia wskaźniki stanu pliku po wystąpieniu błędu lub po napotkaniu końca zbioru. Mogą być przydatne funkcje :

clearerr(fp) - kasuje znacznik EOF i błędu dla pliku

feof(fp) - zwraca !=0 gdy ustawiony jest znacznik końca pliku itp.









Czy tekst był przydatny? Tak Nie
Przeczytaj podobne teksty

Czas czytania: 20 minut