2011-01-17 1 views

risposta

44

In C, questo richiede un puntatore a un array di 16 interi:

void special_case(int (*array)[16]); 

Si sarebbe chiamato con:

int array[16]; 
special_case(&array); 

In C++, è possibile utilizzare un riferimento ad un array, troppo, come mostrato nella risposta di Nawaz. (La domanda chiede C nel titolo, e inizialmente menzionato solo C++ nei tag.)


Qualsiasi versione che utilizza una variante di:

void alternative(int array[16]); 

finisce per essere equivalente a:

void alternative(int *array); 

che in pratica accetterà qualsiasi dimensione di matrice.


La domanda viene posta - vuol special_case() davvero prevenire una dimensione diversa di matrice da essere passato. La risposta è si'.

void special_case(int (*array)[16]); 

void anon(void) 
{ 

    int array16[16]; 
    int array18[18]; 
    special_case(&array16); 
    special_case(&array18); 
} 

Il compilatore (GCC 4.5.2 su MacOS X 10.6.6, come accade) si lamenta (avverte):

$ gcc -c xx.c 
xx.c: In function ‘anon’: 
xx.c:9:5: warning: passing argument 1 of ‘special_case’ from incompatible pointer type 
xx.c:1:6: note: expected ‘int (*)[16]’ but argument is of type ‘int (*)[18]’ 
$ 

Cambia per GCC 4.2.1 - come previsto da Apple - e l'avviso è:

$ /usr/bin/gcc -c xx.c 
xx.c: In function ‘anon’: 
xx.c:9: warning: passing argument 1 of ‘special_case’ from incompatible pointer type 
$ 

L'avviso in 4.5.2 è migliore, ma la sostanza è la stessa.

+0

In realtà impedirebbe all'utente di eseguire un array [17] o array [15]? quando penso a cosa sia esattamente int [], sullo sfondo è un puntatore int ad un array non dinamico. Quindi il programma/il compilatore rifiuterebbe automaticamente una lunghezza diversa, o lo accetterebbe e cercherebbe di gestirlo nel miglior modo possibile? –

+0

Questo è esattamente giusto, ho cancellato la mia risposta dopo il test. Ho pensato che avrei potuto diventare complicato con un altro uso per static (AUFS) e andare con 'void foo di C99 (int a [statico 16]);' ma ho appena realizzato che garantisce * almeno * 16 membri non * esattamente * come voluto da l'OP – SiegeX

+0

Quindi, se passo un array che non è di dimensione 16, non verrà compilato? diciamo che ho int intray [20]; special_case (e intArray). Questo verrà compilato? – armanali

6

& è necessaria in C++:

void foo(int (&a)[16]); // & is necessary. (in C++) 

Nota: & è necessario, altrimenti si può passare array di qualsiasi dimensione!


Per C:

void foo(int (*a)[16]) //one way 
{ 
} 

typedef int (*IntArr16)[16]; //other way 
void bar(IntArr16 a) 
{ 
} 

int main(void) 
{ 
     int a[16]; 
     foo(&a); //call like this - otherwise you'll get warning! 
     bar(&a); //call like this - otherwise you'll get warning! 
     return 0; 
} 

Demo: http://www.ideone.com/fWva6

+2

I riferimenti non fanno parte del linguaggio C. –

+1

@Stephen: hai ragione - ma mentre il titolo richiede C, i tag includono C++. –

+0

@Jonathan: sei rito ma quando ci occupiamo di concetti come questi c o C++ è lo stesso ... non è così ... ora ho aggiunto C++ nel Que – sriks

2

Penso che il modo più semplice per essere typesafe sarebbe quella di dichiarare una struct che contiene la matrice, e passare che:

struct Array16 { 
    int elt[16]; 
}; 


void Foo(struct Array16* matrix); 
11

Esistono diversi modi per dichiarare arr ay-parametri di dimensione fissa:

void foo(int values[16]); 

declina qualsiasi puntatore-to int, ma la matrice di dimensioni serve come documentazione

void foo(int (*values)[16]); 

accetta un puntatore a un array con esattamente 16 elementi

void foo(int values[static 16]); 

accetta un puntatore al primo elemento di un array con almeno 16 elementi

struct bar { int values[16]; }; 
void foo(struct bar bar); 

accetta una struttura che racchiude un array con esattamente 16 elementi, passandoli per valore.

+1

