C ++ işlevinden birden çok değer döndürme


242

C ++ işlevinden birden çok değer döndürmenin tercih edilen bir yolu var mı? Örneğin, iki tamsayıyı bölen ve hem bölümü hem de geri kalanını döndüren bir fonksiyon düşünün. Yaygın olarak gördüğüm bir yol, referans parametreleri kullanmaktır:

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

Bir varyasyon bir değeri döndürüp diğerini referans parametresinden geçirmektir:

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

Başka bir yol, tüm sonuçları içerecek bir yapı beyan etmek ve aşağıdakileri döndürmek olacaktır:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

Bu yollardan biri genel olarak mı tercih ediliyor, yoksa başka öneriler var mı?

Düzenleme: Gerçek dünya kodunda, ikiden fazla sonuç olabilir. Farklı tiplerde olabilirler.

Yanıtlar:


217

İki değer döndürmek için bir std::pair(genellikle typedef'd) kullanıyorum. boost::tupleİkiden std::tuplefazla sonuç için (C ++ 11 ve daha yeni sürümlerde) konusuna bakmalısınız .

C ++ 17'de yapılandırılmış bağlamanın getirilmesiyle geri dönüş std::tuplemuhtemelen kabul edilebilir bir standart haline gelmelidir.


12
Demet için +1. Bir yapıya geri dönen veya referansla geçen büyük nesnelerin performans sonuçlarını aklınızda bulundurun.
Marcin

12
Eğer tuples kullanacaksanız, neden çiftler için kullanmıyorsunuz? Neden özel bir durum var?
Ferruccio

4
Fred, yes boost :: tuple bunu yapabilir :)
Johannes Schaub - litb

46
C ++ 11'de kullanabilirsiniz std::tuple.
Ferruccio

14
Bir işlevden birden fazla değeri kabul etmek istiyorsanız, bunu yapmanın uygun bir yolu std::tie stackoverflow.com/a/2573822/502144
fdermishin

176

C ++ 11'de şunları yapabilirsiniz:

#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;
}

C ++ 17'de:

#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;
}

veya yapılar ile:

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;
}

4
Ben tuples dönen fonksiyonları ile bir endişe var. Yukarıdaki işlev prototipinin başlıkta olduğunu varsayalım, o zaman işlev tanımını anlamadan ilk ve ikinci döndürülen değerlerin ne anlama geldiğini nasıl bilebilirim? bölüm-kalan veya kalan bölüm.
Uchia Itachi

7
@UchiaItachi Fonksiyon parametreleri için aynı endişe, onlara isim verebilirsiniz, ancak dil bunu zorlamıyor bile ve parametre adları okuma sırasında çağrı sitesinde hiçbir değere sahip değil. Ayrıca, tek bir dönüşte, sadece bir türünüz var, ancak isme sahip olmak da yararlı olabilir, tuples ile sorunu sadece iki katına çıkarırsınız, bu yüzden imo, dil sadece bu değil, çeşitli şekillerde kendi kendine belgelenmekten yoksundur.
pepper_chico

1
divide () dönüş türünü açıkça belirtmek istersem son örnek nasıl görünürdü? Sonrasında başka bir yerde sonuç tanımlayacak mıyım yoksa sonuç türü dönüş belirtiminde doğru tanımlayabilir miyim?
Slava

1
@Slava, işlev imzasında bir tür tanımlayamazsınız, türü dışarıda bildirmeniz ve normalde yapıldığı gibi dönüş türü olarak kullanmanız gerekir ( structsatırı işlev gövdesinin dışına taşıyın ve autoişlev dönüşünü değiştirin result.
pepper_chico

3
@pepper_chico İşlev tanımını divideayrı bir cpp dosyasına koymak istersen ne olur ? Hatayı alıyorum error: use of ‘auto divide(int, int)’ before deduction of ‘auto’. Bunu nasıl çözerim?
Adriaan

123

Şahsen, genellikle bir dizi nedenden dolayı dönüş parametrelerini sevmiyorum:

  • çağırmada hangi parametrelerin ins hangilerinin hangileri olduğu açık değildir
  • sonucu yakalamak için genellikle yerel bir değişken oluşturmanız gerekir, ancak geri dönüş değerleri satır içinde kullanılabilir (bu iyi bir fikir olabilir veya olmayabilir, ancak en azından seçeneğiniz vardır)
  • bir "kapı" ve bir işlev için "dışarı kapı" olması daha temiz görünüyor - tüm girdiler buraya giriyor, tüm çıkışlar oraya çıkıyor
  • Argüman listelerimi olabildiğince kısa tutmayı seviyorum

Ayrıca çift / tuple tekniği hakkında bazı çekinceleri var. Temel olarak, dönüş değerlerine genellikle doğal bir düzen yoktur. Kod okuyucunun sonuç sonucunun ilk bölüm veya kalan bölüm olup olmadığını nasıl bilebilir? Uygulayıcı, mevcut kodu bozacak sırayı değiştirebilir. Değerler aynı türdeyse, özellikle derleyici hatası veya uyarısının oluşmaması için bu durum sinsidir. Aslında, bu argümanlar dönüş parametreleri için de geçerlidir.

İşte başka bir kod örneği, bu biraz daha az önemsiz:

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;

Bu, zemin hızını ve rota veya rota ve zemin hızını yazdırıyor mu? Belli değil.

Bununla karşılaştır:

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;

Bunun daha net olduğunu düşünüyorum.

Bu yüzden genel olarak ilk tercihimin yapı tekniği olduğunu düşünüyorum. Pair / tuple fikri bazı durumlarda büyük bir çözüm olabilir. Mümkün olduğunda dönüş parametrelerinden kaçınmak istiyorum.


1
Bir structbenzeri ilan etmek için öneri Velocitygüzel. Ancak bir endişe, isim alanını kirletmesidir. C ++ 11 ile, structuzun bir tür adı olabilir ve bir kullanabilirsiniz varsayalım auto result = calculateResultingVelocity(...).
Hugues

5
+1. Bir işlev , bir şekilde sıralı "şeylerin" değil, bir "şey" döndürmelidir .
DevSolar

1
Bu cevapta açıklanan nedenlerden dolayı std :: pairs / std :: tuples yerine yapıları tercih ediyorum. Ama "kirlilik" ad alanını da sevmiyorum. Benim için ideal çözüm, anonim bir yapı gibi dönmek olacaktır struct { int a, b; } my_func();. Bu, bu şekilde kullanılabilir: auto result = my_func();. Ancak C ++ buna izin vermez: "yeni türler bir dönüş türünde tanımlanamayabilir". Bu yüzden,struct my_func_result_t ...
anton_rh

2
@anton_rh: C ++ 14, yerel türlerin auto , bu yüzden auto result = my_func();önemsiz bir şekilde elde edilebilir.
17'de ildjarn

4
Yaklaşık 15 yıl önce takviyeyi keşfettiğimizde oldukça kullanışlı olduğu için tuple'i çok kullandık. Fazla mesai dezavantajını, özellikle aynı tipteki tupllerde (örneğin tuple <double, double>; hangisinin hangisi olduğu) okunabilirlikte yaşadık. Son zamanlarda, en azından üye değişkenin adının mantıklı bir şeyi işaret ettiği küçük bir POD yapısı oluşturma alışkanlığı içindeyiz.
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 aslında sizin yapısal çözümünüzdür, ancak sizin için önceden tanımlanmıştır ve herhangi iki veri türüne uyum sağlamaya hazırdır.


3
Bu benim basit örneğim için işe yarayacak. Bununla birlikte, genel olarak, ikiden fazla değer döndürülebilir.
Fred Larson

5
Ayrıca kendi kendini belgeleme. DIV için kalan x86 kaydının kalanını hatırlıyor musunuz?
Mark

1
@Mark - Konumsal çözümlerin daha az sürdürülebilir olabileceğini kabul ediyorum. "İzin verme ve bölme" sorunuyla karşılaşabilirsiniz.
Fred Larson

16

Tamamen gerçek fonksiyona ve çoklu değerlerin anlamlarına ve boyutlarına bağlıdır:

  • Eğer kesir örneğinizdeki gibi ise, ben bir yapı veya sınıf örneği ile gider.
  • Eğer gerçekten ilgili değiller ve bir sınıf / yapı içinde gruplandırılamazlarsa, belki de yönteminizi ikiye ayırmalısınız.
  • Döndürdüğünüz değerlerin bellek içi boyutuna bağlı olarak, bir sınıf örneğine veya yapıya bir işaretçi döndürmek veya referans parametreleri kullanmak isteyebilirsiniz.

1
Cevabınızı beğendim ve son merminiz bana, sadece daha karmaşık hale getiren koşullara bağlı olarak değere göre geçmenin çok daha hızlı hale geldiğini okuduğum bir şeyi hatırlatıyor ... cpp-next.com/archive/2009/08/want-speed-pass -by-value
sage

12

Bunun için OO çözümü bir oran sınıfı oluşturmaktır. Herhangi bir ekstra kod almaz (bazılarını kaydeder), önemli ölçüde daha temiz / net olur ve bu sınıfın dışındaki kodu da temizlemenizi sağlayan bazı ekstra refactorings verir.

Aslında birisinin bir yapıya dönmeyi önerdiğini düşünüyorum, ki bu yeterince yakın ama bunun yapıcı ile tamamen düşünülmüş bir sınıf olması gerektiğini ve birkaç yöntem, aslında, aslında bahsettiğiniz "yöntemi" pair) büyük olasılıkla kendisinin bir örneğini döndüren bu sınıfın bir üyesi olmalıdır.

Örneğin sadece bir "Örnek" olduğunu biliyorum, ama gerçek şu ki, eğer fonksiyonunuz herhangi bir fonksiyondan daha fazla bir şey yapmıyorsa, birden fazla değer döndürmek istiyorsanız, neredeyse kesinlikle bir nesneyi kaçırıyorsunuzdur.

Küçük işler yapmak için bu küçük sınıfları oluşturmaktan korkmayın - bu OO'nun büyüsüdür - her yöntem çok küçük ve basit ve her sınıf küçük ve anlaşılır olana kadar onu parçalara ayırırsınız.

Bir şeyin yanlış olduğuna dair bir gösterge olması gereken başka bir şey: OO'da aslında hiçbir veriniz yok - OO veriyi iletmekle ilgili değil, bir sınıfın kendi verilerini dahili olarak yönetmesi ve işlemesi gerekiyor, herhangi bir veri (erişimciler dahil) bir şeyi yeniden düşünmeniz gerekebileceğinin bir işaretidir ..


10

C yapıları dönen (ve dolayısıyla C ++) ile standart için örnek vardır div, ldiv(C99 ve lldivgelen) işlevlerini <stdlib.h>(veya <cstdlib>).

'Dönüş değeri ve dönüş parametreleri karışımı' genellikle en az temiz olanıdır.

Bir fonksiyonun bir durum döndürmesi ve dönüş parametreleri aracılığıyla veri döndürmesi C'de mantıklıdır; bunun yerine hata bilgilerini aktarmak için istisnaları kullanabileceğiniz C ++ 'da daha az açıktır.

İkiden fazla dönüş değeri varsa, yapı benzeri bir mekanizma muhtemelen en iyisidir.


10

C ++ 17 ile ayrıca bir veya daha fazla taşınmaz / taşınamaz değer döndürebilirsiniz (bazı durumlarda). Taşınamaz tiplerin iade edilmesi olasılığı, yeni garantili iade değeri optimizasyonu ile gelir ve agregalarla ve şablonlanmış kurucular olarak adlandırılabilecek şeylerle güzel bir şekilde birleşir .

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();
}

Bununla ilgili güzel olan şey, herhangi bir kopyalamaya veya taşımaya neden olmamasının garanti edilmesidir . Örnek manyyapı değişkenini de yapabilirsiniz. Daha fazla detay:

C ++ 17 varyasyon şablonu 'inşaat kesinti kılavuzu' için varyasyon toplamaları (yapı) ve sözdizimi döndürme


6

Birden çok parametreyi döndürmenin birçok yolu vardır. Ben exhastive olacağım.

Referans parametrelerini kullanın:

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

işaretçi parametrelerini kullanın:

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

Bu, &çağrı sitesinde bir yapmanız gereken avantaja sahiptir , muhtemelen insanları dışarı parametresi olarak uyarır.

Bir şablon yazın ve kullanın:

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)...); }
};

o zaman yapabiliriz:

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

ve her şey yolunda. fooartık bonus olarak iletilen hiçbir değeri okuyamaz.

Verileri koyabileceğiniz bir noktayı tanımlamanın diğer yolları oluşturmak için kullanılabilir out. Örneğin, bir yerlerde bir şeyleri yerleştirmek için geri arama.

Bir yapıyı iade edebiliriz:

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

whick her C ++ sürümünde ve bu ayrıca aşağıdakilere de izin verir:

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

sıfır maliyetle. Parametreler, garantili seçim sayesinde taşınamaz bile.

Biz dönebilir std::tuple:

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

hangi parametreler adlandırılmamış dezavantajı vardır. Bu izin verir:

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

de. Önce bunun yerine şunları yapabiliriz:

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

ki bu biraz daha garip. Ancak garantili seçim burada işe yaramıyor.

Yabancı bir bölgeye (ve bundan sonra out<>!) Gidip, devam geçiş stilini kullanabiliriz:

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

ve şimdi arayanlar:

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

bu stilin bir avantajı, belleği yönetmek zorunda kalmadan rastgele sayıda değer (tek tip tipte) döndürebilmenizdir:

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

valueGeri arama yaparken 500 kez sen denilebilir get_all_values( [&](int value){} ).

Saf delilik için, devamında bir süreklilik bile kullanabilirsiniz.

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

kullanımı şöyle:

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

resultve arasında birden fazla ilişkiye izin verir other.

Yine Kaliforniya değerleriyle bunu yapabiliriz:

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

burada geri aramayı bir sonuç aralığı ile çağırıyoruz. Bunu tekrar tekrar bile yapabiliriz.

Bunu kullanarak, yığından herhangi bir ayırma yapmadan megabaytlık verileri verimli bir şekilde ileten bir işleve sahip olabilirsiniz.

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
}

Şimdi, std::functionbunun için sıfır yükü olmayan tahsisatsız ortamlarda yapacağımız için biraz ağır. Bu yüzden function_viewasla ayırmayan bir tane istiyoruz .

Başka bir çözüm:

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

burada geri çağrıyı alıp çağırmak fooyerine, geri çağrıyı alan bir işlev döndürür.

foo (7) ([&] (int sonucu, int other_result) {/ * kod * /}); bu, ayrı köşeli ayraçlar kullanarak çıkış parametrelerini giriş parametrelerinden ayırır.

İle variantveroutinler, foodönüş türlerinin (veya yalnızca dönüş türünün) bir varyantını oluşturabilirsiniz. Sözdizimi henüz düzeltilmedi, bu yüzden örnek vermeyeceğim.

Sinyaller ve yuvalar dünyasında, bir dizi sinyali ortaya çıkaran bir işlev:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

oluşturmanıza izin verir foozaman uyumsuz çalışan ve sonuç bittiğinde sonucu yayınlayan .

Bu çizgide, bir fonksiyonun bir şey yapmadığı, ancak verilerin bir şekilde bağlanmasını düzenlediği ve işin nispeten bağımsız olduğu çeşitli boru hattı tekniklerimiz var.

foo( int_source )( int_dest1, int_dest2 );

o zaman bu kod değil yapmak kadar bir şey int_sourcebunu sağlamak için tamsayılar vardır. Ne zaman yapar int_dest1ve int_dest2sonuçları almaya başlayın.


Bu cevap diğer cevaplardan daha fazla bilgi içeriyor! özellikle, auto&&[result, other_result]=foo();hem tuple hem de yapı döndüren fonksiyonlar hakkında bilgi . Teşekkürler!
jjmontes

Bu kapsamlı cevabı takdir ediyorum, özellikle de hala C ++ 11 ile sıkışıp kaldım ve bu nedenle diğer insanların önerdiği daha modern çözümlerden bazılarını kullanamıyorum.
GuyGizmo

5

Dönüş değeri için bir yapı veya sınıf kullanın. Kullanmak std::pairşimdilik işe yarayabilir, ancak

  1. daha sonra daha fazla bilgi döndürmek istediğinize karar verirseniz esnek değildir;
  2. işlevin başlıktaki beyanından ne döndürüleceği ve hangi sırada döndürüldüğü çok açık değildir.

Kendini belgeleyen üye değişken adlarına sahip bir yapı döndürmek, işlevinizi kullanan herkes için hataya daha az eğilimli olacaktır. İş arkadaşımın şapkasını bir süreliğine taktığınızda, divide_result2 saniye sonra yapınızın potansiyel bir kullanıcısı olan yapınız benim için kolaydır. Çıkış parametreleri veya gizemli çiftler ve tuples ile uğraşmak okumak için daha fazla zaman alacaktır ve yanlış kullanılabilir. Ve büyük olasılıkla işlevi birkaç kez kullandıktan sonra bile, argümanların doğru sırasını hala hatırlamıyorum.


4

İşleviniz başvuru yoluyla bir değer döndürürse, derleyici diğer işlevleri çağırırken bu değeri bir kayıt defterinde saklayamaz çünkü teorik olarak, ilk işlev kendisine erişilebilen değişkenin adresini genel olarak erişilebilir bir değişkene kaydedebilir ve alt adlandırılmış işlevler derleyicinin (1) diğer işlevleri çağırmadan önce (1) değeri kayıtlardan belleğe kaydetmesi ve (2) bu tür aramalardan herhangi birinden sonra tekrar bellekten gerektiğinde yeniden okuması gerekir.

Referans olarak geri dönerseniz, programınızın optimizasyonu zarar görür


4

Burada, c ++ birden fazla değer (ikiden fazla değer) döndüren bir program yazıyorum. Bu program c ++ 14 (G ++ 4.9.2) içinde yürütülebilir. program bir hesap makinesi gibidir.

#  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;
}

Böylece, bu şekilde bir işlevden birden fazla değer döndürebileceğinizi açıkça anlayabilirsiniz. std :: pair kullanarak sadece 2 değer döndürülebilirken, std :: tuple ikiden fazla değer döndürebilir.


4
C ++ 14 ile bunu daha da temiz hale getirmek için autodönüş türünü de kullanabilirsiniz cal. (IMO).
sfjac

3

Bu gibi işlevlerde out-vals kullanma eğilimindeyim, çünkü başarı / hata kodlarını döndüren bir fonksiyonun paradigmasına sadık kalıyorum ve her şeyi aynı tutmayı seviyorum.


2

Alternatifler arasında diziler, jeneratörler ve kontrolün ters çevrilmesi vardır , ancak burada hiçbiri uygun değildir.

