Programma e algoritmo
Programma: non è detto che termini (se termina con qualsiasi dato allora si può definire algoritmo).
Funzione main
main(): può anche non contenere dati.
int main(): ritorna un intero per indicare che se:
- Restituisce 0: ha fatto ciò che doveva fare.
- Altrimenti restituisce un altro numero (errore).
Se nel Main non metto return, funziona (possibilità data solo dal main il quale ha return di default).
return: è una keyword / parola chiave.
Oggetto cout
cout: oggetto di tipo ostream <<, ::, ;, () operatori.
Tipi di dati
int: se inizia con 'O' si considera ottale; con 'Ox' si considera esadecimale. 'Oul' unsigned long.
'0.0', '0.', '.0': sono numeri con la virgola di tipo double. '0.0f ': numeri con la virgola di tipo float.
Interi: short, int, long, long long, signed e relativi unsigned, unsigned char, char.
char è un intero (piccolo) speciale perché stampando si ottiene un carattere.
Manipolazione del buffer
endl: svuota il buffer (cache): è una funzione.
/n: non svuota il buffer.
Libreria e header file
Libreria: pacchetto che contiene funzioni già pronte.
#include <iostream>: è parte del codice sorgente (header file), non è una libreria. Senza <iostream> ottengo un errore di mancata dichiarazione del cout.
Header file: Un header file (o file di intestazione) è un file che aiuta il programmatore nell'utilizzo di librerie durante la programmazione. Un header file è un semplice file di testo che contiene i prototipi delle funzioni definite nel relativo file cc. I prototipi permettono al compilatore di produrre un codice oggetto che può essere facilmente unito con quello della libreria in futuro, anche senza avere la libreria sottomano al momento.
- Dichiarazione: con essa si indica che esiste un'entità con un certo nome (non forniamo un'implementazione particolare).
- Definizione: si fornisce anche l'implementazione.
int a=0; // è una inizializzazione quindi viene definita e non dichiarata.
extern int a; //fornisce una dichiarazione di variabile senza inizializzarla (grazie all’extern).
extern int a=0; //definizione.
extern std::ostream cout; //write one - in questo modo la modifico una sola volta nel header file e si modifica ovunque. Anche l'operatore << va ridefinito per usare cout e endl.
Compilatore G++
Opzioni:
- - W: warming (attiva) ti dice se c'è qualche errore (“massimo livello”).
- - Wall: controlla “tutti” gli errori.
- - Wextra: controlla errori extra.
- - O: dove salva l'output.
- - E: restituisce il numero di caratteri di "hello.preproc.cc", il file creato dal processore prima di usare il compilatore. "hello.preroce.cc" contiene tutto (anche gli #include).
- - LDD: dice quali librerie invoca il programma.
- - Wcin e Wcout: lavorano su carattere più estesi (con più possibili simboli).
Esempi di codice
"G++ -Wall - Wextra hello.cc -O hello"
Esegue il programma indicando eventuali warming
"(G++ -Wall - Wextra hello.cc -O hello) && echo OK"
Se stampa OK è giusto quindi il programma funziona
"G++ -S -Wall - Wextra hello.cc -O hello.s"
Fa una traduzione in assembler (in codice oggetto). In codice oggetto tutti i nomi vengono resi unici (name engley).
"G++ -C -Wall - Wextra hello.cc -O hello.o"
Genera il codice oggetto in binario.
"G++ -E -Wall - Wextra hello.cc -O hello.preproc.cc"
Restituisce il numero di caratteri di "hello.preproc.cc", il file creato dal processore prima di usare il compilatore.
Preprocessore e unità di traduzione
Preprocessore: svolge funzioni prima del compilatore.
Unità di traduzione: è il file che il compilatore compila.
- Ci sono tante unità di traduzione e su ognuna si applica la compilazione.
Errori
- Errori sintattici: (soggetto -> verbo -> compilazione oggetto) se non si segue questo schema si sbaglia.
- Errori di semantica statica: sommare intero e stringa.
- Errori di semantica dinamica: provo a leggere un indirizzo di un array che però non si a dove punta.
- Errori grammaticali: usare parole che non esistono nel linguaggio.
Undefined Behavior (UB): problema non risolto ed è colpa tua.
unsigned namespace std; evita alcune ambiguità fra i nomi.
using std::cout; , using std::endl; prende dalla libreria std solo i nomi che servono (posso usare cout senza ambiguità).
- Meno “roba si tira dentro” più nomi posso usare per variabili.
- Ma usare tanti using rende il codice meno riutilizzabile.
Le identità di std sono: cout, endl, <<, ...
<< però non ha mai davanti std::
Namespace
Namespace: una collezione di nomi di entità, definite dal programmatore, omogeneamente usate in uno o più file sorgente.
Lo scopo dei namespace è quello di evitare confusione ed equivoci nel caso siano necessarie molte entità con nomi simili, fornendo il modo di raggruppare i nomi per categorie.
- Errore di non dichiarazione
Se si cerca di utilizzare una variabile fuori dallo scope della variabile stessa.
Scope: campo di azione (di una variabile).
- Errore di non definizione
extern int a; cout << a << endl; // errore perché non so cosa stampare
"G++ -Wall - Wextra -nostdlib hello.o a.o -O hello"
- - nostdlib: non include la libreria std
- - hello.o a.o: collego più file oggetto.
Se in hello.o e a.o definisco a, mi darà un errore di doppia definizione.
#include<iostream>: cerca tra gli header file del sistema
#include "a.hh": cerca nella directory stessa il file a.hh
Le entità globali è meglio metterle in un file che poi verrà incluso (a.hh).
Segmentation fault e core dumped
Segmentation fault: Si verifica quando un programma tenta di accedere ad una posizione di memoria alla quale non gli è permesso accedere, oppure quando tenta di accedervi in una maniera che non gli è concessa.
Core dumped: caduta improvvisa del programma. Si verifica ad esempio, se si è riempito lo stack.
Continuando a diminuire un numero intero si arriva al minimo numero intero dopo il quale si passa al valore intero più grande.
- Errore di implementazione: i valori di un intero sono implementati.
Per i numeri senza segno, superando il massimo si ottiene lo 0.
Funzione unsigned long longfatt
unsigned long longfatt(unsigned...)
- Meglio andare a capo.
- Se una funzione richiede un unsigned int nulla vieta che l'utente la richiami con ad es -1
unsigned fact (unsigned n) {if (n==0U) …}
- Unsigned controlla che il valore sia zero unsigned (se l'utente passa -1 non funziona)
- Posso anche indicare con un commento quali sono i valori accettati
Controllare gli errori
Modi:
- Variabile globale: sempre a zero che cambia in base all'errore. Problema: ogni volta che si richiama una funzione, c'è da controllare l'errore.
- Modificare la variabile per generare un errore. Problema: anche qui da controllare ogni volta. Si potrebbero avere dei valori che hanno senso ora ma magari in futuro no.
- Try catch
- Asserzioni (assert)
Try Catch
Eccezioni: di default non potete ignorarle (anche se ci si dimentica, si propagano finché tale errore non viene gestito).
- Non sporca il risultato.
if (n<0) throw 123; //Usando ad esempio codici numerici di errore specificati in un documento a parte.
Il throw però costa quindi scarico la responsabilità sull'utente scrivendo in una documentazione cosa necessita la funzione per funzionare.
Compromesso tra gestione degli errori mia o dell'utente:
Assert
Funzioni che restituiscono vero o falso in modo da sapere la riga di errore (assert (n>=0);).
Per usare assert serve #include<cassert>.
Durante il debug quindi verrà usato assert (controllo) per trovare l'errore.
"G++ -Wall - Wextra -dndebut hello.cc a.cc -O hello"
- - dndebug: non fa il debug
- Se non si fa il debug gli assert non vengono effettuati.
Sapere il valore massimo rappresentabile (in C++)
Massimo per un tipo:
- numeric_limits<char>::max();
- CHAR_MAX
- SCHAR_MAX // S -> signed
- UCHAR_MAX // U -> unsigned
Minimo per un tipo:
- numeric_limits<char>::min();
- CHAR_MIN
- SCHAR_ MIN // S -> signed
- UCHAR_ MIN // U -> unsigned
Modo per avere numeri lunghi a piacere (in C++)
È necessario includere una libreria che gestisca tali numeri.
ESEMPI
File: a.hh
#include <gmpxx.h>
extern int a;//deve essere invocato con n>=0
mpz_class fact(mpz_class n);
File: hello
#include "a.hh"
#include <limits>
#include <iostream>
int a; //SE globale, di default è definita = 0; con “explicit” non sarebbe definita
void funz(...) {
int a; //SE non globale, non è definita di default
auto b = 3.2; //determina in automatico il tipo della variabile
auto int a; //nel c++ 11 non si può più fare; nelle vecchie versioni, definiva “a” di dafault a 0
}
int main() {
std::cout << "hello" << std::endl;
std::cout << a << std::endl;
std::cout << sizeof (int); // restituisce la dimensione in byte del tipo specificato
int i=0;
short j=4;
i + j; // ”j” viene convertito da short ad intero temporaneamente per svolgere l’operazione.
j + j; // anche qui ”j” viene trattato come intero perché è il tipo numerico più efficiente.
j = j + j;// tutte le volte che uso short, char etc in operazioni, essi vengono promossi ad interi e si produce un intero che eventualmente può essere riconvertito in short se viene assegnato a short.
const int SIZE = 100;
int ai[SIZE]; // viene allocato uno spazio di 100; lo spazio da occupare deve essere definito nel momento della dichiarazione
ai[10] = 34; // alla funzione si passa il puntatore al primo elemento del vettore (decadimento del tipo)
//avviene questo decadimento per evitare di dover copiare l'intero array nel momento del passaggio per valore)
//anche in ai[10] = 34; decade il tipo a puntatore
ai[j] == *((ai)+(j)); //true; anche la seconda è una giusta scrittura del linguaggio
// * va a leggere nel puntatore calcolato
&ai[0] == &(*(ai)+(0)) == & (* ai) = ai;
for (int i=0; i < SIZE; ++i)
fai_qualcosa_di_intelligente_con(a[i]); // ”a[i]” è considerato in automatico un puntatore; nel caso successivo no quindi devo specificare
for (int* pi=ai; pi != ai + SIZE; ++pi) //pi non è considerato un puntatore quindi devo specificare
fai_qualcosa_di_intelligente_con(*pi);
int* p = &SIZE; // Errore perché io potrei modificare liberamente il puntatore ma SIZE è const quindi:
const int* p = &SIZE; //puntatore varia, il valore resta costante -> const (int* p) = &SIZE;
int* const p = &SIZE; //puntatore costante, valore variabile -> int* (const p) = &SIZE;
const int* const p = &SIZE; //a sinistra del * si parla dell'oggetto puntato, a destra si parla del puntatore -> oggetto puntato * puntatore -> const (int* const (p)) = &SIZE;
const int* q;
{ const int* p = &SIZE;
q = &p;}
//termine del blocco
std::cout << *q;//Il puntatore ”q” punta ad un valore che però al termine del blocco smette di esistere e quindi il puntatore "penzola"
//Il puntatore deve essere cancellato prima del valore puntato.
int* pi = new int(18); //new = creami una porzione di memoria di intero, inserisci 18, e il puntatore punterà a tale oggetto
delete pi; //elimina oggetto puntato ma non il puntatore
pi = NULL; //punta a vuoto
pi = nullptr; //nuovo standard c++
namespace pippo {
int a = 7;}
...
namespace pippo {
... //qui vale ancora la definizione di a}
int funz(...) {
using pippo::a; // la ridefinizione vale fino al termine di questo blocco
using namespace pippo; // utilizza tutte le dichiarazioni fatte in pippo
static int a=0; // vale anche dopo la chiusura del blocco fino al termine del programma
++a;}
FINE esempi
Il carattere è la cosa più piccola indirizzabile in c++.
Il bool varia ma è almeno 1 byte (in alcune macchine è anche 4 byte).
Nella libreria std vengono definite classi chiamate trace come la classe numeric_limit (#include <limits>).
Il codice generico è un codice che funziona indipendentemente dal dato usato e per fare ciò bisogna sapere i limiti di un tipo.
- Se si converte un intero a floating point si potrebbe avere errore di arrotondamento se il numero intero è troppo grande.
- I floating point vanno usati solo in 2 casi:
- Non vi interessa molto il risultato finale
- State usando un algoritmo in cui si sa che l'errore è contenuto entro un certo range
- Esempio di instabilità dei float: lo 0,1 che è sempre arrotondato.
Bisogna conoscere il motivo per cui certi costrutti esistono per capire se vanno usati o no.
Array
Array è un tipo di dato composto (formato da più dati).
++i --> calcola il valore e poi modifica lo stato del programma.
È meglio il preincremento (per leggibilità perché si capisce prima cosa vuole fare il programmatore).
- Un array si può indicare con il range [ai, ai + size)
* expr: estrae l'oggetto puntato
& expr: estrae l'indirizzo di un oggetto
I puntatori permettono di creare array che non debbano avere la dimensione specificata nel momento della dichiarazione.
Scope di una variabile = DOVE è valida
Ciclo di vita di una variabile = QUANDO nasce e quando muore
Ciclo di vita
- Allocazione statica = variabili globali (in certi casi anche locali). Prima di far partire l'esecuzione si alloca la memoria per le variabili globali. Quando termina il programma, tali variabili verranno deinizializzate (distrutte) e poi deallocate (restituire lo spazio della memoria al sistema).
- Allocazione automatica tramite stack: nascono quando ad esse viene data una definizione (fino ad allora sullo stack non ci sono); viene deallocata al termine del blocco.
- Allocazione dinamica: gestisco io le risorse del programma; decido io quando far morire un oggetto.
Io creo un puntatore pi ad oggetto, ma alla chiusura di un blocco il puntatore muore e quindi non ho più modo di trovare l'oggetto puntato.
Si risolve questo problema facendo delete pi;. Questo comando elimina l'oggetto puntato ma NON il puntatore.
Memory leak = quando si cancella il puntatore senza cancellare l'oggetto puntato.
Campi d'azione
- namespace (campo d'azione globale).
extern std::ostream cout; --> variabile dichiarata a livello di namespace (scatole).
int a=7; --> namespace globale (senza nome).
Una variabile globale è presente ovunque (se la includo).
Eccezioni: se la a viene ridefinita all'interno di un altro blocco, allora la seconda a nasconderà quella globale fino al termine del blocco.
Variabile statica (static): vale come globale (dopo che è stata dichiarata)
Puntatori e riferimenti
int a;
int* pa = &a;
int& ra = a;
const int& ra = a; //posso fare modifiche su ra ma non su a
*pa; //restituisce l'oggetto puntato
a; //si riferisce già all'oggetto
- Col puntatore si ha a che fare con due oggetti (puntatore e oggetto puntato).
- Col riferimento si ha solo un oggetto (che è l'oggetto a cui punta).
- Il puntatore occupa memoria, il riferimento no (l'oggetto occupa sempre memoria).
- Il riferimento non può riferirsi a nulla, il puntatore si.
- Il riferimento è "appiccicato" allo specifico oggetto, il puntatore invece può puntare ad altri oggetti diversi.
Passaggio dei valori ad una funzione
Quando passo i valori ad una funzione come li passo?
- In C SOLO per valore (copia del valore passato)
- In C++ si può passare sia per valore sia per riferimento (la funzione userà un suo nome per riferirsi al valore passato)
void fun(int a) //qualsiasi modifica fatta su a non si ripercuote all'esterno
void fun(int* pa) //le modifiche fatte sul puntatore non si ripercuotono sull'esterno, ma quelle sull'oggetto si
void fun(int &a) //si modifica anche all'esterno l'oggetto a
Si usa sempre il passaggio per valore tranne quando:
- Voglio che la modifica fatta al valore passato si ripercuota sull'esterno.
- Se il valore è troppo grande meglio non copiarlo quindi usare il passaggio per riferimento o puntatore.
Puntatori o riferimento?
- I puntatori sono scomodi
- I riferimenti sono più semplici dato che hanno un solo oggetto
- Passare il puntatore dà l'impressione che la funzione modifichi il puntatore (bisogna specificare se è nullo o no); con i riferimenti non esiste il caso "nullo".
- Uso il puntatore solo se necessario (spostarlo).
Tipi predefiniti scalari e non scalari
Non scalari: array, struct/class; insieme di oggetti dello stesso tipo.
Scalari: valore numerico singolo e le operazioni effettuabili su di esso.
- Deprecato usare ++ su un bool, cioè meglio evitare anche se è possibile farlo senza errori
- Array: blocco di oggetti di dimensione determinata
- struct / class: di default nella struct i valori sono public, nella class invece private.
struct Razionale;
struct Razionale{
int num;
int den;};
Razionale r;
int fun() {
r.num;}
Razionale* pr = &r;
int bar() {*pr.num; //sbagliato
(*pr).num; //giusto
pr --> num; //più elegante di quello sopra ma fa la stessa cosa
a[i] == i[a]; //con i intero ed a array}
sizeof(r); //è sicuramente maggiore o uguale alla somma dei sizeof dei singoli campi
struct Razionale {
int num; //4 byte
char c; //1 byte
int den; //4 byte
short p; //2 byte}
< 4 byte > : num
< 1 byte > < padding 3 byte > : c
< 4 byte > : den
< 2 byte > < padding 2 byte > : p
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Risposte teoria metodologie
-
Metodologie farmacologiche
-
Metodologie biochimiche
-
Teorie e metodologie dell’allenamento