3. Nozioni di base sul linguaggio C#
3.1. Introduction
Tratteremo inizialmente il C# come un linguaggio di programmazione classico. Affronteremo le classi in un secondo momento. In un programma si trovano due elementi:
- i dati
- le istruzioni che li manipolano
In genere si cerca di separare i dati dalle istruzioni:
![]() |
3.2. I dati in C#
C# utilizza i seguenti tipi di dati:
- i numeri interi
- i numeri reali
- i numeri decimali
- i caratteri e le stringhe
- i valori booleani
- gli oggetti
3.2.1. I tipi di dati predefiniti
Tipo C# | Tipo .NET | Dato rappresentato | Suffisso dei valori letterali | Codifica | Intervallo di valori |
Carattere (i) | carattere | 2 byte | carattere Unicode (UTF-16) | ||
Stringa (C) | stringa di caratteri | riferimento a una sequenza di caratteri Unicode | |||
Int32 (S) | numero intero | 4 byte | [-231, 231-1] [–2147483648, 2147483647] | ||
UInt32 (S) | .. | U | 4 byte | [0, 232-1] [0, 4294967295] | |
Int64 (S) | .. | L | 8 byte | [-263, 263 -1] [–9223372036854775808, 9223372036854775807] | |
UInt64 (S) | .. | UL | 8 byte | [0, 264 -1] [0, 18446744073709551615] | |
.. | 1 byte | [-27 , 27 -1] [-128,+127] | |||
Byte (S) | .. | 1 byte | [0 , 28 -1] [0,255] | ||
Int16 (S) | .. | 2 byte | [-215, 215-1] [-32768, 32767] | ||
UInt16 (S) | .. | 2 byte | [0, 216-1] [0,65535] | ||
Singolo (S) | numero reale | F | 4 byte | [1.5 10-45, 3.4 10+38] in valore assoluto | |
Doppio (S) | .. | D | 8 byte | [-1.7 10+308, 1.7 10+308] in valore assoluto | |
Decimale (S) | numero decimale | M | 16 byte | [1.0 10-28,7.9 10+28] in valore assoluto con 28 cifre significative | |
Booleano (S) | .. | 1 byte | vero, falso | ||
Oggetto (C) | riferimento all'oggetto | riferimento all'oggetto |
Sopra, abbiamo affiancato ai tipi C# i loro equivalenti .NET con il commento (S) se si tratta di una struttura e (C) se si tratta di una classe. Si nota che esistono due possibili tipi per un intero a 32 bit: int e Int32. Il tipo int è un tipo C#. Int32 è una struttura appartenente allo spazio dei nomi System. Il suo nome completo è quindi System.Int32. Il tipo int è un alias C# che indica la struttura .NET System.Int32. Allo stesso modo, il tipo C# string è un alias per il tipo .NET System.String. System.String è una classe e non una struttura. I due concetti sono simili, ma presentano la seguente differenza fondamentale:
- una variabile di tipo Structure viene gestita tramite il suo valore
- una variabile di tipo Classe viene gestita tramite il proprio indirizzo (riferimento nel linguaggio orientato agli oggetti).
Sia una structure che una classe sono tipi complessi dotati di attributi e metodi. Pertanto, si potrà scrivere:
Nel codice sopra riportato, il letterale 3 è per impostazione predefinita di tipo C# int, quindi di tipo .NET System.Int32. Questa struttura dispone di un metodo GetType() che restituisce un oggetto che incapsula le caratteristiche del tipo di dati System.Int32. Tra queste, la proprietà FullName restituisce il nome completo del tipo. Si vede quindi che il letterale 3 è un oggetto più complesso di quanto sembri a prima vista.
Ecco un programma che illustra questi diversi punti:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
// esempio 1
int ent = 2;
float fl = 10.5F;
double d = -4.6;
string s = "essai";
uint ui = 5;
long l = 1000;
ulong ul = 1001;
byte octet = 5;
short sh = -4;
ushort ush = 10;
decimal dec = 10.67M;
bool b = true;
Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
}
}
}
- riga 7: dichiarazione di un intero ent
- riga 19: il tipo di una variabile v può essere ottenuto tramite v.GetType().FullName. La dimensione di una struttura S può essere ottenuta tramite sizeof(S). L'istruzione Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) visualizza sullo schermo il testo che costituisce il suo primo parametro, sostituendo ogni notazione {i} con il valore dell'espressione parami.
- riga 22: poiché il tipo string indica una classe e non una struttura, non è possibile utilizzare l'operatore sizeof.
Ecco il risultato dell'esecuzione:
La visualizzazione riporta i tipi .NET e non gli alias C#.
3.2.2. Notazione dei dati letterali
145, -7, 0xFF (esadecimale) | |
100000L | |
134,789, -45E-18 (-45 × 10⁻¹⁸) | |
134.789F, -45E-18F (-45 10-18) | |
100000M | |
'A', 'b' | |
"oggi" "c:\\cap1\\paragrafo3" @"c:\cap1\paragrafo3" | |
vero, falso | |
new DateTime(1954,10,13) (anno, mese, giorno) per il 13/10/1954 |
Si notino le due stringhe letterali: "c:\\chap1\\paragraph3" e @"c:\chap1\paragraph3". Nelle stringhe letterali, il carattere \ viene interpretato. Pertanto "\n" rappresenta il carattere di fine riga e non la sequenza dei due caratteri \ e n. Se si volesse ottenere tale sequenza, bisognerebbe scrivere "\\n", dove la sequenza \\ viene interpretata come un unico carattere \. Si potrebbe scrivere anche @"\n" per ottenere lo stesso risultato. La sintassi @"testo" richiede che il testo venga preso esattamente così come è scritto. A volte questa viene chiamata stringa verbatim.
3.2.3. Dichiarazione dei dati
3.2.3.1. Ruolo delle dichiarazioni
Un programma gestisce dati caratterizzati da un nome e da un tipo. Questi dati vengono memorizzati in memoria. Al momento della compilazione del programma, il compilatore assegna a ciascun dato una posizione in memoria caratterizzata da un indirizzo e da una dimensione. Lo fa avvalendosi delle dichiarazioni effettuate dal programmatore.
Inoltre, queste consentono al compilatore di individuare eventuali errori di programmazione. Ad esempio, l’operazione
x=x*2;
sarà segnalata come errata se x è, ad esempio, una stringa di caratteri.
3.2.3.2. Dichiarazione delle costanti
La sintassi per dichiarare una costante è la seguente:
Ad esempio:
Perché dichiarare le costanti?
- La lettura del programma risulterà più agevole se alla costante viene assegnato un nome significativo:
- Modificare il programma sarà più semplice se la «costante» dovesse cambiare. Quindi, nel caso precedente, se l’aliquota IVA passasse al 33%, l’unica modifica da apportare sarebbe quella di modificare l’istruzione che ne definisce il valore:
Se invece si fosse utilizzato esplicitamente 0,186 nel programma, sarebbe stato necessario modificare numerose istruzioni.
3.2.3.3. Dichiarazione delle variabili
Una variabile è identificata da un nome e si riferisce a un tipo di dati. C# distingue tra maiuscole e minuscole. Pertanto, le variabili FIN e fin sono diverse.
Le variabili possono essere inizializzate al momento della loro dichiarazione. La sintassi per dichiarare una o più variabili è:
dove Identificateur_de_type è un tipo predefinito o un tipo definito dal programmatore. Facoltativamente, una variabile può essere inizializzata al momento della dichiarazione.
È anche possibile non specificare il tipo esatto di una variabile utilizzando la parola chiave var al posto di Identificateur_de_type:
La parola chiave var non significa che le variabili non abbiano un tipo preciso. La variabile variablei ha il tipo del dato valeuri che le viene assegnato. L'inizializzazione è qui obbligatoria affinché il compilatore possa dedurne il tipo della variabile.
Ecco un esempio:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
int i=2;
Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
var j = 3;
Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
var aujourdhui = DateTime.Now;
Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
}
}
}
- riga 6: un dato tipizzato esplicitamente
- riga 7: (dato).GetType().Name è il nome abbreviato di (dato), (dato).GetType().FullName è il nome completo di (dato)
- riga 8: un dato tipizzato implicitamente. Poiché 3 è di tipo int, j sarà di tipo int.
- riga 10: un dato tipizzato implicitamente. Poiché DateTime.Now è di tipo DateTime, aujourdhui sarà di tipo DateTime.
All'esecuzione, si ottiene il seguente risultato:
Una variabile tipizzata implicitamente dalla parola chiave var non può successivamente cambiare tipo. Pertanto, dopo la riga 10 del codice non si potrebbe scrivere la riga:
var aujourdhui = "aujourd'hui";
Vedremo in seguito che è possibile dichiarare un tipo “al volo” all’interno di un’espressione. Si tratta in questo caso di un tipo anonimo, ovvero un tipo a cui l’utente non ha assegnato un nome. Sarà il compilatore ad assegnare un nome a questo nuovo tipo. Se un dato di tipo anonimo deve essere assegnato a una variabile, l’unico modo per dichiararla è utilizzare la parola chiave var.
3.2.4. Le conversioni tra numeri e stringhe di caratteri
nombre.ToString() | |
int.Parse(stringa) oppure System.Int32.Parse | |
long.Parse(stringa) oppure System.Int64.Parse | |
double.Parse (stringa) o System.Double.Parse (stringa) | |
float.Parse (stringa) o System.Float.Parse (stringa) |
La conversione di una stringa in un numero potrebbe non andare a buon fine se la stringa non rappresenta un numero valido. In tal caso viene generato un errore irreversibile denominato eccezione. Questo errore può essere gestito tramite la seguente clausola try/catch:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Se la funzione non genera un'eccezione, si passa all'istruzione successiva; in caso contrario, si passa al corpo della clausola catch e poi all'istruzione successiva. Torneremo in seguito sulla gestione delle eccezioni. Ecco un programma che illustra alcune tecniche di conversione tra numeri e stringhe di caratteri. In questo esempio la funzione affiche visualizza sullo schermo il valore del proprio parametro. Pertanto, affiche(S) visualizza sullo schermo il valore di S, dove S è di tipo string.
using System;
namespace Chap1 {
class P01 {
static void Main(string[] args) {
// dati
const int i = 10;
const long l = 100000;
const float f = 45.78F;
double d = -14.98;
// numero --> stringa
affiche(i.ToString());
affiche(l.ToString());
affiche(f.ToString());
affiche(d.ToString());
//booleano --> stringa
const bool b = false;
affiche(b.ToString());
// stringa --> int
int i1;
i1 = int.Parse("10");
affiche(i1.ToString());
try {
i1 = int.Parse("10.67");
affiche(i1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// stringa --> long
long l1;
l1 = long.Parse("100");
affiche(l1.ToString());
try {
l1 = long.Parse("10.675");
affiche(l1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// stringa --> double
double d1;
d1 = double.Parse("100,87");
affiche(d1.ToString());
try {
d1 = double.Parse("abcd");
affiche(d1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// stringa --> float
float f1;
f1 = float.Parse("100,87");
affiche(f1.ToString());
try {
d1 = float.Parse("abcd");
affiche(f1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
}// fine main
public static void affiche(string S) {
Console.Out.WriteLine("S={0}",S);
}
}// fine classe
}
Righe 30-32: si gestisce l'eventuale eccezione che potrebbe verificarsi. e.Message è il messaggio di errore associato all'eccezione e.
I risultati ottenuti sono i seguenti:
Si noti che i numeri reali sotto forma di stringa di caratteri devono utilizzare la virgola e non il punto decimale. Pertanto si scriverà
ma
3.2.5. Gli array di dati
Un array in C# è un oggetto che consente di raggruppare sotto un unico identificatore dati dello stesso tipo. La sua dichiarazione è la seguente:
n è il numero di dati che l'array può contenere. La sintassi Array[i] indica il dato n. i dove i appartiene all'intervallo [0,n-1]. Qualsiasi riferimento al dato Tableau[i] in cui i non appartiene all’intervallo [0,n-1] provocherà un’eccezione. Un array può essere inizializzato contemporaneamente alla sua dichiarazione:
oppure, più semplicemente:
Gli array hanno una proprietà Length che indica il numero di elementi dell'array.
Un array bidimensionale può essere dichiarato come segue:
Type[,] array=new Type[n,m];
dove n è il numero di righe, m il numero di colonne. La sintassi Tableau[i,j] indica l'elemento j della riga i dell'array. Anche l'array bidimensionale può essere inizializzato contemporaneamente alla sua dichiarazione:
oppure, più semplicemente:
Il numero di elementi in ciascuna delle dimensioni può essere ottenuto tramite il metodo GetLength(i), dove i=0 rappresenta la dimensione corrispondente al primo indice, i=1 quella corrispondente al secondo indice, …
Il numero totale di dimensioni si ottiene con la proprietà Rank, mentre il numero totale di elementi con la proprietà Length.
Un array di array viene dichiarato come segue:
Type[][] array = new Type[n][];
La dichiarazione sopra riportata crea un array di n righe. Ogni elemento array[i] è un riferimento a un array unidimensionale. Questi riferimenti array[i] non vengono inizializzati nella dichiarazione sopra riportata. Il loro valore è il riferimento null.
L'esempio seguente illustra la creazione di un array di array:
// un array di array
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//for
// inizializzazione
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//for j
}//for i
- riga 2: un array noms composto da 3 elementi di tipo string[][]. Ogni elemento è un puntatore ad array (un riferimento a un oggetto) i cui elementi sono di tipo string[].
- righe 3-5: i 3 elementi dell'array noms vengono inizializzati. Ciascuno di essi ora «punta» a un array di elementi di tipo string[]. noms[i][j] è l’elemento j dell’array di tipo string [] a cui fa riferimento noms[i].
- riga 9: inizializzazione dell'elemento nomi[i][j] all'interno di un doppio ciclo. In questo caso, noms[i] è un array di i+1 elementi. Poiché noms[i] è un array, noms[i].Length rappresenta il numero dei suoi elementi.
Ecco un esempio che riunisce i tre tipi di array che abbiamo appena presentato:
using System;
namespace Chap1 {
// matrici
using System;
// classe di test
public class P02 {
public static void Main() {
// un array unidimensionale inizializzato
int[] entiers = new int[] { 0, 10, 20, 30 };
for (int i = 0; i < entiers.Length; i++) {
Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
}//per
// un array bidimensionale inizializzato
double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
for (int i = 0; i < réels.GetLength(0); i++) {
for (int j = 0; j < réels.GetLength(1); j++) {
Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
}//for j
}//for i
// un array di array
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//per
// inizializzazione
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//per j
}//per i
// visualizzazione
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
}//per j
}//per i
}//Principale
}//classe
}//spazio dei nomi
All’esecuzione, otteniamo i seguenti risultati:
3.3. Le istruzioni di base di C#
Si distinguono
1 le istruzioni elementari eseguite dal computer.
2 le istruzioni di controllo dello svolgimento del programma.
Le istruzioni elementari risultano evidenti se si considera la struttura di un microcomputer e delle sue periferiche.
![]() |
-
lettura delle informazioni provenienti dalla tastiera
-
elaborazione delle informazioni
-
scrittura di informazioni sullo schermo
3.3.1. Scrittura sullo schermo
Esistono diverse istruzioni per la scrittura sullo schermo:
Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)
dove expression rappresenta qualsiasi tipo di dato che possa essere convertito in una stringa di caratteri per essere visualizzato sullo schermo. Tutti gli oggetti C# o .NET dispongono di un metodo ToString() utilizzato per effettuare tale conversione.
La classe System.Console consente di accedere alle operazioni di scrittura su schermo (Write, WriteLine). La classe Console dispone di due proprietà, Out ed Error, che sono flussi di scrittura di tipo TextWriter:
- Console.WriteLine() è equivalente a Console.Out.WriteLine() e scrive sul flusso Out solitamente associato alla schermata.
- Console.Error.WriteLine() scrive sul flusso Error, solitamente anch'esso associato allo schermo.
I flussi Out e Error possono essere reindirizzati verso file di testo durante l'esecuzione del programma, come vedremo tra poco.
3.3.2. Lettura dei dati digitati sulla tastiera
Il flusso di dati proveniente dalla tastiera è rappresentato dall’oggetto Console.In di tipo TextReader. Questo tipo di oggetti consente di leggere una riga di testo con il metodo ReadLine:
La classe Console offre un metodo ReadLine associato per impostazione predefinita al flusso In. È quindi possibile scrivere:
La riga digitata sulla tastiera viene memorizzata nella variabile ligne e può quindi essere utilizzata dal programma. Il flusso In può essere reindirizzato verso un file, così come i flussi Out ed Error.
3.3.3. Esempio di operazioni di I/O
Ecco un breve programma che illustra le operazioni di input-output da tastiera/schermo:
using System;
namespace Chap1 {
// classe di test
public class P03 {
public static void Main() {
// scrittura sul flusso Out
object obj = new object();
Console.Out.WriteLine(obj);
// scrittura sul flusso Error
int i = 10;
Console.Error.WriteLine("i=" + i);
// lettura di una riga immessa dalla tastiera
Console.Write("Tapez une ligne : ");
string ligne = Console.ReadLine();
Console.WriteLine("ligne={0}", ligne);
}//fine main
}//fine classe
}
- riga 9: obj è un riferimento a un oggetto
- riga 10: obj viene visualizzato sullo schermo. Per impostazione predefinita, viene chiamato il metodo obj.ToString().
- riga 14: è anche possibile scrivere:
Console.Error.WriteLine("i={0}",i);
Il primo parametro "i={0}" è il formato di visualizzazione, gli altri parametri sono le espressioni da visualizzare. Gli elementi {n} sono parametri "posizionali". All'esecuzione, il parametro {n} viene sostituito dal valore dell'espressione n. n.
Il risultato dell’esecuzione è il seguente:
- riga 1: il risultato visualizzato dalla riga 10 del codice. Il metodo obj.ToString() ha visualizzato il nome del tipo della variabile obj: System.Object. Il tipo object è un alias C# del tipo .NET System.Object.
3.3.4. Reindirizzamento delle I/O
In DOS e UNIX sono presenti tre periferiche standard denominate:
- periferica di ingresso standard - indica per impostazione predefinita la tastiera e porta il n. 0
- periferica di uscita standard - indica per impostazione predefinita lo schermo e ha il n. 1
- periferica di errore standard - indica per impostazione predefinita lo schermo e ha il numero 2
In C#, il flusso di scrittura Console.Out scrive sul dispositivo 1, il flusso di scrittura Console.Error scrive sul dispositivo 2 e il flusso di lettura Console.In legge i dati provenienti dal dispositivo 0.
Quando si avvia un programma in DOS o Unix, è possibile specificare quali saranno i dispositivi 0, 1 e 2 per il programma in esecuzione. Consideriamo la seguente riga di comando:
Aggiungendo gli argomenti argi al programma pg, è possibile reindirizzare le periferiche di I/O standard verso dei file:
il flusso di ingresso standard n. 0 viene reindirizzato al file in.txt. Nel programma, il flusso Console.In preleverà quindi i propri dati dal file in.txt. | |
reindirizza l'uscita n. 1 verso il file out.txt. Di conseguenza, nel programma il flusso Console.Out scriverà i propri dati nel file out.txt | |
idem, ma i dati scritti vengono aggiunti al contenuto attuale del file out.txt. | |
reindirizza l'uscita n. 2 verso il file error.txt. Di conseguenza, nel programma il flusso Console.Error scriverà i propri dati nel file error.txt | |
lo stesso, ma i dati scritti vengono aggiunti al contenuto attuale del file error.txt. | |
Le periferiche 1 e 2 vengono entrambe reindirizzate verso dei file |
Si noti che per reindirizzare i flussi di I/O del programma pg verso dei file, non è necessario modificare il programma pg. È il sistema operativo a determinare la natura delle periferiche 0, 1 e 2. Consideriamo il seguente programma:
using System;
namespace Chap1 {
// reindirizzamenti
public class P04 {
public static void Main(string[] args) {
// lettura flusso In
string data = Console.In.ReadLine();
// scrittura flusso Out
Console.Out.WriteLine("écriture dans flux Out : " + data);
// scrittura flusso Error
Console.Error.WriteLine("écriture dans flux Error : " + data);
}//Main
}//classe
}
Generiamo l'eseguibile da questo codice sorgente:
![]() |
- in [1]: l'eseguibile viene creato facendo clic con il tasto destro del mouse sul progetto / Build
- in [2]: in una finestra DOS, l’eseguibile 04.exe è stato creato nella cartella bin/Release del progetto.
Eseguiamo i seguenti comandi nella finestra DOS [2]:
- riga 1: si inserisce la stringa test nel file in.txt
- righe 2-3: visualizziamo il contenuto del file in.txt per verifica
- riga 4: esecuzione del programma 04.exe. Il flusso In viene reindirizzato al file in.txt, il flusso Out verso il file out.txt, il flusso Error verso il file err.txt. L'esecuzione non genera alcuna visualizzazione.
- righe 5-6: contenuto del file out.txt. Questo contenuto ci mostra che:
- il file in.txt è stato letto
- l'output su schermo è stato reindirizzato al file out.txt
- righe 7-8: verifica analoga per il file err.txt
Si vede chiaramente che i flussi Out e In non scrivono sugli stessi dispositivi, poiché è stato possibile reindirizzarli separatamente.
3.3.5. Assegnazione del valore di un'espressione a una variabile
Ci interessa qui l’operazione variable=expression;
L’espressione può essere di tipo: aritmetico, relazionale, booleano, stringa
3.3.5.1. Interpretazione dell’operazione di assegnazione
L'operazione variable=expression;
è essa stessa un'espressione la cui valutazione avviene nel modo seguente:
- viene valutata la parte destra dell'assegnazione: il risultato è un valore V.
- il valore V viene assegnato alla variabile
- il valore V è anche il valore dell'assegnazione, considerata questa volta come espressione.
È così che l’operazione
è valida. A causa della priorità, verrà valutato l'operatore = più a destra. Si ha quindi
L'espressione V2=espressione viene valutata e ha come valore V. La valutazione di questa espressione ha provocato l'assegnazione di V a V2. L'operatore = successivo viene quindi valutato nella forma:
Il valore di questa espressione è ancora V. La sua valutazione determina l'assegnazione di V a V1.
Pertanto, l'operazione V1=V2=espressione
è un'espressione la cui valutazione
- provoca l'assegnazione del valore di expression alle variabili V1 e V2
- restituisce come risultato il valore expression.
È possibile generalizzare l'espressione del tipo:
3.3.5.2. Espressione aritmetica
Gli operatori delle espressioni aritmetiche sono i seguenti:
-
somma
-
sottrazione
* moltiplicazione
/ divisione: il risultato è il quoziente esatto se almeno uno degli operandi è un numero reale. Se entrambi gli operandi sono interi, il risultato è il quoziente intero. Quindi 5/2 -> 2 e 5,0/2 -> 2,5.
% divisione: il risultato è il resto indipendentemente dalla natura degli operandi, mentre il quoziente è intero. Si tratta quindi dell'operazione modulo.
Esistono diverse funzioni matematiche. Eccone alcune:
radice quadrata | |
Coseno | |
Sen | |
Tangente | |
x alla potenza y (x>0) | |
Esponenziale | |
Logaritmo naturale | |
valore assoluto |
ecc...
Tutte queste funzioni sono definite in una classe C# denominata Math. Quando le si utilizza, è necessario anteporre il nome della classe in cui sono definite. Pertanto si scriverà:
La definizione completa della classe Math è la seguente:





3.3.5.3. Priorità nella valutazione delle espressioni aritmetiche
L'ordine di precedenza degli operatori nella valutazione di un'espressione aritmetica è il seguente (dal più prioritario al meno prioritario):
Gli operatori appartenenti allo stesso blocco [ ] hanno la stessa priorità.
3.3.5.4. Espressioni relazionali
Gli operatori sono i seguenti:
Priorità degli operatori
Il risultato di un'espressione relazionale è il valore booleano false se l'espressione è falsa, true in caso contrario.
Confronto tra due caratteri
Siano dati due caratteri C1 e C2. È possibile confrontarli utilizzando gli operatori
Vengono quindi confrontati i loro codici Unicode, che sono numeri. Secondo l'ordine Unicode si hanno le seguenti relazioni:
Confronto tra due stringhe di caratteri
Vengono confrontate carattere per carattere. La prima disuguaglianza riscontrata tra due caratteri determina una disuguaglianza dello stesso segno tra le stringhe.
Esempi:
Si considerino le stringhe "Gatto" e "Cane"
![]() |
Quest'ultima disuguaglianza permette di affermare che "Gatto" < "Cane".
Supponiamo di confrontare le stringhe "Gatto" e "Gattino". C'è uguaglianza per tutto il tempo fino all'esaurimento della stringa "Gatto". In questo caso, la stringa esaurita viene dichiarata la più "piccola". Si ha quindi la relazione
Funzioni di confronto tra due stringhe
È possibile utilizzare gli operatori relazionali == e != per verificare se due stringhe sono uguali o meno, oppure il metodo Equals della classe System.String. Per le relazioni < <= > >=, occorre utilizzare il metodo CompareTo della classe System.String:
using System;
namespace Chap1 {
class P05 {
static void Main(string[] args) {
string chaine1="chat", chaine2="chien";
int n = chaine1.CompareTo(chaine2);
bool egal = chaine1.Equals(chaine2);
Console.WriteLine("i={0}, egal={1}", n, egal);
Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
}
}
}
Riga 7, la variabile i avrà il valore:
0 se le due stringhe sono uguali
1 se la stringa n. 1 è maggiore della stringa n. 2
-1 se la stringa n. 1 è minore della stringa n. 2
Riga 8: la variabile egal assumerà il valore true se le due stringhe sono uguali, false in caso contrario. Riga 10: si utilizzano gli operatori == e != per verificare se due stringhe sono uguali o meno.
Risultati dell'esecuzione:
3.3.5.5. Espressioni booleane
Gli operatori utilizzabili sono AND (&&) OR (||) NOT (!). Il risultato di un'espressione booleana è un valore booleano.
Priorità degli operatori:
- !
- &&
- ||
double x = 3.5;
bool valide = x > 2 && x < 4;
Gli operatori relazionali hanno la precedenza sugli operatori && e ||.
3.3.5.6. Operazioni bit a bit
Gli operatori
Siano i e j due numeri interi.
sposta i di n bit a sinistra. I bit di riempimento sono zeri. | |
sposta i di n bit a destra. Se i è un numero intero con segno (signed char, int, long), il bit di segno viene mantenuto. | |
esegue l'OR logico bit per bit tra i e j. | |
esegue la logica bit per bit tra i e j (OU). | |
completa i a 1 | |
esegue l'operazione OU EXCLUSIF su i e j |
Si consideri il seguente codice:
short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
- il formato {0:x4} visualizza il parametro n. 0 in formato esadecimale (x) con 4 caratteri (4).
I risultati dell'esecuzione sono i seguenti:
3.3.5.7. Combinazione di operatori
a=a+b può essere scritto come a+=b
a=a-b può essere scritto come a-=b
Lo stesso vale per gli operatori /, %, *, <<, >>, &, |, ^. Pertanto, a=a/2; può essere scritto come a/=2;
3.3.5.8. Operatori di incremento e decremento
La notazione variable++ significa variable=variable+1 oppure variable+=1
La notazione variable-- indica variable=variable-1 oppure variable-=1.
3.3.5.9. L'operatore ternario?
L'espressione
viene calcolata come segue:
1 viene valutata l'espressione expr_cond. Si tratta di un'espressione condizionale con valore vrai o faux
2 Se è vera, il valore dell'espressione è quello di expr1 e expr2 non viene valutata.
3 Se è falsa, avviene il contrario: il valore dell’espressione è quello di expr2 e expr1 non viene valutato.
L'operazione i=(j>4 ? j+1:j-1); assegnerà alla variabile i il valore j+1 se j>4, altrimenti j-1. È come scrivere if(j>4) i=j+1; else i=j-1; ma è più conciso.
3.3.5.10. Priorità generale degli operatori
gd | |
dg | |
dg | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
dg | |
dg |
gd indica che, a parità di priorità, viene osservata la priorità da sinistra a destra. Ciò significa che quando in un'espressione sono presenti operatori con la stessa priorità, viene valutato per primo l'operatore più a sinistra nell'espressione. dg indica una priorità da destra a sinistra.
3.3.5.11. I cambiamenti di tipo
In un'espressione è possibile modificare temporaneamente la codifica di un valore. Questo processo è noto come "cambio di tipo" o, in inglese, "type casting". La sintassi per il cambio di tipo di un valore in un'espressione è la seguente:
Il valore assume quindi il tipo indicato. Ciò comporta una modifica della codifica del valore.
using System;
namespace Chap1 {
class P06 {
static void Main(string[] args) {
int i = 3, j = 4;
float f1=i/j;
float f2=(float)i/j;
Console.WriteLine("f1={0}, f2={1}",f1,f2);
}
}
}
- alla riga 7, f1 avrà il valore 0,0. La divisione 3/4 è una divisione intera poiché entrambi gli operandi sono di tipo int.
- riga 8, (float)i è il valore di i convertito in float. Ora si ha una divisione tra un numero reale di tipo float e un numero intero di tipo int. Viene quindi eseguita la divisione tra i due numeri reali. Anche il valore di j verrà convertito nel tipo float, dopodiché verrà eseguita la divisione tra i due numeri reali. f2 avrà quindi il valore 0,75.
Ecco i risultati dell'esecuzione:
Nell’operazione (float)i:
- i è un valore codificato in modo esatto su 2 byte
- (float) i è lo stesso valore codificato in modo approssimativo come numero reale su 4 byte
Si verifica quindi una transcodifica del valore di i. Tale transcodifica avviene solo per la durata di un calcolo, mentre la variabile i mantiene sempre il proprio tipo int.
3.4. Le istruzioni di controllo dello svolgimento del programma
3.4.1. Arresto
Il metodo Exit definito nella classe Environment consente di interrompere l'esecuzione di un programma.
syntaxe void Exit(int status)
action arrête le processus en cours et rend la valeur status au processus père
Exit provoca la fine del processo in corso e restituisce il controllo al processo chiamante. Il valore di status può essere utilizzato da quest'ultimo. In DOS, questa variabile di stato viene restituita nella variabile di sistema ERRORLEVEL, il cui valore può essere verificato in un file batch. In Unix, con l'interprete di comandi Bourne Shell, è la variabile $? che recupera il valore di status.
interromperà l’esecuzione del programma con un valore di stato pari a 0.
3.4.2. Struttura di selezione semplice
Note:
- la condizione è racchiusa tra parentesi.
- Ogni azione è terminata da un punto e virgola.
- le parentesi graffe non sono seguite da un punto e virgola.
- le parentesi graffe sono necessarie solo se c'è più di un'azione.
- la clausola else può essere assente.
- Non è presente la clausola then.
L'equivalente algoritmico di questa struttura è la struttura «se ... allora ... altrimenti»:
![]() |
Esempio
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
È possibile annidare le strutture di selezione:
A volte si presenta il seguente problema:
using System;
namespace Chap1 {
class P07 {
static void Main(string[] args) {
int n = 5;
if (n > 1)
if (n > 6)
Console.Out.WriteLine(">6");
else Console.Out.WriteLine("<=6");
}
}
}
Nell'esempio precedente, a quale if si riferisce il else della riga 10? La regola prevede che un else si riferisca sempre al if più vicino: if(n>6), alla riga 8, nell’esempio. Consideriamo un altro esempio:
if (n2 > 1) {
if (n2 > 6) Console.Out.WriteLine(">6");
} else Console.Out.WriteLine("<=1");
In questo caso volevamo associare un else al if(n2>1) e non un else al if(n2>6). A causa di quanto osservato in precedenza, siamo costretti a inserire le parentesi graffe in if(n2>1) {...} else ...
3.4.3. Struttura dei casi
La sintassi è la seguente:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default:
actions_sinon;
break;
}
note
- il valore dell'espressione di controllo di switch può essere un numero intero, un carattere o una stringa di caratteri
- L'espressione di controllo è racchiusa tra parentesi.
- La clausola default può essere assente.
- I valori vi sono valori possibili dell'espressione. Se l'espressione ha come valore vi, vengono eseguite le azioni successive alla clausola case vi.
- L'istruzione break esce dalla struttura case.
- Ogni blocco di istruzioni associato al valore vi deve terminare con un'istruzione di salto (break, goto, return, ...); in caso contrario, il compilatore segnala un errore.
Esempio
In algoritmica
selon la valeur de choix
cas 0
fin du module
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
In C#
3.4.4. Strutture di ripetizione
3.4.4.1. Numero di ripetizioni noto
Struttura for
La sintassi è la seguente:
for (i=id;i<=if;i=i+ip){
actions;
}
Note
- i 3 argomenti di for sono racchiusi tra parentesi e separati da punti e virgola.
- Ogni azione di for termina con un punto e virgola.
- La parentesi graffa è necessaria solo se vi è più di un'azione.
- La parentesi graffa non è seguita da un punto e virgola.
L'equivalente algoritmico è la struttura pour:
che può essere tradotta in una struttura tantque:
Struttura foreach
La sintassi è la seguente:
foreach (Type variable in collection)
instructions;
}
Note
- collection è una collezione di oggetti enumerabile. La collezione di oggetti enumerabile che già conosciamo è l'array
- Type è il tipo degli oggetti della collezione. Nel caso di un array, sarebbe il tipo degli elementi dell'array
- variable è una variabile locale al ciclo che assumerà in successione tutti i valori della collezione.
Pertanto, il codice seguente:
string[] amis = { "paul", "hélène", "jacques", "sylvie" };
foreach (string nom in amis) {
Console.WriteLine(nom);
}
visualizzerebbe:
3.4.4.2. Numero di ripetizioni sconosciuto
In C# esistono numerose strutture per questo caso.
Struttura «while»
while(condition){
actions;
}
Il ciclo continua finché la condizione è vera. Il ciclo potrebbe non essere mai eseguito.
Note:
- la condizione è racchiusa tra parentesi.
- Ogni azione è seguita da un punto e virgola.
- la parentesi graffa è necessaria solo se c'è più di un'azione.
- la parentesi graffa non è seguita da un punto e virgola.
La struttura algoritmica corrispondente è la struttura «finché»:
Struttura "ripeti fino a" (do while)
La sintassi è la seguente:
do{
instructions;
}while(condition);
Il ciclo continua finché la condizione non diventa falsa. In questo caso il ciclo viene eseguito almeno una volta.
Note
- la condizione è racchiusa tra parentesi.
- Ogni azione è seguita da un punto e virgola.
- la parentesi graffa è necessaria solo se c'è più di un'azione.
- La parentesi graffa non è seguita da un punto e virgola.
La struttura algoritmica corrispondente è la struttura «ripeti … fino a»:
Struttura «for» (for)
La sintassi è la seguente:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
Il ciclo continua finché la condizione è vera (valutata prima di ogni iterazione). Le istruzioni Instructions_départ vengono eseguite prima di entrare nel ciclo per la prima volta. Le istruzioni Instructions_fin_boucle vengono eseguite dopo ogni iterazione.
note
- le diverse istruzioni in instructions_depart e instructions_fin_boucle sono separate da virgole.
La struttura algoritmica corrispondente è la seguente:
Esempi
I seguenti frammenti di codice calcolano tutti la somma dei primi 10 numeri interi.
int i, somme, n=10;
for (i = 1, somme = 0; i <= n; i = i + 1)
somme = somme + i;
for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;
i = 1; somme = 0;
while (i <= n) { somme += i; i++; }
i = 1; somme = 0;
do somme += i++;
while (i <= n);
3.4.4.3. Istruzioni per la gestione dei cicli
esce dal ciclo for, while, do ... while. | |
passa all'iterazione successiva dei cicli for, while, do ... while |
3.5. Gestione delle eccezioni
Numerose funzioni C# possono generare eccezioni, ovvero errori. Quando una funzione è suscettibile di generare un'eccezione, il programmatore dovrebbe gestirla al fine di ottenere programmi più resistenti agli errori: occorre sempre evitare il "blocco" improvviso di un'applicazione.
La gestione di un'eccezione avviene secondo lo schema seguente:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Se la funzione non genera un'eccezione, si passa all'istruzione successiva; in caso contrario, si passa al corpo della clausola catch e poi all'istruzione successiva. Si notino i seguenti punti:
- e è un oggetto di tipo Exception o derivato. È possibile essere più precisi utilizzando tipi quali IndexOutOfRangeException, FormatException, SystemException, ecc.: esistono diversi tipi di eccezioni. Scrivendo catch (Exception e), si indica che si desidera gestire tutti i tipi di eccezioni. Se il codice della clausola try è in grado di generare diversi tipi di eccezioni, si può voler essere più precisi gestendo l’eccezione con più clausole catch:
try{
code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
traiter l'exception e1
}
} catch ( FormatException e2){
traiter l'exception e2
}
instruction suivante
- È possibile aggiungere alle clausole try/catch una clausola finally:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
finally{
code exécuté après try ou catch
}
instruction suivante
Che vi sia o meno un'eccezione, il codice della clausola finally verrà sempre eseguito.
- Nella clausola catch, potrebbe non essere necessario utilizzare l'oggetto Exception disponibile. Invece di scrivere catch (Exception e){..}, si scrive quindi catch(Exception){...} o, più semplicemente, catch {...}.
- La classe Exception ha una proprietà Message che contiene un messaggio che descrive in dettaglio l’errore verificatosi. Pertanto, se si desidera visualizzarlo, si scriverà:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
...
}//catch
- La classe Exception dispone di un metodo ToString che restituisce una stringa di caratteri indicante il tipo dell’eccezione e il valore della proprietà Message. Si potrà quindi scrivere:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
...
}//catch
Si può scrivere anche:
Il compilatore assegnerà al parametro {0} il valore ex.ToString().
L'esempio seguente mostra un'eccezione generata dall'utilizzo di un elemento di array inesistente:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// dichiarazione e inizializzazione di un array
int[] tab = { 0, 1, 2, 3 };
int i;
// visualizzazione dell'array con un ciclo for
for (i = 0; i < tab.Length; i++)
Console.WriteLine("tab[{0}]={1}", i, tab[i]);
// visualizzazione di un array con un ciclo for each
foreach (int élmt in tab) {
Console.WriteLine(élmt);
}
// generazione di un'eccezione
try {
tab[100] = 6;
} catch (Exception e) {
Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
return;
}//try-catch
finally {
Console.WriteLine("finally ...");
}
}
}
}
Nella riga 18 sopra riportata verrà generata un'eccezione poiché l'array tab non contiene l'elemento n. 100. L'esecuzione del programma produce i seguenti risultati:
- riga 9: si è verificata l'eccezione [System.IndexOutOfRangeException]
- riga 11: è stata eseguita la clausola finally (righe 23-25) del codice, nonostante alla riga 21 fosse presente un'istruzione return per uscire dal metodo. Si noti che la clausola finally viene sempre eseguita.
Ecco un altro esempio in cui si gestisce l’eccezione causata dall’assegnazione di una stringa di caratteri a una variabile di tipo intero quando la stringa non rappresenta un numero intero:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// esempio 2
// Viene richiesto il nome
Console.Write("Nom : ");
// lettura della risposta
string nom = Console.ReadLine();
// viene richiesta l'età
int age = 0;
bool ageOK = false;
while (!ageOK) {
// domanda
Console.Write("âge : ");
// lettura e verifica della risposta
try {
age = int.Parse(Console.ReadLine());
ageOK = age>=1;
} catch {
}//try-catch
if (!ageOK) {
Console.WriteLine("Age incorrect, recommencez...");
}
}//while
// visualizzazione finale
Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
}
}
}
- righe 15-27: il ciclo di immissione dell’età di una persona
- riga 20: la riga digitata sulla tastiera viene trasformata in un numero intero dal metodo int.Parse. Questo metodo genera un'eccezione se la conversione non è possibile. Per questo motivo, l'operazione è stata inserita in un blocco try/catch.
- righe 22-23: se viene generata un'eccezione, si passa al blocco catch dove non viene eseguita alcuna operazione. Pertanto, la variabile booleana ageOK, impostata a false alla riga 14, rimarrà a false.
- riga 21: se si arriva a questa riga, significa che la conversione da stringa a intero è andata a buon fine. Si verifica tuttavia che l’intero ottenuto sia effettivamente maggiore o uguale a 1.
- righe 24-26: viene emesso un messaggio di errore se l’età non è corretta.
Alcuni risultati dell’esecuzione:
3.6. Esempio di applicazione - V1
Ci proponiamo di scrivere un programma che consenta di calcolare l’imposta di un contribuente. Consideriamo il caso semplificato di un contribuente che abbia da dichiarare solo il proprio stipendio (dati del 2004 relativi ai redditi del 2003):
- si calcola il numero di quote del dipendente nbParts=nbEnfants/2 +1 se non è sposato, nbEnfants/2+2 se è sposato, dove nbEnfants è il numero dei suoi figli.
- se ha almeno tre figli, gli spetta una mezza quota in più
- si calcola il suo reddito imponibile R=0,72*S, dove S è il suo stipendio annuale
- si calcola il suo coefficiente familiare QF = R/nbParts
- si calcola l’imposta I. Consideriamo la seguente tabella:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23.888 | 0,2826 | 2668,39 |
38.868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Ogni riga contiene 3 campi. Per calcolare l'imposta I, si cerca la prima riga in cui QF ≤ campo1. Ad esempio, se QF = 5000, si troverà la riga
L'imposta I è quindi pari a 0,0683*R - 291,09*nbParts. Se QF è tale che la relazione QF<=campo1 non è mai soddisfatta, allora vengono utilizzati i coefficienti dell’ultima riga. In questo caso:
0 0,4809 9505,54
il che dà l'imposta I=0,4809*R - 9505,54*nbParts.
Il programma C# corrispondente è il seguente:
using System;
namespace Chap1 {
class Impots {
static void Main(string[] args) {
// tabelle di dati necessarie per il calcolo dell'imposta
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
// si recupera lo stato civile
bool OK = false;
string reponse = null;
while (!OK) {
Console.Write("Etes-vous marié(e) (O/N) ? ");
reponse = Console.ReadLine().Trim().ToLower();
if (reponse != "o" && reponse != "n")
Console.Error.WriteLine("Réponse incorrecte. Recommencez");
else OK = true;
}//while
bool marie = reponse == "o";
// numero di figli
OK = false;
int nbEnfants = 0;
while (!OK) {
Console.Write("Nombre d'enfants : ");
try {
nbEnfants = int.Parse(Console.ReadLine());
OK = nbEnfants >= 0;
} catch {
}// try
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// while
// stipendio
OK = false;
int salaire = 0;
while (!OK) {
Console.Write("Salaire annuel : ");
try {
salaire = int.Parse(Console.ReadLine());
OK = salaire >= 0;
} catch {
}// try
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// while
// calcolo del numero di quote
decimal nbParts;
if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// reddito imponibile
decimal revenu = 0.72M * salaire;
// quoziente familiare
decimal QF = revenu / nbParts;
// ricerca della fascia di imposta corrispondente a QF
int i;
int nbTranches = limites.Length;
limites[nbTranches - 1] = QF;
i = 0;
while (QF > limites[i]) i++;
// l'imposta
int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
// viene visualizzato il risultato
Console.WriteLine("Impôt à payer : {0} euros", impots);
}
}
}
- righe 7-9: i valori numerici sono seguiti dal suffisso M (Money) affinché siano di tipo decimal.
- riga 16:
- Console.ReadLine() restituisce la stringa C1 digitata dalla tastiera
- C1.Trim() rimuove gli spazi iniziali e finali da C1 - restituisce la stringa C2
- C2.ToLower() restituisce la stringa C3, che è la stringa C2 convertita in minuscolo.
- riga 21: il valore booleano marie riceve il valore true o false dalla relazione reponse=="o"
- riga 29: la stringa digitata sulla tastiera viene convertita nel tipo int. Se la conversione non va a buon fine, viene generata un'eccezione.
- riga 30: il valore booleano OK riceve il valore true o false dalla relazione nbEnfants>=0
- righe 55-56: non è possibile scrivere semplicemente nbEnfants/2. Se nbEnfants fosse pari a 3, si avrebbe 3/2, una divisione intera che darebbe 1 e non 1,5. Pertanto, si scrive (decimal)nbEnfants per rendere reale uno degli operandi della divisione e ottenere così una divisione tra numeri reali.
Ecco alcuni esempi di esecuzione:
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros
3.7. Argomenti del programma principale
La funzione principale Main può accettare come parametro un array di stringhe: String[] (o string[]). Questo array contiene gli argomenti della riga di comando utilizzata per avviare l'applicazione. Pertanto, se si avvia il programma P con il seguente comando (DOS):
P arg0 arg1 … argn
e se la funzione Main è dichiarata come segue:
si avrà args[0]="arg0", args[1]="arg1" … Ecco un esempio:
using System;
namespace Chap1 {
class P10 {
static void Main(string[] args) {
// si elencano i parametri ricevuti
Console.WriteLine("Il y a " + args.Length + " arguments");
for (int i = 0; i < args.Length; i++) {
Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
}
}
}
}
Per passare argomenti al codice eseguito, si procederà come segue:
![]() |
- in [1]: clic destro sul progetto / Proprietà
- in [2]: scheda [Debug]
- in [3]: inserire gli argomenti
L'esecuzione fornisce i seguenti risultati:
Si noti che la firma
è valida se la funzione Main non richiede parametri.
3.8. Le enumerazioni
Un'enumerazione è un tipo di dati il cui dominio di valori è un insieme di costanti intere. Consideriamo un programma che deve gestire le menzioni relative a un esame. Ce ne sarebbero cinque: Passable, AssezBien, Bien, TrèsBien, Excellent.
Si potrebbe quindi definire un'enumerazione per queste cinque costanti:
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
Internamente, queste cinque costanti sono codificate da numeri interi consecutivi che iniziano con 0 per la prima costante, 1 per la successiva e così via. Una variabile può essere dichiarata in modo da assumere questi valori nell'enumerazione:
// una variabile che assume i propri valori dall'enumerazione "Mentions"
Mentions maMention = Mentions.Passable;
È possibile confrontare una variabile con i diversi valori possibili dell’enumerazione:
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
È possibile ottenere tutti i valori dell’enumerazione:
// elenco delle menzioni sotto forma di stringhe
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
Allo stesso modo in cui il tipo semplice int è equivalente alla struttura System.Int32, il tipo semplice enum è equivalente alla struttura System.Enum. Questa struttura dispone di un metodo statico GetValues che consente di ottenere tutti i valori di un tipo enumerato passato come parametro. Quest’ultimo deve essere un oggetto di tipo Type, ovvero una classe che contiene informazioni sul tipo di un dato. Il tipo di una variabile v si ottiene tramite v.GetType(). Il tipo di un tipo T si ottiene tramite typeof(T). Quindi, in questo caso, maMention.GetType() restituisce l’oggetto Type dell’enumerazione Mentions, mentre Enum.GetValues(maMention.GetType()) restituisce l'elenco dei valori dell'enumerazione Mentions.
Se ora scriviamo
//elenco delle menzioni sotto forma di numeri interi
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
Riga 2, la variabile del ciclo è di tipo intero. Si ottiene quindi l'elenco dei valori dell'enumerazione sotto forma di interi. L'oggetto di tipo System.Type corrispondente al tipo di dati Mentions si ottiene tramite typeof(Mentions). Si sarebbe potuto scrivere, come in precedenza, maMention.GetType().
Il programma seguente illustra quanto appena descritto:
using System;
namespace Chap1 {
class P11 {
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
static void Main(string[] args) {
// una variabile che assume i propri valori dall'enumerazione Mentions
Mentions maMention = Mentions.Passable;
// visualizzazione del valore della variabile
Console.WriteLine("mention=" + maMention);
// test con valore dell'enumerazione
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
// elenco delle menzioni sotto forma di stringhe
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
//elenco delle menzioni sotto forma di numeri interi
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
}
}
}
I risultati dell'esecuzione sono i seguenti:
3.9. Passaggio dei parametri a una funzione
In questa sede ci interessiamo alle modalità di passaggio dei parametri a una funzione. Consideriamo la seguente funzione statica:
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
Nella definizione della funzione, riga 1, a è definito come parametro formale. È presente solo ai fini della definizione della funzione changeInt. Avrebbe potuto benissimo chiamarsi b. Consideriamo ora un utilizzo di questa funzione:
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
Qui, nell’istruzione della riga 3, ChangeInt(age), age è il parametro effettivo che trasmetterà il proprio valore al parametro formale a. Ci interessa capire in che modo un parametro formale recupera il valore di un parametro effettivo.
3.9.1. Passaggio per valore
L’esempio seguente mostra che i parametri di una funzione vengono passati per impostazione predefinita per valore, ovvero il valore del parametro effettivo viene copiato nel parametro formale corrispondente. Si tratta di due entità distinte. Se la funzione modifica il parametro formale, il parametro effettivo non subisce alcuna modifica.
using System;
namespace Chap1 {
class P12 {
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
}
}
I risultati ottenuti sono i seguenti:
Il valore 20 del parametro effettivo age è stato copiato nel parametro formale a (riga 10). Quest'ultimo è stato poi modificato (riga 11). Il parametro effettivo è rimasto invece invariato. Questa modalità di passaggio è adatta ai parametri di ingresso di una funzione.
3.9.2. Passaggio per riferimento
In un passaggio per riferimento, il parametro effettivo e il parametro formale sono la stessa entità. Se la funzione modifica il parametro formale, anche il parametro effettivo viene modificato. In C#, entrambi devono essere preceduti dalla parola chiave ref:
Ecco un esempio:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// esempio 2
int age2 = 20;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
e i risultati dell’esecuzione:
Il parametro effettivo ha seguito la modifica del parametro formale. Questa modalità di passaggio è adatta ai parametri di uscita di una funzione.
3.9.3. Passaggio per riferimento con la parola chiave out
Consideriamo l’esempio precedente in cui la variabile age2 non sarebbe inizializzata prima della chiamata alla funzione changeInt:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// esempio 2
int age2;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
Quando si compila questo programma, si verifica un errore:
È possibile aggirare l'ostacolo assegnando un valore iniziale a age2. È anche possibile sostituire la parola chiave ref con la parola chiave out. In questo modo si specifica che il parametro è esclusivamente un parametro di uscita e non necessita quindi di un valore iniziale:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// esempio 3
int age3;
ChangeInt3(out age3);
Console.WriteLine("Paramètre effectif age3=" + age3);
}
private static void ChangeInt3(out int a3) {
a3 = 30;
Console.WriteLine("Paramètre formel a3=" + a3);
}
}
}
I risultati dell'esecuzione sono i seguenti:





