PODSTAWY INFORMATYKI Wykład 5
Unie Unia jest zmienną przechowującą w jednym miejscu pamięci wartości różnych typów (w różnym czasie) Kompilator dba o rozmiar unii i rozmieszczenie danych Składnia: podobna do struktur
Unie - przykład union u_tag { int ival; float fval; char *sval; } u; Zmienna u będzie wystarczająco duża, aby przechować największy z typów Dokładny rozmiar jest zależny od implementacji Zmienne tych typów mogą być stosowane w przypisaniach Odczytywany typ powinien być taki, jak ostatnio zapisany Programista jest odpowiedzialny za przechowywanie informacji, który z typów jest aktualnie stosowany W razie próby odczytu innego typu niż zapisany, wyniki są zależne od implementacji
Pola bitowe Przydatne może być zapisanie kilku liczb w jednym słowie maszynowym Często stosowane: jednobitowe flagi Inny przykład: dostęp do urządzeń zewnętrznych wymaga dostępu do fragmentów słowa maszynowego Zwykle stosowane rozwiązanie: maski, przesuwanie, maski... Alternatywa w C: pola bitowe Pole bitowe (bit-field) jest zbiorem sąsiadujących ze sobą bitów w obrębie jednej jednostki pamięci nazywanej „słowem”
Pola bitowe - c.d. Składnia: podobna do structur Przykład: struct { unsigned int is_keyword : 1; unsigned int is_extern : 1; unsigned int is_static : 1; } flags; Definiujemy zmienną flags zawierającą trzy pola jednobitowe. Liczba po dwukropku definiuje liczbę bitów w polu bitowym. Pola są typu unsigned int aby zapewnić nieujemne wartości. Do poszczególnych pól odwołujemy się jak do składowych struktury: flags.is_keyword, flags.is_extern, itd. Pola bitowe zachowują się jak małe liczby całkowite i mogą być stosowane w wyrażeniach tak, jak inne liczby całkowite
Pola bitowe - c.d. Prawie wszystko zależy tu od implementacji Np. dopuszczalność przekroczenia granicy słowa Pola nie muszą być nazywane; sam dwukropek z długością wprowadza anonimowe pole, np. do wypełnienia miejsca Specjalna wartość szerokości 0 może być stosowana do wymuszenia przeniesienia granicy pola do granicy następnego słowa Miejsce dla pól przydzielane od lewej do prawej lub odwrotnie Ostrożnie z dostępem do danych binarnych w ten sposób Programy opierające się na tym nie są przenośne Pola mogą być definiowane tylko jako int (signed lub unsigned) Nie są tablicami, nie mają adresów, nie można do nich stosować &
Pola bitowe przykład /* WYNIKI -1 3 1 */ #include<stdio.h> #include<stdlib.h> int main(void) {struct typ_b{ int a:3; int b:3; int c:2; }bzmi; bzmi.a=-1; bzmi.b=3; bzmi.c=1; printf("%d %d %d",bzmi.a,bzmi.b,bzmi.c); system("pause"); return 0; } /* WYNIKI -1 3 1 */
Inicjalizacja zmiennych Zmienna może być inicjalizowana podczas jej definiowania Składnia: znak = po nazwie definiowanej zmiennej i wyrażenie opisujące jej początkową wartość Przykłady: char esc = '\\'; int i = 7*8; int limit = MAXLINE+1; float eps = 1.0e-5; char msg[] = "ostrzeżenie: ";
Inicjalizacja - c.d. Jeśli zmienna nie jest automatyczna, inicjalizacja jest dokonywana tylko raz (przed rozpoczęciem wykonywania programu), a wyrażenie musi być wyrażeniem stałym Jawnie inicjalizowana zmienna lokalna jest inicjalizowana przy każdym wejściu do bloku, w którym jest definiowana; wyrażenie inicjalizujące może być dowolnym wyrażeniem Zmienne zewnętrzne i statyczne domyślnie inicjalizowane są zerami Zmienne automaczne nieinicjalizowane jawnie nie są inicjalizowane niczym i zawierają przypadkowe wartości Kwalifikator const może być zastosowany do deklaracji każdej zmiennej aby zaznaczyć, że ta zmienna nie będzie zmieniana: const double e = 2.71828182845905; Dla tablicy, const oznacza, że elementy tablicy nie będą zmieniane.
Inicjalizacja tablic Tablica może być inicjalizowana przez listę inicjalizatorów umieszczonych w nawiasach klamrowych, oddzielonych przecinkami Przykład: int dni[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } Jeśli rozmiar tablicy jest pominięty, kompilator oblicza go zliczając podane wartości; w przykładzie powyżej jest ich 12 Jeśli wartości jest za mało, pozostałe będą zerami dla zmiennych zewnętrznych, statycznych i automatycznych Błędem jest podanie zbyt wielu inicjalizatorów Nie da się zdefiniować powtórzeń inicjalizatora ani podać środkowego elementu tablicy bez podawania poprzednich
Inicjalizacja tablic znakowych Tablice znakowe mogą być inicjalizowane w specjalny sposób, przez podanie łańcucha zamiast listy wartości: char mojtekst[] = "text"; jest skrótem równoważnego, ale dłuższego zapisu : char mojtekst[] = { 't', 'e', 'x', 't', '\0' }; Tutaj rozmiar tablicy wynosi 5 (cztery znaki i kończące łańcuch zero '\0')
Inicjalizacja tablic wskaźników Rozważmy funkcję month_name(n), zwracającą wskaźnik do łańcucha zawierającego nazwę n-tego miesiąca Doskonale sprawdzi się tu lokalna tablica statyczna (static) month_name zawiera „prywatną” tablicę łańcuchów znakowych i zwraca wskaźnik do jednego z nich Jak zainicjalizować taką tablicę?
Inicjalizacja tablic wskaźników /* month_name: zwraca nazwę n-tego miesiąca */ char *month_name(int n) { static char *name[] = { "Nieprawidłowy numer miesiąca", "Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień" }; return (n < 1 || n > 12) ? name[0] : name[n]; }
Tablice wielowymiarowe C pozwala definiować „prostokątne”, wielowymiarowe tablice W praktyce są rzadziej stosowane niż tablice wskaźników Tablica definiowana jako dwuwymiarowa jest w rzeczywistości jednowymiarową tablicą elementów, z których każdy jest jednowymiarową tablicą
Tablice wielowymiarowe - c.d. Składnia: jak dla tablic jednowymiarowych Przykład: double matrix[5][10]; matrix[3][7]=45.67; Elementy są umieszczane w pamięci wierszami Najbardziej prawy indeks (nr kolumny) zmienia się najprędzej przy przeglądaniu kolejnych komórek pamięci
Inicjalizacja tablic wielowymiarowych Tablica jest inicjalizowana listą inicjalizatorów umieszczonych w nawiasach klamrowych Każdy wiersz tablicy dwuwymiarowej jest inicjalizowany odpowienią podlistą Przykład: double m[2][3] = {{1.2, 2.3, 3.4}, {5.4, 4.3, 3.2}};
Przekazywanie tablic dwuwymiarowych do funkcji Deklaracja parametru musi zawierać liczbę kolumn Liczba wierszy nie ma znaczenia, bo parametr jest i tak przekazywany jako wskaźnik do tablicy wierszy
Przekazywanie tablic dwuwymiarowych do funkcji - c.d. Jeśli tablica m ma być przesłana do funkcji f, deklaracja powinna wyglądać tak: f(int m[2][13]) { ... } Albo tak: f(int m[][13]) { ... } ponieważ liczba wierszy jest nieistotna Można przekazać ją też tak: f(int (*m)[13]) { ... } co mówi, że parametr jest wskaźnikiem do tablicy 13 liczb int.
Tablice dwuwymiarowe - c.d. ... f(int (*daytab)[13]) { ... } daytab jest wskaźnikiem na tablicę 13 liczb całkowitych Nawiasy są konieczne, ponieważ [] mają wyższy priorytet od * Bez nawiasów, deklaracja int *daytab[13] oznaczałaby tablicę 13 wskaźników na int Ogólnie, tylko pierwszy wymiar tablic jest dowolny; pozostałe muszą zostać podane.
Wskaźniki a tablice wielowymiarowe Rozważmy następujące definicje: int a[10][20]; i int *b[10]; W obu przypadkach, a[3][4] oraz b[3][4] są składniowo i semantycznie poprawnymi odwołaniami do pojedynczej wartości typu int.
Wskaźniki a tablice wielowymiarowe Ale: a jest prawdziwą tablicą dwuwymiarową: 200 elementów rozmiaru int zostało alokowanych w pamięci, a do wyznaczenia adresu elementu stosowany jest standardowy wzór 20 * row +col dla elementu a[row,col]. b, jest tylko tablicą 10 wskaźników i nie inicjalizuje ich; inicjalizacja musi zostać dokonana jawnie, statycznie lub podczas działania programu.
Wskaźniki a tablice wielowymiarowe Porównajmy definicje i schemat dla tablicy wskaźników: char *name[] = {"Illegal month", "Jan", "Feb", "Mar"}; z ich odpowiednikami dla tablicy dwuwymiarowej: char aname[][15] = { "Illegal month", "Jan", "Feb", "Mar" };
Parametry w linii komend Do programu można przekazywać parametry podczas jego uruchamiania (ang. command-line arguments, parameters) Kiedy wywoływana jest funkcja main, może otrzymać dwa parametry: pierwszy (zwykle nazywany argc, skrót od argument count) jest liczbą podanych parametrów, z którymi wywołano program drugi (argv, skrót od argument vector) jest wskaźnikiem na tablicę łańcuchów zawierających parametry, po jednym w każdym elemencie tablicy
Parametry w linii komend - c.d. Przykład: program wypisujący swoje parametry echo.exe hello, world powinien wypisać hello, world
Parametry przekazywane do funkcji main main(int argc, char *argv[]) Pierwszy łańcuch, argv[0] jest nazwą, z którą wywołano program; dlatego argc jest równe co najmniej 1 Jeśli argc jest równe 1, oznacza to brak przekazanych programowi parametrów W poniższym przykładzie: echo.exe hello, world argc == 3, natomiast argv[0], argv[1] i argv[2] są odpowiednio równe "echo.exe", "hello," i "world".
Parametry przekazywane do funkcji main - c.d. Pierwszym opjonalnym parametrem jest argv[1], ostatnim argv[argc-1] Dodatkowo, standard wymaga, aby argv[argc] był równy NULL
Przykład programu z argumetami #include<stdio.h> #include<stdlib.h> #include<string.h> int main(int argc,char*argv[]) { int i; for(i=1;i<argc;i++)printf("%s",argv[i]); return 0; } */ WYNIKI ./a 4 8 3 2 1 argumenty1.c 48321argumenty1.c
Dostęp do plików Zanim plik będzie czytany lub zapisywany, musi zostać otworzony Funkcja biblioteczna fopen otwiera plik fopen na podstawie nazwy pliku dokonuje pewnych negocjacji z systemem operacyjnym i zwraca wskaźnik na strukturę danych FILE; ten wskaźnik jest potem stosowany przez funkcje dostępu do plików Wskaźnik ten (ang. file pointer) wskazuje na strukturę zawierającą informacje o pliku: położenie bufora bieżącą pozycję w buforze informację, czy plik jest czytany czy pisany informację o ewentualnych błędach, końcu pliku itp. Użytkownicy nie muszą znać szczegółów
Dostęp do plików - c.d. W pliku nagłówkowym <stdio.h> zawarta jest deklaracja struktury nazywanej FILE Jedyną definicją wymaganą do użycia fopen jest definicja wskaźnika na taką strukturę: FILE *fp; FILE *fopen(char *name, char *mode); Powyższe instrukcje mówią, że fp jest wskaźnikiem na FILE, a fopen zwraca wskaźnik na FILE. Zauważmy, że FILE jest nazwą typu, jak int, a nie etykietą struktury; jest zdefiniowane przez typedef
Dostęp do plików - c.d. Wywołanie fopen w programie: fp = fopen(name, mode); Pierwszy parametr przekazany fopen to łańcuch zawierający nazwę pliku. Drugi parametr jest trybem dostępu, opisywanym również przez łańcuch znakowy określający, jak użytkownik zamierza używać pliku. Dopuszczalne tryby: read ("r") write ("w") append ("a"). Niektóre systemy odróżniają pliki znakowe i binarne; dla tych ostatnich, należy dołączyć "b" do opisu trybu dostępu.
Dostęp do plików - c.d. Jeśli podejmowana jest próba otwarcia nieistniejącego pliku do zapisu lub dołączania, jest on tworzony (jeśli to możliwe) Otwieranie istniejącego pliku do zapisu powoduje usunięcie poprzedniej jego zawartości Otwierania do dołączania zachowuje ją Próba czytania nieistniejącego pliku jest błędem Inne przyczyny błędów: próba dostępu do pliku, do którego użytkownik nie ma odpowiednich praw W razie błędu, fopen zwraca NULL
Dostęp do plików - c.d. Co zrobić po otwarciu pliku... getc zwraca następny znak odczytany z pliku; wymaga podania wskaźnika do FILE: int getc(FILE *fp) getc zwraca EOF jeśli wystąpił koniec pliku lub błąd putc jest funkcją wyprowadzającą dane: int putc(int c, FILE *fp) putc zapisuje znak c do pliku fp i zwraca ten znak w razie sukcesu lub EOF w razie błędu Tak jak getchar i putchar, getc i putc mogą być makrami, a nie funkcjami.
Dostęp do plików - c.d. Kiedy uruchamiany jest program w C, system operacyjny jest odpowiedzialny za otwarcie trzech plików i powiązanie z nimi odpowiednich wskaźników Tymi plikami są: wejście standardowe, ang. standard input, wyjście standardowe, ang. standard output, strumień błędów, ang. standard error. Odpowiednie wskaźniki nazywane są stdin, stdout i stderr, i zadeklarowane są w <stdio.h> Normalnie stdin jest połączony z klawiaturą, stdout i stderr z konsolą tekstową, ale stdin i stdout mogą zostać przekierowane do plików lub potoków
Formatowane wejście i wyjście Dla formatowanego wejścia i wyjścia, stosowane są funkcje fscanf i fprintf Działają analogicznie jak scanf i printf; dodatkowo wymagają wskaźnika na plik czytany lub pisany: int fscanf(FILE *fp, char *format, ...) int fprintf(FILE *fp, char *format, ...)
Zamykanie plików Funkcja int fclose(FILE *fp) działa odwrotnie do fopen, przerywa połączenie między wskaźnikiem fp a plikiem; zwalnia wskaźnik, który może być użyty do obsługi innego pliku Większość Systemów operacyjnych ma limit liczby jednocześnie otwartych plików; należy więc zwalniać te zasoby, kiedy pliki nie są już używane Jest też inny powód stosowania fclose - ta funkcja opróżnia bufor, w którym putc i inne funkcje gromadzą dane. fclose jest wywoływane automatycznie dla każdego otwartego pliku, jeśli program kończy się normalnie
Obsługa błędów Jeśli do któregoś z plików program nie może się dostać, wypisywany jest komunikat o błędzie na końcu danych wyjściowych Jest to niedopuszczalne, jeśli wyjście trafia do pliku albo na wejście innego programu przez potok Strumień wyjściowy, nazywany stderr, jest stosowany do wypisywania komunikatów dla użytkownika Pojawiają się one normalnie na ekranie nawet, jeśli stdout zostało przekierowane
Kopiowanie plików #include <stdio.h> int main() { FILE *f1, *f2; int c; f1 = fopen("data.bin","rb"); /*otwieranie do odczytu */ f2 = fopen("output.bin","wb"); /* otwieranie do zapisu */ if (!(f1 && f2)) return -1; /* problemy z otwieraniem? */ while ((c=getc(f1))!=EOF) /* pętla główna */ putc(c,f2); fclose(f1); fclose(f2); return 0; }
Wynik programu Kopiuje zawartość pliku data.bin Ala ma kota – zawartość tego pliku do output.bin W pliku output.bin mamy ostatecznie: Ala ma kota
Inne funkcje operujące na plikach feof Testuje warunek końca pliku (end-of-file): int feof( FILE *stream ); Funkcja feof zwraca wartość niezerową po pierwszej nieudanej próbie czytania poza końcem pliku Zwraca 0, jeśli nie było prób czytania poza końcem pliku Nie informuje o innych błędach
Inne funkcje operujące na plikach ferror Testuje, czy wystąpił błąd w obsłudze strumienia int ferror( FILE *stream ); Jeśli nie wystąpił błąd, ferror zwraca 0. W innym przypadku zwraca wartość niezerową. Funkcja ferror testuje błędy zapisu lub odczytu pliku związanego ze strumieniem stream Jeśli wystąpił błąd, znacznik błędu dla tego strumienia jest ustawiony aż do zamknięcia strumienia, przewinięcia lub wywołania dla niego funkcji clearerr
Inne funkcje operujące na plikach clearerr Zeruje znacznik błędu dla strumienia void clearerr( FILE *stream ); Funkcja clearerr zeruje znacznik błędu i końca pliku (EOF) dla strumienia stream. Znaczniki błędów nie są automatycznie zerowane; kiedy jeden z nich zostanie ustawiony, kolejne operacje na strumieniu zwracają komunikat o błędzie do czasu wywołania clearerr, fseek, fsetpos lub rewind
Inne funkcje operujące na plikach fflush Opróżnia bufor obsługujący strumień int fflush( FILE *stream ); Jeśli plik związany ze strumieniem stream został otwarty do zapisu, fflush zapisuje do pliku zawartość bufora związanego ze strumieniem W niektórych systemach, jeśli plik jest otwarty do odczytu, fflush czyści zawartość bufora W innych systemach, fflush w takich przypadkach nie działa
fflush - c.d. #include <stdio.h> #include <conio.h> int main( void ){ int integer; char string[81]; /* Read each word as a string. */ printf( "Enter a sentence of four words with scanf: " ); for( integer = 0; integer < 4; integer++ ) { scanf( "%s", string ); // Security caution! // Beware allowing user to enter data directly into a buffer // without checking for buffer overrun possiblity. printf( "%s\n", string ); } /* You must flush the input buffer before using gets. */ fflush( stdin ); // fflush on input stream is an extension to the C standard printf( "Enter the same sentence with gets: " ); gets( string ); printf( "%s\n", string );}
fflush - c.d. Przykładowe dane Przykładowe wyniki This is a test Enter a sentence of four words with scanf: This is a test This is a test Enter the same sentence with gets: This is a test
Inne funkcje operujące na plikach ftell Podaje bieżącą pozycję w pliku long ftell( FILE *stream ); Funkcja ftell podaje bieżącą pozycję w pliku związanym ze strumieniem stream, jeśli jest z nim związany plik Pozycja podawana jest względem początku strumienia
Przykład ftell /* This program opens a file named CRT_FTELL.C for reading and tries to read 100 characters. It then uses ftell to determine the position of the file pointer and displays this position. */ #include <stdio.h> FILE *stream; int main( void ) { long position; char list[100]; if( (stream = fopen( "crt_ftell.c", "rb" )) != NULL ) /* Move the pointer by reading data: */ fread( list, sizeof( char ), 100, stream ); /* Get position after read: */ position = ftell( stream ); printf( "Position after trying to read 100 bytes: %ld\n", position ); fclose( stream ); }
Przykład ftell - wyniki Plik ftell.c Position after trying to read 100 bytes: 100 ggggggggggggggg -plik k.txt Position after trying to read 100 bytes: 17