C++
Mehrere Werte aus einer Funktion zurückgeben
Suche…
Einführung
In vielen Situationen ist es nützlich, mehrere Werte aus einer Funktion zurückzugeben: Wenn Sie beispielsweise einen Artikel eingeben und Preis und Anzahl auf Lager zurückgeben möchten, kann diese Funktion hilfreich sein. Es gibt viele Möglichkeiten, dies in C ++ zu tun, und die meisten beinhalten die STL. Wenn Sie die STL jedoch aus irgendeinem Grund vermeiden möchten, gibt es noch verschiedene Möglichkeiten, einschließlich structs/classes
und arrays
.
Ausgabeparameter verwenden
Parameter können verwendet werden, um einen oder mehrere Werte zurückzugeben. Diese Parameter müssen nicht const
Zeiger oder Verweise sein.
Verweise:
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;
}
Zeiger:
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;
}
Einige Bibliotheken oder Frameworks verwenden ein leeres 'OUT' #define
, um deutlich zu machen, welche Parameter in der Funktionssignatur Ausgabeparameter sind. Dies hat keine funktionalen Auswirkungen und wird kompiliert, macht aber die Funktionssignatur etwas klarer.
#define OUT
void calculate(int a, int b, OUT int& c) {
c = a + b;
}
Verwenden von std :: tuple
Der Typ std::tuple
kann eine beliebige Anzahl von Werten, die möglicherweise Werte verschiedener Typen enthalten, in einem einzigen Rückgabeobjekt bündeln:
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);
}
In C ++ 17 kann eine geschweifte Initialisierungsliste verwendet werden:
std::tuple<int, int, int, int> foo(int a, int b) {
return {a + b, a - b, a * b, a / b};
}
Das Abrufen von Werten aus dem zurückgegebenen tuple
kann mühsam sein und erfordert die Verwendung der Vorlagenfunktion 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);
Wenn die Typen deklariert werden können, bevor die Funktion zurückkehrt, kann mit std::tie
ein tuple
in vorhandene Variablen entpackt werden:
int add, sub, mul, div;
std::tie(add, sub, mul, div) = foo(5, 12);
Wenn einer der zurückgegebenen Werte nicht benötigt wird, kann std::ignore
verwendet werden:
std::tie(add, sub, std::ignore, div) = foo(5, 12);
Strukturierte Bindungen können verwendet werden, um std::tie
zu vermeiden:
auto [add, sub, mul, div] = foo(5,12);
Wenn Sie ein Tupel von lvalue-Referenzen anstelle eines Tupels von Werten zurückgeben möchten, verwenden Sie std::tie
anstelle von 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);
}
was erlaubt
void increase_least(int& a, int& b) {
std::get<0>(minmax(a,b))++;
}
In seltenen Fällen verwenden Sie std::forward_as_tuple
anstelle von std::tie
; Seien Sie vorsichtig, wenn Sie dies tun, da Provisorien möglicherweise nicht lange genug dauern, um aufgebraucht zu werden.
Verwenden von std :: array
Der Container std::array
kann eine feste Anzahl von Rückgabewerten zusammenfassen. Diese Nummer muss zur Kompilierzeit bekannt sein und alle Rückgabewerte müssen vom selben Typ sein:
std::array<int, 4> bar(int a, int b) {
return { a + b, a - b, a * b, a / b };
}
Dies ersetzt Arrays vom Stil der Form int bar[4]
. Der Vorteil ist, dass jetzt verschiedene c++
std-Funktionen verwendet werden können. Es bietet auch nützliche Member - Funktionen wie at
dem eine sichere Mitglied Zugriffsfunktion mit gebundener Prüfung ist, und eine size
, die die Größe des Arrays ohne Berechnung zurückkehren kann.
Verwenden von std :: pair
Das struct template std::pair
kann genau zwei Rückgabewerte eines beliebigen Typs zusammenfassen:
#include <utility>
std::pair<int, int> foo(int a, int b) {
return std::make_pair(a+b, a-b);
}
Mit C ++ 11 oder höher kann anstelle von std::make_pair
eine Initialisierungsliste verwendet werden:
#include <utility>
std::pair<int, int> foo(int a, int b) {
return {a+b, a-b};
}
Die einzelnen Werte des zurückgegebenen std::pair
können mit den first
und second
std::pair
abgerufen werden:
std::pair<int, int> mrvs = foo(5, 12);
std::cout << mrvs.first + mrvs.second << std::endl;
Ausgabe:
10
Struct verwenden
Eine struct
kann verwendet werden, um mehrere Rückgabewerte zu bündeln:
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);
Anstelle der Zuordnung zu einzelnen Feldern kann ein Konstruktor verwendet werden, um das Erstellen von zurückgegebenen Werten zu vereinfachen:
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);
Die einzelnen Ergebnisse, die von der Funktion foo()
können durch Zugriff auf die Member-Variablen des struct
calc
abgerufen werden:
std::cout << calc.add << ' ' << calc.sub << ' ' << calc.mul << ' ' << calc.div << '\n';
Ausgabe:
17-7 60 0
Hinweis: Wenn Sie eine struct
, werden die zurückgegebenen Werte in einem einzigen Objekt zusammengefasst und können mit aussagekräftigen Namen aufgerufen werden. Dies hilft auch, die Anzahl der überflüssigen Variablen zu reduzieren, die im Bereich der zurückgegebenen Werte erstellt werden.
Um eine auszupacken struct
aus einer Funktion, zurück strukturierte Bindungen können verwendet werden. Dadurch werden die Out-Parameter mit den In-Parametern auf eine Stufe gestellt:
int a=5, b=12;
auto[add, sub, mul, div] = foo(a, b);
std::cout << add << ' ' << sub << ' ' << mul << ' ' << div << '\n';
Die Ausgabe dieses Codes ist identisch mit der obigen. Die struct
wird weiterhin verwendet, um die Werte aus der Funktion zurückzugeben. Dadurch können Sie die Felder individuell bearbeiten.
Strukturierte Bindungen
C ++ 17 führt strukturierte Bindungen ein, die den Umgang mit mehreren Rückgabetypen noch einfacher machen, da Sie sich nicht auf std::tie()
oder manuelles Tupel-Auspacken verlassen müssen:
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';
}
Strukturierte Bindungen können standardmäßig mit std::pair
, std::tuple
und einem beliebigen Typ verwendet werden, dessen nicht statische Datenmitglieder entweder öffentliche direkte Mitglieder oder Mitglieder einer eindeutigen Basisklasse sind:
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;
Wenn Sie Ihren Typ "tupelartig" machen, arbeitet er automatisch mit Ihrem Typ. Ein tuple-like ist ein Typ mit den entsprechenden tuple_size
, tuple_element
und get
tuple_element
:
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");
}
}
das funktioniert jetzt:
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';
}
Verwenden eines Funktionsobjekt-Consumer
Wir können einen Verbraucher bereitstellen, der mit mehreren relevanten Werten aufgerufen wird:
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';
});
Dies wird als "Weiterleitungsstil" bezeichnet .
Sie können eine Funktion, die ein Tupel zurückgibt, in eine Continuous Passing-Style-Funktion anpassen:
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';
};
wobei komplexere Versionen in C ++ 14 oder C ++ 11 beschreibbar sind.
Mit std :: vector
Ein std::vector
kann nützlich sein, um eine dynamische Anzahl von Variablen desselben Typs zurückzugeben. Im folgenden Beispiel wird als Datentyp int
verwendet, aber ein std::vector
kann jeden beliebigen Typ enthalten, der trivial kopierbar ist:
#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;
}
Ausgabe-Iterator verwenden
Mehrere Werte desselben Typs können zurückgegeben werden, indem ein Ausgabe-Iterator an die Funktion übergeben wird. Dies ist besonders häufig bei generischen Funktionen (wie bei den Algorithmen der Standardbibliothek).
Beispiel:
template<typename Incrementable, typename OutputIterator>
void generate_sequence(Incrementable from, Incrementable to, OutputIterator output) {
for (Incrementable k = from; k != to; ++k)
*output++ = k;
}
Verwendungsbeispiel:
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}