Kurs podstawy programowania UMCS, zdjęcie laptopa.

Podstawy programowania – Laboratorium 1

Zadanie 1 – Hello World!

Napisz program w języku C++, wypisujący „Hello World!”.

Rozwiązanie:
//Version 1.0
#include <cstdio>

int main() {
	printf("Hello world!");
	return 0;
}
//Version 2.0
#include <iostream>
using namespace std;

int main() {
    cout << "Hello world!";
    return 0;
}
//Version 3.0
#include <iostream>

int main() {
    std::cout << "Hello world!";
    return 0;
}
Omówienie:

Program w języku C/C++, niezależnie od rozmiaru, jest zbudowany z funkcji i zmiennych/obiektów.

Funkcja zawiera instrukcje określające, jakie operacje procesu obliczeniowego należy wykonać (zadania), zmienne zaś przechowują wartości używane podczas tego procesu (dane).

W naszym rozwiązaniu występuje funkcja o nazwie main. Zwykle funkcje możemy nazywać dowolnie, jednakże main jest funkcją o specjalnej nazwie. Każdy program musi zawierać funkcję o takiej nazwie i to od tego miejsca zaczyna się start naszego programu. Oczywiście całego kodu nie pisze się jedynie w funkcji main. Główna funkcja programu, bo tak można ją nazywać, aby wykonać zadanie, wywołuje inne funkcje. Tutaj warto dodać, że część z tych funkcji zaimplementujecie sami (jeszcze w tym semestrze), a inne będą pochodzić z gotowych bibliotek tak, jak printf() w powyższym kodzie.

Innym znanym zapisem dla funkcji main jest:
int main(int argc, char *argv[])
są to argumenty funkcji, czym są argumenty funkcji i do czego służą dowiecie się na kolejnych lekcjach z tego kursu. W każdym razie, pierwszy argument funkcji int argc mówi o liczbie argumentów, zaś drugi char *argv[] przechowuje wartości tych argumentów w tablicy. Nazewnictwo to wynika z konwencji języka C/C++, ale te argumenty tak nazywać się nie muszą. Zapis int main(int num_args, char** arg_strings) również jest poprawny. Wspomniane argumenty umożliwiają przyjmowanie danych z linii poleceń.

Oprócz nazwy funkcji oraz argumentów, zawarliśmy także informację o typie zwracanej wartości: int (integer), czyli liczba całkowita. Wartość zwracamy za pomocą instrukcji return, ale więcej o funkcjach powiemy sobie w przyszłości. Aktualnie należy pamiętać, że kod funkcji umieszcza się w nawiasach klamrowych { }, a kolejne polecenia rozdzielone są średnikami. Warto dodać, że zazwyczaj funkcja main zwraca wartość 0, która informuje system operacyjny o poprawnym zakończeniu programu. Zwrócenie innej wartości może sygnalizować błąd (wrócimy do tego przy omawianiu funkcji).


Linia 1 (Version 1.0, 2.0 i 3.0): komentarze.
Czym są komentarze? Jak podają źródła w Internecie:

Komentarz jest to notatka sporządzona przez programistę, 
znajdująca się w kodzie źródłowym programu. 
Komentarz w żaden sposób nie wpływa na działanie programu.

W języku C i C++ mamy do dyspozycji trzy rodzaje komentarzy: jednowierszowy, wielowierszowy i wykonany za pomocą dyrektyw preprocesora.

Tych ostatnich nie będziemy omawiać w ramach kursu, jednakże mowa tutaj o #pragma message("Komentarz"). Takie komentarze pojawiają się podczas kompilacji programu, ale nie wpływają na jego logikę.

Komentarz jednowierszowy zaczyna się od dwóch ukośników // i trwa do końca wiersza. Uwaga: Komentarz nie działa wewnątrz tekstu w cudzysłowach:
printf("To // komentarzem nie będzie.");

Komentarz wielowierszowy rozpoczyna się znakami /* i kończy */. Na przykład:
/* jest to komentarz
w wielu liniach */


Generalnie zaleca się stosować komentarze jednowierszowe, gdy dodajemy jakieś opisy działania programu, co jest preferowaną praktyką w C++ ze względu na ich czytelność. Komentarz wielowierszowy przydaje się do dłuższych notatek lub tymczasowego wyłączenia fragmentu kodu. Ponadto często gdy chcemy wymienić jakiś stary algorytm na nowy, np. wersję źle działającego algorytmu komentujemy i w miejsce starego kodu piszemy nową, jednocześnie mając wgląd na to co wcześniej napisaliśmy. Jednakże należy zaznaczyć, że nie jest to idealna praktyka, a do rozwiązywania takich sytuacji programista ma inne narzędzia np. system kontroli wersji.

Wskazówka: Dobry kod komentuje się sam. Wraz ze zwiększaniem wiedzy i umiejętności programistycznych można mieć wrażenie, że komentarze są zbędne. Faktem jest, że programiści z większym doświadczeniem bardzo skromnie komentują kod lub nie robią tego wcale. Wynika to z dobrze napisanego kodu, poprawnie nazwanych zmiennych i funkcji. To nie znaczy, że komentarze nie są potrzebne są momenty, gdy są nieodzowne. Wręcz przeciwnie na początku nauki programowania mogą być bardzo przydatne.


Linia 2 (Version 1.0, 2.0 i 3.0): dyrektywy.
Wszystkie wiersze, które zaczynają się znakiem # nazywamy dyrektywami preprocesora. Każda dyrektywa musi zaczynać się od nowego wiersza. Dyrektywy preprocesora to instrukcje, które są przetwarzane przed właściwą kompilacją programu.

Linijka druga zleca kompilatorowi dołączenie do programu informacji o standardowej bibliotece wejścia-wyjścia. W języku C++ deklaracje funkcji i klas zawarte są w plikach nagłówkowych – ich definicje znajdują się w innych plikach. Taki plik dołączamy do naszego programu przy pomocy dyrektywy preprocesora #include.

Version 1.0: Plik cstdio (stdio.h), dołączony za pomocą dyrektywy preprocesora, zawiera niezbędne dla kompilatora opisy funkcji np. prinf. Jest to standardowa biblioteka, która umożliwia pracę z konsolą i plikami.


Linia 3 (Version 2.0): przestrzeń nazw.
Przestrzeń nazw to pojęcie, którego szczegółowo nie będziemy omawiać w ramach tego kursu. Jednakże w celu uzupełnienia fundamentalnych kwestii należy wiedzieć, że jest to pojęcie, które pozwala organizować kod i unikać konfliktów nazw. W praktyce oznacza to, że w programie możemy mieć funkcje, zmienne czy klasy o takich samych nazwach, ale będą one „schowane” w różnych przestrzeniach nazw, co zapobiega kolizjom.

Przestrzeń nazw możemy sobie określić jako folder, w którym przechowujemy różne rzeczy – mogą to być funkcje, zmienne itp. W ten sposób możemy używać takich samych nazw w różnych „folderach”, bez obawy, że program się pomyli, bo każda z nich jest w innym miejscu.

Za pomocą słów kluczowych using namespace informujemy kompilator, że chcemy aby wszystkie funkcje, klasy i szablony należące do przestrzeni nazw były w głównej przestrzeni i nie wymagały przedrostka. Wyraz występujący po instrukcji using namespace jest istniejącą nazwą przestrzeni. Dla standardowych bibliotek C++ przestrzeń nazwana jest std.

Mamy dwie możliwości korzystania z przestrzeni nazw przedrostek i operator zasięgu (::):
std::cout << "1" << std::endl; (Version 3.0)
lub tak, jak pojawiło się to w kodzie
using namepsace std;
i wtedy std:: pomijamy przy cout.


Linia 5 (Version 1.0): wywołanie funkcji.
To wywołanie funkcji printf ze standardowej biblioteki wejścia-wyjścia. Funkcję wywołuje się podając jej nazwę i w nawiasach listę argumentów. Argumentem nazywamy wartości przekazywane do funkcji, kolejne argumenty są rozdzielone przecinkami.
Warto zuważyć i zapamiętać, że na końcu każdej instrukcji (lub grupy instrukcji) musi znaleźć się średnik ;, który informuje kompilator gdzie ta instrukcja się kończy.

Nasza funkcja posiada jeden argument i jest nim "Hello world!". Funkcja ta wypisuje tekst na standardowe wyjście (zazwyczaj ekran monitora i konsola), w tym przypadku ciąg znaków ujęty w znaki cudzysłów. "Hello world!" najczęściej nazywamy stałą napisową (ang. string), bądź po prostu napisem i na razie będziemy stosować je jedynie w argumentach funkcji printf. Należy dodać, że printf może przyjmować więcej niż jeden argument:
printf("%d", 3 + 4)

Tym razem funkcja printf przyjęła dwa argumenty rodzielone przecinkiem: stałą napisową oraz działanie arytmetyczne. Porównując wywołanie funkcji z treścią którą otrzymamy (wyświetlona zostanie liczba 7) można łatwo zauważyć, że zamiast zapisu %d pokaże się nam liczba 7. Znak % w stałej napisowej jest znakiem specjalnym rozpoczynającym sekwencję sterującą, po którym następuje określenie sposobu prezentacji kolejnych argumentów funkcji printf. Zapis %d informuje, że argumentem będzie liczba całkowita. printf jest znacznie bardziej rozbudowana niż ten jeden podstawowy typ. Nie będziemy się jednak w nią zagłębiać ze względu na to, iż będziemy wykorzystywali inne i znacznie prostsze narzędzie do wypisywania danych.


Linia 6 (Version 2.0), Linia 5 (Version 3.0): strumienie.
Pomimo tego, że poznaliśmy właśnie jedną technikę wypisywania różnych danych na ekranie – pora poznać kolejną. Kolejna technika to używanie strumieni, co jest bardziej idiomatyczną techniką w ramach C++, np. strumienia wyjściowego cout. Zapis cout (skrót od (C)onsole (OUT)put) powoduje skierowanie danych ze zmiennej na standardowy strumień wyjściowy, którym jest ekran monitora, tak samo, jak robiła to funkcja printf. W tej linii kodu występują również znaki << przy zapisie cout, które nazywamy operatorem. Za pomocą operatora <<wskazujemy’ dane, które mają zostać przekazane do strumienia.
cout << "Hello world!";
cout << 3 + 4;


Można zauważyć istotną różnicę w stosunku do printf, a mianowicie brak %d. Powoduje to, że nie musimy znać typu wyświetlanych danych, a co za tym idzie musimy mniej pamiętać, aby otrzymać ten sam efekt. Tym samym znacznie częściej będziemy korzystali ze strumienia wyjściowego std::cout. Wracając do typów, należy pamiętać, że język C++ oferuje kilkanaście typów podstawowych np. liczba całkowita krótka 16-bitowa, zwykła 32-bitowa i długa 64-bitowa. Każda z wcześniej wymienionych liczb może być ze znakiem lub bez. Do tego dochodzą znaki, liczby zmiennoprzecinkowe, wartości logiczne, wskaźniki i być może jeszcze inne podstawowe typy, które w chwili obecnej pominąłem. Dla każdego wyżej wymienionego typu zapis w funkcji printf występujący po % będzie wyglądał inaczej, natomiast w przypadku użycia mechanizmu C++ kompilator 'wie’ co z danym typem zrobić.


Linia 6 (Version 1.0 i 3.0) i 7 (Version 2.0): zwracanie wartości przez funkcję.
Nasza funkcja main, poza wyświetleniem komunikatu, zawiera tylko jedno polecenie do wykonania: zakończ funkcję z wynikiem 0.
return 0;
Liczba ta będzie zwrócona do systemu operacyjnego jako wynik działania programu.
Zauważ średnik kończący instrukcję.

Zadanie 2 – zmienne i operacje arytmetyczne

Napisz program w języku C++, który stworzy dwie zmienne typu całkowitego, przypisze im dowolne wartości i wyświetli ich sumę.

Rozwiązanie:
//Version 1.0
#include <cstdio>

int main() {
    int a, b, c;
    a = 1;
    b = 2;
    c = a + b;
    printf("%d + %d = %d", a, b, c);
    return 0;
}
//Version 2.0
#include <cstdio>

int main() {
	int a = 1;
	int b = 2;
	printf("%d + %d = %d", a, b, a + b);
	return 0;
}
//Version 3.0
#include <iostream>
using namespace std;

int main() {
    int a = 1, b = 2;
    cout << a << " + " << b << " = " << a + b;
    return 0;
}
Omówienie:

Zmienna jest to pewien fragment pamięci o ustalonym rozmiarze, który posiada własny identyfikator (nazwę) oraz może przechowywać pewną wartość, zależną od typu zmiennej.

Nazwy zmiennych nie mogą zawierać polskich znaków diakrytycznych, a dozwolone znaki to: małe litery ( a...z ), duże litery ( A...Z ), podkreślenie ( _ ) i cyfry ( 0...9 ). Ponadto nazwa zmiennej nie może zaczynać się od liczby, zmienne muszą posiadać unikatowe nazwy w obrębie całego bloku. Należy nadmienić, że zmienna int a; nie jest tą samą zmienną co int A;, są to dwie różne zmienne, o różnych nazwach.

Zasięg zmiennych. Zmienne, które zdefiniowaliśmy wewnątrz funkcji main mają zasięg lokalny i nazywamy je zazwyczaj zmiennymi lokalnymi. Zmienna lokalna widoczna jest tylko w obrębie bloku, w którym została zdefiniowana, czyli w obrębie klamry { ... }. Istnieją także inne rodzaje zmiennych np. globalne i statyczne, te pierwszy omówimy w przyszłości.


Linia 5 (Version 1.0): definiowanie zmiennych.
Aby móc skorzystać ze zmiennej należy ją przed użyciem zdefiniować, to znaczy poinformować kompilator, jak zmienna będzie się nazywać i jaki typ ma mieć. Zmienne definiuję w następujący sposób:
typ nazwa_zmiennej;


Linie 5-6 (Version 2.0) i 6 (Version 3.0): inicjowanie zmiennych.
Zmiennej w momencie zdefiniowania można od razu przypisać wartość (zainicjować), za pomocą operatora przypisania =. Zmienne definiujemy i inicjalizujemy w następujący sposób:
typ nazwa_zmiennej = wartość;
Należy pamiętać, że jeśli zmiennej nie przypiszemy żadnej wartości będzie miała wartość przypadkową. Kompilator nie zeruje wartości zmiennych! Jeśli chcemy od razu nadać zmiennej początkową wartość, możemy to zrobić korzystając z przedstawionego wyżej zapisu – dokonujemy wówczas inicjalizacji zmiennej początkową wartością.

Innym, bardziej nowoczesnym, sposobem inicjalizacji zmiennych jest następujący zapis:
typ nazwa_zmiennej{wartość}; np. int a{1};.

Wskazówka: Dobrą praktyką programowania jest zdefiniowanie zmiennych na początku bloku, czyli przed pierwszą instrukcją (kiedyś było to wymagane). Jednakże według nowszych standardów, C i C++ umożliwia zdefiniowanie zmiennej w dowolnym miejscu programu, ale wtedy musimy pamiętać, aby zdefiniować zmienną przed jej użyciem.


Operatory w C/C++ pełnią funkcję podobną do tej pełnionej przez operatory w matematyce. Istnieją różne grupy operatorów i umożliwiają one przeprowadzanie różnych działań na zmiennych.

O jednym operatorze już powiedzieliśmy, jest to operator przypisania =. Operator ten ma łączność prawostronną tzn. obliczanie przypisań następuje z prawa na lewo i zwraca on przypisaną wartość, dzięki czemu może być używany kaskadowo:
a = b = c = 3;
Tym samym wszystkim zmiennym a, b i c zostanie przypisana wartość 3.

Istnieją również operatory arytmetyczne ( +, -, /, *, % ), które pojawiły się w powyższym kodzie. Tak jak w matematyce kolejność działań ma znaczenie, tak w programowaniu, w języku C/C++ jest istotna. Szerzej zostanie to omówione na kolejnych zajęciach z tego kursu.

Należy wspomnieć również o skróconym zapisie, który umożliwia C/C++, o postaci a #= b, gdzie # jest jednym z operatorów: +, -, /, *, %, &, |, ^, << lub >>. Część z nich omówimy w kolejnym wpisie z tego kursu. Powyższy zapis jest równoważny zapisowi: a = a # (b);.

Zadanie 3 – systemy liczbowe

Napisz program w języku C++, wyświetlający tą samą liczbę na różne sposoby. Liczbe całkowitą w formatach: całkowitym, ósemkowym, szesnastkowym.

Rozwiązanie:
#include <cstdio>

int main() {
  int dval= 15; 
  printf("decimal %d, octal %o, hexadecimal %x\n", dval, dval, dval);
  return 0;
}
Omówienie:

Systemy liczbowe
W informatyce możemy zapisywać liczby w różnych systemach liczbowych. Najczęściej używany jest system dziesiętny (czyli liczby, które wszyscy znamy), ale w programowaniu często korzysta się również z innych systemów, takich jak ósemkowy (bazujący na 8 cyfrach) czy szesnastkowy (bazujący na 16 cyfrach). Każdy z tych systemów jest przydatny w różnych kontekstach, zwłaszcza w operacjach na niskim poziomie, np. przy pracy z pamięcią komputera lub w programowaniu systemowym.

System dziesiętny:
To system, którego wszyscy używamy na co dzień, oparty na bazie 10. Każda cyfra w liczbie dziesiętnej ma określoną wagę (jedności, dziesiątki, setki itd.), które są potęgami liczby 10.

System ósemkowy:
W systemie ósemkowym używamy liczb w zakresie od 0 do 7. Każda cyfra w liczbie ósemkowej reprezentuje kolejne potęgi liczby 8. W języku C/C++ liczby w systemie ósemkowym są oznaczane w formacie 0 lub przez samą cyfrę ósemkową przy wyświetlaniu. W przypadku funkcji printf, format %o konwertuje liczbę na ósemkową.

System szesnastkowy:
System szesnastkowy używa 16 cyfr: od 0 do 9, a następnie liter A do F (A=10, B=11, ..., F=15). System ten jest bardzo przydatny w informatyce, ponieważ łatwo konwertuje się go na system binarny (2-kowy), który jest bezpośrednio używany przez komputery. W C++ szesnastkowe liczby można zapisywać z przedrostkiem 0x, a %x w funkcji printf konwertuje liczbę na szesnastkową.

Wskazówka:
Liczbę ósemkową można zainicjować z przedrostkiem 0, np.:
int octal_val = 017; // 15 w systemie dziesiętnym
Liczbę szesnastkową można zainicjować z przedrostkiem 0x, np.:
int hex_val = 0xF; // 15 w systemie dziesiętnym

Alternatywne rozwiązanie:

Poniższe rozwiązanie jest bardziej odpowiednie dla C++, jednakże na razie wykracza za zakres tego kursu.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    int dval = 15;
    cout << "decimal: " << dval << ", octal: " << oct << dval 
         << ", hexadecimal: " << hex << dval << endl;
    return 0;
}

Zadanie 4 – odczytywanie danych.

Napisz program w języku C++, który stworzy dwie zmienne typu całkowitego, przyjmujące wartości wprowadzone przez użytkownika i wyświetli ich sumę.

Rozwiązanie:
//Version 1.0
#include <cstdio>

int main() {
    int a, b;
    scanf("%d", &a);
    scanf("%d", &b);
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}
//Version 2.0
#include <cstdio>

int main() {
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d + %d = %d", a, b, a + b);
    return 0;
}
//Version 3.0
#include <iostream>

int main() {
    int a, b;
    std::cin >> a;
    std::cin >> b;
    std::cout << a << " + " << b << " = " << a + b << std::endl;
    return 0;
}
//Version 4.0
#include <iostream>
using namespace std;

int main() {
	int a, b; 				
	cin >> a >> b;
	cout << a << " + " << b << " = " << a + b << endl;
	return 0;
}
Omówienie:

Omówimy sobie dwa sposoby odczytywania danych z konsoli, wprowadzonych przez użytkownika. Jest to podstawowy sposób interakcji użytkownika z naszym programem.

Linie 6-7 (Version 1.0) i 6 (Version 2.0): funkcja scanf.
Funkcja scanf odczytuje dane ze standardowego wejścia i robi to zgodnie z określonym formatem. Format składa się ze zwykłych znaków (innych niż znak %) oraz sekwencji sterujących, zaczynających się od symbolu procent, po którym następuje:

  • opcjonalna gwiazdka,
  • opcjonalna maksymalna szerokość pola,
  • opcjonalne określenie rozmiaru argumentu,
  • określenie formatu.

Funkcja scanf obsługuje mi. następujące formaty:

  • d, i – odczytuje liczbę całkowitą, argument powinien być wskaźnikiem na int,
  • f – odczytuje liczbę rzeczywistą, nieskończoność lub NaN, argument powinien być wskaźnikiem na float.

Zapis dla odczytania liczby całkowitej będzie następujący:
scanf("%d", &nazwa_zmiennej);
scanf w pierwszym argumencie przyjmuje napis formatujący (z dowolną ilością sekwencji sterujących), a w kolejnych zmienne, do których mają zostać wprowadzone wartości. Zmienne muszą być koniecznie poprzedzone znakiem ampersand &. Ilość zmiennych i typ musi odpowiadać ilości i typowi wystąpień sekwencji sterujących w łańcuchu formatującym.

Uzupełniając, dla osób zainteresowanych szczegółami, efektem działania tej funkcji będzie zapisanie wartości wprowadzonej przez użytkownika pod adres pamięci zmiennej nazwa_zmiennej. W związku tym, jak można się domyślić zapis &nazwa_zmiennej pobiera adres zmiennej w pamięci. O tym więcej przy szczegółowym omówieniu wskaźników.


Linie 6-7 (Version 3.0) i 7 (Version 4.0): obsługa strumienia wejściowego.
Dokładnie tak, jak w przypadku printf częściej będziemy korzystali z czegoś bardziej związanego z C++, czyli obsługi strumieni.

Wczytywanie danych do zmiennych odbywa się za pomocą strumienia std::cin(skrót od (C)onsole (IN)put). Aby móc wczytać dane znajdujące się na standardowym wejściu, należy wykorzystać do tego celu operator >>. W powyższy sposób możemy wczytywać wszystkie podstawowe typy danych – poczynając od liczb całkowitych, przechodząc przez liczby rzeczywiste oraz znaki, a kończąc na tekście. Podczas tych zajęć skupimy się tylko i wyłącznie na wczytywaniu liczb całkowitych i rzeczywistych.

Zwróćmy uwagę, że nawiasy ostre przy wczytywaniu danych wskazują w przeciwną stronę, niż przy wypisywaniu danych na wyjście. Ich ustawienie w pewnym sensie wskazuje kierunek przepływu danych.

Skoro nauczyliśmy się już korzystać ze strumienia wejściowego w podstawowym wymiarze, przyjrzyjmy się teraz jego działaniu. Wyobraźmy więc sobie, że początkowo strumień jest pusty. Wysyłamy następnie żądanie: „daj mi liczbę całkowitą do zmiennej liczba (czyli: std::cin>>liczba). Strumień jest pusty, więc nie można z niego pobrać danych, a więc użytkownik musi wprowadzić nowe dane do strumienia. W sytuacji, gdy w strumieniu pojawi się inny typ niż oczekiwany np. przecinek ( , ) zamiast oczekiwanej liczby całkowitej, operacja wczytania zakończy się niepowodzeniem, a flaga błędu zostanie ustawiona.

Warto omówić również białe znaki w kontekście obsługi strumieni. Białymi znakami nazywamy te, które nie mają swojej reprezentacji wizualnej, a istnieją w tekście. Białe znaki to: spacja, tabulacja, znak nowej linii. Gdy używamy strumienia std::cin>>, ewentualne białe znaki poprzedzające dane są pomijane. Tak więc gdyby w buforze strumienia znajdował się biały znak, strumień by go po prostu pominął i przeszedł do kolejnego znaku.

Podczas wczytywania danych z użyciem cin, jeśli użytkownik wprowadzi dane w nieprawidłowym formacie (np. litery zamiast liczby), strumień ustawi flagę błędu i dalsze wczytywanie nie będzie działać poprawnie. W takich przypadkach programista może sprawdzić stan strumienia i obsłużyć błędną sytuację.Przykład sprawdzenia błędu:
if (!(std::cin >> a)) {
std::cout << "Wprowadzono nieprawidłowe dane!" << std::endl;
}


Chociaż zarówno scanf, jak i cin służą do wczytywania danych od użytkownika, cin jest preferowany w C++ ze względu na swoją prostotę i elastyczność. scanf wymaga bardziej dokładnego określenia formatu danych i stosowania wskaźników, co czyni go bardziej skomplikowanym. cin jest łatwiejszy w użyciu, automatycznie dopasowuje się do różnych typów danych i lepiej współpracuje ze standardową biblioteką C++. Mimo wszystko pojawią się zadania, gdy łatwiej będzie użyć funkcji scanf.


Ważne:

Skoro pojawił się temat ewentualnych błędów, omówmy błędy, które mogą pojawić się w języku C/C++.

  1. Błędy składniowe (syntax errors):
    Są to błędy związane z naruszeniem zasad składni języka, np. brak średnika, nieprawidłowe użycie nawiasów, czy literówki w nazwach funkcji. Kompilator nie jest w stanie skompilować programu, dopóki te błędy nie zostaną usunięte.
    Przykład:
    int a = 10 // Brak średnika
  2. Błędy wykonania (runtime errors):
    Błędy te pojawiają się w trakcie działania programu i są związane z nieprzewidzianymi sytuacjami, które mogą spowodować zakończenie programu z błędem. Mogą być wynikiem operacji takich jak dzielenie przez zero, dostęp do nieprawidłowego obszaru pamięci (segmentation fault) lub wyczerpanie zasobów systemowych (np. pamięci). Takie błędy czasami nazywamy także błędami semantycznymi krytycznymi.
    Przykład:
    int a = 10, b = 0;
    int wynik = a / b; // Dzielenie przez zero

  3. Błędy logiczne (logical errors):
    Błędy logiczne to błędy w algorytmie lub logice programu. Program kompiluje się poprawnie i uruchamia się, ale nie działa zgodnie z oczekiwaniami i daje błędne wyniki. Kompilator nie wykryje takich błędów, ponieważ z punktu widzenia składni wszystko jest poprawne, ale logika programu jest niewłaściwa. Takie błędy nazywamy także błędami semantycznymi.
    Przykład:
    int a = 10, b = 5;
    int wynik = a - b; // Powinno być a + b, ale kod daje zły wynik (5 zamiast 15)

Zadanie 5

Napisz program w języku C++, który obliczy i wyświetli pole prostokąta dla dowolnych długości boków a i b.

Rozwiązanie:
#include <iostream>

int main() {
    int a = 5, b = 2;
    std::cout << "Pole prostokąta: " << a * b << std::endl;
    return 0;
}

Zadanie 6

Napisz program w języku C++, który stworzy dwie zmienne typu zmiennoprzecinkowego, przyjmujące wartości wprowadzone przez użytkownika i wyświetli ich iloczyn.

Rozwiązanie:
#include <iostream>
using namespace std;

int main() {
	float a, b; 	
	cin >> a >> b;
	cout << a << " * " << b << " = " << a * b << endl;
	return 0;
}

Zadanie 7

Napisz program w języku C++, który pobierze ze standardowego wejścia 3 liczby całkowite. Wartości powinny być pobrane za pomocą jednej instrukcji. Następnie wyświetli na standardowym wyjściu ich średnią arytmetyczną.

Rozwiązanie:
#include <iostream>

int main() {
	int a, b, c;
	float avg;

	std::cin >> a >> b >> c;
	avg = (a + b + c) / 3.0f;  //UWAGA! Przy dzieleniu całkowitym liczba zostałaby zaokrąglona w dół.
	std::cout << "Śr. arytmetyczna: " << avg << std::endl;
	
	return 0;
}

Zadanie 8

Dwunasty wyraz ciągu arytmetycznego (an), określonego dla n >= 1, jest równy 30, a suma jego dwunastu początkowych wyrazów jest równa 162. Napisz program, który obliczy pierwszy wyraz tego ciągu.

Rozwiązanie:
#include <iostream>
using namespace std;

int main() {
	int a_12 = 30, S_12 = 162, n = 12;
	int a_1 = S_12 * 2 / n - a_12; 
	
	cout << "Pierwszy wyraz ciągu to: " << a_1 << "\n";
		
	return 0;
}

Zadanie 9

Zmień program tak by potrafił obliczyć pierwszy wyraz ciągu dla danych wprowadzonych przez użytkownika tj. wartość K, K-ty wyraz ciągu arytmetycznego, suma K-tych początkowych wyrazów. Wszystkie wartości poza K powinny być zmiennoprzecinkowe.

Rozwiązanie:
#include <iostream>
using namespace std;

int main() {
	int k;
	float a_k, S_k;
	
	cin >> k >> a_k >> S_k;
	cout << "Pierwszy wyraz ciągu to: " << S_k * 2.0 / k - a_k << endl;
	
	return 0;
}

Zadanie 10

Napisz program w języku C++, który wczyta dwie godziny w formacie HH:mm. Następnie wyświetl o ile minut różnią się podane godziny. Użyj co najwyżej trzy zmienne całkowite.

Rozwiązanie:
#include <cstdio>

int main() {
    int HH, mm, tmp;

    scanf("%d:%d", &HH, &mm);
    tmp = mm + HH * 60;
    scanf("%d:%d", &HH, &mm);
    tmp -= mm + HH * 60;
    printf("Różnnica to: %d", tmp);

    return 0;
}

Zadanie 11

Ze zbioru wszystkich liczb naturalnych dwucyfrowych losujemy kolejno dwa razy po jednej liczbie bez zwracania. Oblicz prawdopodobieństwo zdarzenia polegającego na tym, że suma wylosowanych liczb będzie równa 30. Wynik wyświetl w notacji naukowej.

Rozwiązanie:
#include <cstdio>

int main() {
    printf("%e", 10.0f / (90.0f * 89.0f));
    return 0;
}
Omówienie:

10,20 11,19 12,18 13,17 14,16 i symetryczne.

Zadanie 12

Napisz program w języku C++ wypisujący na ekranie napis "\n \t" oraz napis reprezentujący ścieżkę dostępu do przykładowego katalogu linuksowego "/usr/bin/xorg/" oraz windowsowego "C:\Windows\System".

Rozwiązanie:
#include <iostream>

int main() {
    std::cout << "\n \t" << std::endl;
    std::cout << "/usr/bin/xorg/" << std::endl;
    std::cout << "C:\\Windows\\System" << std::endl;
    
    return 0;
}

Zadanie 13

Podczas przesyłu danych za pomocą połączenia sieciowego, dołączane są dodatkowe informacje pozwalające na weryfikację poprawności przesłanych danych. Załóżmy, że do każdego bajtu dodawane są dwa bity kontrolne.

Napisz program w języku C++, który przyjmie ze standardowego wejścia rozmiar pliku w megabajtach oraz przepustowość łącza użytego do transmisji w megabitach na sekundę. Program powinien wyświetlić po ilu sekundach plik zostanie przesłany, przy założeniu wykorzystania pełnej przepustowości.

Rozwiązanie:
#include <iostream>

int main() {
    double file_size_mb, capacity, file_size_bits, file_size_control_bits, transmission_time;
    
    std::cin >> file_size_mb >> capacity;

    file_size_bits = file_size_mb * 8 * 1024 * 1024; // 1 MB = 8 * 1024 * 1024 bity
    file_size_control_bits = file_size_bits * (10.0 / 8.0); // dodajemy 2 bity do każdych 8
    transmission_time = file_size_control_bits / capacity / 1024 / 1024;

    std::cout << "Czas transmisji pliku wynosi: " << transmission_time << " sekund.\n";

    return 0;
}

Zadanie 14

Prawo Amdahla określa przyspieszenie działania równoległej implementacji algorytmu w stosunku do szeregowej (wykonywanej w jednym wątku). Wyraża się wzorem: 1 / ((1 - P) + P/S),gdzie P oznacza udział obliczeń, które mogą być zrównoleglone, a S określa przyspieszenie tych obliczeń – zazwyczaj odpowiada liczbie wątków. Napisz program w języku C++, który przyjmie w sekundach czas wykonania pewnego algorytmu za pomocą jednego wątku, procentową część operacji, które mogą zostać zrównoleglone oraz liczbę wątków, które będą przetwarzać algorytm równolegle. Program powinien wyświetlić liczbę sekund, którą zajmie wykonanie algorytmu równolegle.

