首页 文章

从C函数返回多个值

提问于
浏览
190

有没有从C函数返回多个值的首选方法?例如,假设一个函数分割两个整数并返回商和余数 . 我经常看到的一种方法是使用参考参数:

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

一种变化是返回一个值并通过引用参数传递另一个值:

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

另一种方法是声明一个struct来包含所有结果并返回:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

这些方式中的一种通常是首选,还是有其他建议?

编辑:在现实世界的代码中,可能会有两个以上的结果 . 它们也可以是不同类型的 .

19 回答

  • 22

    它完全取决于实际功能和多个值的含义及其大小:

    • 如果他们're related as in your fraction example, then I'与结构或类实例一起使用 .

    • 如果将它们分组到类/结构中,那么您可能应该将方法重构为两个 .

    • 根据要返回的值的内存大小,您可能希望返回指向类实例或结构的指针,或使用引用参数 .

  • 10

    为了返回两个值,我使用 std::pair (通常是typedef'd) . 您应该查看 boost::tuple (在C 11和更新版本中,有 std::tuple )以获得两个以上的返回结果 .

    随着C 17中结构化绑定的引入,返回 std::tuple 应该成为公认的标准 .

  • 114

    在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;
    }
    
  • 112

    就个人而言,我通常不喜欢返回参数,原因如下:

    • 在调用中并不总是很明显哪些参数是ins而哪些是out

    • 你通常必须创建一个局部变量来捕获结果,而返回值可以内联使用(这可能是也可能不是一个好主意,但至少你有选择)

    • 对我来说似乎更清楚一个"in door"和一个"out door"到一个功能 - 所有输入都在这里,所有输出都出现在那里

    • 我喜欢让我的论点列表尽可能短

    我对这对/元组技术也有一些保留意见 . 主要是,返回值通常没有自然顺序 . 代码的读者如何知道result.first是商还是余数?并且实现者可以更改顺序,这将破坏现有代码 . 如果值是相同类型,则特别隐蔽,因此不会生成编译器错误或警告 . 实际上,这些参数也适用于返回参数 .

    这是另一个代码示例,这个有点不那么简单:

    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;
    

    我认为这更清楚了 .

    所以我认为我的第一选择是结构技术 . 在某些情况下,对/元组的想法可能是一个很好的解决方案 . 我想尽可能避免返回参数 .

  • 5
    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本质上是你的结构解决方案,但已经为你定义,并准备适应任何两种数据类型 .

  • 12

    对此的OO解决方案是创建比率等级 . 它不需要任何额外的代码(会节省一些),会更清晰/更清晰,并会给你一些额外的重构,让你清理这个类之外的代码 .

    实际上我认为有人建议返回一个结构,这个结构足够接近,但隐藏了这个需要是一个完全经过深思熟虑的类的意图,构造函数和一些方法,实际上,你最初提到的“方法”(作为返回pair)应该最有可能是这个类的成员返回自己的实例 .

    我知道你的例子只是一个“例子”,但事实是除非你的函数比任何函数都要做的更多,如果你想让它返回多个值,你几乎肯定会错过一个对象 .

    不要害怕创建这些小课程来做一些小工作 - 这是OO的魔力 - 你最终会将其分解,直到每个方法都非常小而且简单,每个类都小而易懂 .

    另一件事应该是一个错误指示:在OO中你基本上没有数据 - OO不是关于传递数据,类需要在内部管理和操作它自己的数据,任何数据传递(包括访问器)是一个迹象,你可能需要重新思考一些东西..

  • 4

    With C++17 you can also return one ore more unmovable/uncopyable values (在某些情况下) . 返回不可移动类型的可能性来自新的保证返回值优化,并且它与聚合以及可以称为模板化构造函数的组合很好 .

    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 struct variadic . 更多细节:

    Returning variadic aggregates (struct) and syntax for C++17 variadic template 'construction deduction guide'

  • 9

    使用 divldiv (以及在C99中, lldiv )返回C(以及C)标准中的结构的先例函数来自 <stdlib.h> (或 <cstdlib> ) .

    “返回值和返回参数的混合”通常是最不干净的 .

    让函数返回状态并通过返回参数返回数据在C中是明智的;在C中,您可以使用异常来转发故障信息,这一点不太明显 .

    如果有两个以上的返回值,那么类似结构的机制可能是最好的 .

  • 2

    使用结构或类作为返回值 . 使用 std::pair 可能现在可以使用,但是

    • 如果您稍后决定要返回更多信息,那么它是不灵活的;

    • 它's not very clear from the function'在 Headers 中声明返回的内容和顺序 .

    对于使用您的函数的任何人来说,返回具有自记录成员变量名称的结构可能不会更容易出错 . 把我的同事的帽子放了一会儿,你的_644099_结构很容易让我,你的功能的潜在用户,在2秒后立即理解 . 使用输出参数或神秘对和元组进行混乱会花费更多时间来阅读并且可能使用不当 . 甚至在使用该函数几次后,我仍然不记得参数的正确顺序 .

  • 2

    如果你的函数通过引用返回一个值,那么编译器在调用其他函数时不能将它存储在寄存器中,因为理论上,第一个函数可以保存在全局可访问变量中传递给它的变量的地址,并且任何子函数都可以调用更改它,因此编译器将(1)在调用其他函数之前将寄存器中的值保存回内存;(2)在任何此类调用之后再次从内存中重新读取它时重新读取它 .

    如果您通过引用返回,您的程序优化将受到影响

  • 2

    在这里,我正在编写一个在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 :: pair只能返回2个值,而std :: tuple可以返回两个以上的值 .

  • 3

    我倾向于在这样的函数中使用out-vals,因为我坚持使用返回成功/错误代码的函数的范例,我喜欢保持统一 .

  • 4

    替代方案包括数组,generatorsinversion of control,但这里没有一个是合适的 .

    一些(例如历史Win32中的Microsoft)倾向于使用引用参数来简化,因为很清楚谁分配以及它在堆栈上的外观,减少了结构的扩散,并允许单独的返回值来获得成功 .

    "Pure"程序员更喜欢结构体,假设它是函数值(就像这里的情况一样),而不是's touched incidentally by the function. If you had a more complicated procedure, or something with state, you'd可能使用引用的东西(假设你有理由不使用类) .

  • 2

    我会说没有首选的方法,这取决于你将如何处理响应 . 如果结果将在进一步处理中一起使用,那么结构是有意义的,如果不是,我倾向于将其作为单独的引用传递,除非该函数将在复合语句中使用:

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

    我经常选择在参数列表中通过引用传递'out结构',而不是通过返回新结构的复制开销来传递(但这会让小东西大汗淋漓) .

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

    外出的参数令人困惑吗?作为参考发送的参数表明该值将发生变化(与const引用相反) . 明智的命名也可以消除混乱 .

  • 2

    为什么你坚持使用多个返回值的函数?使用OOP,您可以使用一个类,它提供具有单个返回值的常规函数,以及任何数量的附加“返回值”,如下所示 . 优点是调用者可以选择查看额外的数据成员,但不需要这样做 . 这是复杂数据库或网络调用的首选方法,在发生错误的情况下可能需要大量额外的返回信息 .

    要回答您的原始问题,此示例有一个返回商的方法,这是大多数调用者可能需要的方法,此外,在方法调用之后,您可以将余数作为数据成员 .

    class div{
       public:
          int remainder;
    
          int quotient(int dividend, int divisor){
             remainder = ...;
             return ...;
          }
    };
    
  • 1

    而不是返回多个值,只需返回其中一个值,并在所需函数中引用其他值,例如:

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

    有很多方法可以返回多个参数 . 我会好好的 .

    使用参考参数:

    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){ *t = 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>
      void emplace(Args&&...args) {
        target( T(std::forward<Args>(args)...) );
      }
      template<class X>
      void operator=(X&&x){ emplace(std::forward<X>(x)); }
      template<class...Args>
      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();
    

    在每个版本的C中都可以正常工作,并且在_684117中这也允许:

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

    零成本 . 由于保证省略,甚至无法移动参数 .

    我们可以返回 std::tuple

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

    它的缺点是参数没有命名 . 这允许c++17

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

    同样 . 在c++17之前,我们可以改为:

    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 )
    

    get_all_values( [&](int value){} ) 时, value 回调可被调用500次 .

    对于纯粹的精神错乱,你甚至可以继续使用延续 .

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

    其用途如下:

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

    这将允许 resultother 之间的多个关系 .

    再次使用uniforn值,我们可以这样做:

    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 result,int other_result){/ * code * /});这会通过使用单独的括号来打破输入参数的输出参数 .

    使用 variantc++20协同程序,可以使 foo 成为返回类型变体的生成器(或仅返回类型) . 语法尚未修复,因此我不举例说明 .

    在信号和插槽的世界中,一个暴露一组信号的功能:

    template<class...Args>
    struct broadcaster;
    
    broadcaster<int, int> foo();
    

    允许您创建一个确实工作异步的 foo 并在结束时广播结果 .

    在这一行中,我们有各种管道技术,其中一个函数不做某事,而是安排数据以某种方式连接,并且这样做是相对独立的 .

    foo( int_source )( int_dest1, int_dest2 );
    

    然后这个代码没有做任何事情,直到 int_source 有整数来提供它 . 如果是, int_dest1int_dest2 开始收到结果 .

  • 164

    对于从函数返回多个值的通用系统,Boost元组将是我的首选 .

    可能的例子:

    include "boost/tuple/tuple.hpp"
    
    tuple <int,int> divide( int dividend,int divisor ) 
    
    {
      return make_tuple(dividend / divisor,dividend % divisor )
    }
    
  • 15

    我们可以声明这样的函数,它返回一个结构类型用户定义的变量或指向它的指针 . 通过结构的属性,我们知道C中的结构可以包含多个非对称类型的值(即一个int变量,四个char变量,两个float变量等等......)

相关问题