int * vs int [N] vs int (*) [N] nei parametri delle funzioni. Quale pensi sia meglio?

5

Quando si programma in C (o C ++) ci sono tre modi diversi per specificare il parametro in una funzione che accetta una matrice.

Ecco un esempio (implementando std::accumulate da C ++ in C) che mostra cosa intendo.

Posso scrivere in questo modo:

int accumulate(int n, int *array)
{
    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) {
        sum += array[i];
    }
    return sum;
}

Questo può anche essere scritto su questo (che significa la stessa cosa):

int accumulate(int n, int array[])
{
    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) {
        sum += array[i];
    }
    return sum;
}

Posso anche scrivere in questo modo:

int accumulate(int n, int (*array)[])
{
    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) {
        sum += (*array)[i];
    }
    return sum;
}

Tutte queste opzioni sono molto simili e generano lo stesso codice eseguibile ma hanno una leggera differenza, ovvero il modo in cui il chiamante passa gli argomenti.

Ecco come vengono richiamate le prime due opzioni:

int main(void)
{
    int a[] = {3, 4, 2, 4, 6, 1, -40, 23, 35};
    printf("%d\n", accumulate(ARRAY_LENGTH(a), a));
    return 0;
}

Ecco come viene chiamata l'opzione thrid:

int main(void)
{
    int a[] = {3, 4, 2, 4, 6, 1, -40, 23, 35};
    printf("%d\n", accumulate(ARRAY_LENGTH(a), &a));
    return 0;
}

Si noti che la terza opzione richiede all'utente di specificare esplicitamente l'indirizzo di a con &a . Le prime due opzioni non richiedono questo perché gli array vengono convertiti implicitamente in puntatori dello stesso tipo in C.

Ho sempre preferito il terzo approccio.

Ecco perché:

  • È più coerente con il modo in cui altri tipi vengono passati dai puntatori.

    int draw_point(struct point *p);
    
    int main()
    {
        struct point p = {3, 4};
        draw_point(&p); // Here is the 'address of' operator required.
    }
    
  • Rende possibile utilizzare macro come ARRAY_LENGTH per ottenere la quantità di elementi nella matrice.

    #include <stdio.h>
    #define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))
    
    void this_works(int (*array)[10])
    {
        /* This works! */
        printf("%d\n", ARRAY_LENGTH(*array));
    }
    
    void this_is_invalid_and_dangerous(int array[10])
    {
        /* This does NOT work because 'array' is actually a pointer. */
        printf("%d\n", ARRAY_LENGTH(array));
    }
    

L'unico vantaggio che vedo con int array[] (e int *array ) è che puoi scrivere array[X] invece di (*array)[X] quando vuoi prendere un indice.

Quali sono i tuoi pensieri professionali su questo? Quale approccio pensi sia migliore e perché? Hai mai mescolato i due? Se sì, quando scegli cosa?

Come ho detto, ho sempre preferito usare int (*array)[N] , ma vedo che anche gli altri due approcci sono abbastanza comuni.

    
posta wefwefa3 10.01.2015 - 13:49
fonte

4 risposte

7

In pratica, vedrai

int accumulate( int n, int *array)

il più delle volte. È il più flessibile (può gestire matrici di dimensioni diverse) e il più fedelmente riflette ciò che sta accadendo sotto il cofano.

Non vedrai

int accumulate( int (*array)[N] )

come spesso, dal momento che assume una specifica dimensione di array (la dimensione deve essere specificata).

Se il compilatore supporta la sintassi degli array a lunghezza variabile, puoi fare

int accumulate( size_t n, int (*array)[n] )

ma non so quanto sia comune.

    
risposta data 11.01.2015 - 15:49
fonte
3

In C, quando la notazione dell'array viene utilizzata per un parametro di funzione, viene automaticamente trasformata in una dichiarazione di puntatore, quindi dichiarando i parametri come int * array e int array [] sono equivalenti. Tendo ad usare il secondo perché è più chiaro che la funzione si aspetta un array come argomento.

La dichiarazione del parametro della funzione come int (* array) [] non è equivalente alle precedenti due, viene trasformata in int ** e dovrebbe essere utilizzata quando il valore del parametro stesso potrebbe essere modificato in funzione, ovvero la riallocazione dell'array.

    
risposta data 10.01.2015 - 15:54
fonte
2

Dici che ci sono 3 modi in C e C ++, ma C ++ ne rende effettivamente disponibile un quarto:

template<std::size_t n>
void arrayFunction(std::array<int,n> &array)
{ ...}

Questo ha molti vantaggi rispetto alle soluzioni suggerite:

  • Il parametro per la dimensione dell'array verrà determinato automaticamente in uso dal compilatore, il che significa che non devi specificarlo
  • La dimensione è una costante di tempo di compilazione, il che significa che il compilatore può eseguire molte ottimizzazioni che sono impossibili per le versioni suggerite
  • È impossibile specificare una dimensione errata, poiché la dimensione fa parte del tipo di array
risposta data 11.01.2015 - 16:45
fonte
2

La prima dichiarazione consente inoltre di scrivere la funzione in modo diverso:

int accumulate(int n, int *array)
{
    int sum = 0;
    while (n-- > 0) sum += *array++;
    return sum;
}

quindi non hai bisogno della variabile i.

Qualunque cosa sia idiomatica per la base di codice dovrebbe essere preferita, seguita da qualunque sia la più facile da capire, seguita a una certa distanza da qualunque sia la più performante. La seconda dichiarazione è la più facile da capire.

    
risposta data 12.01.2015 - 04:26
fonte

Leggi altre domande sui tag