Lezione 2 (parte I) - INFN

Indice
Variabili e Tipi Fondamentali in C++ (CAP 2, parte I)
• Tipi semplici propri del linguaggio
Alberto Garfagnini e Marco Mazzocco
• Variabili
• Tipi composti
Università degli studi di Padova
• Il “qualifier” const
• Come operare con i tipi
A.A. 2014/2015
• Come definire le proprie strutture di dati
Introduzione
I tipi aritmetici
• I tipi di dati sono un elemento fondamentale per ogni linguaggio di
programmazione: ci permettono di definire il significato dei dati e quali
operazioni su di essi sono consentite
• Il C++ ha un supporto esteso sui tipi:
3 definisce svariati tipi primitivi
3 fornisce i meccanismi per estenderli e definirne di nuovi
• Il linguaggio definisce un insieme di tipi fondamentali aritmetici per
rappresentare
3 caratteri, interi, booleani e virgola mobile
3 un tipo speciale chiamato void
Tipo
bool
char
wchar_t
char16_t
char32_t
short
int
long
long long
float
double
long double
Descrizione
booleano (true|false)
carattere
carattere esteso
carattere Unicode
carattere Unicode
intero
intero
intero
intero
virgola mobile in singola precisione
virgola mobile in doppia precisione
virgola mobile in precisione estesa
Dimensione Minima
NA
8 bits
16 bits
16 bits
32 bits
16 bits
16 bits
32 bits
64 bits
≥ 6 cifre significative
≥ 10 cifre significative
≥ 10 cifre significative
• Esistono tipi addizionali interi con un numero di bit fissato, definiti nel
header file cstdint
I bool e i char
Gli interi
• Gli interi possono essere:
• Un bool può assumere due valori: true oppure false.
bool is_ready = true;
3 con segno (signed) (rappresentati in base due, notazione Complemento 2)
3 dominio con n-bit : −2n−1 + 1, 2n−1 − 1
• Conversione bool → int:
int ans = true;
int promise = false;
3 senza segno (unsigned) (rappresentati in base due, con tutti i bit a
disposizione per il valore assoluto del numero)
• Conversione int → bool:
3 dominio con n-bit : [0, 2n − 1]
Tipo
short
Bit (specs)
≥ 16
Bit
16
int
≥ short
32
≥ 32 | int
long
32
64
long long
MIN/MAX
-32768
32767
-214783648
214783647
-214783648
214783647
-9223372036854775808
9223372036854775807
//assume valore 1
// assume valore 0
unsigned short
Tipo
Bit (specs)
≥ 16
Bit
16
unsigned int
≥ short
32
bool start = -100;
MAX
65535
bool stop = 0;
4294967295
unsigned long
≥ 32 | int
32
4294967295
• La rappresentazione dei caratteri segue il codice ASCII (8 bit)
64
18446744073709551615
unsigned long long
//assume valore true
// come tutti i valori
// diversi da zero
// assume valore false
• Esistono tre tipi : char, signed char e unsigned char
• Ci sono tipi interi con dimensioni fissate (header <cstdint>) :
• int<N>_t per i tipi con segno e uint<N>_t per quelli senza segno
• La rappresentazione (con o senza segno) dei char è lasciata libera dallo
standard
N = numero di bit del numero
Tipo
char
C++11
3 int8_t, int16_t, int32_t, int64_t
3 uint8_t, uint16_t, uint32_t, uint64_t
I tipi a virgola mobile
Bit (specs)
8
Bit
8
MIN/MAX
0/-127
255/128
Sulla precisione dei tipi a virgola mobile
• I numeri reali sono di tre tipi:
#include <iostream>
Singola precisione
Doppia precisione
Nuovo nello standard
float;
double;
long double;
• Notazione esponenziale (es. +5.37E+16)
• La dimensione non è specificata, tipicamente
float ≤ double ≤ long double
Tipo
Cifre significative
Bit mantissa
Float
6
24
FLT_DIG
FLT_MANT_DIG
Double
Long Double
15
53
DBL_DIG
DBL_MANT_DIG
18
64
LDBL_DIG
LDBL_MANT_DIG
MAX esponente/
MIN esponente
+38
-37
FLT_MAX_10_EXP
FLT_MIN_10_EXP
+308
-307
DBL_MAX_10_EXP
DBL_MIN_10_EXP
+4932
-4931
LDBL_MAX_10_EXP
LDBL_MIN_10_EXP
int main( )
{
using namespace std;
cout.setf(ios_base::fixed, ios_base::floatfield);
float tub = 10.0/3.0;
// preciso a 6 cifre
double mint = 10.0/3.0;
// preciso a 15 cifre
const float MEGA = 1.E6;
cout << "tub = " << tub << endl;
cout << "MEGA tub = " << tub*MEGA << endl;
cout << "10MEGA tub = " << tub*MEGA*10 << endl;
}
tub = 3.333333
cout << "mint = " << mint << endl;
cout << "MEGA mint = " << mint*MEGA « endl;
cout << "10MEGA mint = " << mint*10*MEGA « endl;
MEGA tub = 3333333.250000
return 0;
MEGA mint = 3333333.333333
10MEGA tub = 33333332.000000
mint = 3.333333
10MEGA mint = 3333333.333333
Precisione finita dei tipi a virgola mobile
// fltadd.cpp - esempio sulla precisione finita dei float
#include <iostream>
Quali tipi devo utilizzare ?
• Il C++ non definisce strettamente la dimensione dei tipi di dati in modo da
essere compatibile con diverso hardware
• Alcune regole pratiche:
int main( )
{
float a = 2.34E22f;
float b = a + 1.0f;
3 usare un tipo unsigned se sicuri che i valori non possono essere negativi
std::cout << "a = " << a << std::endl;
std::cout << "b - a = " << b - a << std::endl;
return 0;
}
Il suffisso f indica una costante di tipo float
Output del programma
a = 2.34e+22
b - a = 0
La conversione tra tipi
I La conversione tra tipi avviene automaticamente quando dato un oggetto, il
programma si aspetta un oggetto di un tipo diverso
bool b = 42;
int i = b;
i = 3.14;
double pi = i;
unsigned char c1 = -1;
signed char c2 = 255;
II
III
IV
V
VI
VII
//
//
//
//
//
//
b => true
i => 1
i => 3
pi => 3.0
c1 => 255 con 8-bit chars
c2 undefined
Intero → bool ⇒ false se il valore è 0 e true altrimenti
bool → intero ⇒ 1 se il bool è true e 0 se false
Floating-point → intero ⇒ valore è troncato, parte decimale persa
Intero → Floating-point ⇒ parte decimale è zero. Perdita di precisione possibile
Valore out-of-range → unsigned ⇒ resto del valore, modulo dominio
Valore out-of-range → signed ⇒ risultato indefinito
unsigned char uc = 256;
signed
char sc = 256;
$ g++ -std=c++11 uchar.C
uchar.C:9:8: warning:
large integer implicitly truncated to unsigned type [-Woverflow]
uchar.C:16:8: warning: overflow in implicit constant conversion [-Woverflow]
3 usare int per tutta l’aritmetica tra interi
short è solitamente troppo piccolo e long ha spesso la stessa dimensione
di un int. Usare long long se il numero supera il dominio garantito dagli
int
3 non usare char oppure bool in aspressioni aritmetiche
L’utilizzo di char può essere problematico perchè su alcune architetture è di
tipo signed e in altre unsigned
3 usare double per l’aritmetica a virgola mobile
Sulle architetture odierne (processori a 64-bit) le operazioni a doppia
precisione sono più veloci di quelle in singola. La precisione offerta dai tipi
long double a volte non è necessaria (quindi il double è un risparmio
computazionale
Espressioni con tipi unsigned
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl;
std::cout << u + i << std::endl;
// Stampa: -84
// Stampa: 4294967264
3 nella seconda espressione il numero negativo è convertito a unsigned prima
dell’addizione
unsigned u1 = 42, u2 = 10;
std::cout << u1 - u2 << std::endl;
std::cout << u2 - u1 << std::endl;
// Stampa: 32
// Stampa: 4294967264
3 il fatto che un tipo unsigned non può essere mai minore di zero si riflette nelle
scritture dei cicli for
// Ciclo corretto:
for (int i=10; i>=0; i--) {
std::cout << i << std::endl;
}
// Errato: u e’ sempre >=0 !
for (unsigned u = 10; u >=0; u--) {
std::cout << u << std::endl;
}
Le costanti
L’ aritmetica degli interi
• Ogni costante ha un tipo determinato dal suo formato e valore
Reset point
-32768 +32767
+32768 +32767
+49152
+16384
-16384
• Costanti intere, espresse in base
3 otto se iniziano con uno zero : (es 020)
3 sedici se iniziano con 0x oppure 0X : (es 0x20)
3 dieci (tutti gli altri)
+16384
+65535 +1
0
• Costanti carattere:
3 sono indicate tra i singoli apici : (es ’a’)
-1 +1
0
Interi unsigned
• Costanti a virgola mobile:
3 hanno un punto (es 3.14) oppure un esponente che indica la notazione
scientifica (3.14E-3, o 3.14e-3)
3 lo zero può essere scritto come 0., 0.0, 0e0, 0E0, . . .
Interi con segno
• Costanti stringa (come array di caratteri) :
3 sono indicate tra i doppi apici : (es "Hello World")
Le sequenze di caratteri Escape
Le variabili
• Alcuni caratteri non hanno un immagine visibile (per esempio il carattere "a
• Per poterli rappresentare viene utilizzata una sequenza di due caratteri : ’\’
+ ’carattere’
newline
vertical tab
backslash
carriage return
• Una variabile fornisce un nome ad uno spazio di memoria che contiene dati
che possono essere utilizzati nel programma
capo")
\n
\v
\\
\r
horizontal tab
backspace
question mark
formfeed
\t
\b
\?
\f
alert
double quote
single quote
\a
\"
\’
std::cout << ’\n’;
// prints a newline
std::cout << "\tHi!\n" // tab, text, plus a newline
• È anche possibile scrivere una sequenza di caratteri escape generalizzata
per rappresentare qualsiasi carattere:
7 \x seguito da uno o più cifre esadecimali (es \x4d)
7 \ seguito da uno o più cifre ottali (es \115)
• il tipo della variabile determina la dimensione, la struttura della memoria
utilizzata, il dominio dei valori immagazzinabili e l’insieme delle operazioni
disponibili sulla variabile
Tipo di dato
identificatore
TypeName variableName1, variableName2, . . . ;
identificatore
• La definizione può fornire un valore iniziale per gli elementi che definisce
int sum = 0, value, // tre variabili di tipo int
unit_sold = 0;
// solo la I e la III sono iniz. a 0
Sales_item book;
// oggetto della classe Sales_item
// string e’ una classe della libreria standard del C++
std::string book("8804418052"); // book creato e iniz.
std::cout << "\x4dO\115\12"; // prints MOM and a newline
Un oggetto è una regione di memoria che ha un tipo
Inizializzazioni di oggetti
• Una oggetto inizializzato acquisisce un valore specifico nel momento in cui
viene creato
• si può inizializzate una variabile con una costante, un’espressione o con il
valore di un’altra variabile
double discount_rate = 0.20;
double price = 30.50, discount = price * discount_rate;
List Initialization
• Il C++ definisce diversi modi per inizializzare una variabile
int
int
int
int
• L’utilizzo di parentesi graffe per inizializzare una variabile è stata introdotta
dal C++11 → prende il nome di list initialization
• inizializzazioni e assegnazioni sono operazioni differenti in C++
• si parla di inizializzazione quando si crea una variabile e si inizializza ad un
valore
• con l’assegnazione si sovrascrive una variabile con un nuovo valore
double rate = 0.20;
double discount_rate;
discount_rate = rate;
// inizializzazione
long double pi = 3.14.15926536;
int a{ld}, b = {ld};
// ERRORE: perdita di informazione
int c(ld), d = ld;
// permesso, init con troncamento
• Il compilatore non permetterà l’inizializzazione da liste di una variabile di
C++11
// assegnazione
zero = 0;
zero = {0};
C++11
zero{0};
zero(0);
tipi built-in se questo comporta la perdita di informazione
Identificatori
Default Initialization
• Quando creiamo una variabile senza specificare un valore iniziale, la
variabile è inizializzata al valore di default
• Il valore di default dipende dal tipo della variabile e da dove è stata screata
3 variabili definite fuori da funzioni sono automaticamente inizializzate a zero
7 variabili definite nel corpo di funzioni non sono inizializzate
7 il valore di una variabile non inizializzato non è predicibile
3 Un identificatore (i.e. nome di oggetto) può essere composto da:
3 lettere, cifre e dal carattere underscore
3 non ci sono limiti sulla lunghezza del nome
3 gli identificatori sono case sensitive
// definiamo 4 variabili distinte:
int somename, someName, SomeName, SOMENAME;
• le classi possono definire oggetti con o senza richiesta di inizializzazione.
3 Alcune classi richiedono che ogni oggetto sia inizializzato esplicitamente
std::string global_string;
int global_counter;
int main ()
{
int local_init;
std::string local_string;
...
}
A
// initialized
// init to empty string
• Ci sono un certo numero di convenzioni comunemente accettate per i nomi
delle variabili.
• Seguire una delle convenzioni migliora la leggibilità dei programmi
• un identificatore deve riflettere il significato della variabile
// undefined
// init to empty string
: Le variabili non inizializzate posso causare problemi
RUN-TIME nell’esecuzione dei programmi
• Regole di base:
• gli identificatori sono principalmente in caratteri minuscoli
• la libreria standard del C++ standard usa un carattere underscore come
separatore di parole. Esempio: get_background
• UpperCamelCase: GetBackground
• lowerCamelCase: getBackground
Visibilità delle variabili
3
3
3
3
3
3
Visibilità di una variabile = parte di codice all’interno della quale si può utilizzare
normalmente la visibilità è delimitata dalle parentesi {}
globale (global scope) per tutto ciò che è definito fuori dalle funzioni
di blocco (block scope) per le altre variabili
una variabile definita in global scope è visibile anche in block scope
la visibilità può essere concatenata (nested scope)
#include <iostream>
global scope
int reused = 42;
int main ()
{
local scope
int unique = 0;
std::cout << reused << " " << unique << std::endl;
// 42 0
new objects, hides global reused
int reused = 0;
std::cout << reused << " " << unique << std::endl;
// 0 0
std::cout << ::reused << " " << unique << std::endl; // 42 0
return 0;
request global reused, explicitly
}
A
: Bad Idea definire variabile locale e globale con lo stesso nome