Zwracanie wielu wartości z funkcji C ++

242

Czy istnieje preferowany sposób zwracania wielu wartości z funkcji C ++? Na przykład wyobraź sobie funkcję, która dzieli dwie liczby całkowite i zwraca zarówno iloraz, jak i resztę. Jednym ze sposobów, które często widzę, jest użycie parametrów odniesienia:

void divide(int dividend, int divisor, int& quotient, int& remainder);

Odmianą jest zwrócenie jednej wartości i przekazanie drugiej przez parametr referencyjny:

int divide(int dividend, int divisor, int& remainder);

Innym sposobem byłoby zadeklarowanie struktury zawierającej wszystkie wyniki i zwrócenie:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

Czy jeden z tych sposobów jest ogólnie preferowany, czy są też inne sugestie?

Edycja: w kodzie rzeczywistym mogą występować więcej niż dwa wyniki. Mogą być również różnego rodzaju.

Fred Larson
źródło

Odpowiedzi:

217

Do zwrócenia dwóch wartości używam std::pair(zwykle typedef'd). Powinieneś spojrzeć na boost::tuple(w C ++ 11 i nowszych, istnieje std::tuple) więcej niż dwa wyniki zwracane.

Po wprowadzeniu powiązania strukturalnego w C ++ 17 zwrot std::tuplepowinien prawdopodobnie stać się standardem akceptowanym.

Obrabować
źródło
12
+1 za krotkę. Należy pamiętać o konsekwencjach wydajnościowych dużych obiektów powracających w strukturze a przekazywanych przez odniesienie.
Marcin
12
Jeśli zamierzasz używać krotek, dlaczego nie użyć ich również do par. Dlaczego masz specjalny przypadek?
Ferruccio
4
Fred, tak boost :: tuple może to zrobić :)
Johannes Schaub - litb
46
W C ++ 11 możesz używać std::tuple.
Ferruccio,
14
Jeśli chcesz zaakceptować wiele wartości z funkcji, wygodnym sposobem na to jest skorzystanie z std::tie stackoverflow.com/a/2573822/502144
fdermishin
176

W C ++ 11 możesz:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return  std::make_tuple(dividend / divisor, dividend % divisor);
}

#include <iostream>

int main() {
    using namespace std;

    int quotient, remainder;

    tie(quotient, remainder) = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

W C ++ 17:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return  {dividend / divisor, dividend % divisor};
}

#include <iostream>

int main() {
    using namespace std;

    auto [quotient, remainder] = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

lub z structs:

auto divide(int dividend, int divisor) {
    struct result {int quotient; int remainder;};
    return result {dividend / divisor, dividend % divisor};
}

#include <iostream>

int main() {
    using namespace std;

    auto result = divide(14, 3);

    cout << result.quotient << ',' << result.remainder << endl;

    // or

    auto [quotient, remainder] = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}
pepper_chico
źródło
4
Mam jeden problem z funkcjami zwracającymi krotki. Powiedzmy, że powyższy prototyp funkcji znajduje się w nagłówku, a skąd mam wiedzieć, co oznaczają pierwsze i drugie zwrócone wartości bez zrozumienia definicji funkcji? iloraz-reszta lub reszta-iloraz.
Uchia Itachi,
7
@UchiaItachi Ta sama troska o parametry funkcji, możesz nadać im nazwy, ale język nawet tego nie wymusza, a nazwy parametrów nie mają wartości w witrynie wywoływania podczas czytania. Również przy jednym zwrocie masz tylko typ, ale posiadanie nazwy może być również przydatne, z krotkami po prostu podwajasz problem, więc imo, w języku brakuje tylko samookreślenia na kilka sposobów, nie tylko to.
pepper_chico
1
jak wyglądałby ostatni przykład, gdybym chciał jawnie określić typ zwrotu divide ()? Czy mam następnie zdefiniować wynik w innym miejscu, czy też mogę go zdefiniować bezpośrednio w specyfikacji typu zwrotu?
Slava
1
@Slava nie możesz zdefiniować typu bezpośrednio przy podpisie funkcji, musisz zadeklarować typ na zewnątrz i użyć go jako typu zwracanego, tak jak zwykle to robi (wystarczy przesunąć structlinię poza ciało funkcji i zastąpić autoreturn funkcji result.
pepper_chico
3
@pepper_chico Co zrobić, jeśli chcesz umieścić definicję funkcji dividew osobnym pliku CPP? Dostaję błąd error: use of ‘auto divide(int, int)’ before deduction of ‘auto’. Jak to rozwiązać?
Adriaan
123

Osobiście nie lubię zwracanych parametrów z wielu powodów:

  • w wywołaniu nie zawsze jest oczywiste, które parametry są wewnętrzne, a które zewnętrzne
  • generalnie musisz utworzyć zmienną lokalną, aby przechwycić wynik, podczas gdy zwracane wartości mogą być użyte w tekście (co może, ale nie musi być dobrym pomysłem, ale przynajmniej masz taką opcję)
  • wydaje mi się, że czystsze jest posiadanie funkcji „in door” i „out door” dla funkcji - wszystkie wejścia są tutaj, wszystkie wyjścia wychodzą
  • Lubię, aby moje listy argumentów były jak najkrótsze

Mam też pewne zastrzeżenia do techniki pary / krotki. Zasadniczo często nie ma naturalnego porządku wartości zwracanych. Skąd czytelnik kodu wie, czy wynik. Pierwszy jest ilorazem, czy resztą? I implementator może zmienić kolejność, co zepsuje istniejący kod. Jest to szczególnie podstępne, jeśli wartości są tego samego typu, aby nie był generowany błąd kompilatora ani ostrzeżenie. W rzeczywistości argumenty te dotyczą również parametrów zwracanych.

Oto kolejny przykład kodu, ten nieco mniej trywialny:

pair<double,double> calculateResultingVelocity(double windSpeed, double windAzimuth,
                                               double planeAirspeed, double planeCourse);

pair<double,double> result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.first << endl;
cout << result.second << endl;

Czy to drukuje prędkość i kurs, czy też kurs i prędkość? To nie jest oczywiste.

Porównaj z tym:

struct Velocity {
    double speed;
    double azimuth;
};
Velocity calculateResultingVelocity(double windSpeed, double windAzimuth,
                                    double planeAirspeed, double planeCourse);

Velocity result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.speed << endl;
cout << result.azimuth << endl;

Myślę, że to jest jaśniejsze.

Myślę więc, że moim pierwszym wyborem jest technika struct. Pomysł pary / krotki jest prawdopodobnie świetnym rozwiązaniem w niektórych przypadkach. Chciałbym uniknąć parametrów zwracanych, gdy to możliwe.

Fred Larson
źródło
1
Sugestia zadeklarowania structpolubienia Velocityjest miła. Jednak jedną z obaw jest to, że zanieczyszcza przestrzeń nazw. Podejrzewam, że w C ++ 11 structmoże mieć długą nazwę typu i można jej użyć auto result = calculateResultingVelocity(...).
Hugues,
5
+1. Funkcja powinna zwrócić jedną „rzecz”, a nie uporządkowaną „krotkę rzeczy”.
DevSolar
1
Wolę struktury zamiast std :: pairs / std :: tuples z powodów opisanych w tej odpowiedzi. Ale nie podoba mi się także „zanieczyszczenie” przestrzeni nazw. Idealnym rozwiązaniem dla mnie byłoby zwrócenie anonimowej struktury struct { int a, b; } my_func();. To może być stosowany jako to: auto result = my_func();. Ale C ++ nie pozwala na to: „nowe typy nie mogą być zdefiniowane w typie zwracanym”. Więc muszę tworzyć struktury takie jak struct my_func_result_t...
anton_rh
2
@anton_rh: C ++ 14 umożliwia zwracanie typów lokalnych za pomocą auto, więc auto result = my_func();jest łatwo dostępny.
ildjarn
4
Jakieś 15 lat temu, kiedy odkryliśmy ulepszenie, często używaliśmy krotki, ponieważ jest to całkiem przydatne. Nadgodziny doświadczyliśmy wady w zakresie czytelności, szczególnie w przypadku krotek tego samego typu (np. Krotka <double, double>; która z nich jest która). Dlatego ostatnio bardziej przyzwyczajamy się do wprowadzania małej struktury POD, w której przynajmniej nazwa zmiennej składowej wskazuje coś sensownego.
gast128
24
std::pair<int, int> divide(int dividend, int divisor)
{
   // :
   return std::make_pair(quotient, remainder);
}

std::pair<int, int> answer = divide(5,2);
 // answer.first == quotient
 // answer.second == remainder

std :: pair jest zasadniczo Twoim rozwiązaniem strukturalnym, ale już dla Ciebie zdefiniowanym i gotowym do dostosowania do dowolnych dwóch typów danych.

James Curran
źródło
3
To zadziała w moim prostym przykładzie. Zasadniczo mogą jednak zostać zwrócone więcej niż dwie wartości.
Fred Larson
5
Również nie dokumentuje się sam. Czy pamiętasz, który rejestr x86 jest resztą dla DIV?
Mark
1
@ Mark - Zgadzam się, że rozwiązania pozycyjne mogą być mniej konserwowalne. Możesz napotkać problem „permute and baffle”.
Fred Larson
16

Jest to całkowicie zależne od faktycznej funkcji i znaczenia wielu wartości oraz ich rozmiarów:

  • Jeśli są one powiązane jak w twoim przykładzie ułamkowym, wybrałbym instancję struct lub class.
  • Jeśli nie są tak naprawdę powiązane i nie można ich zgrupować w klasę / strukturę, być może powinieneś przeredagować swoją metodę na dwie.
  • W zależności od wielkości zwracanych wartości w pamięci, możesz chcieć zwrócić wskaźnik do instancji klasy lub struktury lub użyć parametrów odniesienia.
Stewart Johnson
źródło
1
Podoba mi się twoja odpowiedź, a twój ostatni punkt przypomina mi coś, co właśnie przeczytałem, że przekazywanie wartości stało się znacznie szybsze, w zależności od okoliczności, co czyni to bardziej skomplikowanym ... cpp-next.com/archive/2009/08/want-speed-pass -by-wartość
mędrzec
12

Rozwiązaniem OO w tym celu jest utworzenie klasy współczynnika. Nie wymagałoby to żadnego dodatkowego kodu (pozwoliłoby zaoszczędzić trochę), byłoby znacznie czystsze / jaśniejsze i dałoby ci dodatkowe refaktoryzacje pozwalające ci oczyścić kod również poza tą klasą.

Właściwie myślę, że ktoś zalecił zwrócenie struktury, która jest wystarczająco blisko, ale ukrywa zamiar, że musi to być w pełni przemyślana klasa z konstruktorem i kilkoma metodami, w rzeczywistości „metodą”, o której wspominałeś (jako zwracaniem para) najprawdopodobniej powinien być członkiem tej klasy zwracającym instancję samego siebie.

Wiem, że twój przykład był po prostu „Przykładem”, ale faktem jest, że jeśli twoja funkcja nie robi o wiele więcej niż jakakolwiek funkcja powinna robić, jeśli chcesz, aby zwracała wiele wartości, prawie na pewno brakuje obiektu.

Nie bój się tworzyć tych małych klas, aby wykonywać małe prace - to magia OO - w końcu rozkładasz ją, dopóki każda metoda nie będzie bardzo mała i prosta, a każda klasa mała i zrozumiała.

Kolejna rzecz, która powinna wskazywać, że coś było nie tak: w OO zasadniczo nie masz danych - OO nie polega na przekazywaniu danych, klasa musi wewnętrznie zarządzać swoimi własnymi danymi i manipulować nimi, wszelkie przekazywane dane (w tym akcesoria) jest znakiem, że może trzeba coś przemyśleć

Bill K.
źródło
10

Istnieje precedens dla zwracania struktur w standardzie C (a stąd C ++) z funkcjami div, ldiv(i, w C99, lldiv) z <stdlib.h>(lub <cstdlib>).

„Połączenie wartości zwracanej i parametrów zwracanych” jest zwykle najmniej czyste.

Posiadanie funkcji zwracającej status i zwracającej dane poprzez parametry zwracane jest sensowne w C; jest to mniej oczywiste w C ++, gdzie zamiast tego można użyć wyjątków do przekazywania informacji o awarii.

Jeśli są więcej niż dwie zwracane wartości, prawdopodobnie mechanizm podobny do struktury jest prawdopodobnie najlepszy.

Jonathan Leffler
źródło
10

W C ++ 17 możesz również zwrócić jedną lub więcej wartości, których nie można przenieść / których nie można skopiować (w niektórych przypadkach). Możliwość zwracania typów nieprzenośnych pochodzi z nowej optymalizacji gwarantowanej wartości zwrotu i ładnie komponuje się z agregatami oraz konstruktorami szablonowymi .

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};

// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;

auto f(){ return many{string(),5.7, unmovable()}; }; 

int main(){
   // in place construct x,y,z with a string, 5.7 and unmovable.
   auto [x,y,z] = f();
}

Ładna rzeczą jest to, że jest zagwarantowane, aby nie powodowały jakiegokolwiek kopiowania lub przenoszenia. Możesz także utworzyć przykładową manystrukturę variadic. Więcej szczegółów:

Zwracane agregaty variadic (struct) i składnia dla szablonu variadic C ++ 17 „przewodnik po dedukcji konstrukcji”

Johan Lundberg
źródło
6

Istnieje wiele sposobów zwracania wielu parametrów. Będę wyczerpany.

Użyj parametrów referencyjnych:

void foo( int& result, int& other_result );

użyj parametrów wskaźnika:

void foo( int* result, int* other_result );

co ma tę zaletę, że musisz to zrobić & witrynie wywołującej, prawdopodobnie ostrzegając ludzi, że jest to parametr sparametryzowany.

Napisz szablon i użyj go:

template<class T>
struct out {
  std::function<void(T)> target;
  out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
  out(std::optional<T>* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
  out(std::aligned_storage_t<sizeof(T), alignof(T)>* t):
    target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
  template<class...Args> // TODO: SFINAE enable_if test
  void emplace(Args&&...args) {
    target( T(std::forward<Args>(args)...) );
  }
  template<class X> // TODO: SFINAE enable_if test
  void operator=(X&&x){ emplace(std::forward<X>(x)); }
  template<class...Args> // TODO: SFINAE enable_if test
  void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
};

wtedy możemy zrobić:

void foo( out<int> result, out<int> other_result )

i wszystko jest dobrze. foonie jest już w stanie odczytać żadnej wartości przekazanej jako bonus.

Do budowy można wykorzystać inne sposoby definiowania miejsca, w którym można umieścić dane out. Na przykład wywołanie zwrotne, aby umieścić gdzieś różne rzeczy.

Możemy zwrócić strukturę:

struct foo_r { int result; int other_result; };
foo_r foo();

whick działa dobrze w każdej wersji C ++ i pozwala to również:

auto&&[result, other_result]=foo();

przy zerowym koszcie. Parametry nie mogą nawet zostać przeniesione dzięki gwarantowanemu elekcji.

Możemy zwrócić std::tuple:

std::tuple<int, int> foo();

co ma tę wadę, że parametry nie są nazwane. To pozwala:

auto&&[result, other_result]=foo();

także. Przed zamiast tego możemy zrobić:

int result, other_result;
std::tie(result, other_result) = foo();

co jest nieco bardziej niezręczne. Gwarantowane wybranie jednak tutaj nie działa.

Wchodząc na nieznane terytorium (i to jest po out<>!), Możemy użyć stylu przekazywania kontynuacji:

void foo( std::function<void(int result, int other_result)> );

a teraz dzwoniący wykonują:

foo( [&](int result, int other_result) {
  /* code */
} );

zaletą tego stylu jest to, że możesz zwrócić dowolną liczbę wartości (o jednolitym typie) bez konieczności zarządzania pamięcią:

void get_all_values( std::function<void(int)> value )

valuezwrotna można nazwać 500 chwile, kiedy get_all_values( [&](int value){} ).

Dla czystego szaleństwa możesz nawet użyć kontynuacji kontynuacji.

void foo( std::function<void(int, std::function<void(int)>)> result );

którego użycie wygląda następująco:

foo( [&](int result, auto&& other){ other([&](int other){
  /* code */
}) });

co pozwoliłoby na wiele relacji między resulti other.

Ponownie z wartościami Uniforn możemy to zrobić:

void foo( std::function< void(span<int>) > results )

tutaj nazywamy callback z szerokim zakresem wyników. Możemy to zrobić nawet wielokrotnie.

Korzystając z tego, możesz mieć funkcję, która skutecznie przekazuje megabajty danych bez dokonywania alokacji poza stos.

void foo( std::function< void(span<int>) > results ) {
  int local_buffer[1024];
  std::size_t used = 0;
  auto send_data=[&]{
    if (!used) return;
    results({ local_buffer, used });
    used = 0;
  };
  auto add_datum=[&](int x){
    local_buffer[used] = x;
    ++used;
    if (used == 1024) send_data();
  };
  auto add_data=[&](gsl::span<int const> xs) {
    for (auto x:xs) add_datum(x);
  };
  for (int i = 0; i < 7+(1<<20); ++i) {
    add_datum(i);
  }
  send_data(); // any leftover
}

Teraz std::functionjest to trochę trudne, ponieważ robilibyśmy to w środowiskach zero-narzutowych bez alokacji. Chcielibyśmy więc takiego, function_viewktóry nigdy nie przydziela.

Innym rozwiązaniem jest:

std::function<void(std::function<void(int result, int other_result)>)> foo(int input);

gdzie zamiast odbierać oddzwonienie i wywoływać je, foo zamiast tego zwraca funkcję, która odbiera oddzwonienie.

foo (7) ([&] (int wynik, int other_result) {/ * kod * /}); powoduje to przerwanie parametrów wyjściowych od parametrów wejściowych poprzez posiadanie oddzielnych nawiasów.

Z variant icoroutines, możesz stworzyć foogenerator wariantu typów zwracanych (lub po prostu typu zwracanych). Składnia nie jest jeszcze naprawiona, więc nie podam przykładów.

W świecie sygnałów i gniazd funkcja ujawniająca zestaw sygnałów:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

pozwala utworzyć fooasynchronizujący i rozgłaszający wynik po zakończeniu.

W dalszej części tej linii mamy różne techniki potokowe, w których funkcja nie robi nic, ale raczej organizuje połączenie danych w jakiś sposób, a działanie jest względnie niezależne.

foo( int_source )( int_dest1, int_dest2 );

wtedy ten kod nic nie robi , dopóki int_sourcenie zostaną podane liczby całkowite. Kiedy to zrobi int_dest1i int_dest2zacznij otrzymywać wyniki.

Jak - Adam Nevraumont
źródło
Ta odpowiedź zawiera więcej informacji niż inne odpowiedzi! w szczególności informacje o auto&&[result, other_result]=foo();funkcjach zwracających zarówno krotki, jak i struktury. Dzięki!
jjmontes,
Doceniam tę wyczerpującą odpowiedź, zwłaszcza, że ​​wciąż mam problemy z C ++ 11 i dlatego nie mogę korzystać z bardziej nowoczesnych rozwiązań proponowanych przez innych ludzi.
GuyGizmo,
5

Użyj struktury lub klasy jako wartości zwracanej. Korzystanie std::pairmoże na razie działać, ale

  1. jest nieelastyczny, jeśli później zdecydujesz, że chcesz zwrócić więcej informacji;
  2. z deklaracji funkcji w nagłówku nie jest jasne, co jest zwracane i w jakiej kolejności.

Zwrócenie struktury z samodokumentującymi nazwami zmiennych członków prawdopodobnie będzie mniej podatne na błędy dla każdego, kto używa twojej funkcji. Zakładając na chwilę mój kapelusz współpracownika, twoja divide_resultstruktura jest dla mnie, potencjalnego użytkownika twojej funkcji, łatwa do zrozumienia po 2 sekundach. Bałagan z parametrami wyjścia lub tajemniczymi parami i krotkami zajęłoby więcej czasu na odczytanie i może być używany nieprawidłowo. I najprawdopodobniej nawet po kilkukrotnym użyciu funkcji nadal nie będę pamiętał poprawnej kolejności argumentów.

Michel
źródło
4

Jeśli funkcja zwraca wartość przez odwołanie, kompilator nie może zapisać jej w rejestrze podczas wywoływania innych funkcji, ponieważ teoretycznie pierwsza funkcja może zapisać adres zmiennej przekazanej do niej w zmiennej globalnie dostępnej, a wszelkie funkcje wywoływane kolejno zmień go, aby kompilator (1) zapisał wartość z rejestrów z powrotem do pamięci przed wywołaniem innych funkcji i (2) ponownie ją odczytał, gdy będzie potrzebny z pamięci po każdym z takich wywołań.

Jeśli powrócisz przez odniesienie, optymalizacja twojego programu ucierpi

dmityugov
źródło
4

Tutaj piszę program, który zwraca wiele wartości (więcej niż dwie wartości) w c ++. Ten program jest wykonywalny w c ++ 14 (G ++ 4.9.2). program jest jak kalkulator.

#  include <tuple>
# include <iostream>

using namespace std; 

tuple < int,int,int,int,int >   cal(int n1, int n2)
{
    return  make_tuple(n1/n2,n1%n2,n1+n2,n1-n2,n1*n2);
}

int main()
{
    int qut,rer,add,sub,mul,a,b;
    cin>>a>>b;
    tie(qut,rer,add,sub,mul)=cal(a,b);
    cout << "quotient= "<<qut<<endl;
    cout << "remainder= "<<rer<<endl;
    cout << "addition= "<<add<<endl;
    cout << "subtraction= "<<sub<<endl;
    cout << "multiplication= "<<mul<<endl;
    return 0;
}

Możesz więc wyraźnie zrozumieć, że w ten sposób możesz zwrócić wiele wartości z funkcji. przy użyciu std :: pair można zwrócić tylko 2 wartości, a std :: tuple może zwrócić więcej niż dwie wartości.

PRAFULOWA ANAND
źródło
4
W C ++ 14 możesz także użyć autotypu return, calaby uczynić to jeszcze czystszym. (IMO).
sfjac,
3

Często używam out-vals w funkcjach takich jak ta, ponieważ trzymam się paradygmatu funkcji zwracającej kody sukcesu / błędów i lubię utrzymywać jednolitość.

John Dibling
źródło
2

Alternatywy obejmują tablice, generatory i odwrócenie kontroli , ale żadna z nich nie jest tutaj odpowiednia.

Niektóre (np. Microsoft w historycznym Win32) zwykle używają parametrów referencyjnych dla uproszczenia, ponieważ jest jasne, kto przydziela i jak będzie wyglądał na stosie, zmniejsza mnożenie struktur i pozwala na osobną wartość zwrotną dla sukcesu.

„Czyści” programiści preferują strukturę, zakładając, że jest to wartość funkcji (jak w tym przypadku), niż coś, co przypadkowo dotknęło funkcja. Jeśli miałbyś bardziej skomplikowaną procedurę lub coś ze stanem, prawdopodobnie użyłbyś referencji (zakładając, że masz powód nieużywania klasy).

znak
źródło
2

Powiedziałbym, że nie ma preferowanej metody, wszystko zależy od tego, co zrobisz z odpowiedzią. Jeśli wyniki będą używane razem w dalszym przetwarzaniu, wówczas struktury mają sens, jeśli nie, to raczej przekazują je jako indywidualne odwołania, chyba że funkcja będzie używana w instrukcji złożonej:

x = divide( x, y, z ) + divide( a, b, c );

Często wybieram przekazywanie „struktur” przez odniesienie na liście parametrów, zamiast narzutu na kopiowanie zwracania nowej struktury (ale to pocenie się drobiazgów).

void divide(int dividend, int divisor, Answer &ans)

Czy parametry są mylące? Parametr wysłany jako referencja sugeruje, że wartość się zmieni (w przeciwieństwie do stałej referencji). Rozsądne nazewnictwo również usuwa zamieszanie.

Patrick
źródło
1
Myślę, że to trochę mylące. Ktoś czytający kod, który go nazywa, widzi „dziel (a, b, c);”. Nic nie wskazuje na to, że c to outval, dopóki nie sprawdzą podpisu. Ale to jest ogólny strach przed niezmiennymi parametrami odniesienia, a nie konkretem do tego pytania.
Steve Jessop
2

Dlaczego nalegasz na funkcję z wieloma zwracanymi wartościami? Dzięki OOP możesz użyć klasy oferującej regularną funkcję z pojedynczą wartością zwracaną i dowolną liczbą dodatkowych „wartości zwracanych”, jak poniżej. Zaletą jest to, że dzwoniący ma możliwość spojrzenia na dodatkowe elementy danych, ale nie jest to wymagane. Jest to preferowana metoda w przypadku skomplikowanych połączeń z bazą danych lub połączeniami sieciowymi, w których może być potrzebnych wiele dodatkowych informacji zwrotnych w przypadku wystąpienia błędów.

Aby odpowiedzieć na twoje pierwotne pytanie, w tym przykładzie jest metoda zwrócenia ilorazu, czego może potrzebować większość dzwoniących, a dodatkowo po wywołaniu metody resztę można uzyskać jako element danych.

class div{
   public:
      int remainder;

      int quotient(int dividend, int divisor){
         remainder = ...;
         return ...;
      }
};
Roland
źródło
1
Myślę, że są przypadki, w których jest to nieefektywne. Np. Masz pojedynczą pętlę for, która generuje kilka zwracanych wartości. Jeśli podzielisz te wartości na osobne funkcje, będziesz musiał uruchomić pętlę raz dla każdej wartości.
jiggunjer
1
@ jiggunjer Możesz raz uruchomić pętlę i zapisać kilka zwracanych wartości w osobnych elementach danych klasy. Podkreśla to elastyczność koncepcji OOP.
Roland
2

zamiast zwracać wiele wartości, po prostu zwróć jedną z nich i odwołaj się do innych w wymaganej funkcji, na przykład:

int divide(int a,int b,int quo,int &rem)
Anchit Rana
źródło
Czy nie wspomniałem o tym w samym pytaniu? Zobacz także moje zastrzeżenia w mojej odpowiedzi .
Fred Larson
1

Krotka zwiększająca byłaby moim preferowanym wyborem dla uogólnionego systemu zwracania więcej niż jednej wartości z funkcji.

Możliwy przykład:

include "boost/tuple/tuple.hpp"

tuple <int,int> divide( int dividend,int divisor ) 

{
  return make_tuple(dividend / divisor,dividend % divisor )
}
AndyUK
źródło
1

Możemy zadeklarować funkcję tak, aby zwracała do niej zmienną zdefiniowaną przez użytkownika typu struktury lub wskaźnik. A dzięki właściwości struktury wiemy, że struktura w C może przechowywać wiele wartości typów asymetrycznych (tj. Jedną zmienną int, cztery zmienne char, dwie zmienne zmiennoprzecinkowe itd.)

Rohit Hajare
źródło
1

Zrobiłbym to po prostu przez odniesienie, jeśli to tylko kilka zwracanych wartości, ale w przypadku bardziej złożonych typów możesz to po prostu zrobić w następujący sposób:

static struct SomeReturnType {int a,b,c; string str;} SomeFunction()
{
  return {1,2,3,string("hello world")}; // make sure you return values in the right order!
}

użyj „statyczny”, aby ograniczyć zakres typu zwracanego do tej jednostki kompilacji, jeśli ma to być tylko tymczasowy typ zwracany.

 SomeReturnType st = SomeFunction();
 cout << "a "   << st.a << endl;
 cout << "b "   << st.b << endl;
 cout << "c "   << st.c << endl;
 cout << "str " << st.str << endl;

To zdecydowanie nie jest najładniejszy sposób na zrobienie tego, ale zadziała.

Carsten
źródło
-2

Oto pełny przykład tego rodzaju rozwiązania problemu

#include <bits/stdc++.h>
using namespace std;
pair<int,int> solve(int brr[],int n)
{
    sort(brr,brr+n);

    return {brr[0],brr[n-1]};
}

int main()
{
    int n;
    cin >> n;
    int arr[n];
    for(int i=0; i<n; i++)
    {
        cin >> arr[i];
    }

    pair<int,int> o=solve(arr,n);
    cout << o.first << " " << o.second << endl;

    return 0;
}
Shaonsani
źródło