Rozważ następujący program:
#include <iostream>
int main()
{
// get a value from the user
std::cout << "Enter an integer: ";
int num{};
std::cin >> num;
// print the value doubled
std::cout << num << " doubled is: " << num * 2 << '\n';
return 0;
}Program ten składa się z dwóch części koncepcyjnych: Najpierw otrzymujemy wartość od użytkownika. Następnie mówimy użytkownikowi, jaka jest dwukrotność tej wartości.
Chociaż ten program jest na tyle trywialny, że nie musimy go dzielić na wiele funkcji, co by było, gdybyśmy chcieli? Pobieranie wartości całkowitej od użytkownika jest dobrze zdefiniowanym zadaniem, które chcemy, aby nasz program wykonał, więc byłoby dobrym kandydatem na funkcję.
Napiszmy więc program, który to zrobi:
// This program doesn't work
#include <iostream>
void getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
}
int main()
{
getValueFromUser(); // Ask user for input
int num{}; // How do we get the value from getValueFromUser() and use it to initialize this variable?
std::cout << num << " doubled is: " << num * 2 << '\n';
return 0;
}Chociaż ten program jest dobrą próbą rozwiązania, nie do końca działa.
Gdy funkcja getValueFromUser zostanie wywołana, użytkownik zostanie poproszony o wprowadzenie liczby całkowitej zgodnie z oczekiwaniami. Ale wprowadzona przez nich wartość zostaje utracona, gdy getValueFromUser kończy się i kontrola powraca do main. Zmienny num nigdy nie jest inicjowany wartością wprowadzoną przez użytkownika, dlatego program zawsze wypisuje odpowiedź 0.
To, czego nam brakuje, to jakiś sposób getValueFromUser aby zwrócić wartość, którą ponownie wprowadził użytkownik main tak, że main może wykorzystać te dane.
Zwróć wartości
Kiedy piszesz funkcję zdefiniowaną przez użytkownika, możesz określić, czy funkcja zwróci wartość do osoby wywołującej, czy nie. Aby zwrócić wartość wywołującemu, potrzebne są dwie rzeczy.
Po pierwsze, funkcja musi wskazywać, jaki typ wartości zostanie zwrócony. Odbywa się to poprzez ustawienie funkcji typ zwrotu, czyli typ zdefiniowany przed nazwą funkcji. W powyższym przykładzie funkcja getValueFromUser ma typ powrotu void (co oznacza, że wywołującemu nie zostanie zwrócona żadna wartość) i funkcja main ma typ powrotu int (co oznacza wartość typu int zostanie zwrócony dzwoniącemu). Należy pamiętać, że nie określa to, jaka konkretna wartość zostanie zwrócona — określa tylko co typ wartości zostaną zwrócone.
Powiązana treść
Badamy funkcje, które zwracają void dalej w następnej lekcji (2.3 — Funkcje puste (funkcje nie zwracające wartości)).
Po drugie, wewnątrz funkcji, która zwróci wartość, używamy a oświadczenie zwrotne aby wskazać konkretną wartość zwracaną do osoby wywołującej. Instrukcja return składa się z return słowo kluczowe, po którym następuje wyrażenie (czasami nazywane wyrażenie zwrotne), kończąc na średniku.
Kiedy wykonywana jest instrukcja return:
- Wyrażenie zwracane jest oceniane w celu wygenerowania wartości.
- Wartość wygenerowana przez wyrażenie zwrotne jest kopiowana z powrotem do obiektu wywołującego. Ta kopia nazywa się wartość zwracana funkcji.
- Funkcja kończy działanie, a kontrola wraca do osoby wywołującej.
Proces zwracania skopiowanej wartości z powrotem do osoby wywołującej nosi nazwę zwrócić według wartości.
Nomenklatura
Wyrażenie return generuje wartość, która ma zostać zwrócona. Wartość zwracana jest kopią tej wartości.
Funkcja zwracająca wartość zwróci wartość przy każdym wywołaniu.
Przyjrzyjmy się prostej funkcji zwracającej wartość całkowitą i przykładowemu programowi, który ją wywołuje:
#include <iostream>
// int is the return type
// A return type of int means the function will return some integer value to the caller (the specific value is not specified here)
int returnFive()
{
// the return statement provides the value that will be returned
return 5; // return the value 5 back to the caller
}
int main()
{
std::cout << returnFive() << '\n'; // prints 5
std::cout << returnFive() + 2 << '\n'; // prints 7
returnFive(); // okay: the value 5 is returned, but is ignored since main() doesn't do anything with it
return 0;
}Po uruchomieniu ten program wypisuje:
5 7
Wykonanie rozpoczyna się od góry main. W pierwszej instrukcji wywołanie funkcji to returnFive() jest oceniana, co skutkuje funkcją returnFive() wywoływany. Wyrażenie zwrotne 5 jest oceniany w celu uzyskania wartości 5, który jest zwracany do osoby wywołującej i drukowany na konsoli poprzez std::cout.
W drugim wywołaniu funkcji wywołanie funkcji to returnFive jest oceniana, co skutkuje funkcją returnFive zostanie ponownie wezwany. Funkcjonować returnFive zwraca wartość 5 z powrotem do rozmówcy. Wyrażenie 5 + 2 jest oceniany w celu uzyskania wyniku 7, który jest następnie drukowany na konsoli poprzez std::cout.
W trzecim stwierdzeniu funkcja returnFive jest wywoływana ponownie, co skutkuje wartością 5 zostanie zwrócony rozmówcy. Jednak funkcja main nie robi nic ze zwracaną wartością, więc nic więcej się nie dzieje (zwracana wartość jest ignorowana).
Uwaga: Zwracane wartości nie zostaną wydrukowane, chyba że wywołujący wyśle je do konsoli przez std::cout. W ostatnim przypadku powyżej wartość zwracana nie jest wysyłana do std::cout, więc nic nie jest drukowane.
Wskazówka
Gdy wywołana funkcja zwraca wartość, osoba wywołująca może zdecydować się na użycie tej wartości w wyrażeniu lub instrukcji (np. używając jej do inicjacji zmiennej lub wysyłając ją do std::cout) lub zignorować ją (nie robiąc nic innego). Jeśli wywołujący zignoruje zwracaną wartość, zostanie ona odrzucona (nic się z nią nie zrobi).
Naprawianie naszego programu wyzwań
Mając to na uwadze, możemy naprawić program, który zaprezentowaliśmy na początku lekcji:
#include <iostream>
int getValueFromUser() // this function now returns an integer value
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input; // return the value the user entered back to the caller
}
int main()
{
int num { getValueFromUser() }; // initialize num with the return value of getValueFromUser()
std::cout << num << " doubled is: " << num * 2 << '\n';
return 0;
}Kiedy ten program zostanie wykonany, pierwsza instrukcja w main utworzy int zmienną o nazwie num. Kiedy program przejdzie do inicjalizacji num, zobaczy, że istnieje wywołanie funkcji do getValueFromUser(), więc przystąpi do wykonania tej funkcji. Funkcja getValueFromUser, prosi użytkownika o wprowadzenie wartości, a następnie zwraca tę wartość z powrotem do osoby wywołującej (main()). Ta wartość zwracana jest używana jako wartość inicjująca dla zmiennej num. num może być następnie używana tyle razy, ile potrzeba w main().
Wskazówka
Jeśli chcesz użyć wartości zwracanej przez wywołanie funkcji więcej niż raz, zainicjuj zmienną wartością zwracaną, a następnie użyj tej zmiennej tyle razy, ile potrzeba.
Skompiluj ten program samodzielnie i uruchom go kilka razy, aby przekonać się, że to działa.
Ponowna wizyta main()
Masz teraz narzędzia koncepcyjne umożliwiające zrozumienie, jak faktycznie działa funkcja main() . Kiedy program jest wykonywany, system operacyjny wywołuje funkcję main(). Wykonanie następnie wskakuje na górę main(). Instrukcje w main() są wykonywane sekwencyjnie. Na koniec main() zwraca wartość całkowitą (zwykle 0) i program kończy działanie.
W C++ istnieją dwa specjalne wymagania, aby main():
main()zwrócićint.- Jawne wywołania funkcji do
main()są niedozwolone.
void foo()
{
main(); // Compile error: main not allowed to be called explicitly
}
void main() // Compile error: main not allowed to have non-int return type
{
foo();
}Kluczowa informacja
C tak robi Zezwól na jawne wywołanie funkcji main() , więc niektóre kompilatory C++ na to pozwolą ze względu na kompatybilność.
Na razie powinieneś także zdefiniować swoją funkcję main() na dole pliku z kodem, poniżej innych funkcji, i unikać jej jawnego wywoływania.
Dla zaawansowanych czytelników
Powszechnym błędnym przekonaniem jest, że main jest zawsze pierwszą funkcją, która wykonuje.
Zmienne globalne są inicjalizowane przed wykonaniem main. Jeśli inicjator takiej zmiennej wywołuje funkcję, wówczas funkcja ta zostanie wykonana przed main. Omawiamy zmienne globalne w lekcji 7.4 — Wprowadzenie do zmiennych globalnych.
Kody stanu
Być może zastanawiasz się, dlaczego zwracamy 0 z main() i kiedy możemy zwrócić coś innego.
Wartość zwracana z main() jest czasami nazywana a kod stanu (lub rzadziej, kod wyjścia lub rzadko kod powrotu). Kod stanu służy do sygnalizowania, czy program zakończył się sukcesem, czy nie.
Zgodnie z konwencją kod stanu 0 oznacza, że program działał normalnie (co oznacza, że program został wykonany i zachowywał się zgodnie z oczekiwaniami).
Najlepsza praktyka
Twoja main funkcja powinna zwrócić wartość 0 jeśli program działał normalnie.
Kod stanu niezerowy to często używany do wskazania pewnego rodzaju awarii (i chociaż działa to dobrze w większości systemów operacyjnych, ściśle mówiąc, nie ma gwarancji, że będzie przenośny).
Dla zaawansowanych czytelników
Standard C++ definiuje tylko znaczenie 3 kodów stanu: 0, EXIT_SUCCESS, I EXIT_FAILURE. 0 i EXIT_SUCCESS oba oznaczają, że program został wykonany pomyślnie. EXIT_FAILURE oznacza, że program nie został wykonany pomyślnie.
EXIT_SUCCESS i EXIT_FAILURE są makrami preprocesora zdefiniowanymi w <cstdlib> header:
#include <cstdlib> // for EXIT_SUCCESS and EXIT_FAILURE
int main()
{
return EXIT_SUCCESS;
}Jeśli chcesz zmaksymalizować przenośność, powinieneś używać 0 lub EXIT_SUCCESS do wskazania pomyślnego zakończenia lub EXIT_FAILURE w celu wskazania nieudanego zakończenia.
W lekcji omówimy preprocesor i makra preprocesora 2.10 — Wprowadzenie do preprocesora.
Na marginesie…
Kod stanu jest przekazywany z powrotem do systemu operacyjnego. System operacyjny zazwyczaj udostępnia kod stanu dowolnemu programowi, który uruchomił program zwracający kod stanu. Zapewnia to prosty mechanizm dla każdego programu uruchamiającego inny program, pozwalający określić, czy uruchomiony program został pomyślnie wykonany, czy nie.
Funkcja zwracająca wartość, która nie zwraca wartości, spowoduje niezdefiniowane zachowanie
Funkcja zwracająca wartość nazywa się a funkcja zwracająca wartość. Funkcja zwraca wartość, jeśli typ zwracany jest inny niż void.
Funkcja zwracająca wartość muszą zwraca wartość tego typu (za pomocą instrukcji return). W przeciwnym razie nastąpi niezdefiniowane zachowanie.
Powiązana treść
Omawiamy niezdefiniowane zachowanie w lekcji 1.6 — Niezainicjowane zmienne i niezdefiniowane zachowanie.
Oto przykład funkcji, która powoduje niezdefiniowane zachowanie:
#include <iostream>
int getValueFromUserUB() // this function returns an integer value
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
// note: no return statement
}
int main()
{
int num { getValueFromUserUB() }; // initialize num with the return value of getValueFromUserUB()
std::cout << num << " doubled is: " << num * 2 << '\n';
return 0;
}Nowoczesny kompilator powinien wygenerować ostrzeżenie ponieważ getValueFromUserUB zdefiniowano jako zwracanie int ale nie podano instrukcji return. Uruchomienie takiego programu spowodowałoby niezdefiniowane zachowanie, ponieważ getValueFromUserUB() jest funkcją zwracającą wartość, która nie zwraca wartości.
W większości przypadków kompilatory wykryją, czy zapomniałeś zwrócić wartość. Jednak w niektórych skomplikowanych przypadkach kompilator może nie być w stanie poprawnie określić, czy funkcja zwraca wartość we wszystkich przypadkach, więc nie powinieneś na tym polegać.
Najlepsza praktyka
Upewnij się, że funkcje z typami zwracanymi innymi niż void zwracają wartość we wszystkich przypadkach.
Brak zwrócenia wartości z funkcji zwracającej wartość spowoduje niezdefiniowane zachowanie.
Funkcja główna domyślnie zwróci 0, jeśli nie Dostarczono instrukcję return
Jedynym wyjątkiem od reguły mówiącej, że funkcja zwracająca wartość musi zwracać wartość poprzez instrukcję return, jest funkcja main(). Funkcja main() niejawnie zwróci wartość 0 jeśli nie podano instrukcji return. To powiedziawszy, najlepszą praktyką jest jawne zwracanie wartości z main, zarówno w celu pokazania swoich zamiarów, jak i dla zachowania spójności z innymi funkcjami (które będą wykazywać niezdefiniowane zachowanie, jeśli nie zostanie określona wartość zwracana).
Funkcje mogą zwracać tylko jedną wartość
Funkcja zwracająca wartość może zwrócić obiektowi wywołującemu tylko jedną wartość przy każdym wywołaniu.
Zauważ, że podana wartość w instrukcji return nie musi być dosłowny — może być wynikiem dowolnego prawidłowego wyrażenia, w tym zmiennej, a nawet wywołania innej funkcji, która zwraca wartość. W getValueFromUser() przykładzie powyżej zwróciliśmy zmienną input, która zawierała liczbę wprowadzoną przez użytkownika.
Istnieją różne sposoby obejścia ograniczenia funkcji polegającego na zwracaniu tylko jednej wartości, co omówimy w przyszłych lekcjach.
Autor funkcji może zdecydować, co oznacza wartość zwracana
Znaczenie wartości zwracanej przez funkcję jest określane przez autor funkcji. Niektóre funkcje wykorzystują zwracane wartości jako kody stanu, aby wskazać, czy zakończyły się sukcesem, czy niepowodzeniem. Inne funkcje zwracają obliczoną lub wybraną wartość. Inne funkcje nic nie zwracają (przykłady zobaczymy w następnej lekcji).
Ze względu na dużą różnorodność możliwości, dobrze jest udokumentować swoją funkcję komentarzem wskazującym, co oznaczają zwracane wartości. Na przykład:
// Function asks user to enter a value
// Return value is the integer entered by the user from the keyboard
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input; // return the value the user entered back to the caller
}Ponowne użycie funkcji
Teraz możemy zilustrować dobry przypadek ponownego wykorzystania funkcji. Rozważmy następujący program:
#include <iostream>
int main()
{
int x{};
std::cout << "Enter an integer: ";
std::cin >> x;
int y{};
std::cout << "Enter an integer: ";
std::cin >> y;
std::cout << x << " + " << y << " = " << x + y << '\n';
return 0;
}Chociaż ten program działa, jest trochę zbędny. W rzeczywistości ten program narusza jedną z głównych zasad dobrego programowania: Nie powtarzaj się (często w skrócie SUCHY).
Dlaczego powtarzający się kod jest zły? Gdybyśmy chcieli zmienić tekst „Wprowadź liczbę całkowitą:” na inny, musielibyśmy zaktualizować go w dwóch lokalizacjach. A co jeśli chcielibyśmy zainicjować 10 zmiennych zamiast 2? Byłoby to dużo zbędnego kodu (przez co nasze programy byłyby dłuższe i trudniejsze do zrozumienia) i dużo miejsca na wkradanie się literówek.
Zaktualizujmy ten program, aby korzystał z naszej getValueFromUser funkcji, którą opracowaliśmy powyżej:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
int x{ getValueFromUser() }; // first call to getValueFromUser
int y{ getValueFromUser() }; // second call to getValueFromUser
std::cout << x << " + " << y << " = " << x + y << '\n';
return 0;
}Ten program generuje następujące dane wyjściowe:
Enter an integer: 5 Enter an integer: 7 5 + 7 = 12
W tym programie wywołujemy getValueFromUser dwa razy, raz w celu inicjalizacji zmiennej x i raz w celu inicjalizacji zmienna y. Dzięki temu nie musimy duplikować kodu w celu uzyskania informacji od użytkownika i zmniejszamy ryzyko popełnienia błędu. Kiedy już wiemy, że getValueFromUser działa, możemy go wywoływać tyle razy, ile chcemy.
Na tym polega istota programowania modułowego: możliwość napisania funkcji, przetestowania jej, upewnienia się, że działa, a następnie wiedzy, że możemy jej użyć ponownie tyle razy, ile chcemy i będzie ona nadal działać (o ile nie zmodyfikujemy funkcji – w tym momencie będziemy musieli ją ponownie przetestować).
Najlepsza praktyka
Postępuj według DRY: „Nie powtarzaj się”. Jeśli musisz zrobić coś więcej niż raz, zastanów się, jak zmodyfikować kod, aby usunąć jak najwięcej nadmiarowości. Zmienne mogą służyć do przechowywania wyników obliczeń, które należy wykonać więcej niż raz (abyśmy nie musieli powtarzać obliczeń). Za pomocą funkcji można zdefiniować sekwencję instrukcji, które chcemy wykonać więcej niż raz. Pętle (które omówimy w późniejszym rozdziale) można wykorzystać do wykonania instrukcji więcej niż raz.
Jak wszystkie najlepsze praktyki, DRY ma być wytyczną, a nie wartością absolutną. Czytelnik Yariv zauważył że DRY może zaszkodzić ogólnemu zrozumieniu, gdy kod zostanie podzielony na zbyt małe kawałki.
Na marginesie…
(złośliwym) przeciwieństwem DRY jest MOKRY („Zapisz wszystko dwa razy”).
Wnioski
Zwracane wartości umożliwiają funkcjom zwrócenie pojedynczej wartości z powrotem do funkcji wywołujący.
Funkcje pozwalają zminimalizować redundancję w naszych programach.
Czas quizu
Pytanie nr 1
Sprawdź (nie kompiluj) każdy z poniższych programów. Określ, co program wyświetli lub czy wygeneruje błąd kompilatora.
Załóżmy, że masz wyłączoną opcję „traktuj ostrzeżenia jako błędy”.
1a)
#include <iostream>
int return7()
{
return 7;
}
int return9()
{
return 9;
}
int main()
{
std::cout << return7() + return9() << '\n';
return 0;
}1b)
#include <iostream>
int return7()
{
return 7;
int return9()
{
return 9;
}
}
int main()
{
std::cout << return7() + return9() << '\n';
return 0;
}1c)
#include <iostream>
int return7()
{
return 7;
}
int return9()
{
return 9;
}
int main()
{
return7();
return9();
return 0;
}1d)
#include <iostream>
int getNumbers()
{
return 5;
return 7;
}
int main()
{
std::cout << getNumbers() << '\n';
std::cout << getNumbers() << '\n';
return 0;
}1e)
#include <iostream>
int return 5()
{
return 5;
}
int main()
{
std::cout << return 5() << '\n';
return 0;
}Pytanie nr 2
Co oznacza „DRY” i dlaczego warto go przestrzegać?

