Sei sulla pagina 1di 8

Sperimentazione1

Tipi primitivi, cast

Specificatori opzionali:

signed = memorizzo il numero con il segno

unsigned = memorizzo solo numeri senza segno (solo positivi)

sizeof = quanti byte occupa un certo tipo/variabile —> printf(“%d”, sizeof(int));

Per sapere quali sono i valori minimi/massimi da memorizzare:

#include <stdio.h>
#include <limits.h>
int main(void) {
printf("SHRT_MIN=%d\n", SHRT_MIN);
printf("SHRT_MAX=%d\n", SHRT_MAX);
printf("INT_MIN=%d\n", INT_MIN);
printf("INT_MAX=%d\n", INT_MAX);
printf("LONG_MIN=%ld\n", LONG_MIN);
printf("LONG_MAX=%ld\n", LONG_MAX);
printf("USHRT_MAX=%u\n", USHRT_MAX);
printf("UINT_MAX=%u\n", UINT_MAX);
printf("ULONG_MAX=%lu\n", ULONG_MAX);

return 0;
}

Se il valore memorizzato è superiore al valore massimo si avrà un overflow (probabilmente verrà


stampato un numero negativo).

Valutazione cortocircuitata = se c’è un’operazione logica falsa, non controllo anche le altre
(quando c’è && o | | ), rende più efficiente le valutazioni

CAST:

Esempio: programma che converte le temperature da Fahrenheit a Celsius

formula: cels = (fahr - 32)*5*1/9

e posso riscriverla in due modi diversi :

cels = ((fahr – 32) * 5) / 9


cels = (fahr – 32) * (5 / 9)

Nel programma (da vedere sulle slide) cels1 ha i valori corretti, cels2 ha sempre 0 perché fahr,
cels1 e cels2 sono stati dichiarati come interi, mentre cels2 doveva essere float.

Le operazioni su C sono definite per tipi omogenei (=tutti int, tutti float..).

Se faccio un’operazione tra tipi diversi, C prima rende omogenea (promuovendo le variabili —>
cambiando il tipo delle variabili, prendendo i tipi più piccoli e trasformandoli nei tipi più grandi)
l’espressione e poi fa l’operazione.

Cast = mettere il nuovo tipo tra parentesi prima della variabile ed è un comando che dice a C di
convertire quella determinata variabile.

( <tipo> ) <espressione>

i= (int)

Scritto così: (double) ((fahr - 32) * 5) / 9 solo la parte in rossa è soggetta al cast

Scritto così: (double) (((fahr - 32) * 5) / 9) non ha la virgola perché è intero/intero e la conversione è
avvenuta dopo la divisione (troppo tardi, ormai la parte decimale è stata scartata).

Siccome long arriva a 9miliardi di miliardi, se nell’esempio visto prima con la somma di 2 miliardi +
2 miliardi, non vado più in overflow, a meno che non inserisca il risultato nella variabile long. Per
risolvere questo problema facciamo un cast, per far sì che sia una somma tra long e non tra interi.

#include <stdio.h>
#define BIG 2000000000 //2 miliardi
int main(void) {
int b = BIG, c = BIG;
long a;
a = b + (long) c;
printf("%ld\n", a); return 0;
}

Bisogna cercare di non mettere mai dei numeri nel proprio codice (è meglio inserirli nelle costanti).

Quando facciamo la compilazione prima di -o mettere anche -Wall (genera tutti i warnings) e -
pedantic.

Es. gcc file.c -Wall -pedantic -o file.out

04/12/17

I puntatori

= variabile che assume come valore un indirizzo di locazione di memoria.

Si chiama così perché permette di indicare (puntare) a qualcosa che sta in memoria (ad esempio
un’altra variabile).

• riferimento ad altre variabili:

• definire strutture dati dinamiche (possiamo creare nuove zone di memoria);

• realizzare il passaggio di parametri per riferimento (la funzione può cambiare il valore del
parametro).

<tipo base> *<identificatore>;


int *pi; —> variabile pi che contiene un indirizzo di memoria di un’altra variabile che contiene un
intero (pi è una variabile che punta a un’altra variabile di tipo intero).

Bisogna sempre specificare il tipo di variabile che stiamo puntando.

float *pf contiene l’indirizzo di memoria di una variabile float.

double *pd

Tre modi di scrivere: int* pi; int * pi; int *pi;


int v = 5 v
5

int *pi; pi

5
pi = &v pi v

Posso accedere a v tramite v o tramite il puntatore:



int b = *pi: vuol dire “parti da un puntatore pi e guarda dove punta” (deferenziamento)

b 5 pi 5 v

*pi = 9 vuol dire “prendo il numero 9 e lo inserisco nella locazione di memoria di pi (cambio il
valore di v, anche senza nominare v). Siccome *pi è di tipo intero posso assegnarlo a un altro tipo
intero.

