Trả về nhiều giá trị từ hàm C ++


242

Có cách nào ưa thích để trả về nhiều giá trị từ hàm C ++ không? Ví dụ, hãy tưởng tượng một hàm chia hai số nguyên và trả về cả thương và phần còn lại. Một cách tôi thường thấy là sử dụng các tham số tham chiếu:

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

Một biến thể là trả về một giá trị và chuyển giá trị kia qua tham số tham chiếu:

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

Một cách khác là khai báo một cấu trúc để chứa tất cả các kết quả và trả về rằng:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

Là một trong những cách này thường được ưa thích, hoặc có những gợi ý khác?

Chỉnh sửa: Trong mã thế giới thực, có thể có nhiều hơn hai kết quả. Họ cũng có thể có nhiều loại khác nhau.

Câu trả lời:


216

Để trả về hai giá trị, tôi sử dụng một std::pair(thường là typedef'd). Bạn nên xem boost::tuple(trong C ++ 11 và mới hơn, có std::tuple) để biết nhiều hơn hai kết quả trả về.

Với việc giới thiệu ràng buộc có cấu trúc trong C ++ 17, việc trả về std::tuplecó lẽ sẽ trở thành tiêu chuẩn được chấp nhận.


12
+1 cho bộ dữ liệu. Hãy ghi nhớ sự phân nhánh hiệu suất của các đối tượng lớn trở lại trong một cấu trúc so với chuyển qua tham chiếu.
Marcin

12
Nếu bạn sẽ sử dụng bộ dữ liệu, tại sao không sử dụng chúng cho các cặp. Tại sao có trường hợp đặc biệt?
Ferruccio

4
Fred, vâng boost :: tuple có thể làm điều đó :)
Johannes Schaub - litb

46
Trong C ++ 11, bạn có thể sử dụng std::tuple.
Ferruccio

14
Nếu bạn muốn chấp nhận nhiều giá trị từ một hàm, một cách thuận tiện để thực hiện việc này là sử dụng std::tie stackoverflow.com/a/2573822/502144
fdermishin

175

Trong C ++ 11, bạn có thể:

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

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

hoặc với các cấu trúc:

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
Tôi có một mối quan tâm với các chức năng trả về bộ dữ liệu. Nói nguyên mẫu hàm trên nằm trong một tiêu đề, vậy làm thế nào để tôi biết giá trị trả về thứ nhất và thứ hai có nghĩa là gì mà không hiểu định nghĩa hàm? thương số còn lại hoặc còn lại
Uchia Itachi

7
@UchiaItachi Cùng mối quan tâm đối với các tham số chức năng, bạn có thể đặt tên cho chúng, nhưng ngôn ngữ thậm chí không thực thi điều đó và tên tham số không có giá trị tại trang web cuộc gọi khi đọc. Ngoài ra, trong một lần trả lại, bạn chỉ có một loại, nhưng có tên cũng có thể hữu ích, với bộ dữ liệu bạn chỉ cần tăng gấp đôi vấn đề, vì vậy imo, ngôn ngữ chỉ thiếu về việc tự viết tài liệu theo nhiều cách, không chỉ điều này.
pepper_chico

1
ví dụ cuối cùng trông như thế nào nếu tôi muốn chỉ định rõ ràng kiểu trả về của split ()? Sau đó tôi sẽ xác định kết quả ở một nơi khác, hoặc tôi có thể định nghĩa nó đúng trong đặc tả kiểu trả về?
Slava

