Informatyka programowanie w języku C część II dr hab. inż. Mikołaj Morzy
plan wykładu historia języka C zmienne, operatory, instrukcje sterujące wejście/wyjście, funkcje i parametry tablice, wskaźniki, typy złożone makra i pre-procesor biblioteki standardowe alokacja pamięci
funkcje wejścia-wyjścia podstawowe funkcje wyjścia printf(format, argument 1, argument 2,...) puts(argument) fputs(argument, stdout) putchar(znak) podstawowe funkcje wejścia scanf(format, argument 1, argument 2,...) gets(bufor) fgets(bufor, rozmiar, stdin) getchar()
funkcja printf() funkcja drukuje sformatowany tekst format może zawierać znaki specjalne: %% \\ format określa typ zmiennych: %i %d: liczby całkowite %f zmienne typu float lub double %c: znaki %s: łańcuchy znaków dla łańcuchów znaków można określić ich długość #include <stdio.h> int main() { char *pi = "PI"; printf("10%% liczby %2s wynosi %f", pi, 0.1 * 3.14f );
funkcje putchar() i fputs() funkcja putchar() drukuje pojedynczy znak funkcja fputs() drukuje dowolny ciąg znaków #include <stdio.h> int main() { int i; for (i = 1; i <= 100; i++) { printf("%d", i); if (i % 10) putchar(' '); else putchar('\n'); return 0; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
funkcja scanf() umożliwia wczytanie ciągu znaków do zmiennej do funkcji przekazujemy adres zmiennej lub nazwę tablicy funkcja używa tych samych formatów co printf() #include <stdio.h> int main() { char imie[100]; int n; printf("podaj imię: \n"); scanf("%s", imie); while (scanf("%i", &n) == 1) printf("%i do sześcianu wynosi %i \n", n, n*n*n); return 0;
funkcje fgets() i getchar() funkcja fgets() to bezpieczna wersja funkcji gets(), która czyta dane z dowolnego strumienia wejściowego funkcja getchar() wczytuje pojedynczy znak z klawiatury #include <stdio.h> int main(void) { char buffer[128], whole_line = 1, *ch; while (fgets(buffer, sizeof buffer, stdin)) { if (whole_line) { putchar('>'); if (buffer[0]!='>') { putchar(' '); fputs(buffer, stdout); for (ch = buffer; *ch && *ch!='\n'; ++ch); whole_line = *ch == '\n'; if (!whole_line) { putchar('\n'); return 0; Ala ma kota a kot ma Alę Alek ma Asa As to pies Alka > Ala ma kota > a kot ma Alę > Alek ma Asa > As to pies Alka
preprocesor preprocesor to fragment kompilatora, który przeszukuje kod źródłowy w poszukiwaniu dyrektyw #include <plik>: dyrektywa wstawia w danym miejscu treść wskazanego pliku nagłówkowego lub innego pliku #define NAZWA wartość: dyrektywa definiuje stałą, słowo kluczowe lub funkcję #undefine NAZWA: dyrektywa likwiduje stałą, słowo kluczowe lub funkcję #include <stdio.h> #include "/usr/bin/my_header_file.h" #define LICZBA 42 #define SUMA(a+b) (a+b) #undefine EDITOR
instrukcje warunkowe preprocesora preprocesor pozwala kompilować warunkowo fragmenty kodu źródłowego za pomocą poniższych dyrektyw #if: wprowadza warunek, który jeśli nie jest prawdziwy powoduje pominięcie kompilowania kodu, aż do napotkania jednej z poniższych instrukcji #else: spowoduje kompilowanie kodu jeśli warunek za #if jest nieprawdziwy, aż do napotkania jednej z instrukcji #elif: wprowadza nowy warunek, jeśli poprzedni był niespełniony #endif: zamyka ostatnią instrukcję warunkową #ifdef: kod zostanie skompilowany jeśli stała została zdefiniowana #ifndef: dyrektywa działa przeciwnie do #ifdef
instrukcje prekompilatora do kompilacji prekompilator zawiera także dyrektywy przydatne w samym procesie kompilacji #error: przerywa kompilację i wyświetla komunikat #warning: wyświetla ostrzeżenie w trakcie kompilacji #line: zeruje licznik linii w kompilatorze
makroinstrukcje (makra) makro to automatycznie wykonywana instrukcja, jest to do pewnego stopnia zamiennik dla funkcji #include <stdio.h> #define KWADRAT(n) ((n*n)) int main() { printf("2 do kwadratu wynosi %d", KWADRAT(2)); return 0; zagadka: jaki będzie wynik tego programu? #include <stdio.h> #define KWADRAT(n) ((n*n)) int main() { int x = 2; int i = KWADRAT(++x); return 0;
biblioteka standardowa "czysty" język C posiada bardzo niewiele wbudowanych mechanizmów, m.in. nie potrafi niczego pisać na ekranie większość systemów operacyjnych posiada tzw. bibliotekę standardową (bibliotekę języka C) biblioteka to zbiór skompilowanych funkcji które można łączyć z pisanym programem najczęściej, funkcje w bibliotece realizują spójne zadanie biblioteka standardowa nie jest napisana w C funkcje wejścia-wyjścia funkcje matematyczne komunikacja sieciowa
praca z plikami istnieją dwa sposoby pracy z plikami wysokopoziomowy: fopen(), fclose(), fread(), niskopoziomowy: open(), close(), read(), w przypadku pracy wysokopoziomowej z plikiem wykorzystywany jest wskaźnik FILE na strukturę opisującą plik nazwa ścieżka aktualna pozycja w pliku funkcje niskopoziomowe wykorzystują deskryptor pliku (liczbę całkowitą jednoznacznie identyfikującą plik)
przykład pracy z plikiem znakowym #include <stdio.h> #include <stdlib.h> int main () { FILE *fp; /* używamy metody wysokopoziomowej */ char tekst[] = "Hello world!"; if ((fp=fopen("test.txt", "w"))==null) { printf("nie mogę otworzyć pliku test.txt do zapisu!\n"); exit(1); fprintf(fp, "%s", tekst); /* zapisz nasz łańcuch w pliku */ fclose(fp); /* zamknij plik */ return 0;
strumienie język C traktuje wejście z klawiatury, wyjście na ekran lub wejście i wyjście z pliku w dokładnie ten sam sposób każde źródło danych jest reprezentowane przez strumień każdy program automatycznie dostaje trzy otwarte strumienie (należy dołączyć <stdio.h>) stdin: wejście z klawiatury stdout: wyjście na konsolę stderr: wyjście błędów (konsola)
zaawansowany przykład pracy z plikami poniższy program kopiuje wejście z klawiatury do pliku #include <stdio.h> #include <stdlib.h> int main (int argc, char *argv[]) { FILE *fp; int c; if (argc < 2) { fprintf(stderr, "Uzycie: %s nazwa_pliku\n", argv[0]); exit(-1); fp = fopen(argv[1], "w"); if (!fp) { fprintf(stderr, "Nie moge otworzyc pliku %s\n", argv[1]); exit(-1); printf("wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczyc\n"); while ( (c = fgetc(stdin))!= EOF) { fputc(c, stdout); fputc(c, fp); fclose(fp); return 0;
wskaźniki wskaźnik to obiekt zawierający adres w pamięci zmiennej określonego typu wskaźniki są bardzo niebezpieczne ale niezbędne do życia zawartość adres 10001 170 10002 int a = 170; 10003 10004 10005 10006 zawartość adres 95003 10002 95004 int *b = &a;
wskaźniki : analogia
wskaźniki (cd) do użycia wskaźników konieczne są dwa operatory pobranie adresu zmiennej: &zmienna dereferencja wskaźnika: *wskaznik #include <stdio.h> int main (void) { int liczba = 100; printf("adres: %p, wartosc: %d\n", (void*)&liczba, liczba); return 0; uwaga: operator * odnosi się do zmiennej, a nie typu! int* a, b, c; int *a, *b, *c;
dlaczego wskaźniki mają typ: rozważ zmienną typu unsigned int przechowującą wartość 65 530 11111111 11111010 = (unsigned int) 65530 = (unsigned int)* 65530 = (unsigned char)* 255 = (unsigned long)*??? 11111111 11111010????????????????
arytmetyka wskaźników na wskaźnikach można przeprowadzać operacje arytmetyczne dodawania i odejmowania wynikiem operacji arytmetycznej na wskaźniku jest wskazanie na inne miejsce w pamięci int t[] = {1,3,5,7,9; int *p = &t[0]; /* wskaznik pokazuje na pierwszy element tablicy */ p += 2; /* wskaznik przesuniety o dwa elementy (nie bajty!) */ 1 3 5 7 9
tablice i wskaźniki nazwa tablicy jest wskaźnikiem pokazującym na adres pierwszego elementu w tablicy do nawigowania po tablicy można więc wykorzystać wskaźniki int t[] = {100,200,300; int x = t[2]; int y = *(t + 2); /* zmienne x i y zawierają wartość 300 */ int *p; p = &t[1]; /* lub p = (t + 1); */ int z = *p; /* zmienna z zawiera wartość 200 */ 100 200 300 00000000 01100100 00000000 11001000 00000001 00101100
wskaźnik jako argument funkcji jeśli zamiast zmiennej do funkcji zostanie przekazany wskaźnik na zmienną (przekazanie argumentu przez referencję), to zmiany argumentu formalnego funkcji będą widoczne na zewnątrz funkcji #include <stdio.h> void funkcja(int *wskaznik) { *wskaznik = 5; int main () { int z=3; printf("zmienna z=%d\n", z); /* wypisze 3 */ funkcja(&z); przy przekazywaniu do funkcji jako parametru tablicy, można przekazać tablicę, a można przekazać wskaźnik na pierwszy element tablicy void funkcja(int tablica[]) void funkcja(int *tablica) printf("zmienna z=%d\n", z); /* wypisze 5 */
czego nie robić ze wskaźnikami? nigdy nie odwołuj się do komórki wskazywanej przez wskaźnik o wartości NULL nie odwołuj się do komórki wskazywanej przez niezainicjalizowany wskaźnik nie odwołuj się do komórek poza przydzieloną pamięcią int *wsk; printf("zawartosc komorki: %d\n", *(wsk)); /* Błąd */ wsk = 0; /* wsk == NULL */ printf ("zawartosc komorki: %d\n", *(wsk)); /* Błąd */ int tab[] = { 0, 1, 2 ; tab[3] = 3; /* Błąd */
stałe wskaźniki istnieją dwa rodzaje stałych wskaźników wskaźnik który nie może zmienić wartości wskaźnik który nie może zmienić adresu const int *a; int * const a; int i=0; const int *a=&i; int * const b=&i; int const * const c=&i; *a = 1; /* kompilator zaprotestuje */ *b = 2; /* ok */ *c = 3; /* kompilator zaprotestuje */ a = b; /* ok */ b = a; /* kompilator zaprotestuje */ c = a; /* kompilator zaprotestuje */
dynamiczna alokacja pamięci wskaźniki umożliwiają dynamiczne przydzielanie pamięci do zmiennych, w tym, tworzenie tablic o rozmiarze nieznanym na etapie kompilacji int rozmiar; float *tablica; rozmiar = 3; tablica = (float*) malloc(rozmiar * sizeof(*tablica)); tablica[0] = 0.1;... tablica = realloc(tablica, 2 * rozmiar * sizeof(*tablica));... free(tablica);
wskaźniki : podsumowanie
napisy w C język C nie jest przystosowany do przetwarzania napisów biblioteka standardowa jest uboga w funkcje łańcuchowe napisy są specyficznie przechowywane w pamięci napis jest tablicą znaków zakończoną specjalnym znakiem \0 char *tekst = "Ala ma asa"; printf("%c", tekst[2]); /* wydrukuje a */ printf("%c", tekst[10]); /* wydrukuje 0 */ printf("%c", "As to pies Ali"[3]); /* wydrukuje t */ A l a m a a s a \0
napisy jako wskaźniki zmienna przechowująca łańcuch znaków jest w rzeczywistości wskaźnikiem na tablicę z łańcuchem znaków char *tekst = "Ala ma asa"; char tekst[] = "Ala ma asa"; char tekst[] = {'A','l','a',' ','m','a',' ','a','s','a','\0'; char tekst[5] = "Ala ma asa"; /* pisanie po pamięci */ pisanie po pamięci może spowodować segmentation fault pisanie po pamięci może niewidocznie zmodyfikować wartości innych zmiennych
znaki specjalne do przetwarzania napisów w języku C można wykorzystać następujące znaki specjalne znak interpretacja \a alarm (sygnał z terminala) \b usunięcie poprzedniego znaku (backspace) \f wysunięcie strony \r powrót kursora do początku wiersza \n znak nowego wiersza (0x0A LF: Linux, BSD, MacOS X, 0x0A 0x0D LF+CR: DOS, Windows) \" cudzysłów \' apostrof \\ ukośnik (backslash) \t tabulacja pozioma \v tabulacja pionowa \? pytajnik \ooo liczba oktalna (znaki 'o' należy zamienić na cyfrę [0-7]) \xhh \unnnn \unnnnnnnn liczba heksadecymalna (znaki 'h' należy zamienić na [0-9a-f]) uniwersalna nazwa znaku (nnnn: czterocyfrowy heksadecymalny identyfikator znaku) uniwersalna nazwa znaku (nnnnnnnn: ośmiocyfrowy heksadecymalny identyfikator znaku)
porównywanie napisów napisy to wskaźniki, więc proste porównanie dwóch zmiennych łańcuchowych porównuje adresy (a nie wartości) do porównania łańcuchów wykorzystuje się strcmp()oraz strncmp() #include <stdio.h> #include <string.h> int main(void) { char str1[100], str2[100]; int cmp; puts("podaj dwa ciągi znaków: "); fgets(str1, sizeof(str1), stdin); fgets(str2, sizeof(str2), stdin); cmp = strcmp(str1, str2); if (cmp<0) { puts("pierwszy napis jest mniejszy."); else if (cmp>0) { puts("pierwszy napis jest wiekszy."); else { puts("napisy sa takie same."); return 0;
inne przydatne funkcje łańcuchowe kopiowanie napisów: strcpy() char tekst[100]; strcpy(tekst, "Ala ma kota"); char napis[5]; strncpy(napis, "Ala ma kota", sizeof(napis)); napis[sizeof(napis) 1] = 0; łączenie napisów: strcat() char tekst1[10] = "Hello "; char *tekst2 = "world!"; strcat(tekst1, tekst2); puts(tekst1);
typy złożone język C umożliwia tworzenie własnych typów danych typ pochodny typ wyliczeniowy struktura unia najprostszą konstrukcją jest typ pochodny typedef int moj_integer; typedef float liczba_zmiennoprzecinkowa; moj_integer a = 10; liczba_zmiennoprzecinkowa x = 2.34;...
typ wyliczeniowy typ wyliczeniowy pozwala zdefiniować domenę poprawnych wartości kolejne wartości są przechowywane jako liczby całkowite konwencja wymaga użycia wielkich liter enum kierunek {POLNOC, POLUDNIE, WSCHOD, ZACHOD; enum kierunek gdzie_mieszka_mikolaj = POLNOC; switch(gdzie_mieszka_mikolaj) { case POLNOC: printf("mieszka na północy\n"); break; case POLUDNIE: printf("mieszka na północy\n"); break;... można zmienić przyporządkowanie liczb do wartości enum kierunek {POLNOC=10, POLUDNIE=5, WSCHOD=15, ZACHOD=0;
struktury struktura to typ rekordowy, w ramach jednej zmiennej można przechowywać wiele pól różnych typów nazwy, typy i liczbę pól definiuje programista struct Student { int indeks; char imie[50]; char nazwisko[100]; struct Student kowalski; kowalski.indeks = 90800; strcpy(kowalski.imie, "Jan"); strcpy(kowalski.nazwisko, "Kowalski"); struct Student nowak = {90801, "Antoni", "Nowak";...
unie unia to złożony typ danych, który może przechowywać dane różnych typów, ale w danym momencie przechowuje zawsze jeden określony typ union identyfikator { int PESEL; char NIP[13]; char dowod_osobisty[10]; union identyfikator id_kowalskiego; id_kowalskiego.pesel = 70011890123; union identyfikator id_nowaka = {.NIP = "777-777-77-77";...
wskaźniki na struktury i unie język C pozwala na deklarowanie zmiennych wskazujących na zmienne będące strukturami lub uniami operator -> dostępu do pól przez wskaźnik typedef struct { int x, y; wspolrzedne; int main() { wspolrzedne a = { 0, 0 ; wspolrzedne *b = &a; b->x = 10; b->y = 20; printf("ciekawe miejsce: [%i,%i]", a.x, a.y);
zadania napisz program, który wylicza pole i obwód koła o podanym przez użytkownika promieniu napisz program, który zapisze do pliku o dowolnej wybranej przez Ciebie nazwie Twoje imię, nazwisko, wiek i miasto urodzenia (każda informacja w nowej linii) napisz program wyświetlający na ekranie tabliczkę mnożenia w zakresie 10x10 napisz program implementujący sito Erastotenesa