Czas na pokazanie Państwu rzeczy ciekawych, przydatnych, ale nie mających żadnej kategorii. Część z nich będzie działała wyłącznie pod system Linux, ale nie wszystkie.
Komenda system
Jak wyczyścić ekran? No możemy w programie wyświetlić 100 razy nową linię, a jak ekran ma 110? No możemy 1000 razy, a co jeśli ekran ma 10 linii? Operacje wejścia-wyjścia są bardzo wolne. Powiem krótko-w oparciu o standard języka C, bez funkcji danego systemu, czy funkcji biblioteki do manipulowania tekstem takiej jak ncurses.h na systemie Linux nie da się tego zrobić. Ale po co wywarzać otwarte drzwi -na systemie Linux jest komenda:
clear
na systemie Windows komenda
cls
więc użyjmy zewnętrznych komend w programie.
#include <stdio.h> #include <stdlib.h> void czyscEkran(void) { fflush(stdout); #ifndef __WIN32__ system("clear"); #else system("cls"); #endif } int main(void) { printf("Czesc!"); czyscEkran(); printf("Na razie, dziekuje za wspolprace:)\n"); }
W powyższym kodzie użyłem preprocesora żeby wybadać na jakim systemie operacyjnym pracujemy -jeśli nie __WIN32__ (tak, 64bitowe również mają takie makro) to użyj linuksowego clear. Funkcja printf zajmuje się wyświetlaniem tekstu, robi to w sposób buforowany, czyli nie od razu wyświetla to co się do niej poda, dlatego musiałem wymusić wyczyszczenie bufora standardowego wyjścia:
fflush(stdout);.
Niestety powyższa komenda nie umożliwia nam zwrotu tego co zostało wyświetlone przez wywołany program. Jeśli zależy nam na zwróceniu outputu powinniśmy użyć innych rzeczy, takich jak np. potoki (o tym kiedy indziej). Ewentualnie możemy przekierować wydruk do pliku i odczytać z pliku.
Czas
Fajnie jest wiedzieć coś na temat czasu w języku C. Funkcje do zabawy z czasem znajdują się w:
#include <time.h>
Przydatną funkcją jest funkcja time, która zwraca liczbę sekund od 1 stycznia 1970 roku.
#include <stdio.h> #include <time.h> int main(void) { time_t czasTeraz = time(NULL); const unsigned SEKUND_NA_NIEPRZESTEPNY_ROK = 60*60*24*365; printf("Od 1 stycznia 1970 minelo sekund %ld\n", czasTeraz); printf("Czyli okolo %ld lat\n", czasTeraz/SEKUND_NA_NIEPRZESTEPNY_ROK); }
wydruk:
Od 1 stycznia 1970 minelo sekund 1419872474 Czyli okolo 45 lat
Gdy chcemy operować na konkretnej dacie
Fajnie że jest funkcja time(), ale nie jest ona wygodna jeśli chcemy operować na konkretnym czasie.
Np. chcemy dokładnie wiedzieć który dzisiaj i która godzina:
#include <stdio.h> #include <time.h> int main(void) { time_t czasTeraz = time(NULL);; struct tm* strukturaCzasu; strukturaCzasu = localtime ( &czasTeraz ); printf("Dzisiaj mamy: rok %d, miesiac %d, dzien %d, czas %d:%d\n", strukturaCzasu->tm_year + 1900, strukturaCzasu->tm_mon + 1, strukturaCzasu->tm_mday, strukturaCzasu->tm_hour, strukturaCzasu->tm_min); }
O strukturach powiemy kiedy indziej. Natomiast dodałem do roku, ponieważ tm->tm_year zawiera liczbę lat od 1900 roku, a miesiące zaczynają od 0. Struktura tm zawiera również inne pola http://www.cplusplus.com/reference/ctime/tm/
Funkcja localtime konwertuje time_t na struct tm.
Łatwiejsze formatowaie czasu
Na czasie operować można łatwiej niz w ostatnim przykładzie -mamy do tego funkcje: asctime() i ctime(), które na swój sposób wyświetlą nam obecny czas.
#include <stdio.h> #include <time.h> int main(void) { time_t czasTeraz = time(NULL); struct tm* strukturaCzasu; strukturaCzasu = localtime(&czasTeraz); printf("Obecny czas (funkcja asctime()): %s\n", asctime(strukturaCzasu)); printf("Obecny czas (funkcja ctime()): %s\n", ctime(&czasTeraz)); }
Jak Państwo widzą obydwie funkcje zwracają to samo, ale z różnych parametrów, funkcji ctime() wystarczy to co zwróci funkcja time().
Jeśli ktoś chce mieć swój format czasu, to też jest do tego funkcja strftime() http://www.cplusplus.com/reference/ctime/strftime/
#include <stdio.h> #include <time.h> int main(void) { time_t czasTeraz = time(NULL); struct tm* strukturaCzasu = localtime(&czasTeraz); char bufor[80]; strftime(bufor, sizeof(bufor),"Czas wg mojego formatu %d.%m(%B).%Y (%A), %H:%S", strukturaCzasu); puts(bufor); }
Liczenie czasu, oraz czekanie
Żeby program poczekał parę sekund mamy wiele sposobów. Przenośnym jest operowanie na tickach procesora
#include <stdio.h> #include <time.h> void czekaj(int sekundy) { clock_t pozadanaIloscTykniec = clock() + (sekundy * CLOCKS_PER_SEC); while(clock() < pozadanaIloscTykniec); } int main(void) { time_t czasOd, czasDo; czasOd = time(NULL); czekaj(4); czasDo = time(NULL); printf("Operacja zajela %lf sekund\n", difftime(czasDo, czasOd)); }
Niestety takie czekanie mimo iż czeka powoduje ciągłe wykonywanie obliczeń => zużywa nam jeden rdzeń procesora.
Dlatego lepiej użyć funkcji zależnych od platformy:
#include <stdio.h> #include <time.h> #ifdef __linux #include <unistd.h> #elif defined(__WIN32__) #include <windows.h> #endif void czekaj(unsigned sekundy) { #ifdef __WIN32__ Sleep(1000*sekundy); #elif __linux sleep(sekundy); #else clock_t pozadanaIloscTykniec = clock() + (sekundy * CLOCKS_PER_SEC); while(clock() < pozadanaIloscTykniec); #endif } int main(void) { time_t czasOd, czasDo; czasOd = time(NULL); czekaj(4); czasDo = time(NULL); printf("Operacja zajela %lf sekund\n", difftime(czasDo, czasOd)); }
Na różnych systemach jest możliwe jeszcze dokładniejsze liczenie czasu, np. na systemie Linux w milisekundach
https://www.cs.rutgers.edu/~pxk/416/notes/c-tutorials/gettime.html .
Jeśli ktoś jest ciekaw ile czasu potrzebuje jego program do działania, ale nie potrzebuje tego za każdym razem polecam skorzystać z systemowej funkcji time z poziomu konsoli:
time ./program.exe
Assercje
Czyli przerwanie wykonywania programu w sytuacji jeśli pewien warunek nie jest spełniony. Możemy to zrobić delikatnie:
if(warunek) { fprintf(stderr, "Wystapil blad\n"); exit(-1); }
A możemy to zrobić bardziej brutalnie, jeśli błąd jest naprawdę poważny:
#include <assert.h> /* ... */ assert(warunek);
Takie brutalne wyłączenie programu wywołuje funkcje abort();, która to natychmiastowo wyłącza program, nie są dokonywanie czyszczenia buforów ani usuwane zmienne.
Assercje są świetną rzeczą, jeśli są dokonywane w trakcie kompilacji:
#if __STDC_VERSION__ < 199901L #eror "Za stary kompilator jezyka C, wymagany co nie mniej niz kompatybilny z C90!" #endif
Na szczeście wraz z nadejściem standardu języka C z roku 2011 pojawiła się statyczna assercja, która umożliwia zatrzymanie kompilacji programu w momencie gdy warunek nie jest spełniony:
void validacja(void) { _Static_assert(sizeof(int) == 4, "typ int musi miec 4 bajty"); _Static_assert(sizeof(short) >= 2, "typ short musi miec >= 2 bajty"); _Static_assert(sizeof(long) >= 4, "typ long musi miec >= 8 bajty"); }
Funkcje do pracy z tekstem
Bez takich funkcji w standardzie byłoby kiepsko. Oto przykłady funkcji.
strcat() -dopisywanie tekstu na koniec napisu (koniec to pierwsze wystąpienie znaku końca linii ). Funkcja może służyć do łączenia napisów:
char imie[] = "Jan"; char nazwisko[] = "Kowalski"; char imie_nazwisko[100]; strcat(imie_nazwisko, imie); strcat(imie_nazwisko, " "); strcat(imie_nazwisko, nazwisko); printf("imie_nazwisko = %s\n", imie_nazwisko);
sprintf() -służy do wpisywania treści zmiennych o określonym formacie do bufora:
char imie[] = "Jan"; char nazwisko[] = "Kowalski"; char bufor[100]; sprintf(bufor, "%s %s, ma lat %d", imie, nazwisko, 44); puts(bufor);
Wydruk:
Jan Kowalski, ma lat 44
Oczywiście dodam, że zbieżność osób i nazwisk jest przypadkowa:D.
Kopiowanie i porównywanie napisów
Do tego celu służą funkcje strcpy() do kopiowania i strcmp() do porównywania, jeśli napisy są takie same zostanie zwrócona wartość 0.
char tekst[] = "Ala ma kota"; char bufor[100]; strcpy(bufor, tekst); /* kopiuje tekst na bufor */ puts(bufor); if(strcmp(tekst, bufor) == 0) /* porownuje 2 napisy */ puts("Napisy sa takie same"); printf("Napis '%s' ma dlugosc %d\n", tekst, strlen(tekst));
Wyszukiwanie
Zacznijmy od wyszukiwania jednej litery, funkcja strchr() lub strpbrk():
char tekst[] = "Ala ma kota"; const char* wystapienieListeryK = strchr(tekst, 'k'); printf("litera k wystepuje na pozycji %d, od tej pozycji jest napis '%s'\n", wystapienieListeryK - tekst, wystapienieListeryK);
wydruk:
litera k wystepuje na pozycji 7, od tej pozycji jest napis 'kota'
Teraz wyszukiwanie całego ciągu znaków, funkcja strstr():
char tekst[] = "Ala ma kota"; char szukanyTekst[] ="kot"; char* wskaznikDoZnalezionejPozycji; wskaznikDoZnalezionejPozycji = strstr(tekst, szukanyTekst); if(wskaznikDoZnalezionejPozycji) printf("znaleziono tekst '%s' na pozycji %d\n", szukanyTekst, wskaznikDoZnalezionejPozycji-tekst);
i demonstracja wydruku:
znaleziono tekst 'kot' na pozycji 7
Sprawdzanie ile pierwszych znaków należy do zbioru dozwolonych znaków:
char napis[] = "nazywam sie XXX YYY. Mam Lat 23 ..."; char zbiorDozwolonychZnakow[] = "abcdefghijklmnopstwuyz"; unsigned pozycja = strspn(napis, zbiorDozwolonychZnakow); puts(napis); printf ("Dozwolonych znakow jest %d, pierwszy niedozwolony znak '%c'\n", pozycja, napis[pozycja]);
wydruk:
nazywam sie XXX YYY. Mam Lat 23 ... Dozwolonych znakow jest 7, pierwszy niedozwolony znak ' '
Dzielenie ciagu znaków na tokeny, funkcja strtok() http://www.cplusplus.com/reference/cstring/strtok/ .
Z ciekawych funkcji tekstowych mamy jeszcze konwersję znaków dużych na małe i na odwrót:
#include <ctype.h> int tolower(int c); int toupper(int c);
Przydatne są też wszelakie funkcje do klasyfikacji tekstu:
#include <ctype.h> int isalnum(int c); /* sprawdza czy znak jest litera lub cyfra */ int isalpha(int c); /* sprawdza czy znak jest litera */ int isdigit(int c); /* sprawdza czy znak jest liczba */ int isspace(int c); /* sprawdza czy znak jest bialym znakiem */
więcej klasyfikacji w dokumentacji: http://www.cplusplus.com/reference/cctype/
Parsowanie argumentów programu
Często chcemy sterować naszym programem przez podane argumenty uruchomienia programu. Użytkownik może podać różne argumenty, a my chcemy obsługiwać konkretne. Możemy albo sami napisać parsowanie argumentów albo skorzystać z gotowych rozwiązań.
Na Linuxie mamy dostępną funkcję getopt()
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <getopt.h> int main(int argc, char* argv[]) { double liczba = 0; int argument = 0; char napis[100]; int help = 0; memset(napis, 0, sizeof(napis)); int znak; const char* obslugiwaneArgumenty = "a:l:n:hv"; while ( (znak = getopt(argc, argv, obslugiwaneArgumenty)) != -1) { int this_option_optind = optind ? optind : 1; switch (znak) { case 'a': argument = atoi(optarg); printf ("podano argument %d\n", argument); break; case 'l': liczba = atof(optarg); printf ("podano liczbe %lf\n", liczba); break; case 'n': strncpy(napis, optarg, sizeof(napis)-1); printf ("podano napis '%s'\n", napis); break; case 'h': printf ("wlaczono tryb help\n"); help = 1; break; } } if(optind < argc) { printf("nieobslugiwane agrumenty: "); while(optind < argc) printf("%d) %s ", optind, argv[optind++]); printf ("\n"); } if(help) { printf("Obslugiwane argumenty: '%s'\n", obslugiwaneArgumenty); return 0; } printf("Podano: argument=%d\tliczba=%lf\tnapis=%s\n", argument, liczba, napis); return 0; }
uruchomienie przykładowe:
$ gcc tmp.c && ./a.out -a 3 co_to_jest? -l 44 -n 'Ala bez kota' podano argument 3 podano liczbe 44.000000 podano napis 'Ala bez kota' nieobslugiwane agrumenty: 8) co_to_jest? Podano: argument=3 liczba=44.000000 napis=Ala bez kota
Jak Państwo widzą w funkcji getopt o argumentach decyduje argument „obslugiwaneArgumenty”. Jeśli podamy samą literę x to spodziewa się argumentu -x, jeśli podamy literę z dwukropkiem spodziewa się argumentu, który jest ustawiony w zmiennej optarg. Mamy również dostępny indeks przeparsowanych argumentów optind.
Oprócz funkcji getopt mamy również dostępną funkcj getopt_long. Umożliwia ona obsługę argumentów zaróno w formie krótkiej:
-a, jak i długiej -argument. Więcej informacji na temat getopt_long: https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Options.html
Obsługa błędów
Wiele funkcji w sytuacji błędu zwraca kod błędu. Niektóre z nich poza kodem błędu zmieniają wartość specjalnej zmiennej o nazwie:
extern int errno;
ta zmienna jest zdefiniowana w:
#include <errno.h>
Oto przykład obsługi błędów -pierwiastek z ujemnej liczby:
#include <stdio.h> #include <math.h> #include <errno.h> int main(void) { double zmienna = -2.2; double pierwiastek = sqrt(zmienna); if(EDOM == errno) perror("Blad"); else printf("Pierwiastek z %lf = %lf\n", zmienna, pierwiastek); return 0; }
Wydruk:
Blad: Numerical argument out of domain
Niestety w standardzie zmienna errno jest bardzo uboga:
http://www.cplusplus.com/reference/cerrno/errno/
natomiast różne kompilatory mają wiele dodatkowych kodów błędów dla zmiennej errno, oto lista dla kompilatora gcc:
https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
Na szczęście nie musimy sprawdzać wystąpienia każdego błędu w celu wyświetlenia komunikatu z informacją o błędzie, mamy do tego odpowiednie funkcje perror, która wyświetli dla nas treść błędu. Mamy również funkcję strerror(), która zwróci nam napis odzwierciedlający obecny błąd.
Lokalki
Kultury się różnią, anglojęzyczni mają wszystko inne niż reszta krajów, ale poza anglikami występują też pewne różnice w różnych kulturach. Np. znak przecinka w liczbach, znak waluty itp. W języku C jest możliwość ustawienia „lokalek”, czyli odpowiednich zachowań kulturowych.
Oto jak zwykle przykład:
#include <stdio.h> #include <locale.h> int main(void) { setlocale(LC_ALL, ""); /* ustawienie aktualnych lokalek */ struct lconv* naszaLokalka = localeconv(); printf("Symbol lokalnej waluty: %s\n", naszaLokalka->currency_symbol); printf("Miedzynarodowy symbol waluty: %s\n", naszaLokalka->int_curr_symbol); printf("PI = %lf\n", 3.1415926); return 0; }
Oto wydruk -polskie znaki i przecinek zamiast kropki:
Symbol lokalnej waluty: zł Miedzynarodowy symbol waluty: PLN PI = 3,141593
Niestety ustawienie to jest bardzo ubogie, ale jednak jest, więcej na ten temat dokumentacji:
http://www.cplusplus.com/reference/clocale/
Polskie znaki
Jest to problem rodzący się u każdego programisty. Pytanie czy naprawdę jest nam to potrzebne? Jeśli tak zdecydujemy polecam poniższe linki, wydaje się, że dotyczą C++, ale uzasadnienie jest ponadjęzykowe:
http://cpp0x.pl/artykuly/?id=55
http://miroslawzelent.pl/kurs-c-plus-plus-polskie-znaki-konsola-terminal-windows-linux-macos/
Ustawianie akcji podczas wyłączania programu
Sporadycznie zdarza się, że chcemy tuż przed wyjściem z programu dokonać pewnych akcji sprzątających. Da się to zrobić, wystarczy każdą taką akcję zarejestrować funkcją atexit():
#include <stdio.h> #include <stdlib.h> void funkcja1(void) { printf("Koncze program, wykonuje sie %s\n", __FUNCTION__); } void funkcja2(void) { printf("Koncze program, wykonuje sie %s\n", __FUNCTION__); } int main () { atexit(funkcja1); atexit(funkcja2); puts ("Ostatnie wypisanie"); return 0; }
wydruk:
Ostatnie wypisanie Koncze program, wykonuje sie funkcja2 Koncze program, wykonuje sie funkcja1
Ważne -funkcje zostają wywoływane w kolejności odwrotnej do ich rejestrowania funkcją atexit().
I jeszcze pytanie kiedy akcje z atexit zadziałają:
- jeśli zamkniemy program normalnie, czyli wraz z dotarciem do końca funkcji main,
- jeśli wyjdziemy z programu przy pomocy funkcji exit().
Ale funkcje nie zostaną wywołane, jeśli:
- wyjdziemy z programu brutalniej, przy użyciu:
quick_exit(), abort(), terminate(), - w przypadku otrzymania i nieprzechwycenia sygnału naruszenia ochrony pamięci, lub w przypadku zabicia programu przez system operacyjny.
Testy jednostkowe
Tutaj tylko napomnę, gdyż testowanie to bardzo szeroki temat. Jeśli programujemy dla kogoś i planujemy rozwijać nasz produkt może okazać się, że koniecznym będzie dodatkowy narzut programistyczny -konieczność napisania testów!
Testy jednostkowe to automatyczne testy pojedyńczych funkcjonalności programu na wiele różnych sposobów.
Często przyjmuje się, że napisanie kodu to 1/3, natomiast przetestowanie napisanego kodu zajmują 2/3 czasu.
Testy mają swoich zwolenników i przeciwników, moja opinia jest taka -należy zachować zdrowy rozsądek.
Dzięki testom możemy sobie dopisywać funkcjonalności i wiemy czy coś zepsuliśmy -brak obaw przed zmianami kodu.
Testowanie na piechotę nie jest wygodne, dlatego powstały specjalne narzędzia służące do ułatwienia pisania testów, przykłady takich narzędzi:
http://stackoverflow.com/questions/65820/unit-testing-c-code
https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C
Pomysłem na projekt zaliczeniowy jest prościutki programik (wystarczy zestaw jakiś funkcji) + testy do tego przy użyciu któregoś z powyższych narzędzi.
Rozszerzenia jezyka C
Na zajęciach uczymy się standardu języka C, czyli jak pisać programy w języku C i nie przejmować się, że nasz program po przeniesieniu na inny system przestanie działać. Niestety często jak coś piszemy to sprawdzamy czy coś działa, a nie czy to co napisaliśmy jest zgodne ze standardem, z tego powodu może się nam zdażyć napisanie nieprzenośnego kodu. Jeśli będziemy coś pisali dla jakiegoś klienta, który będzie wymagał przenośności między systemami, warto abyśmy zapoznali się z rozszerzeniami języka C dostępnymi pod kompilatorem, pod którym piszemy. Gcc ma sporo rozszerzeń:
https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html
Bardzo ciekawym rozszerzeniem jest wielowątkowy język C: UPC, bardzo ułatwiający pisanie programów wielowątkowych, dla dociekliwych: