3. Dyrektywy preprocesora, interakcja z użytkownikiem - majsylw/Introduction-to-programming-in-C GitHub Wiki

Preprocesor jest programem, który analizuje plik źródłowy (programu, czy też biblioteki) w poszukiwaniu wszystkich wyrażeń zaczynających się od znaku "#". Na podstawie tych instrukcji generuje on kod w "czystym" języku C, który następnie jest kompilowany przez kompilator. Ponieważ za pomocą preprocesora można w pewien sposób sterować kompilatorem, daje on niezwykłe możliwości.

Dyrektywy preprocesora

Dyrektywy preprocesora nie wpływają bezpośrednio na działanie programu. W języku C wszystkie linijki zaczynające się od symbolu "#" są instrukcjami (dyrektywami) dla preprocesora. Nie są elementami języka C i nie podlegają bezpośrednio procesowi kompilacji. Najczęściej używanymi dyrektywami są:

  • #include
  • #define

Dyrektywa #include

Najpopularniejsza dyrektywa, wstawiająca w swoje miejsce treść pliku podanego w nawiasach ostrych lub cudzysłowie. Jej składnia może być następująca:

// kompilator poszuka dołączanego pliku wśród własnych plików nagłówkowych - które najczęściej znajdują się w podkatalogu "includes" w katalogu kompilatora
#include <plik_nagłówkowy_do_dołączenia>
// kompilator poszuka dołączanego pliku w katalogu, w którym znajduje się kompilowany plik
#include "plik_nagłówkowy_do_dołączenia"

/* Przykłady: */
#include<stdio.h> // załączamy plik nagłówkowy biblioteki standardowej wejścia-wyjścia
#include "./katalog1/plik_naglowkowy.h" // załączenie plik_nagłówkowy.h znajdującego się w folderze katalog1

Dyrektywa #define

Linia pozwalająca zdefiniować:

  • stałą,
  • funkcję,
  • słowo kluczowe,
  • makro, które będzie potem podmienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjach warunkowych dla preprocesora. Składnia:
// wariant 1
#define NAZWA_STALEJ WARTOSC
// wariant 2
#define NAZWA_STALEJ

/* Przykłady: */
#define A  5 // spowoduje, że każde wystąpienie znaku A w kodzie zostanie zastąpione piątką
#define SUMA(a,b) ((a)+(b)) // spowoduje, że każde wystąpienie wywołania SUMA zostanie zastąpione przez sumę argumentów
#define B  ((2)+(A)) // jeśli w miejscu wartości znajduje się wyrażenie, to należy je umieścić w nawiasach

Humorystycznie można podejść do tego też w ten sposób: preprocesor

Interakcja z użytkownikiem

Program, które do tej pory pisaliśmy miały poważne ograniczenie - nie dawaliśmy użytkownikowi możliwości zmiany wartości zmiennej. Po umożliwieniu użytkownikowi wpisania dowolnej wartości określonego typu dla zmiennej, program po jej przetworzeniu mógłby wwyświetlać przeróżne rezultaty.

Funkcja scanf()

Aby to osiągnać, potrzebujemy w jakiś sposób umożliwić użytkownikowi wpisanie dowolnej wartości dla zmiennej liczba. Jak się można spodziewać, w języku C, w bibliotece standardowej stdin mamy funkcję scanf(), która umożliwia nam interakcję z użytkownikiem. Funkcja scanf() podobnie jak funkcja print() wykorzystuje znaczniki konwersji. Ogólny format funkcji scanf() jest następujący scanf(łańcuchKontrolny [, dane]);, pobieranie wartości przez funkcję scanf() kończy się po naciśnięciu klawisza Enter. Prawie zawsze wywołanie funkcji scanf() poprzedzone powinno być wywołaniem funkcji printf(), które służy do wyświetlenia pytania lub instrukcji dla użytkownika jakie dane powinien podać, jak widać w poniższym przykładzie.

int liczba;
printf("Podaj liczbe calkowita\n"); // napis zachęcający do wpisania odpowiedniej wartości
scanf("%d",&liczba); // w tym miejscu zapewniamy użytkownikowi możliwość wpisania wartości pod zmienną liczba
print("Twoja liczba to %d\n",liczba); // ta linia pozwoli na wyświetlenie się napisu "Twoja liczba to ..", w miejsce .. pojawi się liczba całkowita wpisana przez uzytkownika 

Funkcja scanf() wymaga, aby przed nazwą każdej zmiennej znajdował się znak &, chyba, że jest to napis - wtedy jest on zbędny.

Funkcje getchar() i putchar()

Funkcja getchar() pobiera pojedynczy znak, natomiast funkcja putchar() drukuje pojedynczy znak na ekranie. Funkcja getchar() buforuje dane wejściowe. Dopóki użytkownik na naciśnie klawisza Enter użytkownik może poprawiać wprowadzane dane. Znak nowej linii (wprowadzony przez wciśnięcie klawisza Enter) pozostaje w buforze, o ile nie zostanie usunięty, np. zebrany poprez wywołanie getchar();.

// Autor: Karol Tarnowski

#include <stdio.h>

main(){
   char inicjalImienia, inicjalNazwiska;

   printf("Podaj inicjal imienia.\n");
   inicjalImienia = getchar();
   getchar(); // pozbywamy sie wcisnietego Entera z bufora

   printf("Podaj inicjal nazwiska.\n");
   inicjalNazwiska = getchar();
   getchar(); // pozbywamy sie wcisnietego Entera z bufora

   printf("Twoje inicjaly to %c. %c.\n",inicjalImienia, inicjalNazwiska);
   return 0;
}

Aby pracować ze znakami niezbędna jest znajomość funkcji z biblioteki ctype. Pozwalają one mędzy innymi na sprawdzenie do jakiej klasy znaków należy wczytany znak, czy też na zamianę wczytanego znaku z małej litery na dużą i na odwrót.

Funkcje gets() i puts()

Jak do tej pory poznaliśmy funkcje, które pozwalają wczytać napis do pierwszego białego znaku (scanf()) lub pojedynczego znaku (getchar()). Funkcja gets() czyta całą linię ze standardowego wejścia (wraz z białymi znakami) i umieszcza ją w tablicy znaków, ostatni znak linii (znak nowego wiersza - '\n') zastępuje zerem (znakiem '\0'). Funkcja puts() natomiast wysyła na standardowe wyjście napis podany przez użytkownika jako jej argument, a następnie znak nowej linii.

// Przykład użycia funkcji gets() i puts()
char napis[11], *n;
n = gets(napis); // wczytujemy cały napis od uzytkownika łącznie z białymi znakami - do końca linii

puts(napis); // wipisujemy cały napis na ekran monitora

Uzbrojony w wiedzę na temat zmiennych, typów danych, rzutowania, funkcji printf i scanf oraz komentarzy możesz swobodnie tworzyć zmienne, wykonując na nich przeróżne operacje, o czym trochę więcej w następnej sekcji.

⚠️ **GitHub.com Fallback** ⚠️