Nel 'void foo (int values ​​[statico 16]);' case, lo standard C non richiede che il compilatore verifichi che l'argomento indicato sia corretto. E, AFAIK, nessun compilatore nemmeno tenta di farlo. Quindi, mentre è bello in teoria, al momento attuale non ti dà alcuna sicurezza - non ci saranno avvisi se lo chiami con un array più piccolo. Speriamo che i compilatori miglioreranno. –

+0

In realtà, clang dà un avvertimento se passi un array troppo corto. Non dà alcun avvertimento se si passa '' & local_int'', sebbene, anche se questo significa che si sta effettivamente dando una matrice di lunghezza 1. –

1

Hai già alcune risposte per C e una risposta per C++, ma c'è un altro modo per farlo in C++.

Come detto Nawaz, per passare una matrice di dimensione N, è possibile farlo in C++:

const size_t N = 16; // For your question. 

void foo(int (&arr)[N]) { 
    // Do something with arr. 
} 

Tuttavia, come di C++ 11, è anche possibile utilizzare std :: contenitore array, che può essere passato con una sintassi più naturale (assumendo una certa familiarità con la sintassi del template).

#include <array> 

const size_t N = 16; 

void bar(std::array<int, N> arr) { 
    // Do something with arr. 
} 

Come contenitore, std :: matrice permette principalmente la stessa funzionalità di una normale matrice stile C, mentre anche l'aggiunta funzionalità aggiuntive.

std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 }; 
int arr2[5] = { 1, 2, 3, 4, 5 }; 

// Operator[]: 
for (int i = 0; i < 5; i++) { 
    assert(arr1[i] == arr2[i]); 
} 

// Fill: 
arr1.fill(0); 
for (int i = 0; i < 5; i++) { 
    arr2[i] = 0; 
} 

// Check size: 
size_t arr1Size = arr1.size(); 
size_t arr2Size = sizeof(arr2)/sizeof(arr2[0]); 

// Foreach (C++11 syntax): 
for (int &i : arr1) { 
    // Use i. 
} 
for (int &i : arr2) { 
    // Use i. 
} 

Tuttavia, a mia conoscenza (che è certamente limitata al momento), l'aritmetica dei puntatori non è al sicuro con std :: array di meno di utilizzare i dati funzione membro() per ottenere l'indirizzo della matrice reale prima. Questo è sia per prevenire future modifiche alla classe std :: array dalla rottura del codice, sia perché alcune implementazioni STL possono memorizzare ulteriori dati oltre alla matrice effettiva.


Si noti che questo sarebbe più utile per il nuovo codice, o se si converte il codice pre-esistente da utilizzare std :: array invece di array in stile C. Dato che std :: array sono tipi aggregati, mancano costruttori personalizzati e quindi non è possibile passare direttamente dall'array in stile C a std :: array (a meno di usare un cast, ma è brutto e può potenzialmente causare problemi in futuro).Per convertirli, si avrebbe invece bisogno di usare qualcosa di simile:

#include <array> 
#include <algorithm> 

const size_t N = 16; 

std::array<int, N> cArrayConverter(int (&arr)[N]) { 
    std::array<int, N> ret; 

    std::copy(std::begin(arr), std::end(arr), std::begin(ret)); 

    return ret; 
} 

Pertanto, se il codice utilizza le matrici in stile C e sarebbe fattibile per convertirlo per usare std :: array, invece, si sarebbe meglio attenersi agli array in stile C.

(Nota: ho specificato dimensioni come N in modo da poter più facilmente riutilizzare il codice ovunque ne abbiate bisogno.)


Edit: Ci sono alcune cose che ho dimenticato di menzionare:

1) La maggior parte delle funzioni di libreria standard C++ progettate per operare su container sono indipendenti dall'implementazione; invece di essere progettati per contenitori specifici, operano su intervalli, utilizzando iteratori. (Questo significa anche che lavorano per std::basic_string e istanze della stessa, come ad esempio std::string.) Ad esempio, std::copy ha il seguente prototipo:

template <class InputIterator, class OutputIterator> 
OutputIterator copy(InputIterator first, InputIterator last, 
        OutputIterator result); 
// first is the beginning of the first range. 
// last is the end of the first range. 
// result is the beginning of the second range. 

Anche se questo può sembrare imponente, in genere non è necessario specificare il modello parametri, e può semplicemente lasciare che il compilatore gestisca quello per te.

std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 }; 
std::array<int, 5> arr2 = { 6, 7, 8, 9, 0 }; 
std::string str1 = ".dlrow ,olleH"; 
std::string str2 = "Overwrite me!"; 