pi 9 v

* = prende un indirizzo e ricava la variabile;

& = prende una variabile e ricava un indirizzo

Quando definisco una variabile l’asterisco indica un puntatore.

Quando lo uso negli assegnamenti (int b= *pi) vuol dire deferenzia il puntatore.

Cosa succede nella memoria:



ogni locazione ha un indirizzo (scritti in modo esadecimale – in C iniziano con 0x…)

[imm]

INIZIALIZZAZIONE DEI PUNTATORI:

Se non inizializzo la variabile avrò dei bit “a caso”, stessa cosa con i puntatori: se non li inizializzo
avranno degli indirizzi di memoria assegnati casualmente, e quindi non sappiamo dove sta
puntando.

Se utilizziamo un puntatore non inizializzato, probabilmente il nostro programma verrà terminato


perché stavamo accedendo a una zona di memoria appartenente a un altro programma.

PASSAGGIO DEI PARAMETRI PER VALORE E PER “RIFERIMENTO”:

Passaggio per valore (by value)= trasferisco una copia del parametro a una variabile. La funzione
che invoco con questo valore non cambia il valore delle variabili del main.

Passaggio per riferimento (by reference)= invoco una funzione passando per una variabile di
riferimento, quindi la funzione può cambiare valore.

Quando invoco una funzione nello stack (=struttura dati) viene si alloca una zona di memoria
(record di attivazione)

Stack:

• LIFO (Last In, First Out) = l’ultima cosa che metto è la prima cosa che tolgo

Push mette qualcosa dentro lo stack, pop la toglie.

push(A) —> A

push(B) —> BA

push( C ) —> CBA

pop —> BA

pop —> A

pop —>

In C le variabili vengono memorizzate nello stack. Il record di attivazione contiene varie


informazioni, tra cui i parametri e le variabili locali della funzione.

Simulazione della struttura dello stack perché esco dalle funzioni partendo dall’ultima inserita.

Quando la funzione termina, il record di attivazione viene distrutto e si ritorna a eseguire la


funzione chiamante.

Quando faccio il passaggio per valore nel record di attivazione della funzione viene messo una
copia della variabile. Quindi la funzione che invoco opera sulla copia della variabile che sta nel
record di attivazione. Quando finisco di eseguire quella funzione il record di attivazione viene
distrutto e tutte le modifiche fatte sulla variabile vengono perse (perché sono state fatte sulla
copia all’interno del record).

In C ogni passaggio dei parametri è per valore.

Esempio:

int incr (int i) {


i = i+1;
return i;
}

int main (void) {


int m = 5;
int val = incr(m);
return 0;
}

Quando arrivo a return 0, val vale 6 e m vale 5 (perché abbiamo lavorato sulla sua copia).

Passaggio per riferimento: possibilità di cambiare il valore delle variabili nel main. In C possiamo
simulare il passaggio per riferimento usando i puntatori.

Invece che passare direttamente la variabile possiamo passare l’indirizzo della variabile.

Esempio:

void incr (int *pi) {


*pi=*pi+1;
}
int main(void) {
int m = 5;
incr(&m); <— passo a incr l’indirizzo di m. Modifico direttamente ciò che è puntato.
return 0;
}

Dopo aver eseguito incr, la variabile m vale 6.

ARRAY E PUNTATORI:

Gli array vengono allocati i n memoria come celle consecutive.

con int a[5] non ho solo 5 celle ma ce n’è anche una extra che contiene l’indirizzo dell’array (del
primo elemento dell’array).

int a[5] è molto simile allo scrivere int *a;

valore di int a[5]: a == &a [0] (prendo l’array di a, accedi al primo intero e ricava il suo indirizzo).

La relazione tra array e puntatori è che quando scrivo un array è come se scrivessi un puntatore.
Non sono però la stessa cosa, perché a un puntatore posso assegnare un valore ma un array non
posso assegnare nuovi valori.

Accedere in due modi diversi al contenuto dell’array:

• array con indici:




#include<stdio.h>

#define DIM 4


int main(void) {

int b [DIM]={1,3,5,7};

int i;


for(i=0; i<DIM; i++) {

printf(“%d”, b[i]);

}

printf(“\n”);


return 0;

}


• Puntatori con indici:




#include<stdio.h>

#define DIM 4


int main(void) {

int b [DIM]={1,3,5,7};

int *pi;

int i;


pi=b;


for(i=0; i<DIM; i++) {

printf(“%d”, b[i]);

}


for(i=0; i<DIM; i++) {

printf(“%d”, p[i]);

}

printf(“\n”);


return 0;

}

PASSAGGIO DI ARRRAY COME PARAMETRI:

quando ho una funzione che ha come parametro un array, per C è identico a scrivere un
parametro come int*a.

