Como faço para retornar vários valores de uma função em C?

87

Se eu tenho uma função que produz um resultado inte um resultado string, como faço para retornar os dois de uma função?

Até onde sei, só posso retornar uma coisa, conforme determinado pelo tipo que precede o nome da função.

Tony Stark
fonte
6
Por stringque você quer dizer "Eu estou usando C ++ e esta é a std::stringclasse" ou "Eu estou usando C e este é um char *ponteiro ou char[]matriz."
Chris Lutz,
bem, no meu caso particular, eram dois ints: um para a 'pontuação' do que eu estava comparando e um para o 'índice' de onde a pontuação máxima foi encontrada. Eu queria usar um exemplo de string aqui apenas para o caso mais geral
Tony Stark
Passe a string por referência e devolva o int. Maneira mais rápida. Nenhuma estrutura necessária.
Stefan Steiger,
1
Uma função que retorna 2 resultados não faz mais de uma coisa? O que o tio Bob diria?
Duncan,

Respostas:

123

Não sei qual stringé o seu , mas vou presumir que ele administra sua própria memória.

Você tem duas soluções:

1: Retorne um structque contém todos os tipos de que você precisa.

struct Tuple {
    int a;
    string b;
};

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

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

2: Use ponteiros para passar valores.

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

Qual você escolhe usar depende muito da preferência pessoal quanto à semântica que você mais gosta.

Travis Gockel
fonte
7
Acho que depende mais de como os valores de retorno estão relacionados. Se o int for um código de erro e a string for um resultado, eles não devem ser colocados juntos em uma estrutura. Isso é simplesmente bobo. Nesse caso, eu retornaria o int e passaria a string como ae char *a size_tpara o comprimento, a menos que seja absolutamente vital para a função alocar sua própria string e / ou retornar NULL.
Chris Lutz,
@Chris concordo totalmente com você, mas não tenho ideia da semântica de uso das variáveis ​​de que ele precisa.
Travis Gockel,
Bom ponto, Chris. Outra coisa que acho que vale a pena destacar é o valor versus referência. Se não me engano, retornar a estrutura conforme mostrado no exemplo significa que uma cópia será feita na devolução, correto? (Estou um pouco trêmulo em C) Enquanto o outro método usa passagem por referência e, portanto, não requer alocação de mais memória. Claro, a estrutura poderia ser retornada por meio de um ponteiro e compartilhar o mesmo benefício, certo? (certificando-se de alocar a memória corretamente e tudo isso, é claro)
RTHarston
@BobVicktor: C não tem nenhuma semântica de referência (que é exclusiva do C ++), então tudo é um valor. A solução dos dois ponteiros (# 2) é passar cópias dos ponteiros para uma função, que getPairentão desreferencia . Dependendo do que você está fazendo (OP nunca esclareceu se esta é realmente uma questão C), a alocação pode ser uma preocupação, mas geralmente não está no terreno C ++ (a otimização do valor de retorno salva tudo isso) e no terreno C, os dados as cópias geralmente acontecem explicitamente (via strncpyou qualquer outro).
Travis Gockel
@TravisGockel Obrigado pela correção. Eu estava me referindo ao fato de que ponteiros são usados, então ele não está copiando os valores, apenas compartilhando o que já foi alocado. Mas você está certo ao dizer que isso não é apropriadamente chamado de passagem por referência em C. E obrigado também pelas outras grandes pepitas de conhecimento. Adoro aprender essas coisinhas sobre idiomas. :)
RTHarston
10

Option 1: Declara uma estrutura com um int e uma string e retorna uma variável de estrutura.

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

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

Option 2: Você pode passar um dos dois via ponteiro e fazer alterações no parâmetro real por meio do ponteiro e retornar o outro como de costume:

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

ou

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

Option 3: Semelhante à opção 2. Você pode passar via ponteiro e não retornar nada da função:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}
codadicto
fonte
Em relação à opção 2, você também deve passar o comprimento da string. int fun(char *param, size_t len)
Chris Lutz,
Agora, isso depende do que a função está fazendo. Se soubermos que tipo de resultado a função insere no array char, poderíamos apenas alocar espaço suficiente para ele e passá-lo para a função. Não há necessidade de passar no comprimento. Algo semelhante ao que usamos, strcpypor exemplo.
codaddict de
7

Como um de seus tipos de resultado é uma string (e você está usando C, não C ++), recomendo passar ponteiros como parâmetros de saída. Usar:

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

e chamá-lo assim:

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

Em geral, prefira fazer a alocação na função de chamada , não dentro da própria função, para que você possa estar o mais aberto possível para diferentes estratégias de alocação.

Jesse Beder
fonte
6

Duas abordagens diferentes:

  1. Passe seus valores de retorno por ponteiro e modifique-os dentro da função. Você declara sua função como nula, mas ela está retornando por meio dos valores passados ​​como ponteiros.
  2. Defina uma estrutura que agregue seus valores de retorno.

Acho que o nº 1 é um pouco mais óbvio sobre o que está acontecendo, embora possa se tornar tedioso se você tiver muitos valores de retorno. Nesse caso, a opção nº 2 funciona muito bem, embora haja alguma sobrecarga mental envolvida na criação de estruturas especializadas para esse propósito.

James Thompson
fonte
1
C não tem referências ;-), embora, como o pôster usado string, pode ser seguro assumir C ++ ...
Travis Gockel
Esqueci completamente disso! Modifiquei minha resposta para usar ponteiros, mas estou claramente na terra do C ++ há muito tempo. :)
James Thompson
6

Crie uma estrutura e defina dois valores dentro e retorne a variável de estrutura.

struct result {
    int a;
    char *string;
}

Você deve alocar espaço para o char *em seu programa.

zs2020
fonte
3

Use ponteiros como parâmetros de sua função. Em seguida, use-os para retornar vários valores.

Bloodlee
fonte
2

Passando parâmetros por referência à função.

Exemplos:

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

Também usando variáveis ​​globais, mas não é recomendado.

Exemplo:

int a=0;

void main(void)
{
    //Anything you want to code.
}
Badr
fonte
4
void main(void)OH, COMO QUEIMA!
Chris Lutz,
O que você quer dizer? @ Chris Lutz
Badr
6
maindeve retornar um código de status. Em * nix, é mais comum declará-lo como int main(int argc, char *argv[]), acredito que o Windows tem convenções semelhantes.
Duncan,
2

Uma abordagem é usar macros. Coloque isso em um arquivo de cabeçalhomultitype.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__)

Isso possibilita retornar até quatro variáveis ​​de uma função e atribuí-las a até quatro variáveis. Como exemplo, você pode usá-los assim:

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

Isto é o que imprime:

(55, 3.9, 24.15)

A solução pode não ser tão portátil porque requer C99 ou posterior para macros variáveis ​​e declarações de variáveis ​​para instruções. Mas acho que foi interessante postar aqui. Outro problema é que o compilador não irá avisá-lo se você atribuir a eles os valores errados, então você deve ter cuidado.

Exemplos adicionais e uma versão baseada em pilha do código usando uniões estão disponíveis em meu repositório github .

Klas. S
fonte
1
Um problema maior de portabilidade é o recurso não padrão__typeof__
MM
Em vez de typeof, você provavelmente poderia usar sizeof. Obtenha o tamanho dos valores de retorno e aloque um array grande o suficiente para armazená-los todos. Então você pode devolvê-lo.
Klas. S