Bazıları (örneğin, tarihsel Win32'deki Microsoft), sadelik için referans parametreleri kullanma eğilimindedir, çünkü yığını kimin tahsis edeceği ve yığına nasıl bakacağı, yapıların çoğalmasını azalttığı ve başarı için ayrı bir dönüş değerine izin verdiği açıktır.

"Saf" programcılar bunu varsayarak, yapı tercih olduğunu (vaka burada olduğu gibi) ziyade en işlevi tarafından tesadüfen dokundu o şeye daha, fonksiyon değeri. Daha karmaşık bir yordamınız veya durumunuz olan bir şey varsa, muhtemelen referanslar kullanırsınız (sınıf kullanmamanız için bir nedeniniz olduğu varsayılarak).


2

Tercih edilen bir yöntem olmadığını söyleyebilirim, hepsi yanıtla ne yapacağınıza bağlı. Sonuçlar daha sonraki işlemlerde birlikte kullanılacaksa, yapılar mantıklıdır, eğer değilse, bileşik kompozit ifadede kullanılmayacaksa, o zaman bireysel referanslar olarak geçme eğilimindeyim:

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

Sık sık yeni bir yapı döndürerek ek yükü geçmek yerine parametre listesinde referansla 'yapıları' geçmeyi tercih ederim (ama bu küçük şeyleri terliyor).

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

Out parametreleri kafa karıştırıcı mı? Referans olarak gönderilen bir parametre, değerin değişeceğini (sabit referansın aksine) önermektedir. Hassas adlandırma da karışıklığı ortadan kaldırır.


1
Bence biraz kafa karıştırıcı. Kodu çağıran biri "böl (a, b, c);" görür. İmzayı görene kadar c'nin bir çıkış olduğuna dair hiçbir belirti yoktur. Ancak bu, bu soruya özgü olmaktan ziyade, sabit olmayan referans parametrelerinin genel bir korkusu.
Steve Jessop

2

Neden birden fazla dönüş değeri olan bir işlev üzerinde ısrar ediyorsunuz? OOP ile, tek bir dönüş değeri olan normal bir işlev ve aşağıdaki gibi herhangi bir sayıda ek "dönüş değeri" sunan bir sınıf kullanabilirsiniz. Avantajı, arayanın ekstra veri üyelerine bakma seçeneğine sahip olması, ancak bunu yapmak zorunda olmamasıdır. Bu, karmaşık veri tabanı veya ağ aramaları için tercih edilen yöntemdir, burada hataların ortaya çıkması durumunda birçok ek dönüş bilgisi gerekebilir.

Orijinal sorunuzu cevaplamak için bu örnekte, çoğu çağrının ihtiyaç duyabileceği bölümü döndürmek için bir yöntem vardır ve ek olarak, yöntem çağrısından sonra geri kalanını bir veri üyesi olarak alabilirsiniz.

class div{
   public:
      int remainder;

      int quotient(int dividend, int divisor){
         remainder = ...;
         return ...;
      }
};

1
Bunun verimsiz olduğu durumlar olduğunu düşünüyorum. Örneğin, birkaç dönüş değeri üreten tek bir for döngüsü var. Bu değerleri ayrı işlevlere ayırırsanız, her değer için döngüden bir kez geçmeniz gerekir.
jiggunjer

1
@jiggunjer Döngüyü bir kez çalıştırabilir ve birkaç dönüş değerini ayrı sınıf veri üyelerinde saklayabilirsiniz. Bu, OOP konseptinin esnekliğinin altını çizmektedir.
Roland

2

birden çok değer döndürmek yerine, bunlardan birini döndürmeniz ve gerekli işlevde başkalarına referans vermeniz yeterlidir:

int divide(int a,int b,int quo,int &rem)

Sorunun kendisinde bundan bahsetmedim mi? Ayrıca benim itirazları bakın Cevabıma .
Fred Larson

1

Takviye grubu, bir fonksiyondan birden fazla değer döndüren genelleştirilmiş bir sistem için tercih edilen seçimim olacaktır.

Olası örnek:

include "boost/tuple/tuple.hpp"

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

{
  return make_tuple(dividend / divisor,dividend % divisor )
}

1

İşlevi, kullanıcı tanımlı bir yapı tipi değişkeni veya bir işaretçi döndürecek şekilde bildirebiliriz. Ve bir yapının özelliğine göre, C'deki bir yapının birden fazla asimetrik tip değeri tutabildiğini biliyoruz (yani bir int değişkeni, dört karakter değişkeni, iki değişken değişken vb.)


1

Sadece birkaç dönüş değeri varsa bunu referans olarak yaparım ama daha karmaşık türleri için de sadece böyle yapabilirsiniz:

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!
}

yalnızca geçici bir dönüş türü olması durumunda dönüş türünün kapsamını bu derleme birimiyle sınırlamak için "statik" kullanın.

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

Bu kesinlikle bunu yapmanın en güzel yolu değil ama işe yarayacak.


-2

İşte bu tür bir sorun çözümünün tam bir örneği

#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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.