Rozwiązanie:
#include <iostream>

int main() {
    double one_thread_time, p, parallel_time;
    int num_threads;

    std::cin >> one_thread_time >> p >> num_threads;

    p /= 100.0;
    parallel_time = one_thread_time * ((1 - p) + p / num_threads); 

    std::cout << "Czas wykonania algorytmu za pomocą " << num_threads << " wątków wynosi:  " << parallel_time << " sekund.\n";

    return 0;
}

Zadanie 15

Zmodyfikuj poprzednie zadanie tak, aby przyjmował czas wykonania algorytmu w formacie hh:mm:ss. W takim samym formacie powinien przyjąć godzinę rozpoczęcia działania Program powinien wyświetlić godzinę zakończenia działania w wariancie równoległym.

Rozwiązanie:
#include <cstdio>

int main() {
    double p;
    unsigned int one_thread_time, hh, mm, ss, num_threads, parallel_time;

    scanf("%2d:%2d:%2d", &hh, &mm, &ss);
    scanf("%lf%d", &p, &num_threads);

    one_thread_time = 3600 * hh + 60 * mm + ss;
    p = p / 100.0;

    parallel_time = one_thread_time * ((1 - p) + p / num_threads);
    hh = parallel_time / 3600;
    mm = (parallel_time % 3600) / 60;
    ss = parallel_time % 60;
    
    printf("Czas wykonania algorytmu za pomocą %d, wątków wynosi: %02d:%02d:%02d.\n", num_threads, hh, mm, ss);

    return 0;
}

Zadanie 16

Współczesne dyski twarde dzielą się na sektory, które są podstawową jednostką wymiany danych. Zazwyczaj sektor ma rozmiar 512 bajtów. Odczytywany i zapisywany na dysku jest zawsze cały sektor. Standardem adresowania tych sektorów jest LBA (ang. logical block addressing). Dane na talerzowych dyskach twardych adresowane są fizycznie za pomocą trzech parametrów, cylindra C, głowicy H (ang. head) i ścieżki T (ang. track). Każdy dysk posiada określoną liczbę głowic na cylinder HPC i liczbę sektorów na ścieżkę SPT. Adres logiczny określa się za pomocą wzoru: A = (C × HPC + H) × SPT + (S − 1). Napisz program w języku C++, który pobierze ze standardowego wejścia wartości: HPC, SPT, C, H i S, a następnie wyświetli adres logiczny za pomocą liczby szesnastkowej.

Rozwiązanie:
#include <iostream>

int main() {
    int HPC, SPT, C, H, S;

    std::cin >> HPC >> SPT >> C >> H >> S;
    std::cout << "Adres logiczny: " << std::hex << (C * HPC + H) * SPT + (S - 1) << std::endl;

    return 0;
}

Zadanie 17

Posiadając informacje z poprzedniego zadania, napisz program w języku C++, który otrzyma ze standardowego wejścia adres logiczny w postaci liczby szesnastkowej oraz dziesiątkowe wartości HPC, SPT. Program powinien wyświetlić wartości C, H, S za pomocą liczb dziesiątkowych.

Rozwiązanie:
#include <iostream>

int main() {
    long long A;
    int HPC, SPT, S, H, C;

    std::cin >> std::hex >> A >> std::dec >> HPC >> SPT;
    std::cout << A << std::endl;

    S = (A % SPT) + 1;
    A /= SPT;
    H = A % HPC;
    C = A / HPC;

    std::cout << "Wartości C, H, S: " << C << ' ' << H << ' ' << S << '\n';

    return 0;
}

Przygotuj się na kolejne laboratorium!

W celu przygotowania się na kolejne zajęcia, spróbuj wykonać poniższe zadania samodzielnie.

Zadanie 1

Napisz program w języku C++, który pobierze ze standardowego wejścia dwie liczby całkowite. Program powinien wyświetlić informację czy są one równe.

Zadanie 2

Napisz program w języku C++, który pobierze ze standardowego wejścia dwie liczby zmiennoprzecinkowe. Program powinien wyświetlić ich iloraz, jeżeli druga liczba jest zerem, należy wyświetlić stosowną informację.

Zadanie 3

Napisz program w języku C++, który pobierze ze standardowego wejścia trzy liczby całkowite: a, b, c. Program powinien wyświetlić informację, czy liczba b, jest większa od a, ale mniejsza od c.

Zadanie 4

Napisz program w języku C++, który pobierze ze standardowego wejścia liczbę całkowitą, a następnie wyświetli czy jest ona podzielna bez reszty przez 6, 3 lub 2.

Zadanie 5

Napisz program w języku C++, który pobierze trzy zmiennoprzecinkowe współczynniki równania kwadratowego i wyświetli rozwiązanie(a) lub informację o braku rozwiązań.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *