Повернення декількох значень з функції C ++


242

Чи є бажаний спосіб повернути декілька значень з функції C ++? Наприклад, уявіть собі функцію, яка ділить два цілих числа і повертає як частник, так і решту. Один із способів, яким я часто бачу, - це використання еталонних параметрів:

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

Варіація полягає в тому, щоб повернути одне значення і передати інше за допомогою еталонного параметра:

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

Іншим способом було б оголосити структуру, що містить усі результати, і повернути:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

Чи є один із цих способів загалом кращим, чи є інші пропозиції?

Редагувати: У реальному коді може бути більше двох результатів. Вони також можуть бути різного типу.

Відповіді:


217

Для повернення двох значень я використовую std::pair(зазвичай typedef'd). Потрібно переглянути boost::tuple(на C ++ 11 і новіших версіях std::tuple) більше двох результатів повернення.

З введенням структурованого зв'язування в C ++ 17 повернення, std::tupleмабуть, має стати прийнятим стандартом.


12
+1 для кортежу. Майте на увазі розгалуження продуктивності великих об'єктів, що повертаються в структурі порівняно з проходженням посилання.
Марцін

12
Якщо ви збираєтеся використовувати кортежі, то чому б не використовувати їх і для пар. Навіщо мати особливий випадок?
Ферруччо,

4
Фред, так збільшити :: кортеж може це зробити :)
Йоханнес Шауб - ліб

46
В C ++ 11 ви можете використовувати std::tuple.
Ферруччо

14
Якщо ви хочете приймати кілька значень з функції, зручний спосіб зробити це полягає у використанні std::tie stackoverflow.com/a/2573822/502144
fdermishin

176

В C ++ 11 ви можете:

#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:

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

або з конструкціями:

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
У мене є одна турбота про функції, що повертають кортежі. Скажімо, вищевказаний прототип функції знаходиться в заголовку, то як я можу знати, що означають перше і друге повернені значення, не розуміючи визначення функції? коефіцієнт-залишок або решта-коефіцієнт.
Uchia Itachi

7
@UchiaItachi Те саме, що стосується параметрів функції, ви можете давати їм імена, але мова навіть цього не виконує, і назви параметрів не мають значення на сайті виклику при читанні. Крім того, при одному поверненні ви просто маєте тип, але мати ім’я також може бути корисно, з кортежами ви просто подвоюєте питання, тому imo, мови просто не вистачає щодо документального документування кількома способами, не тільки цим.
pepper_chico

1
як би виглядав останній приклад, якби я хотів чітко вказати тип повернення поділу ()? Чи потрібно тоді визначити результат десь в іншому місці, або я можу його визначити правильно у специфікації типу повернення?
Слава

1
@Slava ви не можете визначити тип типу підписом функції, вам доведеться оголосити тип зовні і використовувати його як тип повернення, як це зазвичай робиться (просто перемістіть structрядок поза тілом функції та замініть autoповернення функції на result.
pepper_chico

3
@pepper_chico Що робити, якщо ви хочете помістити визначення функції divideв окремий файл cpp? Я отримую помилку error: use of ‘auto divide(int, int)’ before deduction of ‘auto’. Як я це вирішую?
Адріан

123

Особисто мені зазвичай не подобаються параметри повернення з кількох причин:

  • У виклику не завжди очевидно, які параметри є ins, а які outs
  • як правило, ви повинні створити локальну змінну для отримання результату, тоді як значення повернення можна використовувати вбудованим (що може бути, а може і не бути хорошою ідеєю, але принаймні у вас є можливість)
  • мені здається більш чистим мати функцію "у двері" і "у двері" - всі входи входять сюди, всі виходи там
  • Мені подобається тримати мої списки аргументів якомога коротше

У мене також є деякі застереження щодо техніки пар / кортеж. В основному, природного порядку для повернених значень часто немає. Як читач коду може дізнатись, чи є результат.по-перше, коефіцієнт чи залишок? І реалізатор міг змінити порядок, який порушив би існуючий код. Це особливо підступно, якщо значення однотипні, так що помилка компілятора чи попередження не створюються. Власне, ці аргументи застосовуються і до параметрів повернення.

Ось ще один приклад коду, цей трохи менш тривіальний:

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;

Чи є ця друкована підстава швидкісною та курсовою, або курсом і обґрунтованими? Це не очевидно.

Порівняйте з цим:

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;

Я думаю, це зрозуміліше.

Тому я думаю, що мій перший вибір взагалі - це техніка структури. Ідея пари / кортежу, ймовірно, чудове рішення в певних випадках. Я хотів би уникати параметрів повернення, коли це можливо.


1
Пропозиція оголосити structподібну Velocityє приємною. Однак одне занепокоєння полягає в тому, що воно забруднює простір імен. Я припускаю, що для C ++ 11, ім'я structможе мати довге ім'я типу, і його можна використовувати auto result = calculateResultingVelocity(...).
Hugues

5
+1. Функція повинна повертати одну "річ", а не якийсь упорядкований "кортеж речей".
DevSolar

1
Я віддаю перевагу структурам над std :: pair / std :: tuples з причин, описаних у цій відповіді. Але мені також не подобається простір імен "забруднення". Ідеальним рішенням для мене буде повернення анонімних структур на зразок struct { int a, b; } my_func();. Це може бути використано , як це: auto result = my_func();. Але C ++ цього не дозволяє: "нові типи не можуть бути визначені у типі повернення". Тож я маю створювати такі структури, як struct my_func_result_t...
anton_rh

2
@anton_rh: C ++ 14 дозволяє повертати локальні типи за допомогою auto, тому auto result = my_func();можна отримати тривіально.
ildjarn

4
Колись 15 років тому, коли ми виявили прискорення, ми багато використовували кортеж, оскільки це дуже зручно. Овертайм ми відчували недолік у читанні, особливо для кортежів одного типу (наприклад, кортеж <подвійний, подвійний>; який з них є). Тому останнім часом нам більше властиво вводити невелику структуру POD, де хоча б назва змінної члена вказує на щось розумне.
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 :: пара - це по суті ваше структурне рішення, але вже визначене для вас і готове адаптуватися до будь-яких двох типів даних.


3
Це допоможе для мого простого прикладу. В цілому, однак, може бути повернуто більше двох значень.
Фред Ларсон

5
Також не самодокументування. Чи можете ви пригадати, який реєстр x86 є залишком для DIV?
Марк

1
@ Марк - Я погоджуюсь, що позиційні рішення можуть бути менш ретельними. Ви можете зіткнутися з проблемою "перестановка і перестановка".
Фред Ларсон

16

Це повністю залежить від фактичної функції та значення декількох значень та їх розмірів:

  • Якщо вони пов'язані, як у вашому прикладі дробу, то я б пішов із екземпляром структури або класу.
  • Якщо вони насправді не пов'язані і не можуть бути згруповані в клас / структуру, то, можливо, вам слід перефактурувати свій метод на два.
  • Залежно від розміру в пам'яті значень, які ви повертаєте, ви можете повернути вказівник на екземпляр або структуру класу або використовувати еталонні параметри.

1
Мені подобається ваша відповідь, і ваша остання куля нагадує мені про щось, що я щойно прочитав, що проходження повз значення стало набагато швидше, залежно від обставин, що ускладнюють це ... cpp-next.com/archive/2009/08/want-speed-pass -by-value
мудрець

12

Рішенням OO для цього є створення класу співвідношення. Це не займе жодного додаткового коду (заощадило б деякі), було б значно чистішим / чіткішим, і дасть вам додаткові рефактори, що дозволяють також очищати код і поза цим класом.

Насправді я думаю, що хтось рекомендував повернути структуру, яка є досить близькою, але приховує наміри, що це повинен бути повністю продуманий клас із конструктором та декількома методами, насправді "методом", про який ви спочатку згадували (як повернення пара), швидше за все, повинен бути членом цього класу, повертаючи примірник себе.

Я знаю, що ваш приклад був просто "Прикладом", але факт полягає в тому, що, якщо ваша функція не робить більше, ніж будь-яка функція, якщо ви хочете, щоб вона повертала кілька значень, ви майже напевно відсутній об'єкт.

Не бійтеся створювати ці крихітні класи, щоб виконувати невеликі роботи - ось магія ОО - ви в кінцевому підсумку розбиваєте її, поки кожен метод не дуже маленький і простий, і кожен клас невеликий і зрозумілий.

Інша річ, яка мала бути показником того, що щось не так: в ОО ти фактично не маєш даних - ОО не стосується передачі даних, класу потрібно керувати власними даними та керувати ними власними даними, будь-якими переданими даними (включаючи аксесуари) це знак того, що вам може знадобитися щось переосмислити ..


10

Існує прецедент для повернення структур в C (і , отже , C ++) стандарт з div, ldiv(і, в C99, lldiv) функції з <stdlib.h>(або<cstdlib> ).

"Змішання параметрів повернення та повернення", як правило, є найменш чистим.

Наявність функції повернення статусу та повернення даних через параметри повернення чутливо в C; це менш очевидно розумно в C ++, де ви можете використовувати винятки для передачі інформації про помилки.

Якщо є більше двох повернутих значень, то, мабуть, найкращий структуроподібний механізм.


10

За допомогою C ++ 17 ви також можете повернути на одну руду більше незнімних / не скопіюваних значень (у певних випадках). Можливість повернути незмінні типи приходить за допомогою нової оптимізованої гарантованої віддачі, і це непогано складається з агрегатами і тим, що можна назвати шаблоновими конструкторами .

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

Досить річ про те , що це гарантовано не причина будь-якого копіювання або переміщення. Можна зробити прикладmany структури варіантним. Детальніше:

Повернення варіабельних сукупностей (структура) та синтаксису для C ++ 17 варіативного шаблону "Посібник з виведення конструкції"


6

Існує купа способів повернення декількох параметрів. Я буду виснажливим.

Використовувати еталонні параметри:

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

використовувати параметри вказівника:

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

що має ту перевагу, яку вам потрібно зробити & на сайті виклику, можливо, попереджаючи людей, що це не-параметр.

Напишіть шаблон і використовуйте його:

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

тоді ми можемо зробити:

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

і все добре. fooбільше не може читати жодне значення, передане як бонус.

Інші способи визначення місця, на яке можна розмістити дані, можуть бути використані для побудови out. Наприклад, зворотній дзвінок, щоб замінити речі де-небудь.

Ми можемо повернути структуру:

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

whick працює нормально у будь-якій версії C ++ та в це також дозволяє:

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

при нульовій вартості. Параметри навіть не можуть переміщуватися завдяки гарантованому елізію.

Ми можемо повернути std::tuple:

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

що має зворотний бік, що параметри не названі. Це дозволяє:

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

також. До замість цього ми можемо:

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

що просто трохи незручніше. Але гарантований елісій тут не працює.

Зайшовши на чужу територію (і це вже після out<>!), Ми можемо використовувати стиль продовження проходження:

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

і тепер абоненти роблять:

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

Перевага цього стилю полягає в тому, що ви можете повернути довільну кількість значень (з рівномірним типом) без управління пам’яттю:

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

valueзворотний виклик можна назвати 500 раз , коли ви get_all_values( [&](int value){} ).

Для чистого божевілля ви навіть можете використовувати продовження на продовження.

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

використання яких виглядає так:

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

що дозволило б мати багато стосунків між resultта other.

Знову з однозначними значеннями ми можемо це зробити:

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

тут ми називаємо зворотний виклик із розмахом результатів. Ми навіть можемо це робити неодноразово.

Використовуючи це, ви можете мати функцію, яка ефективно передає мегабайти даних, не роблячи жодного розподілу зі стеку.

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
}