std::copy(arr1.begin(), arr1.end(), arr2.begin()); 
// arr2 now stores { 1, 2, 3, 4, 5 }. 

std::copy(str1.begin(), str1.end(), str2.begin()); 
// str2 now stores ".dlrow ,olleH". 
// Not really necessary for full string copying, due to std::string.operator=(), but possible nonetheless. 

causa basandosi su iteratori, queste funzioni sono anche compatibili con array C-stile (come iteratori sono una generalizzazione di puntatori, tutti i puntatori sono per definizione iteratori (ma non tutti iteratori sono necessariamente puntatori)). Questo può essere utile quando si lavora con codice legacy, poiché significa che si ha pieno accesso alle funzioni di intervallo nella libreria standard.

int arr1[5] = { 4, 3, 2, 1, 0 }; 
std::array<int, 5> arr2; 

std::copy(std::begin(arr1), std::end(arr1), std::begin(arr2)); 

Avrete notato da questo esempio e l'ultima che std::array.begin() e std::begin() possono essere usati in modo intercambiabile con std::array. Questo perché std::begin() e std::end() sono implementati in modo tale che per qualsiasi contenitore, hanno lo stesso tipo di ritorno e restituiscono lo stesso valore, come chiamando le funzioni membro begin() e end() di un'istanza di quel contenitore.

// Prototype: 
template <class Container> 
auto begin (Container& cont) -> decltype (cont.begin()); 

// Examples: 
std::array<int, 5> arr; 
std::vector<char> vec; 

std::begin(arr) == arr.begin(); 
std::end(arr) == arr.end(); 

std::begin(vec) == vec.begin(); 
std::end(vec) == vec.end(); 

// And so on... 

array C-stile non hanno funzioni membro, che richiede l'uso di std::begin() e std::end() per loro. In questo caso, le due funzioni sono sovraccariche per fornire i puntatori applicabili, in base al tipo di matrice.

// Prototype: 
template <class T, size_t N> 
T* begin (T(&arr)[N]); 

// Examples: 
int arr[5]; 

std::begin(arr) == &arr[0]; 
std::end(arr) == &arr[4]; 

Come regola generale, se non siete sicuri circa o meno qualsiasi segmento particolare codice dovrà utilizzare le matrici in stile C, è più sicuro da usare std::begin() e std::end().

[Si noti che mentre si utilizzava std::copy() come esempio, l'uso di intervalli ed iteratori è molto comune nella libreria standard. La maggior parte, se non tutte, le funzioni progettate per operare su container (o più specificamente, qualsiasi implementazione di Container concept, come std::array, std::vector e std::string) utilizzano intervalli, rendendoli compatibili con qualsiasi contenitore attuale e futuro, nonché con C matrici in stile. Potrebbero esserci delle eccezioni a questa diffusa compatibilità di cui non sono a conoscenza.]

2) Quando si passa uno std :: array per valore, è possibile che si verifichi un notevole sovraccarico, a seconda della dimensione dell'array.Di conseguenza, di solito è meglio passarlo per riferimento o utilizzare iteratori (come la libreria standard).

// Pass by reference. 
const size_t N = 16; 

void foo(std::array<int, N>& arr); 

3) Tutti questi esempi presuppongono che tutte le matrici nel codice saranno uguali dimensioni, come specificato dalla costante N. Per rendere il tuo codice più indipendente dall'implementazione, puoi utilizzare gli intervalli & iteratori oppure, se desideri mantenere il codice focalizzato sugli array, utilizzare le funzioni basate su modelli. [Sulla this answer to another question.]

template<size_t SZ> void foo(std::array<int, SZ>& arr); 

... 

std::array<int, 5> arr1; 
std::array<int, 10> arr2; 

foo(arr1); // Calls foo<5>(arr1). 
foo(arr2); // Calls foo<10>(arr2). 

Se facendo questo, si può anche andare fino al punto di modello di tipo di nonché membro dell'array, a condizione che il codice può operare su tipi diversi int.

template<typename T, size_t SZ> 
void foo(std::array<T, SZ>& arr); 

... 

std::array<int, 5> arr1; 
std::array<float, 7> arr2; 

foo(arr1); // Calls foo<int, 5>(arr1). 
foo(arr2); // Calls foo<float, 7>(arr2). 

Per un esempio di questo in azione, vedere here.


Se qualcuno vede degli errori che potrei aver perso, sentitevi liberi di indicarli per me di correggerli o di correggerli voi stessi. Penso di averli presi tutti, ma non ne sono sicuro al 100%.