Instrukcja przypisania. W tej lekcji opowiemy o innej metodzie nadawania wartości zmiennej poza jej wczytaniem. Jest nią instrukcja przypisania. Aby nadać zmiennej a typu int wartość 5, możemy użyć następującej instrukcji przypisania:
a = 5;
int a = 5;
Zamiast pojedynczej liczby w przypisaniu może także wystąpić dowolne wyrażenie matematyczne. Przykładowo, jeśli wczytaliśmy wartości zmiennych całkowitych y:
int a, b, x, y;
cin >> a >> x >> b;
y = a * x + b;
W ten sposób wprowadza się zmienną pomocniczą, co nierzadko pomaga uprościć obliczenia. W ten sposób w algorytmie rozwiązywania równania kwadratowego (patrz komentarz do poprzedniej lekcji) moglibyśmy wprowadzić zmienną pomocniczą Delta.
Co ciekawe, zmienna, której przypisujemy nową wartość, może wystąpić także w wyrażeniu po prawej stronie instrukcji przypisania! Np. w takiej instrukcji:
a = a + 3;
Jak to rozumieć? W instrukcji przypisania najpierw obliczana jest wartość wyrażenia po prawej stronie a o 3.
Zmiennym możemy wielokrotnie przypisywać różne wartości. To uzasadnia, dlaczego nazywamy je właśnie zmiennymi.
Czas na przykład. W znakomitej książce Mozaika matematyczna pod redakcją Endre Hodi (Wiedza Powszechna, Warszawa 1987) można znaleźć opis następującej zabawy-zgadywanki.
Bolek oznajmia Alinie sam wynik, po czym ona zawsze potrafi zgadnąć, jakie liczby wypadły na kostkach. W jaki sposób? Tym nie będziemy się tutaj zajmować – nie chcemy Ci psuć zabawy z rozwiązywania zagadki. W zamian za to napiszemy program, który działa zgodnie z procedurą wykonywaną przez Bolka. Wykorzystamy w nim jedną zmienną pomocniczą w, do której zastosujemy przypisania opisane w zagadce.
#include <iostream>
using namespace std;
int main() {
int kostka1, kostka2, kostka3;
cin >> kostka1 >> kostka2 >> kostka3;
int w = kostka1;
w = w * 2;
w = w + 5;
w = w * 5;
w = w + kostka2;
w = w * 10;
w = w + kostka3;
cout << w << endl;
}
Załóżmy dla przykładu, że Bolek wyrzucił odpowiednio 4 oczka, 2 oczka i 1 oczko. Wówczas na powyższy program można spojrzeć jak na ciąg operacji wykonywanych na tablicy (lub na kartce z użyciem ołówka i gumki). Początkowa wartość zmiennej w to 4. Piszemy więc na tablicy liczbę 4. Kolejne przypisanie mówi: weź wartość zmiennej i pomnóż przez 2. Mażemy zatem z tablicy liczbę 4, a w jej miejsce piszemy ww kolejno przez 65, 67, 670 i 671.
Powyższy program moglibyśmy też napisać tak:
#include <iostream>
using namespace std;
int main() {
int kostka1, kostka2, kostka3;
cin >> kostka1 >> kostka2 >> kostka3;
int w1 = kostka1;
int w2 = w1 * 2;
int w3 = w2 + 5;
int w4 = w3 * 5;
int w5 = w4 + kostka2;
int w6 = w5 * 10;
int w7 = w6 + kostka3;
cout << w7 << endl;
}
W ten sposób niepotrzebnie korzystamy z aż siedmiu różnych zmiennych, podczas gdy wystarczy do tego tylko jedna!
Skrócone instrukcje przypisania
W języku C++ są też dostępne skrócone instrukcje przypisania. Otóż zamiast:a = a + b;
a = a - b;
a = a * b;
a = a / b;
a = a % b;
możemy napisać odpowiednio:
a += b;
a -= b;
a *= b;
a /= b;
a %= b;
Używając tych instrukcji, program z kostkami możemy napisać tak:
#include <iostream>
using namespace std;
int main() {
int kostka1, kostka2, kostka3;
cin >> kostka1 >> kostka2 >> kostka3;
int w = kostka1;
w *= 2;
w += 5;
w *= 5;
w += kostka2;
w *= 10;
w += kostka3;
cout << w << endl;
}
Jak okaże się w kolejnych lekcjach, szczególnie często wykonuje się przypisanie polegające na zmniejszeniu lub zwiększeniu wartości danej zmiennej o jeden. Do tego celu można użyć skróconych instrukcji tzw. inkrementacji i dekrementacji:
a++; // to samo co: a += 1;
a--; // to samo co: a -= 1;
W nawiązaniu do pierwszej z tych instrukcji w dość dowcipny sposób stworzono nazwę języka programowania, którym posługujemy się w tym kursie: język C++ jest rozszerzeniem języka programowania C. Co ciekawe, poprzednikiem języka C jest język B, natomiast jego następca – niedawno powstały język będący alternatywą dla C++ – to język D.
A oto inny przykład. Zobaczymy, jak zastosowanie instrukcji przypisania może uprościć rozwiązanie zadania Czas z drugiej lekcji. Przypomnijmy, że należało w nim przeliczyć czas t sekund na zapis w godzinach, minutach i sekundach.
To zadanie można oczywiście rozwiązać bez użycia instrukcji przypisania, jednak z jej pomocą rozwiązanie staje się bardziej przejrzyste. Klucz do rozwiązania stanowi wprowadzenie trzech zmiennych: t, liczbę pełnych minut w tym czasie z wyłączeniem pełnych godzin i pozostałą liczbę sekund. Wartości tych zmiennych możemy obliczać w kolejności s lub odwrotnie. W poniższym programie obraliśmy właśnie kolejność odwrotną, natomiast rozwiązanie wybierające kolejność zgodną z podaną pozostawiamy już uczestnikom kursu.
#include <iostream>
using namespace std;
int main() {
int t;
cin >> t;
int g, m, s;
s = t % 60;
t /= 60;
m = t % 60;
t /= 60;
g = t;
cout << g << "g" << m << "m" << s << "s" << endl;
}
Bardziej złożony przykład
W tym przykładzie wykorzystamy większość elementów języka C++, których nauczyliśmy się do tej pory.
Wyobraźmy sobie, że przyszliśmy do automatu z napojami. Są w nim dostępne puszki z naszym ulubionym napojem, w cenie 6 zł za sztukę. Automat przyjmuje jedynie monety o nominałach 1 zł, 2 zł i 5 zł. Niestety, na automacie znajduje się informacja, że nie wydaje on reszty – tak więc, chcąc zakupić ulubiony napój, musimy zawsze wrzucać do niego odliczoną kwotę 6 zł.
Zakładając, że w automacie dostępnych jest dowolnie wiele puszek naszego ulubionego napoju, chcielibyśmy wiedzieć, ile puszek możemy kupić za pomocą monet, które aktualnie mamy w kieszeni. Napiszemy program, który wczyta liczby poszczególnych typów monet, które mamy, i wypisze liczbę puszek, które chcemy kupić.
Myślenie nad rozwiązaniem warto rozpocząć od rozważenia wszystkich możliwych układów dających łącznie 6 zł. Są to:
6 = 5 + 1 6 = 2 + 2 + 2 6 = 2 + 2 + 1 + 1 6 = 2 + 1 + 1 + 1 + 1 6 = 1 + 1 + 1 + 1 + 1 + 1
Łatwo zauważyć, że w naszym zadaniu jest istotne, których układów będziemy używać. Np. w przypadku posiadania w kieszeni sześciu monet jednozłotowych i sześciu pięciozłotowych nie opłaca nam się ani razu używać układu 1+1+1+1+1+1, gdyż wtedy monety pięciozłotowe nam się już na nic nie przydadzą. Lepiej jest kupić sześć puszek napoju, korzystając z układu 5+1.
Zauważmy, że w ogóle najbardziej opłaca nam się używać układu 5+1. Jest to jedyny układ, w którym występuje moneta pięciozłotowa, więc jeśli którejś monety o tym nominale nie zużyjemy w ten sposób, to nie wykorzystamy jej już wcale. Co więcej, jest to jedyny układ używający tylko jednej złotówki. Gdyby nam miała zostać na końcu jakaś pięciozłotówka, a którąś jednozłotówkę wykorzystalibyśmy w innym układzie, moglibyśmy wyjąć ją z tego innego układu i stworzyć z tych dwóch monet układ 5+1. W ten sposób liczba stworzonych układów, czyli liczba kupionych puszek, na pewno by nie zmalała. Kontynuując to postępowanie, możemy uzasadnić, że rzeczywiście opłaca nam się stworzyć możliwie wiele układów 5+1.
Dalej możemy uzasadnić podobnie, że kolejnym najbardziej opłacalnym układem jest 2+2+2. Jeśli mamy układ z monetami dwu- i jednozłotowymi i odpowiednio wiele dwuzłotówek poza nim, możemy podmienić jednozłotówki z układu na te dwuzłotówki, otrzymując układ 2+2+2. Wtedy kupujemy puszkę, zużywając te dwuzłotówki, i kontynuujemy to podejście, aż zostanie nam łącznie mniej niż trzy dwuzłotówki. Nietrudno zobaczyć, że możemy je wszystkie umieścić w jednym układzie typu 2+2+1+1 lub 2+1+1+1+1, jeśli oczywiście wystarczy nam jednozłotówek, a pozostałe jednozłotówki wrzucać już do automatu po sześć.
Opisane pomysły możemy zapisać w postaci pierwszego rozwiązania. Użyjemy w nim zmiennej pomocniczej wynik, w której zapamiętamy liczbę już zakupionych puszek.
#include <iostream>
using namespace std;
int main() {
int n1, n2, n5;
cin >> n1 >> n2 >> n5;
int wynik = 0;
/* 6 = 5 + 1 */
if (n5 >= n1) {
wynik += n1;
n5 -= n1;
n1 = 0;
} else {
// Pamietaj o zaktualizowaniu wszystkich zmiennych!
wynik += n5;
n1 -= n5;
n5 = 0;
}
/* 6 = 2 + 2 + 2 */
wynik += n2 / 3;
n2 %= 3;
/* 6 = 2 + 2 + 1 + 1
6 = 2 + 1 + 1 + 1 + 1 */
if (n2 >= 1 && 2 * n2 + n1 >= 6) {
// Pamietaj o zaktualizowaniu wszystkich zmiennych!
wynik++;
n1 -= 6 - 2 * n2;
n2 = 0;
}
/* 6 = 1 + 1 + 1 + 1 + 1 + 1 */
wynik += n1 / 6;
n1 %= 6;
cout << wynik << endl;
}
Mamy tu trochę przypadków. Warto zastanowić się, czy nie dało się tego zadania rozwiązać prościej.
Zacznijmy od początku rozwiązania. Liczba układów 5+1, które jesteśmy w stanie stworzyć, jest równa min(n1,n5)min(n1,n5). Okazuje się, że w języku C++ dostępne są funkcje min i max. Aby mieć do nich dostęp, należy na początku programu dopisać jeden dodatkowy wiersz, w którym zadeklarujemy chęć używania podstawowych algorytmów w języku C++:
#include <iostream>
#include <algorithm>
using namespace std;
Jednak nawet gdybyśmy o tym nie wiedzieli, moglibyśmy zamiast nich napisać prostą instrukcję warunkową, tak jak w jednym z zadań domowych w lekcji 3.
To pozwoli nam uprościć część rozwiązania dotyczącą przypadku 5+1. A co z pozostałymi przypadkami? Okazuje się, że z nimi będzie jeszcze prościej. Wyobraźmy sobie, że wszystkie monety dwu- i jednozłotowe, jakie pozostały nam po rozważaniu przypadku 5+1, ustawiliśmy w rzędzie – najpierw dwuzłotówki, a potem jednozłotówki. Jeśli będziemy je wrzucać do automatu w tym porządku, zakupimy dokładnie
(2 * n2 + n1) / 6;
puszek napoju. A przecież więcej się nie da, co wynika z sumy nominałów tych monet! Tak więc możemy wszystkie pozostałe przypadki skleić w jeden. Drugie rozwiązanie poniżej.
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n1, n2, n5;
cin >> n1 >> n2 >> n5;
/* 6 = 5 + 1 */
int wynik = min(n1, n5);
n1 -= wynik;
/* pozostale */
wynik += (2 * n2 + n1) / 6;
cout << wynik << endl;
}