Array
Ilio Catallo – info@iliocatallo.it
Outline
¤ Introduzione agli array
¤ Array e puntatori
¤ Array e funzioni
¤ Array multidimensionali
¤ Array e iteratori
Introduzione agli array
Che cos’è un array?
¤ Un array si definisce specificandone:
¤ Il tipo dato comune ad ogni cella
¤ Il nome dell’array
¤ La sua dimensione
Un array è un contenitore di oggetti sequenziali
di un singolo tipo dato e di dimensione fissa
int numbers[5]; // an array of five int’s named ‘numbers’
¤ Il compilatore riserva il quantitativo di memoria
necessaria per accomodare 5 elementi di tipo int
¤ Le celle riservate sono contigue
¤ Ogni oggetto nell’array numbers è associato ad un
indice, che permette di accedere all’oggetto
¤ L’indice 0 è associato al primo elemento, l’indice 4 all’ultimo
Rappresentazione in memoria
numbers:
Assegnamento ed inizializzazione
¤ Cosa è possibile fare:
¤ Inizializzare un array con una lista di inizializzazione
int numbers[] = {1, 7, 13}; // ok, initialization list
Assegnamento ed inizializzazione
¤ Cosa non è possibile fare:
¤ Inizializzare un array come una copia di un’altro array
¤ Assegnare un array
int numbers[] = {1, 7, 13};
int other_numbers[] = numbers // error, copy initialization
int more_numbers[3];
more_numbers[] = {1, 7, 13} // error, assignment
Array non inizializzati
¤ Se in un una funzione si definisce un array senza
inizializzarlo, il valore iniziale degli elementi dipende dal
loro tipo T
¤ Se T è un tipo predefinito, gli elementi sono inizializzati a
default (default initialization)
¤ Altrimenti, si usa il costruttore di default di T per inizializzare
tutti gli elementi
¤ Se T non prevede un costruttore di default, il programma
non compila
Accedere agli elementi di un array
¤ Per accedere all’i-esimo elemento nell’array si utilizza
l’operatore [] (indexing operator*)
*anche detto subscript operator
int numbers[] = {1, 7, 13};
std::cout << "first element: ” << numbers[0] << std::endl;
Accessi fuori dall’intervallo
¤ Accessi fuori dall’intervallo ammissibile di un array sono
considerati undefined behavior
¤ Il programma compila, ma non è possibile prevedere cosa
accadrà dopo aver effettuato l’accesso errato
int numbers[3];
std::cout << numbers[100]; // undefined behavior
Accessi fuori dall’intervallo
¤ A differenza di altri linguaggio, il C++ non segnala in fase
d’esecuzione questo tipo di errore
int numbers[3];
std::cout << numbers[100]; // undefined behavior
Tipo dato degli elementi di un array
¤ Ogni elemento dell’array è del tipo specificato al
momento della definizione dell’array
¤ È possibile manipolare i singoli elementi di numbers come
una qualsiasi altra variabile di tipo int
int numbers[] = {1, 7, 13};
int x = numbers[2]; // initialize the int variable
// ‘x’ with another int
// variable (numbers[2])
Tipo dato di un array
¤ Quando definiamo un array, specifichiamo il tipo dato
dei singoli elementi:
¤ int non denota il tipo dato di numbers, bensì il tipo dato
dei suoi elementi numbers[0]…numbers[4]
int numbers[5]; // an array of five int’s named ‘numbers’
Tipo dato di un array
¤ Eppure numbers è una variabile, e come tale deve
avere un tipo dato
int numbers[5]; // an array of five int’s named ‘numbers’
Qual è il tipo dato di un array?
Tipo dato di un array
¤ Il tipo array è un tipo dato composto, in quanto dipende
da due fattori distinti:
¤ Il tipo dato T degli elementi
¤ Il numero di elementi m
Per un tipo dato T, T[m] è il tipo dato array di m elementi di tipo T
Tipo dato di un array
¤ Esempio: Il tipo dato di numbers è int[5], cioè
“array di 5 elementi di tipo int”
int numbers[5]; // an array of five int’s named ‘numbers’
Importanza del tipo composto
¤ È sufficiente che una sola delle due quantità (tipo T,
dimensione m) cambi per cambiare il tipo dato dell’array
¤ numbers e other_numbers non sono variabili dello
stesso tipo:
¤ numbers è di tipo int[5]
¤ other_numbers è di tipo int[10]
int numbers[5]; // numbers is of type int[5]
char letters[5]; // letters is of type char[5]
int other_numbers[10]; // other_numbers is of type int[10]
int other_letters[10]; // other_letters is of type char[10]
Dimensioni di un array
¤ La dimensione m è parte del tipo dato dell’array e deve
quindi essere nota a tempo di compilazione (compile-
time)
¤ Le uniche quantità che il compilatore può conoscere
prima che il codice venga eseguito sono le espressioni
costanti
int numbers[5];
5 è un letterale ed è
quindi un’espressione
costante, perchè noto a
compile-time
Dimensioni di un array
¤ Un’espressione costante può essere memorizzata in una
variabile costante, usando:
¤ il qualifier const
¤ lo specifier constexpr (dal C++11)
Dimensioni di un array
¤ Il tipo dato usato per mantenere la dimensione di un
array è size_t
constexpr size_t DIM = 5; // C++11
size_t const DIM = 5; // C++03
Dimensioni di un array
¤ size_t è un tipo intero senza segno in grado di
memorizzare la dimensione del più grande array
allocabile
constexpr size_t DIM = 5; // C++11
size_t const DIM = 5; // C++03
Array e puntatori
Puntatore al primo elemento
¤ Supponiamo di voler memorizzare in un puntatore
l’indirizzo del primo elemento di un array
¤ numbers[0] è un oggetto di tipo int ed ha un indirizzo,
possiamo memorizzare tale indirizzo in un puntatore a
int
int numbers[] = {1, 7, 13, 5, 9};
int* first_element_ptr = &numbers[0];
Puntatore al primo elemento
1 7 13 5 9numbers: int[5]
&numbers[0]first_elem_ptr: int*
Puntatore al primo elemento
¤ È possibile ottenere il l’indirizzo del primo elemento di un
array utilizzando un’espressione compatta
¤ Dato un array, ad esempio:
¤ Le seguenti due espressioni sono equivalenti:
int numbers[] = {1, 7, 13, 5, 9};
int* first_element_ptr = &numbers[0];
int* first_element_ptr = numbers;
Indirizzo del
primo elemento
Nome
dell’array
Decadimento a puntatore
¤ Come può mai funzionare questa cosa?
¤ first_element_ptr è di tipo int*
¤ numbers è di tipo int[5]
int* first_element_ptr = numbers;
Decadimento a puntatore
¤ Il tipo int[5] viene implicitamente convertito a int*
¤ Il risultato di tale conversione è un puntatore al primo
elemento dell’array
int* first_element_ptr = numbers;
Decadimento a puntatore
¤ Questo fenomeno prende il nome di array-to-pointer
decay
int* first_element_ptr = numbers;
Decadimento a puntatore
¤ La conversione non trasforma l’array in un puntatore
¤ Le variabili non cambiano tipo, numbers sarà sempre un
int[5]
int* first_element_ptr = numbers;
Decadimento a puntatore
¤ Cosa accade?
¤ Viene creato un puntatore temporaneo di tipo int*,
risultato del decadimento di numbers
¤ Il contenuto di tale puntatore (cioè l’indirizzo di numbers[0])
viene copiato in first_element_ptr
¤ Al termine dell’istruzione, il puntatore temporaneo viene
distrutto
int* first_element_ptr = numbers;
Decadimento a puntatore
¤ Nel valutare un’espressione che coinvolge un array di
tipo T[m], il compilatore:
¤ Se l’espressione è corretta, mantiene il tipo T[m]
¤ In caso contrario, converte T[m] a T*
int numbers[] = {1, 7, 13, 5, 9};
size_t numbers_size = sizeof(numbers); // numbers is
// treated as a
// int[5]
int* ptr_to_1st_element = numbers; // numbers is converted
// to int*
Perdita di informazione
¤ Il fenomeno si chiama decadimento perchè viene persa
dell’informazione
¤ Una volta convertito in puntatore, non è più possibile
conoscere la dimensione dell’array
¤ Ho solo un puntatore al primo elemento, ma quanti elementi
sono presenti nell’array?
¤ In altre parole, l’unico punto in comune tra i tipi dato
T[m] e T[n] è che entrambi decadono a T*
¤ I rispettivi decadimenti condividono lo stesso tipo dato T*
Aritmetica dei puntatori
¤ L’operatore [] permette di accedere e manipolare gli
elementi di un array
¤ Un secondo modo di interagire con gli elementi di un
array è utilizzare l’aritmetica dei puntatori
Aritmetica dei puntatori
¤ L’aritmetica dei puntatori si compone di un insieme di
operazione (aritmetiche) sui puntatori, in particolare:
¤ Incremento e decremento
¤ Addizione e sottrazione
¤ Confronto
¤ Assegnamento
Aritmetica dei puntatori
¤ Dato un puntatore ptr al primo elemento di un array,
l’espressione ptr + i restituisce un puntatore all’i-esimo
elemento dell’array
int numbers[] = {1, 7, 13, 5, 9};
int* first_element_ptr = numbers;
int* third_element_ptr = first_element_ptr + 2;
int* fifth_element_ptr = first_element_ptr + 4;
std::cout << "the third element is " << *third_element_ptr;
third_element_ptr è un
puntatore, per ottenere il
valore puntatato serve
l’operatore di indirezione *
Aritmetica dei puntatori
1 7 13 5 9numbers: int[5]
first_el_ptr:
first_el_ptr + 2 first_el_ptr + 4
=
fifth_el_ptr:
=
third_el_ptr: int*
Aritmetica dei puntatori
¤ Grazie al decadimento è possibile evitare l’uso di variabili
d’appoggio (come first_element_ptr):
¤ Le parentesi sono importanti
¤ *numbers + 4 è equivalente a numbers[0] + 4
¤ *(numbers + 4) è equivalente a numbers[4]
int numbers[] = {1, 7, 13, 5, 9};
std::cout << "the third element is " << *(numbers + 2);
std::cout << "the fifth element is " << *(numbers + 4);
Indexing operator vs.
aritmetica dei puntatori
¤ L’operatore [] è definito in termini di aritmetica dei
puntatori
¤ L’operatore [] è dunque una scrittura sintetica per
effettuare una somma su puntatore
Dato un array a ed un indice i,
l'operazione a[i] è implementata come *(a + i)
Indexing operator vs.
aritmetica dei puntatori
¤ L’operatore [] è in realtà un operatore definito sui
puntatori (si può usare sugli array grazie al decadimento)
int* first_elem_ptr = numbers;
std::cout << first_elem_ptr[3]; // implemented as
// *(first_elem_ptr + 3)
Indexing operator vs.
aritmetica dei puntatori
¤ Le due seguenti scritture sono equivalenti (la somma è
commutativa)
std::cout << numbers[2]; // implemented as *(numbers + 2)
std::cout << 2[numbers]; // implemented as *(2 + numbers)
Gli array non sono puntatori
¤ RICORDA: Gli array non sono puntatori
¤ Sono due tipi dati distinti, anche se strettamente legati
¤ Per convincersene è sufficiente notare che la loro
rappresentazione in memoria è diversa
…a: T[m]
ptr: T*
0 m-1
Array e funzioni
La funzione sum_int_array
¤ Vogliamo scrivere una funzione sum_int_array che
restituisca la somma dei valori contenuti in un array di
interi
int main() {
int numbers[] = {1, 7, 13, 5, 9};
int sum = sum_int_array(numbers);
std::cout << ”the elements sum up to " << sum
<< std::endl;
}
Viene stampato 35
Passare array a funzioni
¤ Non è possibile passare ad una funzione un array per
copia
¤ Non è possibile inizializzare l’array della funzione
chiamata copiando il contenuto dell’array della funzione
chiamante
Passaggio per indirizzo
¤ Nel passaggio per indirizzo viene fornita in ingresso alla
funzione chiamata una copia dell’indirizzo del parametro
che si vuole passare
¤ IDEA: fare in modo che sum_int_array riceva in
ingresso:
¤ Un puntatore alla prima celladell’array
¤ La dimensione dell’array
int sum_int_array(int* array, size_t dim_array);
Passaggio per indirizzo
¤ La funzione chiamante fornisce l’indirizzo del primo
elemento dell’array e la dimensione dell’array
int main() {
int numbers[] = {1, 7, 13, 5, 9};
int sum = sum_int_array(numbers, 5);
}
Il 1° parametro in ingresso a
sum_int_array è un
puntatore, quindi l’array
numbers decade a int*
int sum_int_array(int* array, size_t dim_array);
Passaggio per indirizzo
¤ Grazie al decadimento, è possibile usare il nome
dell’array per ottenere l’indirizzo del primo elemento
int main() {
int numbers[] = {1, 7, 13, 5, 9};
int sum = sum_int_array(numbers, 5);
}
Il 1° parametro in ingresso a
sum_int_array è un
puntatore, quindi l’array
numbers decade a int*
int sum_int_array(int* array, size_t dim_array);
Passaggio per indirizzo
¤ L’implementazione di sum_int_array è dunque
¤ L’operatore [] ci permette di manipolare il puntatore array
con la stessa sintassi che useremmo per un vero array
int sum_int_array(int* array, size_t dim_array) {
int sum = 0;
for (size_t i = 0; i < dim_array; ++i)
sum += array[i];
return sum;
}
Sintassi alternativa
¤ Esiste una sintassi alternativa per acquisire l’indirizzo del
primo elemento di un array
¤ I tre seguenti prototipi di sum_int_array sono equivalenti:
¤ NOTA: tale equivalenza vale unicamente quando si dichiara
una funzione
int sum_int_array(int* array, size_t dim_array);
int sum_int_array(int array[], size_t dim_array);
int sum_int_array(int array[5], size_t dim_array);
Sintassi alternativa
¤ L’utilizzo di uno dei due prototipi alternativi non significa
che l’array verrà passato per copia
¤ la variabile array è di tipo int*, a prescindere da quale
scrittura si utilizzi
¤ I prototipi alternativi danno l’illusione di agire su degli array
¤ Sono una delle cause della confusione tra array e puntatori
int sum_int_array(int array[], size_t dim_array);
int sum_int_array(int array[5], size_t dim_array);
Array multidimensionali
Array multidimensionali
¤ Gli array multidimensionali permettono estendere il
concetto di array a più di una dimensione
1 7 14
8 6 12
27 32 5
Array multidimensionali
¤ Nei normali array è sufficiente un solo indice per
identificare un elemento dell’array
¤ Negli array multidimensionali sono necessari tanti indici
quante dimensioni
1 7 14
8 6 12
27 32 5
indice di
riga i
indice di
colonna j
Array di array
1 7 14
8 6 12
27 32 5
1 7 14
8 6 12
27 32 5
! Array multidimensionale Array di array
Array di array
¤ In C++ non esistono array multidimensionali
¤ Gli array multidimensionali vengono realizzati mediante
array di array, cioè array i cui elementi sono a loro volta
array
Array di array
¤ Come per ogni array si definisce il numero di elementi
¤ Ogni elemento è però a sua volta un array
¤ Bisogna specificare una seconda quantità: il numero di
elementi in ogni sotto-array
int matrix[4][3]; // matrix is an array of 4 elements;
// each element is a int[3]
Array di array
¤ Esempio: matrix è un array di 4 elementi di tipo int[3]
¤ È la dimensione più interna che determina il numero di
sotto-array
int matrix[4][3];
Array di array
¤ L’uso di alias per i tipi dato può aiutarci a rendere più
chiaro il concetto
¤ matrix_row è un altro nome per il tipo int[3]
using matrix_row = int[3];
Array di array
¤ L’uso di alias per i tipi dato può aiutarci a rendere più
chiaro il concetto
¤ Così che matrix sia definibile come:
matrix_row matrix[4]; // an array of 4 elements of type
// matrix_row (i.e., of int[3])
using matrix_row = int[3];
Rappresentazione in memoria
¤ Abbiamo introdotto due rappresentazioni per gli array
multidimensionali
1 7 14
8 6 12
27 32 5
1 7 14
8 6 12
27 32 5
!
Rappresentazione in memoria
¤ Tali rappresentazioni sono intuitive, ma non riflettono
come un array multidimensionale è realmente
memorizzato in memoria
1 7 14
8 6 12
27 32 5
1 7 14
8 6 12
27 32 5
!
Rappresentazione in memoria
¤ Sappiamo che la memoria è modellata come una
sequenza di celle di memoria
¤ Gli array multidimensionale sono dunque memorizzati
come una sequenza di celle contigue
matrix: 1 7 14 8 16 12 27 32 5
matrix[0] matrix[1] matrix[2]
Inizializzazione
¤ Gli array multidimensionali possono essere inizializzati
mediante una lista di inizializzazione
¤ Le parentesi {} demarcano l’inizio e la fine di ogni riga
¤ Una scrittura equivalente (anche se meno leggibile):
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
int matrix[3][3] = {1, 7, 14, 8, 16, 12, 27, 32, 5};
Accedere agli elementi
¤ Per accedere all’(i,j)-esimo elemento si utilizza una
sequenza di operatori []
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
std::cout << "element (2,2): " << matrix[2][2];
Puntatore al primo elemento
¤ Supponiamo di voler memorizzare in un puntatore
l’indirizzo del primo elemento di un array multidim.
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
Puntatore al primo elemento
¤ matrix è un array di righe
¤ matrix[0] è di tipo int[3] (prima riga della matrice)
¤ Per salvarne l’indirizzo, è necessario un puntatore a int[3]
¤ Come si definisce un puntatore a tipo T[m]?
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
Puntatore ad array
¤ La sintassi per dichiarare un puntatore ad array è
leggermente diversa da quella usata fino ad adesso:
¤ Le parentesi sono importanti
int numbers[] = {1, 7, 13, 5, 9};
int (*numbers_ptr)[5] = &numbers; // numbers_ptr points to
// numbers
int* array_of_pointers[5] // array of 5 elements
// of type pointers to int
int (*pointer_to_array)[5] // pointer to an array
// of 5 elements of type int
Puntatore ad array
1 7 13 5 9 int[5]
&numbersnumbers_ptr: int(*)[5]
numbers:
Puntatore al primo elemento
¤ Possiamo ottenere un puntatore al primo elemento di un
array multidimensionale come:
¤ Ovviamente possiamo usare il decadimento:
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
int (*ptr_to_first_row)[3] = &matrix[0];
int (*ptr_to_first_row)[3] = matrix;
Puntatore al primo elemento
matrix: int[3][3]
&matrix[0]ptr_to_first_row: int(*)[3]
!
27 32 5
!
1 7 14
!
27 32 5
!
Alias per i sotto-array
¤ L’uso di alias per i tipi dato può nuovamente aiutarci a
rendere più leggibile il codice:
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
using matrix_row = int[3];
matrix_row* ptr_to_first_row = matrix;
Aritmetica dei puntatori
¤ L’aritmetica dei puntatori è definita anche su array
multidim.
¤ matrix è un array di righe
¤ Ogni elemento dell’array matrix è una riga della matrice
¤ Spostarsi di un elemento vuol dire passare alla riga successiva
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
using matrix_row = int[3];
matrix_row* ptr_to_first_row = matrix;
matrix_row* ptr_to_second_row = ptr_to_first_row + 1;
matrix_row* ptr_to_third_row = ptr_to_first_row + 2;
Aritmetica dei puntatori
matrix: int[3][3]
ptr_to_first_row
int(*)[3]
!
27 32 5
!
1 7 14
!
27 32 5
!
ptr_to_second_row ptr_to_third_row
=
ptr_to_first_row + 1
=
ptr_to_first_row + 2
Aritmetica dei puntatori
¤ Dato un puntatore ad una riga dell’array multidim.
¤ Si dereferenzia il puntatore per ottenere la riga
¤ Si utilizza l’aritmetica dei puntatori per per ottenere un
particolare elemento della riga
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
using matrix_row = int[3];
matrix_row* ptr_to_third_row = matrix + 2;
std::cout << "element (2,2): " << *(*ptr_to_third_row + 2);
terza riga dell’array multidim.
terzo elemento della terza riga
Aritmetica dei puntatori
¤ Grazie al decadimento, possiamo combinare le due
operazioni aritmetiche per ottenere un elemento
dell’array multidimensionale
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
using matrix_row = int[3];
matrix_row* ptr_to_third_row = matrix + 3;
std::cout << "element (2,2): " << *(*(matrix + 2) + 2);
terza riga dell’array multidim.
terzo elemento della terza riga
Indexing operator vs.
aritmetica dei puntatori
¤ Il comportamento dell’operatore [] rimane invariato
¤ Non è una nuova definizione dell’operatore, le due
operazioni vengono solo eseguite in cascata
¤ Si può pensare ad a[i][j] come (a[i])[j]:
¤ Si estrae la riga i-esima
¤ Da questa si seleziona il j-esimo elemento
Dato un array multdimensionale a e due indici i e j,
l'operazione a[i][j] è implementata come *(*(a + i) + j)
Passare array multidimensionali
a funzioni
¤ Supponiamo di voler scrivere una funzione
sum_int_matrix che sommi i valori di un array multidim.
di interi
int main () {
int matrix[3][3] = {{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
int sum = sum_int_matrix(matrix);
std::cout << "the elements sum up to " << sum
<< std::endl;
}
Passaggio per indirizzo
¤ IDEA: fare in modo che sum_int_matrix accetti un
puntatore alla prima riga
¤ Un puntatore alla prima riga permette di:
¤ Muoversi tra gli elementi della stessa riga
¤ Muoversi tra righe successive
int sum_int_matrix(int (*matrix)[3], size_t row_num);
Puntatore ad un array di 3
elementi di tipo int, cioè un
puntatore ad un riga
dell’array multidimensionale
Passaggio per indirizzo
¤ Quando si passano array multidim. tutte le dimensioni
tranne la prima devono essere note a compile-time
¤ L’accesso ad un array multidim. di tipo T[m][n] avviene
mediante un puntatore a tipo T[n]
¤ L’uso del puntatore ci permette di non conoscere m, ma il
valore n deve comunque essere noto
int sum_int_matrix(int (*matrix)[3], size_t row_num);
int sum_int_array(int* array, size_t dim_array);
In caso di array
nessuna
dimensione è
nota compile-
time
Il numero di elementi in
ogni riga deve essere
noto a compile-time
Passaggio per indirizzo
¤ L’implementazione di sum_int_matrix è dunque:
¤ L’operatore [] ci permette di manipolare il puntatore
matrix con la stessa sintassi che useremmo per un vero
array multidimensionale
int sum_int_matrix(int (*matrix)[3], size_t row_num) {
int sum = 0;
for (size_t i = 0; i < row_num; ++i)
for (size_t j = 0; j < 3; ++j)
sum += matrix[i][j];
return sum;
}
Sintassi alternativa
¤ Come per gli array, esiste una sintassi alternativa per
acquisire l’indirizzo della prima riga di un array multidim.
¤ I tre seguenti prototipi di sum_int_matrix sono
equivalenti:
¤ NOTA: tale equivalenza vale unicamente quando si
dichiara una funzione
int sum_int_matrix(int (*matrix)[3], size_t row_num);
int sum_int_matrix(int matrix[][3], size_t row_num);
int sum_int_matrix(int matrix[3][3], size_t row_num);
Array e iteratori
Container
¤ Esempi:
Un array è un contenitore di oggetti sequenziali
di un singolo tipo dato e di dimensione fissa
Un contenitore è un oggetto in grado di memorizzare altri oggetti
(detti elementi)
Un vector è un contenitore di oggetti sequenziali
di un singolo tipo dato e di dimensione variabile
Container
¤ Esempi di container:
¤ Array
¤ std::vector
¤ std::map
¤ std::multimap
¤ std::unordered_map
¤ std::set
¤ std::unordered_set
¤ std::multiset
¤ std::list
Iteratori
¤ Come suggerito dal nome, gli iteratori sono utilizzati per
iterare (scorrere) tra gli elementi di un container
¤ In questo modo:
¤ Il container ha il solo compito di utilizzare una strategia di
memorizzazione per preservare gli elementi in memoria
¤ L’iteratore ha il solo compito di fornire uno strumento di
accesso agli elementi
Iteratori
¤ Ogni iteratore è associato ad un elemento del
corrispettivo container
1 7 13 5 9numbers:
it_1st_elem
int[5]
it_3rd_elem
Iteratore di inizio e fine
¤ Ogni iteratore è associato ad un elemento del
corrispettivo container
¤ In particolare, per ogni container identifichiamo due
iteratori speciali:
¤ Un iteratore posizionato in corrispondenza del 1° elemento
¤ Un iteratore in posizione successiva all’ultimo elemento
Iteratore di inizio e fine
1 7 13 5 9numbers:
begin_iterator
int[5]
end_iterator
Iteratore di inizio e fine
¤ Il C++11/14 fornisce due funzioni per ottenere facilmente
entrambi gli iteratori:
auto begin_it = std::begin(numbers);
auto end_it = std::end(numbers);
Iteratore
¤ Si può chiedere ad un iteratore:
¤ di recuperare un elemento
¤ di muoversi da un elemento all’altro
Recuperare un elemento
¤ Ogni iteratore è associato ad un elemento del
corrispettivo container
¤ Dato un iteratore, è possibile ottenere il valore a lui
associato
auto begin_it = std::begin(numbers);
std::cout << "the first element of numbers is: "
<< *begin_it << std::endl;
Anteponendo l’asterisco
otteniamo il valore associato
all’iteratore begin_it
Muoversi da un elemento all’altro
¤ Possiamo usare l’operazione aritmetica di somma per
spostare l’iteratore di una posizione
auto it = std::begin(numbers);
std::cout << "first element: " << *it << std::endl;
++it;
std::cout << "second element: " << *it << std::endl;
Sommando +1 al valore
dell’iteratore mi sposta alla
posizione successiva
Muoversi da un elemento all’altro
1 7 13 5 9numbers:
it
int[5]
++it
Muoversi da un elemento all’altro
1 7 13 5 9numbers:
it
int[5]
Iteratori
¤ Un iteratore è una generalizzazione del concetto di
puntatore
¤ In particolare, come con i puntatori:
¤ Un iteratore è un oggetto che punta ad un altro oggetto
¤ L’elemento puntato è recuperabile mediante l’operatore *
¤ È possibile muoversi puntare all’elemento successivo
mediante operazioni aritmetiche
Navigare l’array
¤ Una volta disponibili gli iteratori di inizio e di fine è
possibile navigare l’array
for (auto it = begin_it; it != end_it; ++it)
std::cout << *it << std::endl;
1 7 13 5 9numbers:
begin_it int*
int[5]
end_itit
Algoritmi standard
¤ Gli iteratori possono essere utilizzati per compiere una
moltitudine di operazioni sui container
¤ Tutti gli algoritmi standard del C++ accettano in ingresso
una coppia di iteratori
¤ La coppia delimita la porzione del container su cui si vuole
agire
¤ In questo modo ogni algoritmo può essere usato con un
qualsiasi container, purchè esponga degli iteratori
Algoritmi standard
¤ Esempio: copiare un container
¤ Esempio: sommare i valori in un container
¤ Il codice non sarebbe cambiato anche se numbers fosse
stato un vector o un set
int other_numbers[5];
std::copy(std::begin(numbers),
std::end(numbers), std::begin(other_numbers));
int sum = std::accumulate(std::begin(numbers),
std::end(numbers), 0);
Bibliografia
Bibliografia
¤ S. B. Lippman, J. Lajoie, B. E. Moo, C++ Primer (5th Ed.)
¤ B. Stroustrup, The C++ Programming Language (4th Ed.)
¤ The Gang of Four, Design Patterns - Elements of Reusable
Object Oriented Software
¤ HP, Standard Template Library Programmer's Guide
https://www.sgi.com/tech/stl/
¤ Stackoverflow FAQ, “How do I use arrays in C++?”
http://stackoverflow.com/questions/4810664/how-do-i-use-
arrays-in-c

Array in C++

  • 1.
    Array Ilio Catallo –info@iliocatallo.it
  • 2.
    Outline ¤ Introduzione agliarray ¤ Array e puntatori ¤ Array e funzioni ¤ Array multidimensionali ¤ Array e iteratori
  • 3.
  • 4.
    Che cos’è unarray? ¤ Un array si definisce specificandone: ¤ Il tipo dato comune ad ogni cella ¤ Il nome dell’array ¤ La sua dimensione Un array è un contenitore di oggetti sequenziali di un singolo tipo dato e di dimensione fissa int numbers[5]; // an array of five int’s named ‘numbers’
  • 5.
    ¤ Il compilatoreriserva il quantitativo di memoria necessaria per accomodare 5 elementi di tipo int ¤ Le celle riservate sono contigue ¤ Ogni oggetto nell’array numbers è associato ad un indice, che permette di accedere all’oggetto ¤ L’indice 0 è associato al primo elemento, l’indice 4 all’ultimo Rappresentazione in memoria numbers:
  • 6.
    Assegnamento ed inizializzazione ¤Cosa è possibile fare: ¤ Inizializzare un array con una lista di inizializzazione int numbers[] = {1, 7, 13}; // ok, initialization list
  • 7.
    Assegnamento ed inizializzazione ¤Cosa non è possibile fare: ¤ Inizializzare un array come una copia di un’altro array ¤ Assegnare un array int numbers[] = {1, 7, 13}; int other_numbers[] = numbers // error, copy initialization int more_numbers[3]; more_numbers[] = {1, 7, 13} // error, assignment
  • 8.
    Array non inizializzati ¤Se in un una funzione si definisce un array senza inizializzarlo, il valore iniziale degli elementi dipende dal loro tipo T ¤ Se T è un tipo predefinito, gli elementi sono inizializzati a default (default initialization) ¤ Altrimenti, si usa il costruttore di default di T per inizializzare tutti gli elementi ¤ Se T non prevede un costruttore di default, il programma non compila
  • 9.
    Accedere agli elementidi un array ¤ Per accedere all’i-esimo elemento nell’array si utilizza l’operatore [] (indexing operator*) *anche detto subscript operator int numbers[] = {1, 7, 13}; std::cout << "first element: ” << numbers[0] << std::endl;
  • 10.
    Accessi fuori dall’intervallo ¤Accessi fuori dall’intervallo ammissibile di un array sono considerati undefined behavior ¤ Il programma compila, ma non è possibile prevedere cosa accadrà dopo aver effettuato l’accesso errato int numbers[3]; std::cout << numbers[100]; // undefined behavior
  • 11.
    Accessi fuori dall’intervallo ¤A differenza di altri linguaggio, il C++ non segnala in fase d’esecuzione questo tipo di errore int numbers[3]; std::cout << numbers[100]; // undefined behavior
  • 12.
    Tipo dato deglielementi di un array ¤ Ogni elemento dell’array è del tipo specificato al momento della definizione dell’array ¤ È possibile manipolare i singoli elementi di numbers come una qualsiasi altra variabile di tipo int int numbers[] = {1, 7, 13}; int x = numbers[2]; // initialize the int variable // ‘x’ with another int // variable (numbers[2])
  • 13.
    Tipo dato diun array ¤ Quando definiamo un array, specifichiamo il tipo dato dei singoli elementi: ¤ int non denota il tipo dato di numbers, bensì il tipo dato dei suoi elementi numbers[0]…numbers[4] int numbers[5]; // an array of five int’s named ‘numbers’
  • 14.
    Tipo dato diun array ¤ Eppure numbers è una variabile, e come tale deve avere un tipo dato int numbers[5]; // an array of five int’s named ‘numbers’ Qual è il tipo dato di un array?
  • 15.
    Tipo dato diun array ¤ Il tipo array è un tipo dato composto, in quanto dipende da due fattori distinti: ¤ Il tipo dato T degli elementi ¤ Il numero di elementi m Per un tipo dato T, T[m] è il tipo dato array di m elementi di tipo T
  • 16.
    Tipo dato diun array ¤ Esempio: Il tipo dato di numbers è int[5], cioè “array di 5 elementi di tipo int” int numbers[5]; // an array of five int’s named ‘numbers’
  • 17.
    Importanza del tipocomposto ¤ È sufficiente che una sola delle due quantità (tipo T, dimensione m) cambi per cambiare il tipo dato dell’array ¤ numbers e other_numbers non sono variabili dello stesso tipo: ¤ numbers è di tipo int[5] ¤ other_numbers è di tipo int[10] int numbers[5]; // numbers is of type int[5] char letters[5]; // letters is of type char[5] int other_numbers[10]; // other_numbers is of type int[10] int other_letters[10]; // other_letters is of type char[10]
  • 18.
    Dimensioni di unarray ¤ La dimensione m è parte del tipo dato dell’array e deve quindi essere nota a tempo di compilazione (compile- time) ¤ Le uniche quantità che il compilatore può conoscere prima che il codice venga eseguito sono le espressioni costanti int numbers[5]; 5 è un letterale ed è quindi un’espressione costante, perchè noto a compile-time
  • 19.
    Dimensioni di unarray ¤ Un’espressione costante può essere memorizzata in una variabile costante, usando: ¤ il qualifier const ¤ lo specifier constexpr (dal C++11)
  • 20.
    Dimensioni di unarray ¤ Il tipo dato usato per mantenere la dimensione di un array è size_t constexpr size_t DIM = 5; // C++11 size_t const DIM = 5; // C++03
  • 21.
    Dimensioni di unarray ¤ size_t è un tipo intero senza segno in grado di memorizzare la dimensione del più grande array allocabile constexpr size_t DIM = 5; // C++11 size_t const DIM = 5; // C++03
  • 22.
  • 23.
    Puntatore al primoelemento ¤ Supponiamo di voler memorizzare in un puntatore l’indirizzo del primo elemento di un array ¤ numbers[0] è un oggetto di tipo int ed ha un indirizzo, possiamo memorizzare tale indirizzo in un puntatore a int int numbers[] = {1, 7, 13, 5, 9}; int* first_element_ptr = &numbers[0];
  • 24.
    Puntatore al primoelemento 1 7 13 5 9numbers: int[5] &numbers[0]first_elem_ptr: int*
  • 25.
    Puntatore al primoelemento ¤ È possibile ottenere il l’indirizzo del primo elemento di un array utilizzando un’espressione compatta ¤ Dato un array, ad esempio: ¤ Le seguenti due espressioni sono equivalenti: int numbers[] = {1, 7, 13, 5, 9}; int* first_element_ptr = &numbers[0]; int* first_element_ptr = numbers; Indirizzo del primo elemento Nome dell’array
  • 26.
    Decadimento a puntatore ¤Come può mai funzionare questa cosa? ¤ first_element_ptr è di tipo int* ¤ numbers è di tipo int[5] int* first_element_ptr = numbers;
  • 27.
    Decadimento a puntatore ¤Il tipo int[5] viene implicitamente convertito a int* ¤ Il risultato di tale conversione è un puntatore al primo elemento dell’array int* first_element_ptr = numbers;
  • 28.
    Decadimento a puntatore ¤Questo fenomeno prende il nome di array-to-pointer decay int* first_element_ptr = numbers;
  • 29.
    Decadimento a puntatore ¤La conversione non trasforma l’array in un puntatore ¤ Le variabili non cambiano tipo, numbers sarà sempre un int[5] int* first_element_ptr = numbers;
  • 30.
    Decadimento a puntatore ¤Cosa accade? ¤ Viene creato un puntatore temporaneo di tipo int*, risultato del decadimento di numbers ¤ Il contenuto di tale puntatore (cioè l’indirizzo di numbers[0]) viene copiato in first_element_ptr ¤ Al termine dell’istruzione, il puntatore temporaneo viene distrutto int* first_element_ptr = numbers;
  • 31.
    Decadimento a puntatore ¤Nel valutare un’espressione che coinvolge un array di tipo T[m], il compilatore: ¤ Se l’espressione è corretta, mantiene il tipo T[m] ¤ In caso contrario, converte T[m] a T* int numbers[] = {1, 7, 13, 5, 9}; size_t numbers_size = sizeof(numbers); // numbers is // treated as a // int[5] int* ptr_to_1st_element = numbers; // numbers is converted // to int*
  • 32.
    Perdita di informazione ¤Il fenomeno si chiama decadimento perchè viene persa dell’informazione ¤ Una volta convertito in puntatore, non è più possibile conoscere la dimensione dell’array ¤ Ho solo un puntatore al primo elemento, ma quanti elementi sono presenti nell’array? ¤ In altre parole, l’unico punto in comune tra i tipi dato T[m] e T[n] è che entrambi decadono a T* ¤ I rispettivi decadimenti condividono lo stesso tipo dato T*
  • 33.
    Aritmetica dei puntatori ¤L’operatore [] permette di accedere e manipolare gli elementi di un array ¤ Un secondo modo di interagire con gli elementi di un array è utilizzare l’aritmetica dei puntatori
  • 34.
    Aritmetica dei puntatori ¤L’aritmetica dei puntatori si compone di un insieme di operazione (aritmetiche) sui puntatori, in particolare: ¤ Incremento e decremento ¤ Addizione e sottrazione ¤ Confronto ¤ Assegnamento
  • 35.
    Aritmetica dei puntatori ¤Dato un puntatore ptr al primo elemento di un array, l’espressione ptr + i restituisce un puntatore all’i-esimo elemento dell’array int numbers[] = {1, 7, 13, 5, 9}; int* first_element_ptr = numbers; int* third_element_ptr = first_element_ptr + 2; int* fifth_element_ptr = first_element_ptr + 4; std::cout << "the third element is " << *third_element_ptr; third_element_ptr è un puntatore, per ottenere il valore puntatato serve l’operatore di indirezione *
  • 36.
    Aritmetica dei puntatori 17 13 5 9numbers: int[5] first_el_ptr: first_el_ptr + 2 first_el_ptr + 4 = fifth_el_ptr: = third_el_ptr: int*
  • 37.
    Aritmetica dei puntatori ¤Grazie al decadimento è possibile evitare l’uso di variabili d’appoggio (come first_element_ptr): ¤ Le parentesi sono importanti ¤ *numbers + 4 è equivalente a numbers[0] + 4 ¤ *(numbers + 4) è equivalente a numbers[4] int numbers[] = {1, 7, 13, 5, 9}; std::cout << "the third element is " << *(numbers + 2); std::cout << "the fifth element is " << *(numbers + 4);
  • 38.
    Indexing operator vs. aritmeticadei puntatori ¤ L’operatore [] è definito in termini di aritmetica dei puntatori ¤ L’operatore [] è dunque una scrittura sintetica per effettuare una somma su puntatore Dato un array a ed un indice i, l'operazione a[i] è implementata come *(a + i)
  • 39.
    Indexing operator vs. aritmeticadei puntatori ¤ L’operatore [] è in realtà un operatore definito sui puntatori (si può usare sugli array grazie al decadimento) int* first_elem_ptr = numbers; std::cout << first_elem_ptr[3]; // implemented as // *(first_elem_ptr + 3)
  • 40.
    Indexing operator vs. aritmeticadei puntatori ¤ Le due seguenti scritture sono equivalenti (la somma è commutativa) std::cout << numbers[2]; // implemented as *(numbers + 2) std::cout << 2[numbers]; // implemented as *(2 + numbers)
  • 41.
    Gli array nonsono puntatori ¤ RICORDA: Gli array non sono puntatori ¤ Sono due tipi dati distinti, anche se strettamente legati ¤ Per convincersene è sufficiente notare che la loro rappresentazione in memoria è diversa …a: T[m] ptr: T* 0 m-1
  • 42.
  • 43.
    La funzione sum_int_array ¤Vogliamo scrivere una funzione sum_int_array che restituisca la somma dei valori contenuti in un array di interi int main() { int numbers[] = {1, 7, 13, 5, 9}; int sum = sum_int_array(numbers); std::cout << ”the elements sum up to " << sum << std::endl; } Viene stampato 35
  • 44.
    Passare array afunzioni ¤ Non è possibile passare ad una funzione un array per copia ¤ Non è possibile inizializzare l’array della funzione chiamata copiando il contenuto dell’array della funzione chiamante
  • 45.
    Passaggio per indirizzo ¤Nel passaggio per indirizzo viene fornita in ingresso alla funzione chiamata una copia dell’indirizzo del parametro che si vuole passare ¤ IDEA: fare in modo che sum_int_array riceva in ingresso: ¤ Un puntatore alla prima celladell’array ¤ La dimensione dell’array int sum_int_array(int* array, size_t dim_array);
  • 46.
    Passaggio per indirizzo ¤La funzione chiamante fornisce l’indirizzo del primo elemento dell’array e la dimensione dell’array int main() { int numbers[] = {1, 7, 13, 5, 9}; int sum = sum_int_array(numbers, 5); } Il 1° parametro in ingresso a sum_int_array è un puntatore, quindi l’array numbers decade a int* int sum_int_array(int* array, size_t dim_array);
  • 47.
    Passaggio per indirizzo ¤Grazie al decadimento, è possibile usare il nome dell’array per ottenere l’indirizzo del primo elemento int main() { int numbers[] = {1, 7, 13, 5, 9}; int sum = sum_int_array(numbers, 5); } Il 1° parametro in ingresso a sum_int_array è un puntatore, quindi l’array numbers decade a int* int sum_int_array(int* array, size_t dim_array);
  • 48.
    Passaggio per indirizzo ¤L’implementazione di sum_int_array è dunque ¤ L’operatore [] ci permette di manipolare il puntatore array con la stessa sintassi che useremmo per un vero array int sum_int_array(int* array, size_t dim_array) { int sum = 0; for (size_t i = 0; i < dim_array; ++i) sum += array[i]; return sum; }
  • 49.
    Sintassi alternativa ¤ Esisteuna sintassi alternativa per acquisire l’indirizzo del primo elemento di un array ¤ I tre seguenti prototipi di sum_int_array sono equivalenti: ¤ NOTA: tale equivalenza vale unicamente quando si dichiara una funzione int sum_int_array(int* array, size_t dim_array); int sum_int_array(int array[], size_t dim_array); int sum_int_array(int array[5], size_t dim_array);
  • 50.
    Sintassi alternativa ¤ L’utilizzodi uno dei due prototipi alternativi non significa che l’array verrà passato per copia ¤ la variabile array è di tipo int*, a prescindere da quale scrittura si utilizzi ¤ I prototipi alternativi danno l’illusione di agire su degli array ¤ Sono una delle cause della confusione tra array e puntatori int sum_int_array(int array[], size_t dim_array); int sum_int_array(int array[5], size_t dim_array);
  • 51.
  • 52.
    Array multidimensionali ¤ Gliarray multidimensionali permettono estendere il concetto di array a più di una dimensione 1 7 14 8 6 12 27 32 5
  • 53.
    Array multidimensionali ¤ Neinormali array è sufficiente un solo indice per identificare un elemento dell’array ¤ Negli array multidimensionali sono necessari tanti indici quante dimensioni 1 7 14 8 6 12 27 32 5 indice di riga i indice di colonna j
  • 54.
    Array di array 17 14 8 6 12 27 32 5 1 7 14 8 6 12 27 32 5 ! Array multidimensionale Array di array
  • 55.
    Array di array ¤In C++ non esistono array multidimensionali ¤ Gli array multidimensionali vengono realizzati mediante array di array, cioè array i cui elementi sono a loro volta array
  • 56.
    Array di array ¤Come per ogni array si definisce il numero di elementi ¤ Ogni elemento è però a sua volta un array ¤ Bisogna specificare una seconda quantità: il numero di elementi in ogni sotto-array int matrix[4][3]; // matrix is an array of 4 elements; // each element is a int[3]
  • 57.
    Array di array ¤Esempio: matrix è un array di 4 elementi di tipo int[3] ¤ È la dimensione più interna che determina il numero di sotto-array int matrix[4][3];
  • 58.
    Array di array ¤L’uso di alias per i tipi dato può aiutarci a rendere più chiaro il concetto ¤ matrix_row è un altro nome per il tipo int[3] using matrix_row = int[3];
  • 59.
    Array di array ¤L’uso di alias per i tipi dato può aiutarci a rendere più chiaro il concetto ¤ Così che matrix sia definibile come: matrix_row matrix[4]; // an array of 4 elements of type // matrix_row (i.e., of int[3]) using matrix_row = int[3];
  • 60.
    Rappresentazione in memoria ¤Abbiamo introdotto due rappresentazioni per gli array multidimensionali 1 7 14 8 6 12 27 32 5 1 7 14 8 6 12 27 32 5 !
  • 61.
    Rappresentazione in memoria ¤Tali rappresentazioni sono intuitive, ma non riflettono come un array multidimensionale è realmente memorizzato in memoria 1 7 14 8 6 12 27 32 5 1 7 14 8 6 12 27 32 5 !
  • 62.
    Rappresentazione in memoria ¤Sappiamo che la memoria è modellata come una sequenza di celle di memoria ¤ Gli array multidimensionale sono dunque memorizzati come una sequenza di celle contigue matrix: 1 7 14 8 16 12 27 32 5 matrix[0] matrix[1] matrix[2]
  • 63.
    Inizializzazione ¤ Gli arraymultidimensionali possono essere inizializzati mediante una lista di inizializzazione ¤ Le parentesi {} demarcano l’inizio e la fine di ogni riga ¤ Una scrittura equivalente (anche se meno leggibile): int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; int matrix[3][3] = {1, 7, 14, 8, 16, 12, 27, 32, 5};
  • 64.
    Accedere agli elementi ¤Per accedere all’(i,j)-esimo elemento si utilizza una sequenza di operatori [] int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; std::cout << "element (2,2): " << matrix[2][2];
  • 65.
    Puntatore al primoelemento ¤ Supponiamo di voler memorizzare in un puntatore l’indirizzo del primo elemento di un array multidim. int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}};
  • 66.
    Puntatore al primoelemento ¤ matrix è un array di righe ¤ matrix[0] è di tipo int[3] (prima riga della matrice) ¤ Per salvarne l’indirizzo, è necessario un puntatore a int[3] ¤ Come si definisce un puntatore a tipo T[m]? int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}};
  • 67.
    Puntatore ad array ¤La sintassi per dichiarare un puntatore ad array è leggermente diversa da quella usata fino ad adesso: ¤ Le parentesi sono importanti int numbers[] = {1, 7, 13, 5, 9}; int (*numbers_ptr)[5] = &numbers; // numbers_ptr points to // numbers int* array_of_pointers[5] // array of 5 elements // of type pointers to int int (*pointer_to_array)[5] // pointer to an array // of 5 elements of type int
  • 68.
    Puntatore ad array 17 13 5 9 int[5] &numbersnumbers_ptr: int(*)[5] numbers:
  • 69.
    Puntatore al primoelemento ¤ Possiamo ottenere un puntatore al primo elemento di un array multidimensionale come: ¤ Ovviamente possiamo usare il decadimento: int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; int (*ptr_to_first_row)[3] = &matrix[0]; int (*ptr_to_first_row)[3] = matrix;
  • 70.
    Puntatore al primoelemento matrix: int[3][3] &matrix[0]ptr_to_first_row: int(*)[3] ! 27 32 5 ! 1 7 14 ! 27 32 5 !
  • 71.
    Alias per isotto-array ¤ L’uso di alias per i tipi dato può nuovamente aiutarci a rendere più leggibile il codice: int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; using matrix_row = int[3]; matrix_row* ptr_to_first_row = matrix;
  • 72.
    Aritmetica dei puntatori ¤L’aritmetica dei puntatori è definita anche su array multidim. ¤ matrix è un array di righe ¤ Ogni elemento dell’array matrix è una riga della matrice ¤ Spostarsi di un elemento vuol dire passare alla riga successiva int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; using matrix_row = int[3]; matrix_row* ptr_to_first_row = matrix; matrix_row* ptr_to_second_row = ptr_to_first_row + 1; matrix_row* ptr_to_third_row = ptr_to_first_row + 2;
  • 73.
    Aritmetica dei puntatori matrix:int[3][3] ptr_to_first_row int(*)[3] ! 27 32 5 ! 1 7 14 ! 27 32 5 ! ptr_to_second_row ptr_to_third_row = ptr_to_first_row + 1 = ptr_to_first_row + 2
  • 74.
    Aritmetica dei puntatori ¤Dato un puntatore ad una riga dell’array multidim. ¤ Si dereferenzia il puntatore per ottenere la riga ¤ Si utilizza l’aritmetica dei puntatori per per ottenere un particolare elemento della riga int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; using matrix_row = int[3]; matrix_row* ptr_to_third_row = matrix + 2; std::cout << "element (2,2): " << *(*ptr_to_third_row + 2); terza riga dell’array multidim. terzo elemento della terza riga
  • 75.
    Aritmetica dei puntatori ¤Grazie al decadimento, possiamo combinare le due operazioni aritmetiche per ottenere un elemento dell’array multidimensionale int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; using matrix_row = int[3]; matrix_row* ptr_to_third_row = matrix + 3; std::cout << "element (2,2): " << *(*(matrix + 2) + 2); terza riga dell’array multidim. terzo elemento della terza riga
  • 76.
    Indexing operator vs. aritmeticadei puntatori ¤ Il comportamento dell’operatore [] rimane invariato ¤ Non è una nuova definizione dell’operatore, le due operazioni vengono solo eseguite in cascata ¤ Si può pensare ad a[i][j] come (a[i])[j]: ¤ Si estrae la riga i-esima ¤ Da questa si seleziona il j-esimo elemento Dato un array multdimensionale a e due indici i e j, l'operazione a[i][j] è implementata come *(*(a + i) + j)
  • 77.
    Passare array multidimensionali afunzioni ¤ Supponiamo di voler scrivere una funzione sum_int_matrix che sommi i valori di un array multidim. di interi int main () { int matrix[3][3] = {{1, 7, 14}, {8, 16, 12}, {27, 32, 5}}; int sum = sum_int_matrix(matrix); std::cout << "the elements sum up to " << sum << std::endl; }
  • 78.
    Passaggio per indirizzo ¤IDEA: fare in modo che sum_int_matrix accetti un puntatore alla prima riga ¤ Un puntatore alla prima riga permette di: ¤ Muoversi tra gli elementi della stessa riga ¤ Muoversi tra righe successive int sum_int_matrix(int (*matrix)[3], size_t row_num); Puntatore ad un array di 3 elementi di tipo int, cioè un puntatore ad un riga dell’array multidimensionale
  • 79.
    Passaggio per indirizzo ¤Quando si passano array multidim. tutte le dimensioni tranne la prima devono essere note a compile-time ¤ L’accesso ad un array multidim. di tipo T[m][n] avviene mediante un puntatore a tipo T[n] ¤ L’uso del puntatore ci permette di non conoscere m, ma il valore n deve comunque essere noto int sum_int_matrix(int (*matrix)[3], size_t row_num); int sum_int_array(int* array, size_t dim_array); In caso di array nessuna dimensione è nota compile- time Il numero di elementi in ogni riga deve essere noto a compile-time
  • 80.
    Passaggio per indirizzo ¤L’implementazione di sum_int_matrix è dunque: ¤ L’operatore [] ci permette di manipolare il puntatore matrix con la stessa sintassi che useremmo per un vero array multidimensionale int sum_int_matrix(int (*matrix)[3], size_t row_num) { int sum = 0; for (size_t i = 0; i < row_num; ++i) for (size_t j = 0; j < 3; ++j) sum += matrix[i][j]; return sum; }
  • 81.
    Sintassi alternativa ¤ Comeper gli array, esiste una sintassi alternativa per acquisire l’indirizzo della prima riga di un array multidim. ¤ I tre seguenti prototipi di sum_int_matrix sono equivalenti: ¤ NOTA: tale equivalenza vale unicamente quando si dichiara una funzione int sum_int_matrix(int (*matrix)[3], size_t row_num); int sum_int_matrix(int matrix[][3], size_t row_num); int sum_int_matrix(int matrix[3][3], size_t row_num);
  • 82.
  • 83.
    Container ¤ Esempi: Un arrayè un contenitore di oggetti sequenziali di un singolo tipo dato e di dimensione fissa Un contenitore è un oggetto in grado di memorizzare altri oggetti (detti elementi) Un vector è un contenitore di oggetti sequenziali di un singolo tipo dato e di dimensione variabile
  • 84.
    Container ¤ Esempi dicontainer: ¤ Array ¤ std::vector ¤ std::map ¤ std::multimap ¤ std::unordered_map ¤ std::set ¤ std::unordered_set ¤ std::multiset ¤ std::list
  • 85.
    Iteratori ¤ Come suggeritodal nome, gli iteratori sono utilizzati per iterare (scorrere) tra gli elementi di un container ¤ In questo modo: ¤ Il container ha il solo compito di utilizzare una strategia di memorizzazione per preservare gli elementi in memoria ¤ L’iteratore ha il solo compito di fornire uno strumento di accesso agli elementi
  • 86.
    Iteratori ¤ Ogni iteratoreè associato ad un elemento del corrispettivo container 1 7 13 5 9numbers: it_1st_elem int[5] it_3rd_elem
  • 87.
    Iteratore di inizioe fine ¤ Ogni iteratore è associato ad un elemento del corrispettivo container ¤ In particolare, per ogni container identifichiamo due iteratori speciali: ¤ Un iteratore posizionato in corrispondenza del 1° elemento ¤ Un iteratore in posizione successiva all’ultimo elemento
  • 88.
    Iteratore di inizioe fine 1 7 13 5 9numbers: begin_iterator int[5] end_iterator
  • 89.
    Iteratore di inizioe fine ¤ Il C++11/14 fornisce due funzioni per ottenere facilmente entrambi gli iteratori: auto begin_it = std::begin(numbers); auto end_it = std::end(numbers);
  • 90.
    Iteratore ¤ Si puòchiedere ad un iteratore: ¤ di recuperare un elemento ¤ di muoversi da un elemento all’altro
  • 91.
    Recuperare un elemento ¤Ogni iteratore è associato ad un elemento del corrispettivo container ¤ Dato un iteratore, è possibile ottenere il valore a lui associato auto begin_it = std::begin(numbers); std::cout << "the first element of numbers is: " << *begin_it << std::endl; Anteponendo l’asterisco otteniamo il valore associato all’iteratore begin_it
  • 92.
    Muoversi da unelemento all’altro ¤ Possiamo usare l’operazione aritmetica di somma per spostare l’iteratore di una posizione auto it = std::begin(numbers); std::cout << "first element: " << *it << std::endl; ++it; std::cout << "second element: " << *it << std::endl; Sommando +1 al valore dell’iteratore mi sposta alla posizione successiva
  • 93.
    Muoversi da unelemento all’altro 1 7 13 5 9numbers: it int[5] ++it
  • 94.
    Muoversi da unelemento all’altro 1 7 13 5 9numbers: it int[5]
  • 95.
    Iteratori ¤ Un iteratoreè una generalizzazione del concetto di puntatore ¤ In particolare, come con i puntatori: ¤ Un iteratore è un oggetto che punta ad un altro oggetto ¤ L’elemento puntato è recuperabile mediante l’operatore * ¤ È possibile muoversi puntare all’elemento successivo mediante operazioni aritmetiche
  • 96.
    Navigare l’array ¤ Unavolta disponibili gli iteratori di inizio e di fine è possibile navigare l’array for (auto it = begin_it; it != end_it; ++it) std::cout << *it << std::endl; 1 7 13 5 9numbers: begin_it int* int[5] end_itit
  • 97.
    Algoritmi standard ¤ Gliiteratori possono essere utilizzati per compiere una moltitudine di operazioni sui container ¤ Tutti gli algoritmi standard del C++ accettano in ingresso una coppia di iteratori ¤ La coppia delimita la porzione del container su cui si vuole agire ¤ In questo modo ogni algoritmo può essere usato con un qualsiasi container, purchè esponga degli iteratori
  • 98.
    Algoritmi standard ¤ Esempio:copiare un container ¤ Esempio: sommare i valori in un container ¤ Il codice non sarebbe cambiato anche se numbers fosse stato un vector o un set int other_numbers[5]; std::copy(std::begin(numbers), std::end(numbers), std::begin(other_numbers)); int sum = std::accumulate(std::begin(numbers), std::end(numbers), 0);
  • 99.
  • 100.
    Bibliografia ¤ S. B.Lippman, J. Lajoie, B. E. Moo, C++ Primer (5th Ed.) ¤ B. Stroustrup, The C++ Programming Language (4th Ed.) ¤ The Gang of Four, Design Patterns - Elements of Reusable Object Oriented Software ¤ HP, Standard Template Library Programmer's Guide https://www.sgi.com/tech/stl/ ¤ Stackoverflow FAQ, “How do I use arrays in C++?” http://stackoverflow.com/questions/4810664/how-do-i-use- arrays-in-c