Зараз std::functionце трохи важко, тому що ми це робимо в умовах нульових витрат без розподілу. Тож ми хотіли б того, function_viewщо ніколи не виділяється.

Ще одне рішення:

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

де замість прийому зворотного дзвінка та виклику його, fooзамість цього повертається функція, яка приймає зворотний виклик.

foo (7) ([&] (результат int, int other_result) {/ * код * /}); це розбиває вихідні параметри з вхідних параметрів, маючи окремі дужки.

З variant іУ результаті, ви можете зробити fooгенератор варіанту повернення типів (або просто тип повернення). Синтаксис ще не виправлений, тому я не наведу прикладів.

У світі сигналів і слотів функція, яка виставляє набір сигналів:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

дозволяє створити fooасинхронний режим, що працює, і передає результат після його завершення.

У цій лінії у нас є різноманітні методи трубопроводу, де функція щось не робить, а організовує підключення даних якимось чином, і це відносно незалежно.

foo( int_source )( int_dest1, int_dest2 );

тоді цей код нічого не робить, поки int_sourceне буде надати цілі числа. Коли це станеться, int_dest1і int_dest2починайте отримувати результати.


Ця відповідь містить більше інформації, ніж інші відповіді! зокрема, інформація про auto&&[result, other_result]=foo();функції, що повертають як кортежі, так і структури. Дякую!
jjmontes

Я ціную цю вичерпну відповідь, тим більше, що я все ще тримаюся C ++ 11 і тому не можу використовувати деякі більш сучасні рішення, які пропонують інші.
GuyGizmo

5

Використовуйте структуру або клас для повернення значення. Використання std::pairможе працювати зараз, але

  1. це негнучко, якщо ви вирішите пізніше ви хочете отримати більше інформації;
  2. з декларації функції в заголовку не дуже ясно, що повертається і в якому порядку.

Повернення структури з іменами змінних членів самодокументування, ймовірно, буде менш схильним до помилок для тих, хто використовує вашу функцію. Надягнувши мою шапку на колегу на мить, ваша divide_resultструктура, мені, потенційному користувачеві вашої функції, легко зрозуміти через 2 секунди. Заплутатися з параметрами вихідних даних або таємничими парами та кортежами знадобиться більше часу для читання, і їх можна використовувати неправильно. І, швидше за все, навіть після використання функції кілька разів я все ще не пам’ятаю правильний порядок аргументів.


4

Якщо ваша функція повертає значення за допомогою посилання, компілятор не може зберігати її в реєстрі при виклику інших функцій, оскільки теоретично перша функція може зберегти адресу змінної, переданої їй у глобально доступну змінну, і будь-які функції, що називаються впорядку, можуть змінити його, так що компілятору доведеться (1) зберегти значення з регістрів назад у пам'ять перед викликом інших функцій і (2) перечитати його, коли це потрібно знову з пам'яті після будь-якого з таких дзвінків.

Якщо ви повернетесь за посиланням, оптимізація вашої програми постраждає


4

Тут я пишу програму, яка повертає декілька значень (більше двох значень) в c ++. Ця програма може виконуватися в c ++ 14 (G ++ 4.9.2). Програма як калькулятор.

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

Отже, ви можете чітко зрозуміти, що таким чином ви можете повернути кілька значень з функції. використовуючи std :: пару, можна повернути лише 2 значення, тоді як std :: tuple може повернути більше двох значень.


4
З C ++ 14 ви також можете використовувати autoтип повернення, calщоб зробити це ще більш чистим. (ІМО).
sfjac

3

Я схильний використовувати out-vals у таких функціях, тому що я дотримуюся парадигми функції, що повертає коди успіху / помилок і мені подобається зберігати рівномірність.


2

