C Language
Array
Ricerca…
introduzione
Le matrici sono tipi di dati derivati, che rappresentano una raccolta ordinata di valori ("elementi") di un altro tipo. La maggior parte degli array in C ha un numero fisso di elementi di qualsiasi tipo, e la sua rappresentazione memorizza gli elementi in modo contiguo nella memoria senza spazi vuoti o padding. C consente array multidimensionali i cui elementi sono altri array e anche matrici di puntatori.
C supporta matrici allocate dinamicamente le cui dimensioni sono determinate in fase di esecuzione. C99 e versioni successive supporta matrici di lunghezza variabile o VLA.
Sintassi
- nome del tipo [lunghezza]; / * Definisce la matrice di 'tipo' con nome 'nome' e lunghezza 'lunghezza'. * /
- int arr [10] = {0}; / * Definire una matrice e inizializzare TUTTI gli elementi a 0. * /
- int arr [10] = {42}; / * Definire una matrice e inizializzare i primi elementi a 42 e il resto a 0. * /
- int arr [] = {4, 2, 3, 1}; / * Definire e inizializzare un array di lunghezza 4. * /
- arr [n] = valore; / * Imposta il valore all'indice n. * /
- value = arr [n]; / * Ottieni valore all'indice n. * /
Osservazioni
Perché abbiamo bisogno di array?
Gli array forniscono un modo per organizzare gli oggetti in un aggregato con il proprio significato. Ad esempio, le stringhe C sono matrici di caratteri ( char
s) e una stringa come "Hello, World!" ha un significato come un aggregato che non è inerente ai personaggi individualmente. Allo stesso modo, gli array sono comunemente usati per rappresentare vettori e matrici matematiche, così come liste di molti tipi. Inoltre, senza un modo per raggruppare gli elementi, bisognerebbe affrontarli individualmente, ad esempio tramite variabili separate. Non solo è ingombrante, non ospita facilmente raccolte di lunghezze diverse.
Gli array sono convertiti implicitamente in puntatori nella maggior parte dei contesti .
Tranne quando appare come l'operando dell'operatore sizeof
, l'operatore _Alignof
(C2011), o l'operatore unario &
(indirizzo-di), o come letterale stringa usato per inizializzare un (altro) array, una matrice viene convertita implicitamente in ( "decade") un puntatore al suo primo elemento. Questa conversione implicita è strettamente accoppiata alla definizione dell'operatore di sottoscrizione dell'array ( []
): l'espressione arr[idx]
è definita come equivalente a *(arr + idx)
. Inoltre, poiché l'aritmetica del puntatore è commutativa, *(arr + idx)
è anche equivalente a *(idx + arr)
, che a sua volta è equivalente a idx[arr]
. Tutte queste espressioni sono valide e valutano allo stesso valore, a condizione che idx
o arr
sia un puntatore (o un array, che decompone a un puntatore), l'altro è un numero intero e il numero intero è un indice valido nell'array a cui punta il puntatore.
Come caso speciale, osserva che &(arr[0])
è equivalente a &*(arr + 0)
, che semplifica l' arr
. Tutte queste espressioni sono intercambiabili ovunque l'ultimo decadimento di un puntatore. Questo semplicemente esprime ancora una volta che un array decade in un puntatore al suo primo elemento.
Al contrario, se l'operatore address-of viene applicato a una matrice di tipo T[N]
( ie &arr
), allora il risultato ha tipo T (*)[N]
e punta all'intero array. Questo è distinto da un puntatore al primo elemento dell'array, almeno rispetto all'aritmetica del puntatore, che è definita in termini della dimensione del tipo puntato.
I parametri di funzione non sono matrici .
void foo(int a[], int n);
void foo(int *a, int n);
Sebbene la prima dichiarazione di foo
utilizzi la sintassi di tipo array per il parametro a
, tale sintassi viene utilizzata per dichiarare un parametro di funzione che dichiara tale parametro come puntatore al tipo di elemento dell'array. Pertanto, la seconda firma per foo()
è semanticamente identica alla prima. Ciò corrisponde al decadimento dei valori di matrice nei puntatori in cui vengono visualizzati come argomenti di una chiamata di funzione, in modo tale che se una variabile e un parametro di funzione vengono dichiarati con lo stesso tipo di matrice, il valore di tale variabile è adatto per l'uso in una chiamata di funzione come argomento associato al parametro.
Dichiarazione e inizializzazione di un array
La sintassi generale per dichiarare un array monodimensionale è
type arrName[size];
dove type
può essere un tipo built-in o tipi definiti dall'utente come le strutture, arrName
è un identificatore definito dall'utente e la size
è una costante intera.
La dichiarazione di una matrice (una matrice di 10 variabili int in questo caso) è fatta in questo modo:
int array[10];
ora contiene valori indeterminati. Per assicurarti che mantenga valori zero durante la dichiarazione, puoi farlo:
int array[10] = {0};
Le matrici possono anche avere inizializzatori, questo esempio dichiara un array di 10 int
's, dove i primi 3 int
conterranno i valori 1
, 2
, 3
, tutti gli altri valori saranno zero:
int array[10] = {1, 2, 3};
Nel suddetto metodo di inizializzazione, il primo valore nell'elenco sarà assegnato al primo membro dell'array, il secondo valore sarà assegnato al secondo membro dell'array e così via. Se la dimensione dell'elenco è inferiore alla dimensione dell'array, come nell'esempio precedente, i membri rimanenti dell'array verranno inizializzati su zeri. Con l'inizializzazione della lista designata (ISO C99), è possibile l'inizializzazione esplicita dei membri dell'array. Per esempio,
int array[5] = {[2] = 5, [1] = 2, [4] = 9}; /* array is {0, 2, 5, 0, 9} */
Nella maggior parte dei casi, il compilatore può dedurre la lunghezza dell'array per te, questo può essere ottenuto lasciando vuote le parentesi quadre:
int array[] = {1, 2, 3}; /* an array of 3 int's */
int array[] = {[3] = 8, [0] = 9}; /* size is 4 */
La dichiarazione di una matrice di lunghezza zero non è consentita.
Array a lunghezza variabile (VLA in breve) sono stati aggiunti in C99 e resi facoltativi in C11. Sono uguali agli array normali, con una differenza importante: la lunghezza non deve essere nota al momento della compilazione. Gli VLA hanno una durata di archiviazione automatica. Solo i riferimenti agli VLA possono avere una durata di archiviazione statica.
size_t m = calc_length(); /* calculate array length at runtime */
int vla[m]; /* create array with calculated length */
Importante:
I VLA sono potenzialmente pericolosi. Se l'array vla
nell'esempio sopra richiede più spazio nello stack rispetto a quello disponibile, lo stack andrà in overflow. L'utilizzo di VLA è quindi spesso scoraggiato da guide di stile e da libri ed esercizi.
Cancellazione dei contenuti dell'array (azzeramento)
A volte è necessario impostare un array su zero, dopo che l'inizializzazione è stata eseguita.
#include <stdlib.h> /* for EXIT_SUCCESS */
#define ARRLEN (10)
int main(void)
{
int array[ARRLEN]; /* Allocated but not initialised, as not defined static or global. */
size_t i;
for(i = 0; i < ARRLEN; ++i)
{
array[i] = 0;
}
return EXIT_SUCCESS;
}
Una scorciatoia comune al ciclo precedente consiste nell'utilizzare memset()
da <string.h>
. Il passaggio array
come mostrato di seguito lo fa decadere da un puntatore al suo 1 ° elemento.
memset(array, 0, ARRLEN * sizeof (int)); /* Use size explicitly provided type (int here). */
o
memset(array, 0, ARRLEN * sizeof *array); /* Use size of type the pointer is pointing to. */
Come in questo esempio, l' array
è un array e non solo un puntatore al primo elemento di un array (vedi la lunghezza dell'array sul perché questo è importante) è possibile una terza opzione per 0-out dell'array:
memset(array, 0, sizeof array); /* Use size of the array itself. */
Lunghezza della matrice
Gli array hanno lunghezze fisse che sono note nell'ambito delle loro dichiarazioni. Tuttavia, è possibile ea volte conveniente calcolare le lunghezze degli array. In particolare, questo può rendere il codice più flessibile quando la lunghezza dell'array viene determinata automaticamente da un inizializzatore:
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
/* size of `array` in bytes */
size_t size = sizeof(array);
/* number of elements in `array` */
size_t length = sizeof(array) / sizeof(array[0]);
Tuttavia, nella maggior parte dei contesti in cui un array appare in un'espressione, viene automaticamente convertito in ("decays to") un puntatore al suo primo elemento. Il caso in cui un array è l'operando dell'operatore sizeof
è uno di un piccolo numero di eccezioni. Il puntatore risultante non è di per sé un array e non contiene alcuna informazione sulla lunghezza dell'array da cui è stato derivato. Pertanto, se tale lunghezza è necessaria insieme al puntatore, ad esempio quando il puntatore viene passato a una funzione, deve essere convogliato separatamente.
Ad esempio, supponiamo di voler scrivere una funzione per restituire l'ultimo elemento di una matrice di int
. Continuando da quanto sopra, potremmo chiamarlo così:
/* array will decay to a pointer, so the length must be passed separately */
int last = get_last(array, length);
La funzione potrebbe essere implementata in questo modo:
int get_last(int input[], size_t length) {
return input[length - 1];
}
Si noti in particolare che sebbene la dichiarazione input
dei parametri sia simile a quella di un array, in realtà dichiara l' input
come un puntatore (su int
). È esattamente equivalente a dichiarare input
come input
int *input
. Lo stesso sarebbe vero anche se venisse data una dimensione. Ciò è possibile perché gli array non possono mai essere argomenti reali delle funzioni (decadono in base ai puntatori quando compaiono nelle espressioni di chiamata di funzione) e possono essere visualizzati come mnemonici.
È un errore molto comune tentare di determinare la dimensione dell'array da un puntatore, che non può funzionare. NON FARLO:
int BAD_get_last(int input[]) {
/* INCORRECTLY COMPUTES THE LENGTH OF THE ARRAY INTO WHICH input POINTS: */
size_t length = sizeof(input) / sizeof(input[0]));
return input[length - 1]; /* Oops -- not the droid we are looking for */
}
In effetti, quel particolare errore è così comune che alcuni compilatori lo riconoscono e lo mettono in guardia. clang
, ad esempio, emetterà il seguente avviso:
warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
int length = sizeof(input) / sizeof(input[0]);
^
note: declared here
int BAD_get_last(int input[])
^
Impostazione dei valori negli array
L'accesso ai valori dell'array avviene generalmente tramite parentesi quadre:
int val;
int array[10];
/* Setting the value of the fifth element to 5: */
array[4] = 5;
/* The above is equal to: */
*(array + 4) = 5;
/* Reading the value of the fifth element: */
val = array[4];
Come effetto collaterale degli operandi verso l'operatore +
che è scambiabile (-> legge commutativa) il seguente è equivalente:
*(array + 4) = 5;
*(4 + array) = 5;
così come le prossime affermazioni sono equivalenti:
array[4] = 5;
4[array] = 5; /* Weird but valid C ... */
e anche loro due:
val = array[4];
val = 4[array]; /* Weird but valid C ... */
C non esegue alcuna verifica dei limiti, l'accesso ai contenuti al di fuori dell'array dichiarato non è definito ( Accesso alla memoria oltre il blocco assegnato ):
int val;
int array[10];
array[4] = 5; /* ok */
val = array[4]; /* ok */
array[19] = 20; /* undefined behavior */
val = array[15]; /* undefined behavior */
Definire l'array e accedere all'elemento dell'array
#include <stdio.h>
#define ARRLEN (10)
int main (void)
{
int n[ ARRLEN ]; /* n is an array of 10 integers */
size_t i, j; /* Use size_t to address memory, that is to index arrays, as its guaranteed to
be wide enough to address all of the possible available memory.
Using signed integers to do so should be considered a special use case,
and should be restricted to the uncommon case of being in the need of
negative indexes. */
/* Initialize elements of array n. */
for ( i = 0; i < ARRLEN ; i++ )
{
n[ i ] = i + 100; /* Set element at location i to i + 100. */
}
/* Output each array element's value. */
for (j = 0; j < ARRLEN ; j++ )
{
printf("Element[%zu] = %d\n", j, n[j] );
}
return 0;
}
Allocare e azzerare l'inizializzazione di un array con dimensioni definite dall'utente
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int * pdata;
size_t n;
printf ("Enter the size of the array: ");
fflush(stdout); /* Make sure the prompt gets printed to buffered stdout. */
if (1 != scanf("%zu", &n)) /* If zu is not supported (Windows?) use lu. */
{
fprintf("scanf() did not read a in proper value.\n");
exit(EXIT_FAILURE);
}
pdata = calloc(n, sizeof *pdata);
if (NULL == pdata)
{
perror("calloc() failed"); /* Print error. */
exit(EXIT_FAILURE);
}
free(pdata); /* Clean up. */
return EXIT_SUCCESS;
}
Questo programma tenta di eseguire la scansione di un valore intero senza segno dall'input standard, allocare un blocco di memoria per un array di n
elementi di tipo int
chiamando la funzione calloc()
. La memoria è inizializzata a tutti gli zeri da quest'ultimo.
In caso di successo, la memoria viene rilasciata dalla chiamata a free()
.
Iterazione attraverso una matrice in modo efficiente e ordine di riga maggiore
Le matrici in C possono essere viste come un blocco contiguo di memoria. Più precisamente, l'ultima dimensione dell'array è la parte contigua. Lo chiamiamo l' ordine di riga-maggiore . La comprensione di questo e il fatto che un guasto di cache carica una linea di cache completa nella cache quando l'accesso ai dati memorizzati nella cache per evitare successive difetti di cache, possiamo vedere perché l'accesso una matrice di dimensione 10000x10000 con array[0][0]
potenzialmente caricare array[0][1]
nella cache, ma l'accesso array[1][0]
subito dopo genererebbe un secondo errore di cache, dato che è sizeof(type)*10000
byte di distanza array[0][0]
, e quindi sicuramente non sulla stessa riga della cache. Ecco perché iterare in questo modo è inefficiente:
#define ARRLEN 10000
int array[ARRLEN][ARRLEN];
size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
for(j = 0; j < ARRLEN; ++j)
{
array[j][i] = 0;
}
}
E l'iterazione di questo è più efficiente:
#define ARRLEN 10000
int array[ARRLEN][ARRLEN];
size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
for(j = 0; j < ARRLEN; ++j)
{
array[i][j] = 0;
}
}
Allo stesso modo, questo è il motivo per cui quando si ha a che fare con un array con una dimensione e più indici (diciamo 2 dimensioni qui per semplicità con gli indici i e j) è importante scorrere attraverso l'array in questo modo:
#define DIM_X 10
#define DIM_Y 20
int array[DIM_X*DIM_Y];
size_t i, j;
for (i = 0; i < DIM_X; ++i)
{
for(j = 0; j < DIM_Y; ++j)
{
array[i*DIM_Y+j] = 0;
}
}
O con 3 dimensioni e indici i, j e k:
#define DIM_X 10
#define DIM_Y 20
#define DIM_Z 30
int array[DIM_X*DIM_Y*DIM_Z];
size_t i, j, k;
for (i = 0; i < DIM_X; ++i)
{
for(j = 0; j < DIM_Y; ++j)
{
for (k = 0; k < DIM_Z; ++k)
{
array[i*DIM_Y*DIM_Z+j*DIM_Z+k] = 0;
}
}
}
O in un modo più generico, quando abbiamo un array con N1 x N2 x ... x Nd elementi, d dimensioni e indici annotati come n1, n2, ..., e l'offset è calcolato come questo
Immagine / formula presa da: https://en.wikipedia.org/wiki/Row-major_order
Matrici multidimensionali
Il linguaggio di programmazione C consente array multidimensionali . Ecco la forma generale di una dichiarazione di array multidimensionale -
type name[size1][size2]...[sizeN];
Ad esempio, la seguente dichiarazione crea un array intero tridimensionale (5 x 10 x 4):
int arr[5][10][4];
Array bidimensionali
La forma più semplice di array multidimensionale è l'array bidimensionale. Un array bidimensionale è, in sostanza, un elenco di matrici monodimensionali. Per dichiarare un array intero bidimensionale di dimensioni mxn, possiamo scrivere come segue:
type arrayName[m][n];
Dove type
può essere qualsiasi tipo di dati C valido ( int
, float
, ecc.) E arrayName
può essere qualsiasi identificatore C valido. Una matrice bidimensionale può essere visualizzata come una tabella con m
righe e n
colonne. Nota : l'ordine ha importanza in C. L'array int a[4][3]
non è lo stesso dell'array int a[3][4]
. Il numero di righe viene prima dato che C è una riga, la lingua principale.
Un array bidimensionale a
, che contiene tre righe e quattro colonne, può essere mostrato come segue:
Quindi, ogni elemento dell'array a
è identificato da un nome di elemento del modulo a[i][j]
, dove a
è il nome dell'array, i
rappresenta quale riga e j
rappresenta quale colonna. Ricorda che le righe e le colonne sono indicizzate a zero. Questo è molto simile alla notazione matematica per le matrici 2-D subscript.
Inizializzazione di matrici bidimensionali
Gli array multidimensionali possono essere inizializzati specificando i valori tra parentesi per ogni riga. Di seguito viene definito un array con 3 righe in cui ogni riga ha 4 colonne.
int a[3][4] = {
{0, 1, 2, 3} , /* initializers for row indexed by 0 */
{4, 5, 6, 7} , /* initializers for row indexed by 1 */
{8, 9, 10, 11} /* initializers for row indexed by 2 */
};
Le parentesi graffe nidificate, che indicano la riga desiderata, sono facoltative. La seguente inizializzazione è equivalente all'esempio precedente:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
Mentre il metodo di creazione di array con parentesi graffe nidificate è facoltativo, è fortemente incoraggiato in quanto è più leggibile e più chiaro.
Accesso a elementi di array bidimensionali
È possibile accedere a un elemento di una matrice bidimensionale utilizzando gli indici, ovvero l'indice di riga e l'indice di colonna dell'array. Ad esempio -
int val = a[2][3];
L'affermazione precedente prenderà il 4o elemento dalla 3a riga dell'array. Controlliamo il seguente programma in cui abbiamo usato un ciclo annidato per gestire un array bidimensionale:
#include <stdio.h>
int main () {
/* an array with 5 rows and 2 columns*/
int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
int i, j;
/* output each array element's value */
for ( i = 0; i < 5; i++ ) {
for ( j = 0; j < 2; j++ ) {
printf("a[%d][%d] = %d\n", i,j, a[i][j] );
}
}
return 0;
}
Quando il codice sopra è compilato ed eseguito, produce il seguente risultato:
a[0][0]: 0
a[0][1]: 0
a[1][0]: 1
a[1][1]: 2
a[2][0]: 2
a[2][1]: 4
a[3][0]: 3
a[3][1]: 6
a[4][0]: 4
a[4][1]: 8
Array tridimensionale:
Una matrice 3D è essenzialmente una matrice di matrici di array: è una matrice o una raccolta di array 2D e una matrice 2D è una matrice di matrici 1D.
Mappa memoria array 3D:
Inizializzazione di una matrice 3D:
double cprogram[3][2][4]={
{{-0.1, 0.22, 0.3, 4.3}, {2.3, 4.7, -0.9, 2}},
{{0.9, 3.6, 4.5, 4}, {1.2, 2.4, 0.22, -1}},
{{8.2, 3.12, 34.2, 0.1}, {2.1, 3.2, 4.3, -2.0}}
};
Possiamo avere array con qualsiasi numero di dimensioni, sebbene sia probabile che la maggior parte degli array che verranno creati sarà di una o due dimensioni.
Iterare attraverso un array usando i puntatori
#include <stdio.h>
#define SIZE (10)
int main()
{
size_t i = 0;
int *p = NULL;
int a[SIZE];
/* Setting up the values to be i*i */
for(i = 0; i < SIZE; ++i)
{
a[i] = i * i;
}
/* Reading the values using pointers */
for(p = a; p < a + SIZE; ++p)
{
printf("%d\n", *p);
}
return 0;
}
Qui, nella inizializzazione p
nel primo for
condizione del ciclo, l'array a
decade ad un puntatore al suo primo elemento, come sarebbe in quasi tutti i luoghi in cui è utilizzato un tale variabile array.
Quindi, ++p
esegue l'aritmetica del puntatore sul puntatore p
e cammina uno alla volta attraverso gli elementi dell'array, e fa riferimento a essi delegandoli con *p
.
Passaggio di array multidimensionali a una funzione
Gli array multidimensionali seguono le stesse regole degli array monodimensionali quando li passano a una funzione. Tuttavia, la combinazione di decay-to-pointer, la precedenza degli operatori e i due diversi modi di dichiarare una matrice multidimensionale (array di matrici contro array di puntatori) possono rendere la dichiarazione di tali funzioni non intuitiva. L'esempio seguente mostra i modi corretti per passare gli array multidimensionali.
#include <assert.h>
#include <stdlib.h>
/* When passing a multidimensional array (i.e. an array of arrays) to a
function, it decays into a pointer to the first element as usual. But only
the top level decays, so what is passed is a pointer to an array of some fixed
size (4 in this case). */
void f(int x[][4]) {
assert(sizeof(*x) == sizeof(int) * 4);
}
/* This prototype is equivalent to f(int x[][4]).
The parentheses around *x are required because [index] has a higher
precedence than *expr, thus int *x[4] would normally be equivalent to int
*(x[4]), i.e. an array of 4 pointers to int. But if it's declared as a
function parameter, it decays into a pointer and becomes int **x,
which is not compatable with x[2][4]. */
void g(int (*x)[4]) {
assert(sizeof(*x) == sizeof(int) * 4);
}
/* An array of pointers may be passed to this, since it'll decay into a pointer
to pointer, but an array of arrays may not. */
void h(int **x) {
assert(sizeof(*x) == sizeof(int*));
}
int main(void) {
int foo[2][4];
f(foo);
g(foo);
/* Here we're dynamically creating an array of pointers. Note that the
size of each dimension is not part of the datatype, and so the type
system just treats it as a pointer to pointer, not a pointer to array
or array of arrays. */
int **bar = malloc(sizeof(*bar) * 2);
assert(bar);
for (size_t i = 0; i < 2; i++) {
bar[i] = malloc(sizeof(*bar[i]) * 4);
assert(bar[i]);
}
h(bar);
for (size_t i = 0; i < 2; i++) {
free(bar[i]);
}
free(bar);
}