Làm cách nào để trả về nhiều giá trị từ một hàm trong C?


87

Nếu tôi có một hàm tạo ra một kết quả intvà một kết quả string, làm cách nào để trả về cả hai từ một hàm?

Theo như tôi có thể nói, tôi chỉ có thể trả về một thứ, được xác định bởi kiểu đứng trước tên hàm.


6
Ý của stringbạn là "Tôi đang sử dụng C ++ và đây là std::stringlớp" hoặc "Tôi đang sử dụng C và đây là một char *con trỏ hoặc char[]mảng."
Chris Lutz

tốt, trong trường hợp cụ thể của tôi, chúng là hai int: một cho 'điểm' của những gì tôi đang so sánh và một cho 'chỉ số' của nơi tìm thấy điểm tối đa đó. tôi muốn sử dụng một ví dụ về chuỗi ở đây chỉ cho trường hợp tổng quát hơn
Tony Stark

Chuyển chuỗi theo tham chiếu và trả lại int. Cách nhanh nhất. Không cần cấu trúc.
Stefan Steiger

1
Không phải một hàm trả về 2 kết quả thực hiện nhiều hơn một việc? Chú Bob sẽ nói gì?
Duncan

Câu trả lời:


123

Tôi không biết của bạn stringlà gì , nhưng tôi sẽ giả định rằng nó quản lý bộ nhớ của chính nó.

Bạn có hai giải pháp:

1: Trả về a structchứa tất cả các loại bạn cần.

struct Tuple {
    int a;
    string b;
};

struct Tuple getPair() {
    Tuple r = { 1, getString() };
    return r;
}

void foo() {
    struct Tuple t = getPair();
}

2: Sử dụng con trỏ để chuyển giá trị.

void getPair(int* a, string* b) {
    // Check that these are not pointing to NULL
    assert(a);
    assert(b);
    *a = 1;
    *b = getString();
}

void foo() {
    int a, b;
    getPair(&a, &b);
}

Việc bạn chọn sử dụng cái nào phụ thuộc phần lớn vào sở thích cá nhân về bất kỳ ngữ nghĩa nào bạn thích hơn.


7
Tôi nghĩ rằng nó phụ thuộc nhiều hơn vào mức độ liên quan của các giá trị trả về. Nếu int là mã lỗi và chuỗi là kết quả, thì chúng không nên được đặt cùng nhau trong một cấu trúc. Điều đó thật ngớ ngẩn. Trong trường hợp đó, tôi sẽ trả về int và chuyển chuỗi dưới dạng a char *và a size_tcho độ dài trừ khi nó thực sự quan trọng để hàm cấp phát chuỗi và / hoặc trả về của chính nó NULL.
Chris Lutz

@Chris Tôi hoàn toàn đồng ý với bạn, nhưng tôi không biết ngữ nghĩa sử dụng của các biến mà anh ấy cần.
Travis Gockel

Điểm tốt, Chris. Một điều khác tôi nghĩ là đáng chỉ ra là giá trị so với tham chiếu. Nếu tôi không nhầm, việc trả lại cấu trúc như được hiển thị trong ví dụ có nghĩa là một bản sao sẽ được tạo khi trả lại, điều đó có đúng không? (Tôi hơi run về C) Trong khi phương pháp kia sử dụng tham chiếu truyền và do đó không yêu cầu cấp phát thêm bộ nhớ. Tất nhiên, cấu trúc có thể được trả về thông qua con trỏ và chia sẻ cùng một lợi ích phải không? (đảm bảo phân bổ bộ nhớ đúng cách và tất nhiên là tất cả)
RTHarston

@BobVicktor: C hoàn toàn không có ngữ nghĩa tham chiếu (chỉ dành riêng cho C ++), vì vậy mọi thứ đều là một giá trị. Giải pháp hai con trỏ (# 2) là chuyển các bản sao của con trỏ vào một hàm, getPairsau đó sẽ bỏ tham chiếu . Tùy thuộc vào những gì bạn đang làm (OP không bao giờ làm rõ nếu đây thực sự là một câu hỏi C), phân bổ có thể là một mối quan tâm, nhưng nó thường không phải trong đất C ++ (tối ưu hóa giá trị trả về tiết kiệm tất cả điều này) và trong C đất, dữ liệu các bản sao thường diễn ra rõ ràng (thông qua strncpyhoặc bất cứ điều gì).
Travis Gockel

@TravisGockel Cảm ơn bạn đã chỉnh sửa. Tôi đang đề cập đến thực tế là con trỏ được sử dụng, vì vậy nó không sao chép các giá trị, chỉ chia sẻ những gì đã được cấp phát. Nhưng bạn đã đúng khi nói rằng điều đó không được gọi là tham chiếu qua trong C. Và cảm ơn vì những nguồn kiến ​​thức tuyệt vời khác. Tôi thích học những điều nhỏ nhặt này về ngôn ngữ. :)
RTHarston

10

Option 1: Khai báo một cấu trúc với một int và chuỗi và trả về một biến cấu trúc.

struct foo {    
 int bar1;
 char bar2[MAX];
};

struct foo fun() {
 struct foo fooObj;
 ...
 return fooObj;
}

Option 2: Bạn có thể chuyển một trong hai thông qua con trỏ và thực hiện các thay đổi đối với tham số thực tế thông qua con trỏ và trả về tham số kia như bình thường:

int fun(char **param) {
 int bar;
 ...
 strcpy(*param,"....");
 return bar;
}

hoặc là

 char* fun(int *param) {
 char *str = /* malloc suitably.*/
 ...
 strcpy(str,"....");
 *param = /* some value */
 return str;
}

Option 3: Tương tự như tùy chọn 2. Bạn có thể chuyển cả hai thông qua con trỏ và không trả về gì từ hàm:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}

Đối với tùy chọn 2, bạn cũng nên chuyển độ dài của chuỗi. int fun(char *param, size_t len)
Chris Lutz

Bây giờ điều đó phụ thuộc vào những gì chức năng đang làm. Nếu chúng ta biết loại kết quả mà hàm nhồi vào mảng char, chúng ta có thể chỉ cần cấp đủ không gian cho nó và chuyển nó vào hàm. Không cần vượt qua độ dài. Một cái gì đó tương tự như cách chúng tôi sử dụng strcpychẳng hạn.
codaddict

7

Vì một trong các loại kết quả của bạn là chuỗi (và bạn đang sử dụng C, không phải C ++), tôi khuyên bạn nên chuyển con trỏ làm tham số đầu ra. Sử dụng:

void foo(int *a, char *s, int size);

và gọi nó như thế này:

int a;
char *s = (char *)malloc(100); /* I never know how much to allocate :) */
foo(&a, s, 100);

Nói chung, bạn nên thực hiện phân bổ trong hàm đang gọi , không phải bên trong chính hàm, để bạn có thể cởi mở nhất có thể cho các chiến lược phân bổ khác nhau.


6

Hai cách tiếp cận khác nhau:

  1. Chuyển các giá trị trả về của bạn bằng con trỏ và sửa đổi chúng bên trong hàm. Bạn khai báo hàm của mình là void, nhưng nó trả về thông qua các giá trị được chuyển vào dưới dạng con trỏ.
  2. Xác định cấu trúc tổng hợp các giá trị trả lại của bạn.

Tôi nghĩ rằng số 1 rõ ràng hơn một chút về những gì đang xảy ra, mặc dù nó có thể trở nên tẻ nhạt nếu bạn có quá nhiều giá trị trả về. Trong trường hợp đó, tùy chọn số 2 hoạt động khá tốt, mặc dù có một số chi phí tinh thần liên quan đến việc tạo các cấu trúc chuyên biệt cho mục đích này.


1
C không có tài liệu tham khảo ;-), mặc dù kể từ khi tấm áp phích sử dụng string, nó có thể là an toàn để giả định C ++ ...
Travis Gockel

Hoàn toàn quên điều đó! Tôi đã sửa đổi câu trả lời của mình để sử dụng con trỏ, nhưng rõ ràng tôi đã ở trong vùng đất C ++ quá lâu. :)
James Thompson

6

Tạo một cấu trúc và đặt hai giá trị bên trong và trả về biến cấu trúc.

struct result {
    int a;
    char *string;
}

Bạn phải phân bổ không gian cho char *chương trình của mình.


3

Sử dụng con trỏ làm tham số hàm của bạn. Sau đó, sử dụng chúng để trả về nhiều giá trị.


2

Bằng cách truyền các tham số bằng tham chiếu đến hàm.

Ví dụ:

 void incInt(int *y)
 {
     (*y)++;  // Increase the value of 'x', in main, by one.
 }

Cũng bằng cách sử dụng các biến toàn cục nhưng nó không được khuyến khích.

Thí dụ:

int a=0;

void main(void)
{
    //Anything you want to code.
}

4
void main(void)OH LÀM THẾ NÀO RỒI!
Chris Lutz

Ý anh là gì? @ Chris Lutz
Badr

6
mainphải trả về một mã trạng thái. Dưới * nix, thông thường hơn là khai báo nó int main(int argc, char *argv[]), tôi tin rằng Windows có các quy ước tương tự.
Duncan

2

Một cách tiếp cận là sử dụng macro. Đặt cái này vào một tệp tiêu đềmultitype.h

#include <stdlib.h>

/* ============================= HELPER MACROS ============================= */

/* __typeof__(V) abbreviation */

#define TOF(V) __typeof__(V)

/* Expand variables list to list of typeof and variable names */

#define TO3(_0,_1,_2,_3) TOF(_0) v0; TOF(_1) v1; TOF(_2) v2; TOF(_3) v3;
#define TO2(_0,_1,_2)    TOF(_0) v0; TOF(_1) v1; TOF(_2) v2;
#define TO1(_0,_1)       TOF(_0) v0; TOF(_1) v1;
#define TO0(_0)          TOF(_0) v0;

#define TO_(_0,_1,_2,_3,TO_MACRO,...) TO_MACRO

#define TO(...) TO_(__VA_ARGS__,TO3,TO2,TO1,TO0)(__VA_ARGS__)

/* Assign to multitype */

#define MTA3(_0,_1,_2,_3) _0 = mtr.v0; _1 = mtr.v1; _2 = mtr.v2; _3 = mtr.v3;
#define MTA2(_0,_1,_2)    _0 = mtr.v0; _1 = mtr.v1; _2 = mtr.v2;
#define MTA1(_0,_1)       _0 = mtr.v0; _1 = mtr.v1;
#define MTA0(_0)          _0 = mtr.v0;

#define MTA_(_0,_1,_2,_3,MTA_MACRO,...) MTA_MACRO

#define MTA(...) MTA_(__VA_ARGS__,MTA3,MTA2,MTA1,MTA0)(__VA_ARGS__)

/* Return multitype if multiple arguments, return normally if only one */

#define MTR1(...) {                                                           \
    typedef struct mtr_s {                                                    \
      TO(__VA_ARGS__)                                                         \
    } mtr_t;                                                                  \
    mtr_t *mtr = malloc(sizeof(mtr_t));                                       \
    *mtr = (mtr_t){__VA_ARGS__};                                              \
    return mtr;                                                               \
  }

#define MTR0(_0) return(_0)

#define MTR_(_0,_1,_2,_3,MTR_MACRO,...) MTR_MACRO

/* ============================== API MACROS =============================== */

/* Declare return type before function */

typedef void* multitype;

#define multitype(...) multitype

/* Assign return values to variables */

#define let(...)                                                              \
  for(int mti = 0; !mti;)                                                     \
    for(multitype mt; mti < 2; mti++)                                         \
      if(mti) {                                                               \
        typedef struct mtr_s {                                                \
          TO(__VA_ARGS__)                                                     \
        } mtr_t;                                                              \
        mtr_t mtr = *(mtr_t*)mt;                                              \
        MTA(__VA_ARGS__)                                                      \
        free(mt);                                                             \
      } else                                                                  \
        mt

/* Return */

#define RETURN(...) MTR_(__VA_ARGS__,MTR1,MTR1,MTR1,MTR0)(__VA_ARGS__)

Điều này giúp bạn có thể trả về tối đa bốn biến từ một hàm và gán chúng cho tối đa bốn biến. Ví dụ, bạn có thể sử dụng chúng như sau:

multitype (int,float,double) fun() {
    int a = 55;
    float b = 3.9;
    double c = 24.15;

    RETURN (a,b,c);
}

int main(int argc, char *argv[]) {
    int x;
    float y;
    double z;

    let (x,y,z) = fun();

    printf("(%d, %f, %g\n)", x, y, z);

    return 0;
}

Đây là những gì nó in:

(55, 3.9, 24.15)

Giải pháp có thể không khả thi vì nó yêu cầu C99 trở lên cho các macro đa dạng và khai báo biến for-statement. Nhưng tôi nghĩ rằng nó đã đủ thú vị để đăng ở đây. Một vấn đề khác là trình biên dịch sẽ không cảnh báo bạn nếu bạn gán giá trị sai cho chúng, vì vậy bạn phải cẩn thận.

Các ví dụ bổ sung và phiên bản mã dựa trên ngăn xếp bằng cách sử dụng công đoàn, có sẵn tại kho lưu trữ github của tôi .


1
Một vấn đề lớn hơn về tính di động là tính năng không chuẩn__typeof__
MM

Thay vì typeof, bạn có thể sử dụng sizeof. Lấy kích thước của các giá trị trả về và phân bổ một mảng đủ lớn để lưu trữ tất cả. Sau đó, bạn có thể trả lại nó.
Klas. S
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.