void myFunction(int a[]) {…} è equivalente a void myFunction(int *a) {…}


Ogni modifica che faccio sul puntatore si riflette anche sull’array.

Esempio:

#include <stdio.h>
#include <ctype.h> //per il prototipo di toupper

void inMaiuscolo (char s[]) {


int i;
for (i=0; s[i] != '\0'; i++) ha messo la condizione dentro al for
s[i] = toupper(s[i]); //rende maiuscolo un carattere

int main (void) {


char str[] = "PiPpO";
inMaiuscolo(str);
printf("%s\n", str);
return 0;

char [s] è come char*s

Con le stringhe in scanf non metto la & perché non posso modificare il valore del char. Non serve
neanche quando passiamo un vettore.

11/12/17

Scope, strutture

Scope = visibilità, porzione di codice in cui l’identificatore è definito e ha senso. Se usiamo una
variabile al di fuori del suo scope è come se non fosse definita.

Nello stesso scope non posso definire la variabile per due volte ma posso farlo nello stesso
blocco di codice.

Se l’identificatore si riferisce a una funzione inizia dal punto in cui viene definita e che termina alla
fine del file sorgente. Posso richiamarla dal punto in cui la dichiaro fino alla fine del codice.

Correzione esempio della slide:

1° modo —> definisco la funzione prima di utilizzarla:

#include<stdio.h>

int f(int x, int y) {

return 2*x-3*y;

}

int main (void) {

int a= f(3,5);

printf(“%d”, a);

return 0;

2° modo —> lascio la definizione in un’altro punto e metto prima il prototipo della funzione f:

#include<stdio.h>

int f(int x, int y); // prototipo della funzione f

int main (void) {

int a=f(3,5);

printf(“%d”, a);

return 0;

int f(int x, int y) {

return 2*x+3*y

ls /usr/include/c scritto sul terminale ti fa vedere cosa viene supportato da include

☛ include serve a includere i prototipi delle funzioni (tra cui ad esempio il prototipo di printf)

pow(x,y) è una funzione che prende x e lo eleva all y

(bisogna per aggiungere #include<math.h>

[ http://pythontutor.com/c.html sito per vedere in tempo reale l’output del programma ]

STRUTTURE

struct <etichetta> { 

definizione-di-variabili

}; 


Esempio:

struct studente {

char nome[20];

int eta; —> definizione tipo “struct studente"



char sesso;

float media;

};


non definiamo una variabile ma un tipo. Struct studente possiamo applicarlo a più variabili.

Non c’è locazione di memoria ma solo la definizione di una struttura.

struct studente stud1, stud2; —> definizione di 2 variabili studenti

Si possono definire anche degli array, (es. struct studente elenco ]50]) ogni elemento è di tipo
“struct studente”.

Accediamo ai membri dell’array con <nome-variabile>.<nome-membro> es. c1.valore o c1.seme

Posso anche combinare strutture + puntatori (posso avere dei puntatori a strutture)

si usa ->

struct data *pd : pd è un puntatore alla variabili di tipo struct data.

Come accedere a d:

d.giorno=11; oppure (*pd).giorno=11; equiv. a pd->giorno=11;

d. mese=12; oppure (*pd).mese=12; equiv. a pd->mese=12;

d.anno=2017; oppure (*pd).anno=2017; equiv. a pd->anno=2017;

1: dichiaro la struttura;

2: se voglio che si allochi la memoria definisco le variabili del tipo definito prima;

C cerca di allineare i dati alla parola di memoria per avere un trasferimento più efficiente.

se un intero coincide con una word allora basta una lettura. Se si accavalla tra 2 words allora ci
sarà bisogno di 2 letture.

non basta confrontare i due blocchi di memoria

➤posso assegnare variabili di un tipo struttura a variabili dello stesso tipo di struttura ma non
posso confrontarle.

Esempio:

struct data {

int giorno, mese, anno;

};

void annosuccessivo(struct data x) { // se modifico questo, modifico la copia del parametro

x.anno=x.anno+1;

int main (void) {

struct data d;

d.giorno=11;

d.mese=12;

d.anno=2017;

printf(“anno: %d\n”, d.anno);

annosuccessivo(d); //passo per valore una copia di struct data

printf(“anno; %d\n”, d.anno);

return 0;

Con puntatori:
struct data {

int giorno, mese, anno;

};

void annosuccessivo(struct data *x) { // se modifico questo, modifico la copia del parametro

x->anno=x->anno+1;

int main (void) {

struct data d;

d.giorno=11;

d.mese=12;

d.anno=2017;

printf(“anno: %d\n”, d.anno);

annosuccessivo(&d); //passo per valore una copia di struct data

printf(“anno; %d\n”, d.anno);

return 0;

Potrebbero piacerti anche