Che materia stai cercando?

Anteprima

ESTRATTO DOCUMENTO

L'operatore di assegnamento (=) assegna il valore dell'espressione alla sua destra a alla

variabile posta alla sua sinistra. Permette anche di eseguire assegnamenti multipli.

L'operatore assegnamento si può comporre anche con altri operatori:

+= a+=b equivale a scrivere a=a+b

­= a­=b equivale a scrivere a=a­b

*= a*=b equivale a scrivere a=a*b

/= a/=b equivale a scrivere a=a/b

%= a%=b equivale a scrivere a=a%b (resto della divisione, solo se variabili intere)

Operatori aritmetici

+ ­ * / %

Operatori di incremento e decremento

++ incremento e ­­ decremento, che rispettivamente sommano e sottraggono 1 alla variabile che

hanno come argomento. È corretto scrivere ++(++x), ma dato che l'operatore di post

incremento restituisce il valore della variabile, esso non può essere scritto come (x++)++, dato

che non si può applicare un operatore ad un valore ma solo ad una variabile. Se gli operatori ++

e ­­ si usano come prefissi, l'operazione di incremento o decremento si effettua prima

dell'operazione di assegnamento, se si usano come suffissi l'assegnamento si effettua prima

dell'incremento o decremento.

Preincremento ++x equivale a x+=1 o x=x+1 restituisce la variabile x

Postincremento x++ equivale a x+=1 o x=x+1 restituisce il valore della variabile x

Stessa cosa per il decremento ­­

Operatori relazionali

Gli operatori relazionali si usano in espressioni della forma espressione1 operatore_relazionale

espressione2 dove espressione1 e 2 sono espressioni compatibili C++. L'operatore relazionale

produce 0 o 1 a seconda del risultato del confronto.

== (uguale)

!= (diverso)

> (maggiore) (o >=)

< (minore) (o <=)

Per valutare una successione di disuguaglianze come s<z<e si deve utilizzare la seguente

espressione (s<z) && (z<e)

Operatori logici

Detti anche booleani sono: ! (not), && (and), || (or). Vale la tabella di verità

a b a&&b a||b !a

V V V V F

V F F V F

F V F V V

F F F F V

Gli operatori matematici hanno precedenza sugli operatori relazionali e gli operatori relazionali

hanno precedenza sugli operatori logici.

Gli operandi a sinistra degli operatori logici vengono sempre valutati per primi, se il loro valore è

già sufficiente a determinare il risultato l'operando destro non viene valutato per riparmiare tempo

della CPU. Questa proprietà si dice valutazione in cortocircuito.

Operatori di manipolazione dei bit

O bitwise, eseguono operazioni logiche sui bit degli operandi. Si applicano soltanto a char e int.

& bit a bit

| or inclusivo bit a bit

^ or esclusivo bit a bit (XOR) (aut­aut)

~ complemento a 1 (inversione di tutti i bit)

<< spostamento di tutti i bit verso sinistra

>> spostamento di tutti i bit verso destra

0&0==0; 0&1==0; 1&0==0; 1&1==1;

0|0==0; 0|1==1; 1|0==1; 1|1==1;

0^0==0; 0^1==1; 1^0==1; 1^1==0;

Gli operatori di traslazione si scrivono nella forma valore << numero_di_bit o viceversa! dove il

valore può essere di tipo int o char, il numero di bit determina di quanti bit si sposterà il valore.

Gli operatori bitwise si possono anche comporre con =.

Operatore condizionale

È un operatore ternario che restituisce un valore che dipende dal valore dell'espressione

booleana al primo membro. Il formato è: espressione_booleana ? se_vera : se_falsa

Si valuta un espressione booleana, se è true viene restituito il valore in se_vera, se false il valore

in se_falsa.

Operatore virgola

Permette di combinare due o più espressioni che saranno valutate da sinistra verso destra.

Operatore sizeof()

Serve per conoscere le dimensioni in byte di un tipo di dato o variabile. Facilità la portabilità e la

leggibilità del codice, dato che tale operatore viene valutato in fase di compilazione e può variare

da macchina a macchina.

Conversioni di tipo

Si può convertire un valore di tipo in un valore di un altro tipo. Tale azione si dice casting. C++

realizza molte conversioni automaticamente (conversioni implicite) ma ha anche operatori che

permettono al programmatore di impostarle esplicitamente (conversioni esplicite).

C++ convert i valori quando si assegna un valore di un tipo aritmetico a una variabile di un altro

tipo aritmetico, converte i valori quando si combinano tipi misti nelle espressioni, converte i valori

quando si passano argomenti alle funzioni.

Conversione implicita

I tipo fondamentali possono essere usati liberamente nelle espressioni perché le conversioni si

eseguono automaticamente. Gli operatori con tipo a precisione più bassa si convertono nei tipi a

precisione più alta (promozione di tipo).

Conversioni aritmetiche

Assicurano che gli operandi di un operatore binario aritmetico o logico si convertano ad un tipo

comune prima che si valuti l'operatore; tale tipo sarà quello dell risultato dell'operazione. Anche

qui avviene la promozione di tipo per gerarchia. Ad esempio i tipi più piccoli di int (char, unsigned

int,...) si convertono ad int, bool si converte ad int. Si effettuano tali conversioni per mantenere le

informazioni.

Conversioni esplicite

Il C++ offre diverse forme di casting.

tipo (espr)

(tipo) espr

const_cast <tipo> (espr)

dinamic_cast <tipo> (espr)

reinterpret_cast <tipo> (espr)

static_cast <tipo> (espr)

Input/Output da console

Per gestire l'I/O in C++ è necessario includere la libreria iostream che contiene quattro oggetti:

cin, cout, cerr, clog.

Un flusso (stream) è una sequenza di caratteri preparati per leggere o scrivere da/su qualsiasi

dispositivo di I/O.

I sistemi operativi hanno 3 flussi standard predefiniti. Standard input, standard error, standard

output. Sia in lettura che in scrittura i bytes vengono interpretati come caratteri.

Input (cin)

cin rappresenta il flusso di input e utilizza l'operatore di estrazione >> per estrarre caratteri da

esso e metterli in qualche variabile. Se non si redirige, cin legge da tastiera.

Lettura: viene prelevata dallo stram un'opportuna sequenza di caratteri, viene convertita nella

sequenza interna del dato, secondo quanto specificato dal tipo di dato della variabile

destinazione.

Output (cout)

Rappresenta il flusso di output e utilizza l'operatore di inserzione << che inserisce i dati che si

trovano alla sua destra nel'oggetto alla sua sinistra.

Per andare a capo si può usare o la sequenza di escape \n o endl.

Redirezione dei flussi

Sia in lettura che in scrittura i tra flussi possono essere rediretti dal sistema operativo verso la

memoria di massa:

programma < file (legge l'input da un file)

programma > file (scrive l'output su file, riscrivendolo)

programma >> file (scrive l'output su file, prolungandolo)

Flussi di file (file stream)

#include <fstream>

Definisco un oggetto fstream. Ad es fstream tubo;

Definisco una variabile. Ad es char x;

Per leggere: tubo.open("miofile", ios::in);

tubo >> x;

tubo.close();

Per scrivere (sostituire): tubo.open("miofile", ios::out);

tubo << x;

tubo.close();

Per continuare a scrivere (append): tubo.open("miofile", ios::app)

tubo << x;

tubo.close();

Blocchi e visibilità degli identificatori

Blocco: sequenza di istruzioni racchiuse fra parentesi graffe

Blocco innestato: inizia e finisce dentro un altro blocco

Campo di visibilità di un identificatore (scope): parte del programma in cui l'oggetto può essere

usato

Se un oggetto è dichiarato dentro un blocco il suo campo di visibilità ca dal punto in cui è

dichiarato fino alla fine del blocco stesso. Un oggetto globale è dichiarato fuori dal main.

Un oggetto locale ad un blocco: non può essere modificato da istruzioni esterne a quel blocco,

maschera eventuali altri oggetti dichiarati col suo stesso identificatore nei blocchi più esterni, non

esiste in memoria fino a che non viene eseguito il blocco di istruzioni in cui è definito, termina di

esistere quando viene eseguita l'ultima istruzione del blocco.

Spazio dei nomi (namespace)

È una caratteristica del C++. È una regione di codice definita e nominata dal programmatore che

contiene definizioni di oggetti, funzioni, variabili, .. Ecc. Si può accedere a questi elementi al di

fuori dello spazio anteponendo al loro nome quello dello spazio stesso. Per accedere agli

elementi di uno spazio dei nomi si deve anteporre il nome dello stesso al nome degli elementi

ponendo fra i due :: . Per semplificare si può usare la direttiva using namespace.

I namespace si possono annidare uno dentro l'altro.

La definizione di uno namespace si può anche spezzare in più parti.

Programmazione strutturata

Le strutture di controllo determinano il flusso di esecuzione di un programma. Vi sono tre tipi di

strutture di controllo: sequenza, selezione, ripetizione.

L'istruzione if

if(espressione booleana) istruzione;

L'istruzione if analizza l'espressione booleana al suo interno, se vera procede con l'istruzione,

se falsa non fa niente. In tutti e due i casi successivamente passa all'istruzione successiva al'if.

Istruzione condizionale doppia if else

If(espressione booleana) istruzione1 else istruzione2

L'istruzione if valuta l'espressione booleana, se vera esegue istruzione1, altrimenti esegue

istruzione2.

Se abbiamo più di due alternative si possono "annidare" più istruzioni if una dentro l'altra. Se il

codice di istruzioni non viene posto in blocco, l'else si riferisce sempre all'if più vicino!

Al posto di if else posso anche usare l'operatore condizionale ?: che corrisponde proprio ad if

else.

L'istruzione switch

Si utilizza per selezionare una tra molteplici alternative

switch(selettore)

{ case etichetta1: istruzioni1; break;

case etichetta2: istruzioni2; break;

....

default: istruzioni;

}

Il selettore è un'espressione semplice che può essere int, char, bool ma non float, double o

string. Ogni etichetta ha un valore costante diverso dalle altre etichette. Se il valore del selettore

è uguale a una delle etichette case, allora si eseguirà la corrispondente sequenza di istruzioni e

si continueranno ad eseguire le successive sequenze di istruzioni fino ad incontrare break. Se il

valore del selettore non è uguale a nessuna etichetta case non si esegue nulla, a me no che non

sia inserita l'etichetta default.

Se non inserisco il break a fine istruzione, il programma continuerà a fare le istruzioni anche

degli altri rami.

Le strutture cicliche

Un ciclo è la ripetizione controllata di una sequenza di istruzioni. La sequenza si dice corpo del

ciclo ed ogni ripetizione si chiama iterazione del ciclo. L'iterazione è controllata da una

condizione, un'espressione booleana che prima o poi dovrà diventare falsa per terminare il ciclo.

Il corpo può essere un'istruzione singola o un blocco di istruzioni.

­ ciclo: gruppo di istruzioni eseguite ripetutamente fino a che una condizione rimane true

­ ripetizione controllata da un contatore: è definita (è noto quante volte il ciclo sarà eseguito) e si

utilizza una variabile di controllo usata per contare le ripetizioni.

­ ripetizione controllata da una sentinella: è indefinita (usata quando il numero di ripetizioni non è

noto a priori), il termine del ciclo è indicato dal valore della sentinella.

L'istruzione while

while (condizione) istruzione

Si valuta la prima condizione e si esegue eventualmente il corpo del ciclo. Se la condizione è

falsa in partenza non si esegue nulla, altrimenti il ciclo continuerà fino a che la condizione non

risulta falsa. Si può usare anche l'istruzione break per uscire eventualmente da un ciclo infinito.

Ciclo for

for (inizializzazione; condizione; passo)

corpo del ciclo

Il passo solitamente è l'incremento di una variabile, il contatore del while ad esempio. Le variabili

dichiarate nell'inizializzazione del ciclo for, sono locali al blocco ma possono non essere di tipo

int e possono essere modificate nel passo, possono essere più di una.

È meglio evitare che il col del for modifichi le variabili di controllo per garantire l'uscita dal ciclo.

Si può non inizializzare un for: for (;;) istruzione. In questo caso si può uscire dal ciclo inserendo

in break.

Ciclo do while

do istruzione while condizione

Comincia eseguendo istruzione e solo dopo valuta condizione. Se questa è vera istruzione viene

ripetuta e così via fino a quando condizione non diventa falsa.

I cicli si possono annidare uno dentro l'altro. Ogni volta che si ripete il ciclo esterno vengono

ripetuti anche quelli interni

Differenze

while è particolarmente usato quando l'iterazione non è controllata da contatore. Si utilizza

quando si vuole saltare il ciclo se la condizione è falsa.

do while è usato quando si deve eseguire almeno un'iterazione. La condizione di controllo segue

l'esecuzione del corpo del ciclo

for è usato quando il numero di iterazioni si conosce in anticipo e quindi l'iterazione può essere

controllata da un contatore. La condizione di controllo precede l'esecuzione del corpo del ciclo.

Istruzioni di salto

­ continue: termina solo l’iterazione corrente del ciclo;

­ break: termina il ciclo e passa all’istruzione successiva;

­ goto: termina il ciclo e salta ad una certa istruzione;

Programmazione strutturata

Ignora le istruzioni di salto: permette raffinamenti top­down in cui gli interventi sono indipendenti

dai precedenti e dai successivi, genera algoritmi più leggibili, semplifica il debugging e la

mantainance. E’ possibile implementare qualsiasi algoritmo prescindendo dalle istruzioni di

salto?

Teorema di Böhm­Jacopini (1966)

Ogni funzione computabile può essere implementata in un linguaggio di programmazione che

combina sottoprogrammi in tre maniere:

­ sequenza (prima un sottoprogramma poi il successivo);

­ selezione (eseguire uno dei sottoprogrammi in base alla variabile booleana);

­ ripetizione (eseguire un sottoprogramma fino a che la variabile booleana è vera).

Funzioni

Sottoprogrammi che mettono in corrispondenza funzionale certi dati in input con altri prodotti in

output. Possono essere mandate in esecuzione da altri programmi e soprattutto servono ad

evitare ripetizioni nel codice. Rende la programmazione modulare, ovvero il programma si divide

in vari moduli, le funzioni, ciascuno dei quali risolve un compito specifico. Raggruppando funzioni

in una libreria si creano librerie tematiche, che possono essere utilizzate in altri programmi.

Struttura di una funzione:

tipo_di_output nomeFunzione (parametri)

{ corpo della funzione;

return espressione;

}

Il tipo di ritorno è il tipo del valore restituito dalla funzione (void se non restituisce alcun valore). Il

nome della funzione è il suo identificatore. I parametri sono i valori che la funzione prenderà in

input. L’espressione è il valore che restituirà la funzione.

Non si possono dichiarare funzioni annidate, ma si possono richiamare tra loro. Tutte le variabili

e costanti definite in una funzione sono locali ad essa. Mediante la parola return si può ritornare il

valore restituito al programma chiamante.

Se non si specifica il tipo di dato di ritorno, si sottintende int. Molte funzioni non restituiscono

nulla e si usano solo come subroutines; tali funzioni prendono il nome di procedure (void).

Chiamata ad una funzione

Una funzione (funzione chiamata) va in esecuzione quando viene chiamata dal programma

principale main() o da un’altra funzione (funzione chiamante). La funzione chiamata inizia con la

prima istruzione dopo la parentesi graffa e termina con la prima istruzione return o l’ultima

istruzione prima della parentesi graffa chiusa.

Per definire una funzione senza argomenti è possibili inserire void tra le parentesi

voi nome(void)

Prototipi delle funzioni

La dichiarazione (prototipo) di una funzione è simile alla definizione ma: non ha il corpo (che

verrà definito altrove), deve specificare il tipo dei parametri fondamentali (ma non

necessariamente il nome) e l’intestazione deve terminare con il ; . Ciò è utile quando la funzione

si trova ad esempio in un altro programma che viene poi collegato a questo. Il prototipo fornisce

quindi sufficienti informazioni al compilatore per verificare che la funzione venga chiamata

correttamente rispetto al numero e al tipo di parametri che utilizza. E’ obbligatorio mettere il

punto e virgola al termine perchè il prototipo è un’istruzione. Il C++ distingue tra dichiarazione e

definizione, infatti quando una funzione viene definita, oltre ad elencarne le caratteristiche, viene

anche riservato spazio in memoria. Rispetto alla dichiarazione che informa solo dell’esistenza di

una certa funzione, la definizione specifica anche il luogo dove essa esiste.

Un prototipo con un numero non specificato si rappresenta con punti sospensivi …

Ad es: int esempi(int a, …);

Passaggio di parametri ad una funzione

Un parametro attuale può essere un valore costante, una variabile o un’espressione generica.

per passare variabili come argomenti attuali alle funzioni ci sono due modi: per valore o per

riferimento.

Passaggio per valore

Nell’atto della chiamata la funzione riceve i valori delle variabili, non le variabili stesse. Se la

funzione modificherà il valore del suo argomento formale, queste modifiche non interesseranno

la variabile passata. In altre parole: la funziona modifica il valore della variabile, ma al di fuori

della funzione stessa tali modifiche non hanno effetto.

Passaggio per riferimento

Quando una funzione deve modificare il valore della variabile che le è stata passata come

argomento, si deve utilizzare il metodo del passaggio per riferimento. In questo caso non viene

passata la variabile, ma il valore del suo indirizzo in memoria, cioè il suo riferimento. Esso sarà

quindi usato all’interno della funzione per indirizzare la variabile esterna da modificare.

Tale metodo può essere implementato attraverso i puntatori (C) o i riferimenti (C++).

Metodo dei puntatori

I puntatori sono variabili destinate a contenere indirizzi di memoria.

Ad es

void scambia(int* a, int* b)

{ int ausiliare = *a;

*a=*b;

*b=ausiliare;

}

La funzione ha come argomenti due puntatori ad interi a e b. Il corpo della funzione usa poi le

espressioni *a e *b per deferenziare i puntatori, cioè per leggere gli indirizzi lì contenuti e risalire

quindi alle variabili di tipo int da loro referenziate. Alla chiamata non verranno passate le variabili

ma i loro indirizzi di memoria che si indica anteponendo & (ampersand) al nome della variabile:

scambia(&i, &j);

Metodo dei riferimenti

Un riferimento ad una variabile è semplicemente un (altro) nome della variabile stessa.

Ad es

void scambia(int& m, int& n)

{ int ausiliare = m;

m = n;

n = ausiliare;

}

La funzione ha come argomenti formali due riferimenti ad interi m e n. Se chiamiamo la funzione

con due variabili, ad esempio i e j, la funzione userà m e n come alias di i e j; qualunque modifica

effettuata dalla funzione su m ed n non avrà alcun effetto su i e j: scambia(i, j);

Se antepongo const alla dichiarazione degli argomenti di una funzione, questi saranno di sola

lettura e qualsiasi modifica di essi all’interno della funzione produrrà errore di compilazione.

Argomenti di default

In C++ è possibile definire funzioni in cui alcuni argomenti assumono un valore di default. Se

all’atto di chiamata non viene passato alcun valore, la funzione assegnerà il valore di default.

Ad es

char funzdef(int arg1=1, char c=’A’, float f_val=45.7f)

Se all’atto della chiamata non vengono forniti valori la funzione assegnerà i valori definiti. I valori

si possono cambiare semplicemente cambiando la variabile con i nuovi valori (si possono anche

cambiare alcuni dei valori, gli altri assumeranno il valore di default; tuttavia, il vincolo è che se si

omette un argomento bisogna anche omettere tutti quelli alla sua destra).

Gli argomenti di default devono passare per valore (no per riferimento), devono essere costanti.

Visibilità

La visibilità di una variabile è la zona del programma in cui essa è accessibile. Questa è visibile

quando la funzione può accedere ad essa. Esistono 4 tipi di visibilità: programma, file sorgente,

funzione e blocco. Normalmente la visibilità è data da dova le variabile viene definita, ma si può

modificare con gli specificatori static, extern, auto e register.

­ visibilità di programma: sono variabili globali e possono essere referenziate da qualunque

funzione del programma. Per rendere globale una variabile basta dichiararla all’inizio del

programma fuori da qualunque funzione;

­ visibilità di file: una variabile definita fuori da qualsiasi funzione la cui dichiarazione contiene la

parola riservata static ha visibilità di file. Tali variabili possono essere referenziate dal punto in cui

sono dichiarate fino alla fine del sorgente;

­ visibilità di funzione: le variabili dichiarate dentro il corpo di una funzione sono locali e possono

essere utilizzate solo al suo interno;

­ visibilità di blocco: una variabile dichiarata dentro un blocco può essere referenziata in

qualunque parte del blocco;

Le variabili locali oltre ad avere visibilità ristretta non occupano memoria fino a che la funzione in

cui sono dichiarate viene eseguita. Quando la funzione non è attiva, tali variabili non esistono in

memoria. DI conseguenza un’istruzione esterna alla funzione non può modificare il valore di una

variabile locale alla funzione e due o più funzioni possono dare lo stesso nome a una propria

variabile locale.

Classi di immagazzinamento (auto, extern, register, static e typedef)

Possono modificare la visibilità di una variabile.

­ variabili automatiche

Le variabili locali a una funzione si dicono automatiche (auto) perchè si assegna loro spazio in

memoria automaticamente alla chiamata della funzione e si rimuovono automaticamente

all’uscita. La parola riservata auto è opzionale.

­ variabili esterne

In C++ si può utilizzare una variabile globale definita in un altro file sorgente dichiarandola

localmente con la parola riservata extern. In questo modo si dice al compilatore che la variabile è

definita in un altro file sorgente che sarà linkato insieme. Se una definizione inizia con la parola

extern non è una e vera e propria definizione ma una dichiarazione: una definizione è anche

dichiarazione ma non viceversa (una variabile si definisce una volta sola ma si può dichiarare

quante volte si vuole).

­ variabili di registro

Con la parola riservata register si dice al compilatore di porre la variabile in uno dei registri

hardware del microprocessore. Tuttavia il compilatore non è detto che lo faccia, visto che i

registri hardware non sono molti e quindi può decidere di ignorare la richiesta. Una variabile di

registro deve essere locale a una funzione, ovvero non può essere globale a un programma.

Può essere utile da utilizzare come variabile di controllo di un ciclo, in modo che la CPU non

perde tempo a cercare tale variabile in memoria.

­ variabili statiche

Al contrario delle variabili automatiche, quelle statiche non si cancellano quando la funzione

termina. Una variabile statica si inizializza una volta per tutte. Si dichiara anteponendo la parola

static alla dichiarazione. La parola riservata static può essere utilizzata per occultare variabili

globali da altri file sorgenti.

Visibilità delle funzioni

Di default tutte le funzioni sono extern e quindi visibili da altri moduli di programma. Si possono

dichiarare funzioni anche con static e non sarà così possibile utilizzarla in altri moduli di

programma.

Funzioni inline

Sono convenienti per aumentare la velocità del programma quando devono essere chiamate

spesso e il loro codice è breve. Una funzione normale è un blocco di codice mandato in

esecuzione da un’altra funzione. L’indirizzo dell’istruzione successiva alla chiamata della

funzione viene memorizzato nello stack in maniera da potervi accedere una volta che

l’esecuzione della funzione sarà conclusa. Il blocco di codice termina con un’istruzione speciale

che recupera dallo stack l’indirizzo di ritorno e manderà in esecuzione l’istruzione successiva.

Per una funzione inline il compilatore ricopia realmente il codice delle funzione in ogni punto in

cui essa viene invocata. Il programma sarà così più veloce perchè non si dovrà eseguire il

codice associato alla chiamata della funzione. Tuttavia, il programma aumenta la sua

dimensione. Per creare una funzione inline bisogna inserire la parola riservata inline all’inizio

dell’intestazione: inline tipo nome(argomenti) (return (codice);)

Concetto e uso di funzioni di libreria

Sono raccolte di funzioni per operazioni comuni. Le funzioni che appartengono allo stesso

gruppo si dichiarano nello stesso header file.

­ Funzioni di carattere

<ctype.h> definisce un gruppo di funzioni per la manipolazione dei caratteri. Tutte le funzioni

restituiscono un tipo booleano. Esistono diverse funzioni per le verifiche alfanumeriche:

isalpha(c) (ritorna true se c è maiuscola o minuscola), islower(c) (true se c è minuscola),

isupper(c) (true se c è maiuscola), isdigit(c) (true se c è cifra), isxdigit(c) (true se c è

esadecimale), isalnum(c) (true se c è una cifra o un carattere alfabetico). Alcune funzioni

verificano la leggibilità di caratteri speciali: iscntrl(c) (true se c è carattere di controllo), isgraph(c)

(true se c è un carattere stampabile, non di controllo), isprint(c) (true se c è stampabile incluso

lo spazio), ispunct(c) (true se c è interpunzione), isspace(c) (true se c è spazio). Esistono

funzioni non booleane che servono a trasformare lettere maiuscole in minuscole o viceversa:

tolower(c) (converte c in minuscola), toupper(c) (converte c in maiuscola).

­ Funzioni numeriche

La maggior parte si trovano in <math.h>, alcune in <stdlib.h>. Le funzioni matematiche più

comuni sono: ceil(x) (arrotonda all’intero vicino più comune), fabs(x) (restituisce il valore

assoluto di x), floor(x) (arrotonda per difetto all’intero più vicino), pow(x, y) (calcola x elevato a y),

sqrt(x) (restituisce la radice quadrata di x). <math.h> include anche funzioni trigonometriche

(richiedono i radianti! 1rad=gradi*(π/180)): acos(x) (calcola l’arcoseno di x), asin(x) (calcola

l’arcoseno di x), atan(x) (calcola l’arcotangente di x), atan2(x, y) (calcola l’arcotangente di x

diviso y), cos(x) (calcola il coseno di x), sin(x) (calcola il seno di x), tan(x) (calcola la tangente di

x). La libreria cmath include anche funzioni logaritmiche ed esponenziali: exp(x) (calcola

l’esponenziale di x), log(x) (calcola il logaritmo naturale di x), log10(x) (calcola il logaritmo

decimale di x).

­ Funzioni aleatorie

rand() (genera un numero casuale fra 0 e RAND_MAX), randomize() (inizializza il generatore di

numeri aleatori con un seme aleatorio ottenuto a partire da una chiamata alla funzione time),

srand(seme) (inizializza il generatore di numeri aleatori in base al valore dell’argomento seme),

random(num) (restituisce un valore casuale tra 0 e num­1).

­ Funzioni di data e ora

Definite nell’header file time.h: clock(void) (restituisce il tempo di CPU in secondi dall’esecuzione

del programma), time(ora) (restituisce il numero di secondi trascorsi dalla mezzanotte del 1

gennaio 1970, questo valore di tempo di mette in ora).

Compilazione modulare

I programmi più grandi sono più facili da gestire se si dividono in vari file sorgente, moduli,

ognuno dei quali può contenere una o più funzioni. Questi moduli verranno poi compilati

separatamente ma linkati insieme, a ogni ricompilazione verranno ricompilati solo i moduli

modificati. Le funzioni possono essere referenziate tra i vari moduli; l’uso della parola static è

utile per evitare conflitti di nomi (sono preferibili variabili locali a globali).

Sovraccaricamento delle funzioni

Il sovraccaricamento delle funzioni (overheading) permette di utilizzare più funzioni con lo stesso

nome, ma con almeno un argomento di tipo diverso e/o con un diverso numero di argoementi. Il

compilatore C++ verifica il tipo di parametro inviato per determinare qual’è la funzione da

chiamare. C++ determina tra le funzioni sovraccaricate quale deve chiamare in base al numero

di parametri passati o dal tipo (un tipo deve essere diverso da funzione a funzione). Regole di

selezione: se esiste si seleziona la funzione che mostra la corrispondenza esatta tra il numero e

i tipi dei parametri formali e attuali; se tale funzione non esiste, si seleziona una funzione in cui il

matching dei parametri formali e attuali avviene tramite una conversione automatica di tipo; la

corrispondenza dei tipi può essere forzata attraverso casting; se una funzione sovraccaricata ha

un numero indeterminato di argomenti (...) è selezionata in mancanza di matching più precisi.

Ricorsione

Una funzione ricorsiva è una funzione che chiama sé stessa, direttamente o indiritamente. La

ricorsione diretta è il processo per il quale una funzione invoca sé stessa, indiretta se sono

implicate due o più funzioni. Una funzione ricorsiva deve avere una condizione di terminazione o

risulterà infinita.

Template di funzioni

I template forniscono un meccanismo per creare funzioni generiche. Una funzione generica è

una funzione che può supportare simultaneamente differenti tipi di dato per i suoi parametri. I

template sono utili quando occorre utilizzare la stessa funzione con differenti tipi di argomenti.

Un template ha il seguente formato:

template <class tipo>

dichiarazione delle funzione

Il simbolo tipo indica al compilatore che può essere sotituito dal tipo di dato appropriato.

Ad es

template <class T>

T max(T a, T b)

il template funzione max si dichiara con un unico tipo generico T e con due argomenti.

Array

Un array (o vettore) è una sequenza di oggetti dello stesso tipo. Gli oggetti si chiamano elementi

dell’array e si numerano consecutivamente 0, 1, 2, 3, ...

Il tipo degli elementi è definito dall’utente.

I numeri che indicano gli elementi dell’array si dicono indici dell’arra e il loro compito è quello di

localizzare l’elemento dentro l’array e di fornire accesso diretto ad esso.

Un array viene immagazzinato in memoria sequenzialmente su posizioni di memoria contigue.

Un array si definisce in modo simile agli altri tipi di dato m si deve indicare tra parentesi quadre la

sua dimensione (o lunghezza).

tipo nomeArray[numeroElementi]

Si può accedere a ogni elemento dell’array medianto uno stesso nome e un indice scorrevole:

nomeArray[0] indica il primo elemento. C++ non verifica che gli indici dell’array stiano dentro la

dimensione definita, accedendo quindi ad un elemento al di fuori dell’array il compilatore non

segnala errore. Si possono referenziare elementi anche utilizzando espressioni per gli indici, ad

esempio: vendite[totale + 5].

Allocamento in memoria degli array

Gli elementi dell’array si posizionano in blocchi contigui di memoria.

La funzione sizeof() restituisce il numero di byte necessari per contenere il dato o il tipo di dato

che le è stato passato come argomento. Se si usa per un array restituisce il numero di byte

occupato da tutto l’array.

A differenza di altri linguaggi, il C++ non verifica che il valore dell’indice sia inferiore alla

lunghezza dell’array.

Inizializzazione di un array

Si può usare l’operatore di assegnamento per assegnare valori ad ogni elemento dell’array.

Questo metodo non è pratico quando l’array contiene parecchi elementi. Un metodo più

efficiente è tale formato: int nomArray[dim] = {el1, el2, el3, … , eldim};

Quando si inizializza un array in questo modo la dimensione è falcoltativa, visto che il

compilatore conterà il numero di valori dentro le parentesi.

In altri casi si potrebbe usare l’istruzione for per inizializzare tutti gli elementi:

for (i=0; i<=5; i++)

numeri[i]=0;

Ogni elemento da 1 a 5 prenderà valore 0.

Se si inizializza un array static o globale il compilatore assegnerà tutti valori 0 a meno che non si

definiscano manualmente.

Array di caratteri o stringhe di testo

C++ rappresenta le stringhe (sequenze di caratteri) utilizzando array di caratteri.

char miaStringa[] = “ABCDEF”;

con la convenzione che le stringhe terminano con un carattere nullo. Le stringhe sono array di

caratteri ma non tutti gli array di caratteri sono stringhe. Le stringhe hanno come ultimo carattere

‘\0’ ma non è detto che questo sia l’ultimo elemento del vettore: il compilatore aggiunge il

carattere nullo subito dopo l’ultimo elemento della stringa.

Non si può assegnare una stringa a un array al di fuori della definizione. Per fare ciò si può usare

la funzione di libreria strcpy(nome, “Stringa”).

Se l’array non ha sufficienti spazi per contenere la stringa si va in buffer overflow e il compilatore

scriverà in altri spazi di memoria.

Array multidimensionali

Gli array oltre che di una dimensione, possono essere di più dimensioni. Un array di due

dimensioni prende il nome di tabella o matrice:

tipo nome [numRighe] [numCol];

Un array di due dimensioni è in realtà un array di array, cioè un array unidimensionale i cui

elementi sono array.

int tabella [2] [3] = {{51, 52, 53}, {54, 55, 56}}

Per accedere agli elementi di un array bidimensionale bisogna specificare l’indice di riga e quello

di colonna: nome [indiceRiga] [indiceColonna] = valore elemento;

Array di più di tre dimensioni sono difficilmente usati. Un esempio per un array di tre dimensioni è

un libro: char libro [pagine] [righe] [colonne]; ogni pagine è un array bidimensionale. Per

accedere ai singoli elementi del libro è possile utilizzare cilci annidati.

Passaggio di vettori come parametri

In C++ gli array si passano per riferimento. Ovvero quando s’invoca una funzione le si passa un

array come parametro, C++ tratta la chiamata come se vi fosse l’operatore & davanti al nome

della variabile.

Si possono definire funzioni che accettano array di valore double come parametri. Se la funzione

deve conoscere la dimensione dell’array si deve aggiungere un secondo parametro per questo:

double SommaDiDati(double dati[], int n);

Quando si passa un array ad una funzione si passa in realtà solo l’indirizzo del suo primo

elemento, che è proprio il nome dell’array, ma la funzione accede direttamente alle celle di

memoria dell’array. Passare il nome dell’array per valore significa passare l’array per riferimento.

Poichè le stringhe sono vettori di caratteri, passare una stringa a una funzione significa passare

un vettore. Per far conoscere la loro dimensione le stringhe usano il metodo della sentinella che

risulta il carattere nullo. Funzioni che hannno argomenti formali di tipo array possono modificare

gli array passati come argomenti attuali! (Passaggio per riferimento).

Limiti dei vettori in C++

­ non si possono eseguire operazioni di confronto;

­ non si può assegnare sull’intero array;

­ non si possono eseguire operazioni aritmetiche;

­ funzioni non possono restituire array;

Ordinamento di vettori

E’ la procedura tramite la quale si dispongono gli elementi dell’array in un ordine specifico.

Algoritmi di ordinamento sono: per inserzione, a bolle (bubble­sort), per selezione

(selection­sort), rapido (quick­sort), per fusione (merge­soft).

Quick­sort

E’ il più efficiente, si basa sulla divisione del vettore in tre partizione, sinistra, centrale (pivot) e

destra. Supponendo l’ordine crescente, gli elementi di sinistra dovranno essere più piccoli del

primo elemento di destra e ciò è possibile confrontando con il pivot al centro. L’elemento pivot

può essere scelto a caso all’interno del vettore.

void QuickSort(QS_TYPE list[], int beg, int end)

{ QS_TYPE piv; QS_TYPE tmp;

int l,r,p;

while (beg<end) // This while loop will substitude the second recursive call

{ l = beg; p = (beg+end)/2; r = end;

piv = list[p];

while (1)

{ while ( (l<=r) && ( QS_COMPARE(list[l],piv) <= 0 ) ) l++;

while ( (l<=r) && ( QS_COMPARE(list[r],piv) > 0 ) ) r­­;

if (l>r) break;

tmp=list[l]; list[l]=list[r]; list[r]=tmp;

if (p==r) p=l;

l++; r­­;

}

list[p]=list[r]; list[r]=piv;

r­­;

// Select the shorter side & call recursion. Modify input param. for loop

if ((r­beg)<(end­l))

{ QuickSort(list, beg, r);

beg=l;

}

else

{ QuickSort(list, l, end);

end=r;

}

}

}

Strutture

Un struttura è una collezione di elementi denominati campi, ognuno dei quali può contenere un

dato di tipo diverso.

Una struttura può contenere qualunque numero di campi.

Formato della dichiarazione:

struct nome_della_struttura

{ tipocampo1 nomecampo1;

tipocampo2 nomecampo2;

tipocampon nomecampon;

}

Dopo aver dichiarato il tipo di dato si possono definire variabili di quel tipo. Le variabili di tipo

struct si possono definire in due modi: elencandole dopo la parentesi graffa di chiusura dello

struct o scrivendo il nome della struttura seguito dalle variabili.

Le strutture, al contrario dei vettori, possono essere assegnate.

Ad es: libro libro4 = libro5;

Una variabile di tipo struttura si può inizializzare in qualunque punto del programma, anche nella

definizione. I valori si devono dare nell’ordine in cui sono definiti i corrispondenti campi nella

struttura. Se vi sono più valori di inizializzazione che campi della struttura il compilatore segnala

errore; viceversa i campi non inizializzati prendono i valori di default.

L’operatore sizeof, utilizzato per una struttura, darò come output la somma dei byte occupati da

ciascun campo.

Per accedere in lettura o scrittura a un singolo campo della struttura si utilizza l’operatore ( . ).

Ad esempio per assegnare un valore al campo di una variabile struct:

variabile.campo = valore;

Strutture annidate

Un campo di una struttura può essere a sua volta di tipo struttura. Se strutture diverse

possiedono parti identiche, definire queste ultime come sottostrutture fa risparmiare tempo e

riduce la possibilità di errore.

Ad es

struct info

{ char nome[30];

char indirizzo[25];

char città[20];

}

struct impiegato

{ struct info anagrafica;

}

Quindi se voglio inizializzare un dato dovrò scrivere impiegato.anagrafica.nome = …

L’accesso a campi dato di strutture annidate richiede l’uso di operatori punto in cascata.

Array di strutture

Si può creare un array di struttre così come un qualunque altro tipo. Gli array di strutture sono

utili per rappresentare basi di dati (database), cioè collezioni di record da gestire in maniera

efficiente. La dichiarazione di un array di strutture è simile a qualunque array:

tipoStruct Struct[dim];

Per accedere ai campi di ognuno degli elementi struttura si utilizzano l’indice dell’array e

l’operatore punto.

Array come campi

I campi delle strutture possono essere vettori

Strutture come parametri

C++ permette di passare strutture come parametri attuali alle funzioni, sia per valore che per

riferimento. Nel primo caso se la struttura è grande si può avere una notevole perdita di

efficienza.

Funzioni membri di strutture

In C++ le strutture possono anche contenere funzioni.

Unioni

Le unioni somigliano alle strutture, ma anzichè allocare i campi in maniera contigua esse li

sovrappongono nella stessa posizione di memoria.

union nome {

tipo1 campo1;

tipo2 campo2;

}

La quantità di memoria riservata per una unione è data dal campo più grande, perchè i campi

condividono la stessa zona di memoria. Le unioni servono per risparmiare memoria,

specialmente quando servono diverse variabili ma non tutte nello stesso momento.

Per riferirsi ai campi di un’unione si utilizza l’operatore punto (.).

Enumerazioni

Una enumerazione è un tipo definito dall’utente costituito da un insieme di costanti.

enum nome {

enumeratore1 = espressione_costante1,

enumeratore2 = espressione_costante2,

enumeratore3 = espressione_costante3,

}

Typedef

La parola riservata typedef serve per creare un sinonimo di un tipo di dato, fondamentale o

definito dall’utente.

Ad es chiamo double come lunghezza: typedef double lunghezza;

Riferimenti

Definendo una variabile se ne determinano il nome, il tipo e l’indirizzo di memoria. All’indirizzo

della variabile si accede mediante l’operatore di indirizzo &, che va applicato al nome della

variabile.

Un riferimento a una variabile è un ulteriore nome per essa. Il C++ introduce il riferimento al tipo

che si dichiara utilizzando l’operatore & come suffisso al tipo di dato riferito. Il riferimento al tipo

viene usato come tipo normale in una definizione di variabile; tuttavia, tale variabile deve essere

inizializzata contestualmente alla definizione, deve quindi essere seguita dall’operatore

assegnamento e dal nome di una variabile già definita dello stesso tipo del tipo riferito.

Il carattere & ha tre usi in C++:

1. Se si utilizza come prefisso al nome di una variabile ne restituisce l’indirizzo;

2. Se si utilizza come suffisso a un tipo nella definizione di un riferimento, quest’ultimo è

dichiarato sinonimo della variabile utilizzata per la sua inizializzazione;

3. Se si utilizza come suffisso a un tipo nella dichiarazione dei parametri formali di una funzione,

questi ultimi sono dichiarati riferimenti delle corrispondenti variabili passate alla funzione.


ACQUISTATO

1 volte

PAGINE

39

PESO

662.90 KB

PUBBLICATO

+1 anno fa


DETTAGLI
Corso di laurea: Corso di laurea in ingegneria informatica e dell'automazione
SSD:

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher henry0894 di informazioni apprese con la frequenza delle lezioni di Fondamenti di informatica e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Politecnico delle Marche - Univpm o del prof Dragoni Aldo Franco.

Acquista con carta o conto PayPal

Scarica il file tutte le volte che vuoi

Paga con un conto PayPal per usufruire della garanzia Soddisfatto o rimborsato

Recensioni
Ti è piaciuto questo appunto? Valutalo!

Altri appunti di Fondamenti di informatica

Riassunto esame Fondamenti di Informatica, prof. Dragoni, libro consigliato Fondamenti di Programmazione in C++ di Aguilar
Appunto
Informatica - Introduzione
Appunto
Informatica - Digitalizzazione dei numeri
Appunto
Fondamenti di informatica -archittettura di Von Neumann e Assembly
Appunto