C++
Zwracanie kilku wartości z funkcji
Szukaj…
Wprowadzenie
Istnieje wiele sytuacji, w których warto zwrócić kilka wartości z funkcji: na przykład, jeśli chcesz wprowadzić towar i zwrócić cenę i liczbę w magazynie, ta funkcja może być przydatna. Jest wiele sposobów na zrobienie tego w C ++ i większość z nich wymaga STL. Jeśli jednak chcesz uniknąć STL z jakiegoś powodu, istnieje jeszcze kilka sposobów, aby to zrobić, w tym structs/classes
i arrays
.
Korzystanie z parametrów wyjściowych
Parametry mogą być użyte do zwrócenia jednej lub więcej wartości; parametry te muszą być const
wskaźnikami lub referencjami.
Bibliografia:
void calculate(int a, int b, int& c, int& d, int& e, int& f) {
c = a + b;
d = a - b;
e = a * b;
f = a / b;
}
Wskaźniki:
void calculate(int a, int b, int* c, int* d, int* e, int* f) {
*c = a + b;
*d = a - b;
*e = a * b;
*f = a / b;
}
Niektóre biblioteki lub frameworki używają pustego „OUT” #define
aby było całkowicie oczywiste, które parametry są parametrami wyjściowymi w sygnaturze funkcji. Nie ma to wpływu funkcjonalnego i zostanie skompilowane, ale sprawi, że podpis funkcji będzie nieco wyraźniejszy;
#define OUT
void calculate(int a, int b, OUT int& c) {
c = a + b;
}
Używanie std :: tuple
Typ std::tuple
może łączyć dowolną liczbę wartości, potencjalnie włączając wartości różnych typów, w jeden obiekt zwracany:
std::tuple<int, int, int, int> foo(int a, int b) { // or auto (C++14)
return std::make_tuple(a + b, a - b, a * b, a / b);
}
W C ++ 17 można użyć stężonej listy inicjalizatora:
std::tuple<int, int, int, int> foo(int a, int b) {
return {a + b, a - b, a * b, a / b};
}
Pobieranie wartości ze zwróconej tuple
może być kłopotliwe, wymagając użycia funkcji szablonu std::get
:
auto mrvs = foo(5, 12);
auto add = std::get<0>(mrvs);
auto sub = std::get<1>(mrvs);
auto mul = std::get<2>(mrvs);
auto div = std::get<3>(mrvs);
Jeśli typy można zadeklarować przed powrotem funkcji, można użyć std::tie
do rozpakowania tuple
do istniejących zmiennych:
int add, sub, mul, div;
std::tie(add, sub, mul, div) = foo(5, 12);
Jeśli jedna ze zwróconych wartości nie jest potrzebna, można użyć std::ignore
:
std::tie(add, sub, std::ignore, div) = foo(5, 12);
W celu uniknięcia std::tie
można zastosować wiązania strukturalne :
auto [add, sub, mul, div] = foo(5,12);
Jeśli chcesz zwrócić krotkę odwołań do wartości zamiast krotki wartości, użyj std::tie
zamiast std::make_tuple
.
std::tuple<int&, int&> minmax( int& a, int& b ) {
if (b<a)
return std::tie(b,a);
else
return std::tie(a,b);
}
co pozwala
void increase_least(int& a, int& b) {
std::get<0>(minmax(a,b))++;
}
W niektórych rzadkich przypadkach użyjesz std::forward_as_tuple
zamiast std::tie
; bądź ostrożny, jeśli to zrobisz, ponieważ tymczasowe mogą nie trwać wystarczająco długo, aby je pochłonąć.
Używanie std :: array
Kontener std::array
może łączyć ze sobą stałą liczbę zwracanych wartości. Ta liczba musi być znana w czasie kompilacji, a wszystkie zwracane wartości muszą być tego samego typu:
std::array<int, 4> bar(int a, int b) {
return { a + b, a - b, a * b, a / b };
}
Zastępuje to tablice w stylu c formularza int bar[4]
. Zaletą jest to, że można na nim teraz używać różnych funkcji st c++
. Zapewnia on również użyteczne funkcje składowe jak at
który jest bezpieczny dostęp do funkcji członek z sprawdzanie zakresu i size
, który pozwala na powrót do rozmiaru tablicy bez kalkulacji.
Używanie std :: pair
Szablon struct std::pair
może łączyć ze sobą dokładnie dwie zwracane wartości dowolnych dwóch typów:
#include <utility>
std::pair<int, int> foo(int a, int b) {
return std::make_pair(a+b, a-b);
}
W wersji C ++ 11 lub nowszej można użyć listy inicjalizującej zamiast std::make_pair
:
#include <utility>
std::pair<int, int> foo(int a, int b) {
return {a+b, a-b};
}
Poszczególne wartości zwróconego std::pair
można odzyskać za pomocą first
i second
obiektu pary:
std::pair<int, int> mrvs = foo(5, 12);
std::cout << mrvs.first + mrvs.second << std::endl;
Wynik:
10
Korzystanie z struct
struct
mogą być wykorzystane do zebrania wielu wartości powrotne:
struct foo_return_type {
int add;
int sub;
int mul;
int div;
};
foo_return_type foo(int a, int b) {
return {a + b, a - b, a * b, a / b};
}
auto calc = foo(5, 12);
Zamiast przypisania do poszczególnych pól można użyć konstruktora, aby uprościć konstruowanie zwracanych wartości:
struct foo_return_type {
int add;
int sub;
int mul;
int div;
foo_return_type(int add, int sub, int mul, int div)
: add(add), sub(sub), mul(mul), div(div) {}
};
foo_return_type foo(int a, int b) {
return foo_return_type(a + b, a - b, a * b, a / b);
}
foo_return_type calc = foo(5, 12);
Poszczególne wyniki zwrócone przez funkcję foo()
można pobrać, uzyskując dostęp do zmiennych calc
struct
calc
:
std::cout << calc.add << ' ' << calc.sub << ' ' << calc.mul << ' ' << calc.div << '\n';
Wynik:
17 -7 60 0
Uwaga: Podczas używania struct
zwracane wartości są grupowane w jednym obiekcie i dostępne za pomocą znaczących nazw. Pomaga to również zmniejszyć liczbę zewnętrznych zmiennych utworzonych w zakresie zwracanych wartości.
Aby rozpakować struct
zwróconą z funkcji, można użyć powiązań strukturalnych . Dzięki temu parametry wyjściowe są traktowane równo z parametrami wejściowymi:
int a=5, b=12;
auto[add, sub, mul, div] = foo(a, b);
std::cout << add << ' ' << sub << ' ' << mul << ' ' << div << '\n';
Wyjście tego kodu jest identyczne jak powyżej. struct
jest nadal używany do zwracania wartości z funkcji. Pozwala to na indywidualne podejście do pól.
Wiązania strukturalne
C ++ 17 wprowadza powiązania strukturalne, co sprawia, że jeszcze łatwiej jest radzić sobie z wieloma typami zwrotów, ponieważ nie musisz polegać na std::tie()
ani wykonywać ręcznego rozpakowywania krotki:
std::map<std::string, int> m;
// insert an element into the map and check if insertion succeeded
auto [iterator, success] = m.insert({"Hello", 42});
if (success) {
// your code goes here
}
// iterate over all elements without having to use the cryptic 'first' and 'second' names
for (auto const& [key, value] : m) {
std::cout << "The value for " << key << " is " << value << '\n';
}
Powiązania strukturalne mogą być domyślnie używane z std::pair
, std::tuple
i dowolnym typem, którego niestatyczni członkowie danych są publicznymi członkami bezpośrednimi lub członkami jednoznacznej klasy bazowej:
struct A { int x; };
struct B : A { int y; };
B foo();
// with structured bindings
const auto [x, y] = foo();
// equivalent code without structured bindings
const auto result = foo();
auto& x = result.x;
auto& y = result.y;
Jeśli sprawisz, że twój typ będzie podobny do krotki, będzie on automatycznie działał z twoim typem. tuple_size
jest typem z odpowiednim tuple_size
, tuple_element
i get
:
namespace my_ns {
struct my_type {
int x;
double d;
std::string s;
};
struct my_type_view {
my_type* ptr;
};
}
namespace std {
template<>
struct tuple_size<my_ns::my_type_view> : std::integral_constant<std::size_t, 3>
{};
template<> struct tuple_element<my_ns::my_type_view, 0>{ using type = int; };
template<> struct tuple_element<my_ns::my_type_view, 1>{ using type = double; };
template<> struct tuple_element<my_ns::my_type_view, 2>{ using type = std::string; };
}
namespace my_ns {
template<std::size_t I>
decltype(auto) get(my_type_view const& v) {
if constexpr (I == 0)
return v.ptr->x;
else if constexpr (I == 1)
return v.ptr->d;
else if constexpr (I == 2)
return v.ptr->s;
static_assert(I < 3, "Only 3 elements");
}
}
teraz to działa:
my_ns::my_type t{1, 3.14, "hello world"};
my_ns::my_type_view foo() {
return {&t};
}
int main() {
auto[x, d, s] = foo();
std::cout << x << ',' << d << ',' << s << '\n';
}
Używanie konsumenta obiektu funkcji
Możemy zapewnić konsumentowi, który zostanie wezwany z wieloma odpowiednimi wartościami:
template <class F>
void foo(int a, int b, F consumer) {
consumer(a + b, a - b, a * b, a / b);
}
// use is simple... ignoring some results is possible as well
foo(5, 12, [](int sum, int , int , int ){
std::cout << "sum is " << sum << '\n';
});
Jest to znane jako „styl przekazywania kontynuacji” .
Możesz dostosować funkcję zwracającą krotkę do funkcji stylu przekazywania kontynuacji poprzez:
template<class Tuple>
struct continuation {
Tuple t;
template<class F>
decltype(auto) operator->*(F&& f)&&{
return std::apply( std::forward<F>(f), std::move(t) );
}
};
std::tuple<int,int,int,int> foo(int a, int b);
continuation(foo(5,12))->*[](int sum, auto&&...) {
std::cout << "sum is " << sum << '\n';
};
bardziej złożone wersje są zapisywalne w C ++ 14 lub C ++ 11.
Używanie std :: vector
std::vector
może być przydatny do zwracania dynamicznej liczby zmiennych tego samego typu. W poniższym przykładzie użyto int
jako typu danych, ale std::vector
może zawierać dowolny typ, który można w prosty sposób skopiować:
#include <vector>
#include <iostream>
// the following function returns all integers between and including 'a' and 'b' in a vector
// (the function can return up to std::vector::max_size elements with the vector, given that
// the system's main memory can hold that many items)
std::vector<int> fillVectorFrom(int a, int b) {
std::vector<int> temp;
for (int i = a; i <= b; i++) {
temp.push_back(i);
}
return temp;
}
int main() {
// assigns the filled vector created inside the function to the new vector 'v'
std::vector<int> v = fillVectorFrom(1, 10);
// prints "1 2 3 4 5 6 7 8 9 10 "
for (int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
return 0;
}
Korzystanie z Iteratora wyjściowego
Kilka wartości tego samego typu można zwrócić, przekazując iterator wyjściowy do funkcji. Jest to szczególnie powszechne w przypadku funkcji ogólnych (takich jak algorytmy biblioteki standardowej).
Przykład:
template<typename Incrementable, typename OutputIterator>
void generate_sequence(Incrementable from, Incrementable to, OutputIterator output) {
for (Incrementable k = from; k != to; ++k)
*output++ = k;
}
Przykładowe użycie:
std::vector<int> digits;
generate_sequence(0, 10, std::back_inserter(digits));
// digits now contains {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}