1
@Slava bạn không thể xác định một loại ngay tại chữ ký hàm, bạn sẽ phải khai báo loại bên ngoài và sử dụng nó làm loại trả về, như thường được thực hiện (chỉ cần di chuyển structdòng bên ngoài thân hàm và thay thế autohàm trả về bằng result.
pepper_chico

3
@pepper_chico Nếu muốn đặt định nghĩa hàm dividevào một tệp cpp riêng thì sao? Tôi nhận được lỗi error: use of ‘auto divide(int, int)’ before deduction of ‘auto’. Tôi giải quyết điều này như thế nào?
Adriaan

123

Cá nhân, tôi thường không thích các tham số trả về vì một số lý do:

  • không phải lúc nào cũng rõ ràng trong lời gọi tham số nào là tham số và bên ngoài
  • bạn thường phải tạo một biến cục bộ để bắt kết quả, trong khi các giá trị trả về có thể được sử dụng nội tuyến (có thể là một ý tưởng hay, nhưng ít nhất bạn có tùy chọn)
  • đối với tôi có vẻ như "cửa trong" và "cửa ngoài" cho một chức năng - tất cả các yếu tố đầu vào đều ở đây, tất cả các đầu ra đều xuất hiện ở đó
  • Tôi muốn giữ danh sách đối số của mình càng ngắn càng tốt

Tôi cũng có một số bảo lưu về kỹ thuật cặp / tuple. Chủ yếu, thường không có thứ tự tự nhiên cho các giá trị trả về. Làm thế nào là người đọc mã để biết liệu result.first là thương số hay phần còn lại? Và người thực hiện có thể thay đổi thứ tự, sẽ phá vỡ mã hiện có. Điều này đặc biệt xảo quyệt nếu các giá trị cùng loại để không tạo ra lỗi hoặc cảnh báo trình biên dịch. Trên thực tế, các đối số này áp dụng để trả về các tham số là tốt.

Đây là một ví dụ mã khác, cái này ít tầm thường hơn một chút:

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;

Điều này có in nền tảng và khóa học, hoặc khóa học và tốc độ? Nó không rõ ràng.

So sánh với điều này:

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;

Tôi nghĩ rằng điều này là rõ ràng hơn.

Vì vậy, tôi nghĩ rằng sự lựa chọn đầu tiên của tôi nói chung là kỹ thuật struct. Ý tưởng cặp / tuple có thể là một giải pháp tuyệt vời trong một số trường hợp nhất định. Tôi muốn tránh các tham số trả về khi có thể.


1
Gợi ý để tuyên bố một lượt structthích Velocitylà một điều tốt đẹp. Tuy nhiên, một mối quan tâm là nó gây ô nhiễm không gian tên. Tôi cho rằng với C ++ 11, structcó thể có một tên loại dài và người ta có thể sử dụng auto result = calculateResultingVelocity(...).
Hugues

5
+1. Một hàm sẽ trả về một "thứ", không phải là "bộ thứ" được sắp xếp theo cách nào đó.
DevSolar

1
Tôi thích cấu trúc hơn std :: cặp / std :: tuples vì ​​những lý do được mô tả trong câu trả lời này. Nhưng tôi cũng không thích không gian tên "ô nhiễm". Giải pháp lý tưởng cho tôi sẽ là trả lại cấu trúc ẩn danh như thế nào struct { int a, b; } my_func();. Điều này có thể được sử dụng như thế này : auto result = my_func();. Nhưng C ++ không cho phép điều này: "các loại mới có thể không được xác định trong loại trả về". Vì vậy, tôi phải tạo ra các cấu trúc như struct my_func_result_t...
anton_rh

2
@anton_rh: C ++ 14 cho phép trả về các kiểu cục bộ auto, vì vậy auto result = my_func();có thể thu được một cách tầm thường.
ildjarn

4
Khoảng 15 năm trước, khi chúng tôi phát hiện ra boost, chúng tôi đã sử dụng tuple rất nhiều vì nó khá tiện dụng. Làm thêm giờ chúng tôi đã gặp phải bất lợi về khả năng đọc đặc biệt là đối với các bộ dữ liệu có cùng loại (ví dụ: tuple <double, double>; cái nào là cái nào). Vì vậy, gần đây chúng ta có thói quen giới thiệu một cấu trúc POD nhỏ trong đó ít nhất tên của biến thành viên biểu thị điều gì đó hợp lý.
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 :: cặp về cơ bản là giải pháp cấu trúc của bạn, nhưng đã được xác định cho bạn và sẵn sàng thích ứng với bất kỳ hai loại dữ liệu nào.


3
Điều đó sẽ làm việc cho ví dụ đơn giản của tôi. Tuy nhiên, nói chung, có thể có nhiều hơn hai giá trị được trả về.
Fred Larson

5
Cũng không tự viết tài liệu. Bạn có thể nhớ đăng ký x86 nào là phần còn lại của DIV không?
Đánh dấu

1
@Mark - Tôi đồng ý rằng các giải pháp vị trí có thể ít được bảo trì hơn. Bạn có thể gặp vấn đề "hoán vị và vách ngăn".
Fred Larson

16

Nó hoàn toàn phụ thuộc vào chức năng thực tế và ý nghĩa của nhiều giá trị và kích thước của chúng:

  • Nếu chúng có liên quan như trong ví dụ phân số của bạn, thì tôi sẽ đi với một thể hiện cấu trúc hoặc lớp.
  • Nếu chúng không thực sự liên quan và không thể được nhóm thành một lớp / struct thì có lẽ bạn nên cấu trúc lại phương thức của mình thành hai.
  • Tùy thuộc vào kích thước trong bộ nhớ của các giá trị bạn trả về, bạn có thể muốn trả về một con trỏ tới một thể hiện của lớp hoặc struct hoặc sử dụng các tham số tham chiếu.

1
Tôi thích câu trả lời của bạn và viên đạn cuối cùng của bạn nhắc nhở tôi về một điều tôi vừa đọc rằng việc chuyển qua giá trị đã nhanh hơn nhiều tùy thuộc vào hoàn cảnh khiến việc này trở nên phức tạp hơn ... cpp-next.com/archive/2009/08/want-speed-pass -by-value
hiền nhân

12

Giải pháp OO cho việc này là tạo ra một lớp tỷ lệ. Nó sẽ không mất thêm bất kỳ mã nào (sẽ tiết kiệm được một số), sẽ sạch hơn / rõ ràng hơn và sẽ cung cấp cho bạn một số phép tái cấu trúc bổ sung cho phép bạn dọn sạch mã bên ngoài lớp này.

Trên thực tế tôi nghĩ rằng ai đó đã đề nghị trả về một cấu trúc, đủ gần nhưng che giấu ý định rằng đây cần phải là một lớp được suy nghĩ đầy đủ với hàm tạo và một vài phương thức, trên thực tế, "phương thức" mà bạn đã đề cập ban đầu (như trả về cặp) rất có thể là một thành viên của lớp này trả về một thể hiện của chính nó.

Tôi biết ví dụ của bạn chỉ là một "Ví dụ", nhưng thực tế là trừ khi chức năng của bạn hoạt động nhiều hơn bất kỳ chức năng nào nên làm, nếu bạn muốn nó trả về nhiều giá trị, bạn gần như chắc chắn thiếu một đối tượng.

Đừng ngại tạo ra các lớp nhỏ này để thực hiện các công việc nhỏ - đó là điều kỳ diệu của OO - cuối cùng bạn sẽ phá vỡ nó cho đến khi mọi phương thức đều rất nhỏ và đơn giản và mọi lớp đều nhỏ và dễ hiểu.

Một điều khác đáng lẽ là một chỉ báo cho thấy có gì đó không đúng: trong OO về cơ bản bạn không có dữ liệu - OO không phải là về việc truyền dữ liệu, một lớp cần quản lý và thao tác dữ liệu của chính nó, bất kỳ dữ liệu nào đi qua (kể cả người truy cập) là một dấu hiệu cho thấy bạn có thể cần suy nghĩ lại về một cái gì đó ..


10

Đã có tiền lệ trả về các cấu trúc trong tiêu chuẩn C (và do đó là C ++) với các hàm div, ldiv(và, trong C99, lldiv) từ <stdlib.h>(hoặc <cstdlib>).

'Kết hợp giá trị trả về và tham số trả về' thường ít sạch nhất.

Có một hàm trả về một trạng thái và trả về dữ liệu thông qua các tham số trả về là điều hợp lý trong C; Rõ ràng là không hợp lý trong C ++ khi bạn có thể sử dụng ngoại lệ để chuyển tiếp thông tin lỗi.

Nếu có nhiều hơn hai giá trị trả về, thì một cơ chế giống như cấu trúc có lẽ là tốt nhất.


10

Với C ++ 17, bạn cũng có thể trả về một quặng nhiều giá trị không thể di chuyển / không thể quét được (trong một số trường hợp nhất định). Khả năng trả về các loại không thể di chuyển được thông qua tối ưu hóa giá trị trả về được đảm bảo mới và nó kết hợp độc đáo với các tổng hợp , và những gì có thể được gọi là các hàm tạo templated .

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

Điều khá hay về điều này là nó được đảm bảo không gây ra bất kỳ sự sao chép hay di chuyển nào. Bạn có thể làm cho ví dụ manystruct matrixdic quá. Thêm chi tiết:

Trả về các tổng hợp (struct) và cú pháp cho mẫu C ++ 17 'hướng dẫn khấu trừ xây dựng'


6

Có một loạt các cách để trả về nhiều tham số. Tôi sẽ rất phấn khởi.

Sử dụng tham số tham chiếu:

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

sử dụng tham số con trỏ:

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

có lợi thế là bạn phải thực hiện &tại trang web cuộc gọi, có thể cảnh báo mọi người rằng đó là một tham số ngoài.

Viết một mẫu và sử dụng 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)...); }
};

sau đó chúng ta có thể làm:

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

và tất cả đều tốt fookhông còn có thể đọc bất kỳ giá trị nào được chuyển vào dưới dạng phần thưởng.

Các cách khác để xác định một vị trí bạn có thể đặt dữ liệu có thể được sử dụng để xây dựng out. Một cuộc gọi lại để đặt những thứ ở đâu đó, ví dụ.

Chúng ta có thể trả về một cấu trúc:

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

Whick hoạt động tốt trong mọi phiên bản của C ++ và trong điều này cũng cho phép:

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

với chi phí bằng không. Các thông số thậm chí không thể được di chuyển nhờ cuộc bầu cử được đảm bảo.

Chúng tôi có thể trả lại một std::tuple:

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

trong đó có nhược điểm là các tham số không được đặt tên. Điều này cho phép:

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

cũng. Trước thay vào đó chúng ta có thể làm:

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

mà chỉ là một chút vụng về Tuy nhiên, cuộc bầu cử được đảm bảo không hoạt động ở đây.

Đi vào lãnh thổ xa lạ (và đây là sau out<>!), Chúng ta có thể sử dụng kiểu tiếp tục đi qua:

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

và bây giờ người gọi làm:

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

một lợi ích của kiểu này là bạn có thể trả về một số giá trị tùy ý (với loại thống nhất) mà không phải quản lý bộ nhớ:

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