Альтернативи включають масиви, генератори та інверсію управління , але жоден тут не підходить.

Деякі (наприклад, Microsoft в історичному Win32) прагнуть використовувати еталонні параметри для простоти, тому що зрозуміло, хто виділяє і як це буде виглядати на стеку, зменшує розповсюдження структур і дозволяє окреме повернене значення для успіху.

«Чисті» програмісти воліють - структуру, припускаючи , що це значення функції (як в даному випадку), а не те , що доторкнувся до слову функції. Якщо у вас була більш складна процедура або щось із станом, ви, ймовірно, використовували посилання (якщо вважати, що у вас є причина не використовувати клас).


2

Я б сказав, що немає кращого методу, все залежить від того, що ви збираєтеся робити з відповіддю. Якщо результати будуть використані разом у подальшій обробці, тоді структури мають сенс, якщо ні, я б схильний передавати їх як окремі посилання, якщо тільки функція не буде використовуватися у складеному викладі:

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

Я часто вирішую передавати "структури" за посиланням у списку параметрів, а не мати пропуск за копією накладних повернень нової структури (але це пітливість дрібних речей).

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

Параметри заплутані? Параметр, надісланий як посилання, припускає, що значення буде змінюватися (на відміну від const-посилання). Розумне називання також усуває плутанину.


1
Я думаю, це трохи заплутано. Хтось хто читає код, який називає його, бачить "ділити (a, b, c);". Немає жодних ознак, що c - вихід, поки вони не шукають підпису. Але це загальний страх перед нестандартними референтними парамами, а не специфічним для цього питання.
Стів Джессоп

2

Чому ви наполягаєте на функції з кількома поверненими значеннями? З OOP ви можете використовувати клас, що пропонує звичайну функцію з одним значенням повернення та будь-якою кількістю додаткових "повернутих значень", як показано нижче. Перевага полягає в тому, що абонент має можливість перегляду додаткових членів даних, але цього не потрібно робити. Це кращий метод для складної бази даних або мережевих дзвінків, де може знадобитися багато додаткової інформації повернення у випадку помилок.

Щоб відповісти на ваше первісне запитання, у цьому прикладі є спосіб повернути коефіцієнт, який може знадобитися більшості абонентів, і, крім того, після виклику методу ви можете отримати решту як член даних.

class div{
   public:
      int remainder;

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

1
Я думаю, що є випадки, коли це малоефективно. Наприклад, у вас є одинарний цикл, який генерує кілька повернених значень. Якщо ви розділите ці значення на окремі функції, вам потрібно буде пропустити цикл один раз для кожного значення.
jiggunjer

1
@jiggunjer Ви можете запустити цикл один раз і зберегти кілька повернених значень в окремих членах даних класу. Це підкреслює гнучкість концепції OOP.
Роланд

2

замість повернення декількох значень, просто поверніть одне з них і зробіть посилання інших у потрібній функції, наприклад:

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

Хіба я не згадував про це у самому питанні? Також дивіться мої заперечення у моїй відповіді .
Фред Ларсон

1

Збільшити кортеж був би моїм кращим вибором для узагальненої системи повернення більше одного значення з функції.

Можливий приклад:

include "boost/tuple/tuple.hpp"

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

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

1

Ми можемо оголосити функцію такою, що вона повертає змінну, визначену типом структури, або вказівник на неї. А за властивістю структури ми знаємо, що структура в C може містити кілька значень асиметричних типів (тобто одна змінна int, чотири змінних char, дві float змінні тощо)


1

Я б просто зробив це за посиланням, якщо це лише кілька повернених значень, але для більш складних типів ви також можете це зробити так:

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

використовуйте "статичний", щоб обмежити область типу повернення цим блоком компіляції, якщо він призначений лише для тимчасового типу повернення.

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

Це, безумовно, не найкрасивіший спосіб це зробити, але це спрацює.


-2

Ось повний приклад такого виду вирішення проблеми

#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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.