cuộc valuegọi lại có thể được gọi 500 lần khi bạn get_all_values( [&](int value){} ).

Đối với chứng điên cuồng thuần túy, bạn thậm chí có thể sử dụng phần tiếp theo cho phần tiếp theo.

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

có công dụng như:

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

điều này sẽ cho phép nhiều mối quan hệ giữa một resultother.

Một lần nữa với các giá trị của California, chúng ta có thể làm điều này:

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

ở đây, chúng tôi gọi lại cuộc gọi với một loạt các kết quả. Chúng tôi thậm chí có thể làm điều này nhiều lần.

Sử dụng điều này, bạn có thể có một chức năng truyền hiệu quả megabyte dữ liệu mà không cần thực hiện bất kỳ phân bổ nào ngoài ngăn xếp.

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
}

Bây giờ, std::functionhơi nặng nề cho việc này, vì chúng ta sẽ thực hiện việc này trong môi trường không phân bổ không chi phí. Vì vậy, chúng tôi muốn một function_viewcái không bao giờ phân bổ.

Một giải pháp khác là:

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

trong đó thay vì thực hiện cuộc gọi lại và gọi nó, foothay vào đó sẽ trả về một hàm nhận cuộc gọi lại.

foo (7) ([&] (int result, int other_result) {/ * code * /}); điều này phá vỡ các tham số đầu ra từ các tham số đầu vào bằng cách có dấu ngoặc riêng.

Với variantcoroutines, bạn có thể tạo ra foomột biến thể của các kiểu trả về (hoặc chỉ là kiểu trả về). Cú pháp chưa được sửa, vì vậy tôi sẽ không đưa ra ví dụ.

Trong thế giới của tín hiệu và khe, một chức năng hiển thị một tập hợp các tín hiệu:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

cho phép bạn tạo một foohoạt động không đồng bộ và phát kết quả khi kết thúc.

Ở dòng này, chúng ta có nhiều kỹ thuật đường ống, trong đó một chức năng không làm gì đó mà chỉ sắp xếp để dữ liệu được kết nối theo một cách nào đó và việc thực hiện tương đối độc lập.

foo( int_source )( int_dest1, int_dest2 );

sau đó mã này không làm gì cả cho đến khi int_sourcecó số nguyên để cung cấp nó. Khi nó làm, int_dest1int_dest2bắt đầu nhận kết quả.


Câu trả lời này chứa nhiều thông tin hơn các câu trả lời khác! đặc biệt, thông tin về auto&&[result, other_result]=foo();các hàm trả về cả bộ dữ liệu và cấu trúc. Cảm ơn!
jjmontes

Tôi đánh giá cao câu trả lời thấu đáo này, đặc biệt là vì tôi vẫn bị mắc kẹt với C ++ 11 và do đó không thể sử dụng một số giải pháp hiện đại hơn mà người khác đề xuất.
GuyGizmo

5

Sử dụng một cấu trúc hoặc một lớp cho giá trị trả về. Sử dụng std::paircó thể làm việc bây giờ, nhưng

  1. nó không linh hoạt nếu bạn quyết định sau này bạn muốn trả lại nhiều thông tin hơn;
  2. nó không rõ ràng lắm từ khai báo của hàm trong tiêu đề cái gì được trả về và theo thứ tự nào.

Trả về một cấu trúc với các tên biến thành viên tự ghi lại có thể sẽ ít bị lỗi hơn đối với bất kỳ ai sử dụng hàm của bạn. Đặt mũ đồng nghiệp của tôi trong giây lát, divide_resultcấu trúc của bạn dễ dàng cho tôi, một người dùng tiềm năng của chức năng của bạn, để hiểu ngay sau 2 giây. Loay hoay với các tham số ouput hoặc các cặp và bộ dữ liệu bí ẩn sẽ mất nhiều thời gian hơn để đọc qua và có thể được sử dụng không chính xác. Và rất có thể ngay cả sau khi sử dụng hàm một vài lần, tôi vẫn không nhớ thứ tự đúng của các đối số.


4

Nếu hàm của bạn trả về một giá trị thông qua tham chiếu, trình biên dịch không thể lưu nó vào một thanh ghi khi gọi các hàm khác bởi vì về mặt lý thuyết, hàm đầu tiên có thể lưu địa chỉ của biến được truyền cho nó trong một biến có thể truy cập toàn cầu và bất kỳ hàm nào được gọi là hàm có thể thay đổi nó, vì vậy trình biên dịch sẽ có (1) lưu giá trị từ các thanh ghi trở lại bộ nhớ trước khi gọi các hàm khác và (2) đọc lại nó khi cần lại từ bộ nhớ sau bất kỳ cuộc gọi nào như vậy.

Nếu bạn quay lại bằng cách tham khảo, tối ưu hóa chương trình của bạn sẽ bị ảnh hưởng


4

Ở đây, tôi đang viết một chương trình trả về nhiều giá trị (nhiều hơn hai giá trị) trong c ++. Chương trình này được thực thi trong c ++ 14 (G ++ 4.9.2). chương trình giống như một máy tính.

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

Vì vậy, bạn có thể hiểu rõ rằng bằng cách này, bạn có thể trả về nhiều giá trị từ một hàm. sử dụng std :: cặp chỉ có thể trả về 2 giá trị trong khi std :: tuple có thể trả về nhiều hơn hai giá trị.


4
Với C ++ 14, bạn cũng có thể sử dụng autokiểu trả về calđể làm cho điều này thậm chí còn sạch hơn. (IMO).
sfjac

3

Tôi có xu hướng sử dụng các hàm ngoài trong các hàm như thế này, vì tôi tuân theo mô hình của hàm trả về mã thành công / lỗi và tôi muốn giữ mọi thứ đồng nhất.


2

Các lựa chọn thay thế bao gồm mảng, máy phát điệnđảo ngược điều khiển , nhưng không có cái nào phù hợp ở đây.

Một số (ví dụ Microsoft trong Win32 lịch sử) có xu hướng sử dụng các tham số đơn giản để đơn giản, vì rõ ràng ai sẽ phân bổ và giao diện trên ngăn xếp, giảm sự tăng sinh của các cấu trúc và cho phép giá trị trả về riêng biệt để thành công.

Các lập trình viên "thuần túy" thích cấu trúc, giả sử đó giá trị hàm (như trường hợp ở đây), thay vì một cái gì đó được chạm ngẫu nhiên bởi hàm. Nếu bạn có một quy trình phức tạp hơn, hoặc một cái gì đó có trạng thái, có lẽ bạn sẽ sử dụng tài liệu tham khảo (giả sử bạn có lý do để không sử dụng một lớp học).


2

Tôi muốn nói rằng không có phương pháp ưa thích, tất cả phụ thuộc vào những gì bạn sẽ làm với phản hồi. Nếu các kết quả sẽ được sử dụng cùng nhau trong quá trình xử lý tiếp theo thì các cấu trúc có ý nghĩa, nếu không tôi sẽ có xu hướng vượt qua như các tham chiếu riêng lẻ trừ khi hàm sẽ được sử dụng trong câu lệnh tổng hợp:

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

Tôi thường chọn chuyển 'ra các cấu trúc' bằng cách tham chiếu trong danh sách tham số thay vì vượt qua bằng cách sao chép chi phí trả về một cấu trúc mới (nhưng điều này làm đổ mồ hôi các công cụ nhỏ).

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

Là ra các thông số khó hiểu? Một tham số được gửi dưới dạng tham chiếu cho thấy giá trị sẽ thay đổi (trái ngược với tham chiếu const). Đặt tên hợp lý cũng loại bỏ nhầm lẫn.


1
Tôi nghĩ nó hơi khó hiểu. Ai đó đang đọc mã gọi nó sẽ thấy "chia (a, b, c);". Không có dấu hiệu nào cho thấy c là một giá trị cho đến khi họ tra cứu chữ ký. Nhưng đó là nỗi sợ chung của các thông số tham chiếu không liên quan, hơn là đặc biệt cho câu hỏi này.
Steve Jessop

2

Tại sao bạn nhấn mạnh vào một hàm có nhiều giá trị trả về? Với OOP, bạn có thể sử dụng một lớp cung cấp một hàm thông thường với một giá trị trả về duy nhất và bất kỳ số lượng "giá trị trả về" bổ sung nào như bên dưới. Ưu điểm là người gọi có lựa chọn xem xét các thành viên dữ liệu bổ sung, nhưng không bắt buộc phải làm điều này. Đây là phương pháp ưa thích cho các cuộc gọi mạng hoặc cơ sở dữ liệu phức tạp, trong đó có thể cần nhiều thông tin trả về bổ sung trong trường hợp xảy ra lỗi.

Để trả lời câu hỏi ban đầu của bạn, ví dụ này có một phương thức trả về thương số, đó là điều mà hầu hết người gọi có thể cần, và ngoài ra, sau cuộc gọi phương thức, bạn có thể lấy phần còn lại làm thành viên dữ liệu.

class div{
   public:
      int remainder;

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

1
Tôi nghĩ rằng có những trường hợp này là không hiệu quả. Ví dụ: bạn có một vòng lặp for duy nhất tạo ra một số giá trị trả về. Nếu bạn chia các giá trị đó thành các hàm riêng biệt, bạn cần chạy qua vòng lặp một lần cho mỗi giá trị.
jiggunjer

1
@jiggunjer Bạn có thể chạy vòng lặp một lần và lưu trữ một số giá trị trả về trong các thành viên dữ liệu lớp riêng biệt. Điều này nhấn mạnh tính linh hoạt của khái niệm OOP.
Roland

2

thay vì trả về nhiều giá trị, chỉ cần trả về một trong số chúng và tạo tham chiếu của các giá trị khác trong hàm được yêu cầu, ví dụ:

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

Tôi đã không đề cập đến điều này trong chính câu hỏi? Ngoài ra, xem phản đối của tôi trong câu trả lời của tôi .
Fred Larson

1

Boost tuple sẽ là lựa chọn ưa thích của tôi cho một hệ thống tổng quát trả về nhiều hơn một giá trị từ một hàm.

Ví dụ có thể:

include "boost/tuple/tuple.hpp"

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

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

1

Chúng ta có thể khai báo hàm sao cho, nó trả về một biến cấu trúc do người dùng định nghĩa hoặc một con trỏ tới nó. Và theo tính chất của một cấu trúc, chúng ta biết rằng một cấu trúc trong C có thể chứa nhiều giá trị của các loại không đối xứng (nghĩa là một biến int, bốn biến char, hai biến float và v.v.


1

Tôi sẽ chỉ làm điều đó bằng cách tham chiếu nếu nó chỉ có một vài giá trị trả về nhưng đối với các loại phức tạp hơn, bạn cũng có thể làm như thế này:

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

sử dụng "tĩnh" để giới hạn phạm vi của loại trả về cho đơn vị biên dịch này nếu nó chỉ có nghĩa là loại trả về tạm thời.

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

Đây chắc chắn không phải là cách đẹp nhất để làm điều đó nhưng nó sẽ hoạt động.


-2

Dưới đây là một ví dụ đầy đủ về loại giải pháp vấn đề như vậy

#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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.