3. Nozioni di base sul linguaggio C#
3.1. Introduzione
Inizieremo considerando C# come un linguaggio di programmazione classico. Tratteremo le classi in seguito. In un programma ci sono due elementi:
- i dati
- le istruzioni che li gestiscono
In genere cerchiamo di separare i dati dalle istruzioni:
![]() |
3.2. Dati C#
C# utilizza i seguenti tipi di dati:
- numeri interi
- numeri reali
- numeri decimali
- caratteri e stringhe
- booleani
- oggetti
3.2.1. Tipi di dati predefiniti
Tipo C# | Tipo .NET | Dati rappresentati | Suffisso valori letterali | Codifica | Dominio dei valori |
Carattere (S) | 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 | [-2³¹; 2³¹-1] [-2147483648; 2147483647] | ||
UInt32 (S) | .. | U | 4 byte | [0, 2³²-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⁻⁴⁵, 3,4 × 10³⁸ in termini assoluti | |
Doppio (S) | .. | D | 8 byte | [-1,7 × 10³⁰⁸, 1,7 × 10³⁰⁸ in termini assoluti | |
Decimale (S) | numero decimale | M | 16 byte | [1,0 10⁻²⁸, 7,9 10²⁸] in valore assoluto con 28 cifre significative | |
Booleano (S) | .. | 1 byte | vero, falso | ||
Oggetto (C) | riferimento a un oggetto | riferimento all'oggetto |
Sopra, i tipi C# sono indicati dal loro tipo .NET equivalente, con il commento (S) se il tipo è una struttura e (C) se il tipo è una classe. Si è scoperto che esistono due possibili tipi per un intero a 32 bit: int e Int32. Il tipo int è un tipo C#. Int32 è una struttura appartenente a System. Il suo nome completo è System.Int32. Il tipo int è un alias C# per la struttura .NET System.Int32. Analogamente, la stringa C# è un alias per il tipo .NET System.String. System.String è una classe e non una struttura. I due concetti sono simili, ma con la seguente differenza fondamentale:
- una variabile di tipo Struttura può essere manipolata tramite il suo valore
- una variabile di tipo Classe può essere manipolata tramite il suo indirizzo (riferimento nel linguaggio degli oggetti).
Una struttura, come una classe, è un tipo complesso con attributi e metodi. Ad esempio, possiamo scrivere:
Quanto sopra è il letterale 3, che in C# è di tipo int per impostazione predefinita, ovvero System.Int32 in .NET. Questa struttura dispone di un metodo GetType() che restituisce un oggetto che incapsula le caratteristiche del tipo di dati System.Int32. Tra queste vi è la proprietà FullName, che restituisce il nome completo del tipo. Come si può notare, il letterale 3 è più complesso di quanto sembri a prima vista.
Ecco un programma che illustra questi punti:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
// example 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, ...) scrive sullo schermo il testo che è il suo primo parametro, sostituendo ogni notazione con {i} con il valore dell'espressione parami.
- riga 22: l'operatore stringa non può essere utilizzato, poiché designa una classe e non una struttura sizeof.
Ecco il risultato:
La visualizzazione produce tipi .NET, non alias C#.
3.2.2. Notazione dei dati letterali
145, -7, 0xFF (esadecimale) | |
100000L | |
134,789, -45E-18 (-45 10-18) | |
134.789F, -45E-18F (-45 10-18) | |
100000M | |
'A', 'b' | |
"today" "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 successione dei due caratteri \ e n. Se volessimo ottenere questa successione, dovremmo scrivere "\\n", dove la sequenza viene interpretata come un singolo carattere \. Si potrebbe anche scrivere @"\n" per ottenere lo stesso risultato. La sintassi @"testo" richiede che il testo venga preso esattamente come scritto. Questo viene talvolta chiamato stringa letterale.
3.2.3. Reportistica dei dati
3.2.3.1. Ruolo delle dichiarazioni
Un programma manipola dati caratterizzati da un nome e da un tipo. Questi dati vengono memorizzati in memoria. Quando il programma viene tradotto, il compilatore assegna a ciascun elemento di dati una posizione di memoria caratterizzata da un indirizzo e da una dimensione. Lo fa con l'aiuto delle dichiarazioni effettuate dal programmatore.
Consentono inoltre al compilatore di rilevare errori di programmazione. Ad esempio, l'
x=x*2;
sarà dichiarato errato se x è una stringa, ad esempio.
3.2.3.2. Dichiarazione delle costanti
La sintassi per dichiarare una costante è la seguente:
Ad esempio:
Perché dichiarare le costanti?
- Il programma sarà più facile da leggere se alla costante viene assegnato un nome significativo:
- Modificare il programma sarà più semplice se la "costante" cambia. Ad esempio, nel caso precedente, se l'aliquota IVA cambia al 33%, l'unica modifica da apportare sarà quella di modificare l'istruzione che ne definisce il valore:
Se nel programma fosse stato utilizzato esplicitamente il valore 0,186, sarebbe stato necessario modificare molte istruzioni.
3.2.3.3. Dichiarazione delle variabili
Una variabile è identificata da un nome e fa riferimento a un tipo di dati. C# distingue tra maiuscole e minuscole. Pertanto, FIN ed end sono diversi.
Le variabili possono essere inizializzate al momento della dichiarazione. La sintassi per dichiarare una o più variabili è:
dove Identificatore_di_tipo è un tipo predefinito o un tipo definito dal programmatore. Facoltativamente, una variabile può essere inizializzata oltre che dichiarata.
È anche possibile evitare di specificare il tipo esatto di una variabile utilizzando la parola chiave var al posto di Identificatore_di_tipo :
La parola chiave var non significa che le variabili non abbiano un tipo specifico. Alla variabile variablei viene assegnato il valore del tipo di dati valuei. L'inizializzazione è qui obbligatoria affinché il compilatore possa dedurre 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: dati tipizzati esplicitamente
- riga 7: (data).GetType().Name è il nome abbreviato di (data), (data).GetType().FullName è il nome completo di (data)
- riga 8: dati tipizzati implicitamente. Poiché 3 è di tipo int, j sarà di tipo int.
- riga 10: dati tipizzati implicitamente. Poiché il tipo DateTime.Now è DateTime, il tipo today sarà DateTime.
All'esecuzione, otteniamo il seguente risultato:
Una variabile tipizzata implicitamente dalla parola chiave var non può poi cambiare tipo. Ad esempio, dopo la riga 10 del codice, la riga:
var aujourdhui = "aujourd'hui";
Vedremo più avanti che è possibile dichiarare un tipo "al volo" in un'espressione. In questo caso, si tratta di un tipo anonimo, a cui l'utente non ha assegnato un nome. Il compilatore assegnerà un nome a questo nuovo tipo. Se un tipo anonimo deve essere assegnato a una variabile, l'unico modo per dichiararlo è utilizzare la parola chiave var.
3.2.4. Conversioni tra numeri e stringhe
nombre.ToString() | |
int.Parse(string) oppure System.Int32.Parse | |
long.Parse(string) o System.Int64.Parse | |
double.Parse(string) o System.Double.Parse(string) | |
float.Parse(string) o System.Float.Parse(string) |
La conversione di una stringa in un numero potrebbe non riuscire se la stringa non rappresenta un numero valido. Un errore fatale chiamato exception. Questo errore può essere gestito dal try/catch seguente:
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 più avanti sulla gestione delle eccezioni. Ecco un programma che illustra alcune tecniche per la conversione tra numeri e stringhe. In questo esempio, la funzione poster scrive il valore del suo parametro sullo schermo. Pertanto, poster(S) scrive il valore di S sullo schermo, dove S è di tipo stringa.
using System;
namespace Chap1 {
class P01 {
static void Main(string[] args) {
// data
const int i = 10;
const long l = 100000;
const float f = 45.78F;
double d = -14.98;
// number --> string
affiche(i.ToString());
affiche(l.ToString());
affiche(f.ToString());
affiche(d.ToString());
//boolean --> string
const bool b = false;
affiche(b.ToString());
// string --> 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);
}
// chain --> 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);
}
// chain --> 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);
}
// string --> 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 hand
public static void affiche(string S) {
Console.Out.WriteLine("S={0}",S);
}
}// end of class
}
Le righe 30-32 gestiscono la possibile eccezione che può verificarsi. e.Message è il messaggio di errore associato all'eccezione e.
I risultati sono i seguenti:
Si noti che i numeri reali in forma di stringa devono utilizzare la virgola e non il punto decimale. Pertanto, scriviamo
ma
3.2.5. Tabelle dati
Un array in C# è un oggetto utilizzato per raggruppare dati dello stesso tipo sotto lo stesso identificatore. La sua dichiarazione è la seguente:
n è il numero di elementi che l'array può contenere. La sintassi Table[i] indica il dato numero i, dove i appartiene all'intervallo [0,n-1]. Qualsiasi riferimento al dato Table[i] in cui i non appartiene all'intervallo [0,n-1] genererà un'eccezione. Un array può essere inizializzato oltre che dichiarato:
oppure semplicemente:
Gli array hanno una proprietà chiamata Length che indica il numero di elementi presenti nell'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 Table[i,j] indica l'elemento j nella riga i della tabella. L'array bidimensionale può anche essere inizializzato contemporaneamente alla sua dichiarazione:
oppure semplicemente:
Il numero di elementi in ciascuna dimensione può essere ottenuto utilizzando GetLength(i) dove i=0 rappresenta la dimensione corrispondente al primo indice, i=1 la dimensione corrispondente al secondo indice, ...
Il numero totale di dimensioni si ottiene con la proprietà Rank, il numero totale di elementi con la proprietà Length.
Una tabella di tabelle viene dichiarata come segue:
Type[][] array=new Type[n][];
L'istruzione sopra riportata crea un array di n righe. Ogni elemento table[i] è un riferimento ad 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 tabelle:
// un tableau de tableaux
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//for
// initialisation
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: una tabella denominata names 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 della tabella names vengono inizializzati. Ciascuno ora "punta" a un array di elementi di tipo string[]. names[i][j] è l'array di tipo string[] a cui fa riferimento names[i].
- riga 9: inizializzazione dell'elemento names[i][j] all'interno di un doppio ciclo. Qui names[i] è un array di i+1 elementi. Poiché names[i] è un array, names[i].Length è il suo numero di elementi.
Ecco un esempio dei tre tipi di tabella che abbiamo appena presentato:
using System;
namespace Chap1 {
// tables
using System;
// test class
public class P02 {
public static void Main() {
// an initialized 1-dimensional array
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]);
}//for
// an initialized 2-dimensional array
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
// a table of tables
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//for
// initialization
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
// display
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]);
}//for j
}//for i
}//Main
}//class
}//namespace
All'esecuzione, otteniamo i seguenti risultati:
3.3. Istruzioni di base in C#
Si distingue tra
1 le istruzioni elementari eseguite dal computer.
2 istruzioni per il controllo della sequenza del programma.
Le istruzioni elementari diventano chiare se si considera la struttura di un microcomputer e delle sue periferiche.
![]() |
-
lettura delle informazioni dalla tastiera
-
elaborazione delle informazioni
-
Scrittura delle informazioni sullo schermo
3.3.1. Scrivere sullo schermo
Esistono diverse istruzioni per la scrittura sullo schermo:
Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)
dove espressione è qualsiasi tipo di dati che può essere convertito in una stringa per essere visualizzato sullo schermo. Tutti gli oggetti C# o .NET dispongono di un metodo ToString() che viene utilizzato per eseguire questa conversione.
La classe System.Console consente di accedere alle operazioni di scrittura su schermo (Write, WriteLine). La classe Console ha due proprietà, Out ed Error, che sono di tipo TextWriter:
- Console.WriteLine() è equivalente a Console.Out.WriteLine() e scrive su Out, solitamente associato allo schermo.
- Console.Error.WriteLine() scrive sullo stream Error, solitamente associato allo schermo.
I flussi Out ed Error possono essere reindirizzati a file di testo durante l'esecuzione del programma, come vedremo tra poco.
3.3.2. Lettura dei dati digitati
Il flusso di dati proveniente dalla tastiera è rappresentato dall'oggetto Console.In di tipo TextReader. Questo tipo di oggetto può essere utilizzato per leggere una riga di testo utilizzando il metodo ReadLine :
La classe Console offre un metodo ReadLine associato per impostazione predefinita a In. Possiamo quindi scrivere:
La riga digitata sulla tastiera viene memorizzata nella variabile ligne e può quindi essere utilizzata dal programma. Lo stream In può essere reindirizzato a un file, come Out ed Error.
3.3.3. Esempio di input-output
Ecco un breve programma per illustrare le operazioni di I/O da tastiera/schermo:
using System;
namespace Chap1 {
// test class
public class P03 {
public static void Main() {
// write to Out feed
object obj = new object();
Console.Out.WriteLine(obj);
// write to the Error stream
int i = 10;
Console.Error.WriteLine("i=" + i);
// reading a line entered on the keyboard
Console.Write("Tapez une ligne : ");
string ligne = Console.ReadLine();
Console.WriteLine("ligne={0}", ligne);
}//fine hand
}//end of class
}
- riga 9: obj è un riferimento a un oggetto
- riga 10: obj viene visualizzato sullo schermo. Per impostazione predefinita, viene chiamato 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". In fase di esecuzione, il parametro {n} viene sostituito dal valore dell'espressione n.
Il risultato è 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 I/O
In DOS e UNIX esistono tre dispositivi standard denominati:
- dispositivo di input standard - di default è la tastiera ed è numerato 0
- dispositivo di output standard - di default è lo schermo ed è numerato 1
- dispositivo di errore standard - di default è lo schermo ed è numerato 2
In C#, 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 dal dispositivo 0.
Quando si esegue un programma in DOS o Unix, è possibile impostare quali dispositivi (0, 1 e 2) verranno utilizzati per il programma in esecuzione. Si consideri la seguente riga di comando:
Come illustrato nel programma argi pg, i dispositivi di I/O standard possono essere reindirizzati verso dei file:
il flusso di input standard n. 0 viene reindirizzato al file in.txt. Nel programma, Console.In preleverà quindi i propri dati dal file in.txt. | |
reindirizza l'output n. 1 al file out.txt. Ciò significa che nel programma Console.Out scriverà i propri dati nel file out.txt | |
idem, ma i dati scritti vengono aggiunti al contenuto corrente del file out.txt. | |
reindirizza l'output n. 2 al file error.txt. Ciò significa che nel programma Console.Error scriverà i propri dati nel file error.txt | |
idem, ma i dati scritti vengono aggiunti al contenuto del file corrente error.txt. | |
I dispositivi 1 e 2 vengono entrambi reindirizzati a |
Si noti che per reindirizzare i flussi I/O dal pg ai file, non è necessario modificare il pg. Il sistema operativo determina la natura delle periferiche 0, 1 e 2. Si consideri il seguente programma:
using System;
namespace Chap1 {
// redirections
public class P04 {
public static void Main(string[] args) {
// lecture flux In
string data = Console.In.ReadLine();
// write Out feed
Console.Out.WriteLine("écriture dans flux Out : " + data);
// écriture flux Error
Console.Error.WriteLine("écriture dans flux Error : " + data);
}//Main
}//class
}
Generiamo l'eseguibile di 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: la stringa test nel file in.txt
- righe 2-3: visualizza il contenuto del file in.txt per la verifica
- riga 4: esecuzione del programma 04.exe. Il flusso In viene reindirizzato a in.txt, il flusso Out a out.txt, il flusso Error a 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
- la visualizzazione sullo schermo è stata reindirizzata a out.txt
- righe 7-8: controllo simile per il file err.txt
Possiamo vedere chiaramente che i flussi Out e In non scrivono sugli stessi dispositivi, poiché sono stati reindirizzati separatamente.
3.3.5. Assegnazione del valore di un'espressione a una variabile
Qui ci interessa l'operazione variable=expression;
L'espressione può essere di tipo: aritmetico, relazionale, booleano, caratteri
3.3.5.1. Interpretazione dell'operazione di assegnazione
L'operazione variabile=espressione;
è essa stessa un'espressione la cui valutazione avviene come segue:
- viene valutato il lato destro dell'assegnazione: il risultato è un valore V.
- il valore V viene assegnato alla variabile
- il valore V è anche il valore dell'assegnazione, questa volta considerata come un'espressione.
Ecco come funziona il
è valida. A causa della precedenza, verrà valutato l'operatore = più a destra. Abbiamo quindi
L'espressione V2=espressione viene valutata e le viene assegnato il valore V. La valutazione di questa espressione ha fatto sì che V venisse assegnato a V2. L'operatore = successivo viene quindi valutato come:
Il valore di questa espressione è ancora V. La sua valutazione fa sì che V venga assegnato a V1.
Quindi l'espressione V1=V2=
è un'espressione la cui valutazione
- fa sì che il valore di espressione delle variabili V1 e V2
- porta al valore di expression.
Questo può essere generalizzato a un'espressione come:
3.3.5.2. Espressione aritmetica
Gli operatori per le espressioni aritmetiche sono i seguenti:
-
somma
-
sottrazione
* moltiplicazione
/ divisione: il risultato è il quoziente esatto se almeno uno degli operandi è 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, essendo il quoziente un numero intero. Si tratta quindi dell'operazione modulo.
Esistono varie funzioni matematiche. Eccone alcune:
radice quadrata | |
coseno | |
Sinus | |
Tangente | |
x elevato a y (x>0) | |
Esponenziale | |
logaritmo neperiano | |
valore assoluto |
ecc...
Tutte queste funzioni sono definite in una classe C# chiamata Math. Quando vengono utilizzate, devono essere precedute dal nome della classe in cui sono definite. Ad esempio, scrivi:
La definizione completa della classe Math è la seguente:





3.3.5.3. Priorità per la valutazione delle espressioni aritmetiche
La priorità degli operatori nella valutazione di un'espressione aritmetica è la seguente (dalla priorità più alta a quella più bassa):
Gli operatori all'interno dello 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 caratteristiche
Consideriamo due caratteri C1 e C2. Possono essere confrontati utilizzando gli operatori
Vengono quindi confrontati i loro codici Unicode, che sono numeri. Nell'ordine Unicode, abbiamo le seguenti relazioni:
Confronto tra due stringhe
Vengono confrontate carattere per carattere. La prima disuguaglianza riscontrata tra due caratteri determina una disuguaglianza dello stesso significato tra le stringhe.
Esempi:
Confronta le stringhe "Cat" e "Dog"
![]() |
Questa ultima disuguaglianza implica che "Gatto" < "Cane".
Oppure si confrontino le catene "Gatto" e "Gattino". Si ha un pareggio fino a quando la catena "Gatto" non è esaurita. In questo caso, la catena esaurita viene dichiarata la "più piccola". Abbiamo quindi la relazione
Funzioni per il confronto tra due stringhe
È possibile utilizzare gli operatori relazionali == e != per verificare l'uguaglianza di due stringhe, oppure la classe Equals di System.String. Per le relazioni < <= > >=, utilizzare la classe CompareTo di 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 entrambe le stringhe sono uguali
1 se il canale 1 è maggiore del canale 2
-1 se la catena n. 1 è minore della catena n. 2
Riga 8: la variabile egal avrà il valore true se entrambe le stringhe sono uguali, false in caso contrario. La riga 10 utilizza gli operatori == e != per verificare se due stringhe sono uguali o meno.
Risultati dell'esecuzione:
3.3.5.5. Espressioni booleane
Gli operatori che possono essere utilizzati 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 come operatori di priorità && e ||.
3.3.5.6. Elaborazione bit a bit
Operatori
Siano i e j due numeri interi.
sposta i di n bit a sinistra. I bit in entrata 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 di i e j bit per bit. | |
esegue l'operazione logica OR (OU) di i e j bit per bit. | |
completa i a 1 | |
effettua l'OR ESCLUSIVO di i e j |
Oppure 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 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 /, %, *, <<, >>, &, |, ^. Quindi 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 o anche variable+=1
Scrivere variable-- significa variable=variable-1 o anche variable-=1.
3.3.5.9. L'operatore ternario?
L'espressione
viene valutata come segue:
1 viene valutata l'espressione expr_cond. Si tratta di un'espressione condizionale con un vero o falso
2 Se vera, il valore dell'espressione è quello di expr1 ed expr2 non viene valutata.
3 Se falsa, si verifica il contrario: il valore dell'espressione è quello di expr2 ed expr1 non viene valutata.
L'operazione i=(j>4 ? j+1:j-1); verrà assegnata alla variabile i: j+1 se j>4, j-1 altrimenti. È 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, in caso di priorità uguale, si osserva la priorità da sinistra a destra. Ciò significa che quando in un'espressione sono presenti operatori di pari priorità, viene valutato per primo l'operatore più a sinistra nell'espressione. dg indica la priorità da destra a sinistra.
3.3.5.11. Modifiche di tipo
In un'espressione, è possibile modificare temporaneamente la codifica di un valore. Questa operazione è nota come modifica del tipo di dati o conversione di tipo. La sintassi per modificare il tipo di un valore in un'espressione è la seguente:
Il valore assume quindi il tipo indicato. Ciò comporta una modifica nella 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);
}
}
}
- 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 trasformato in float. Ora abbiamo una divisione tra un numero reale di tipo float e un intero di tipo int. Si tratta della divisione tra numeri reali. Anche il valore di j verrà trasformato in un float, quindi si dividono i due numeri reali. f2 avrà quindi il valore 0,75.
Ecco i risultati:
In (float)i :
- i è un valore codificato esatto a 2 byte
- (float) i è lo stesso valore codificato come approssimazione reale a 4 byte
Il valore di i. Questa transcodifica avviene solo per la durata di un calcolo, e la variabile i mantiene sempre il suo tipo int.
3.4. Istruzioni di controllo del flusso del programma
3.4.1. Stop
L'Exit definito nell'Ambiente interrompe l'esecuzione del programma.
syntaxe void Exit(int status)
action arrête le processus en cours et rend la valeur status au processus père
Exit termina il processo corrente 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 Shell Bourne, la variabile $? recupera il valore di status.
interromperà l'esecuzione del programma con un valore di stato pari a 0.
3.4.2. Struttura di scelta semplice
note:
- la condizione è racchiusa tra parentesi.
- ogni azione è terminata da un punto e virgola.
- le parentesi graffe non sono seguite da punti e virgola.
- le parentesi graffe sono necessarie solo se c'è più di un'azione.
- la clausola else può essere assente.
- Non esiste una clausola then.
L'equivalente algoritmico di questa struttura è if .. then ... otherwise :
![]() |
esempio
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
Le strutture di scelta possono essere annidate:
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 l'else della riga 10? La regola è che un else si riferisce sempre all'if più vicino: if(n>6), riga 8, nell'esempio. Consideriamo un altro esempio:
if (n2 > 1) {
if (n2 > 6) Console.Out.WriteLine(">6");
} else Console.Out.WriteLine("<=1");
Qui volevamo inserire un else nel caso if(n2>1) e nessun else nel caso if(n2>6). A causa dell'osservazione precedente, siamo obbligati a inserire le parentesi graffe nel caso if(n2>1) {...} else ...
3.4.3. Struttura case
La sintassi è la seguente:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default:
actions_sinon;
break;
}
note
- il valore dello switch può essere un numero intero, un carattere o una stringa
- l'espressione è racchiusa tra parentesi.
- la clausola default può essere assente.
- i valori vi sono valori possibili dell'espressione. Se il valore dell'espressione è vi, vengono eseguite le azioni associate alla clausola case vi.
- L'istruzione break ci porta fuori dalla struttura case.
- ogni blocco di istruzioni collegato a un valore vi deve terminare con un'istruzione di diramazione (break, goto, return, ...) altrimenti il compilatore segnala un errore.
esempio
Visita algoritmi
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 noto di ripetizioni
Struttura per
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 for termina con 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.
L'equivalente algoritmico è il ciclo for:
che può essere tradotto come:
Struttura foreach
La sintassi è la seguente:
foreach (Type variable in collection)
instructions;
}
Note
- collection è una raccolta di oggetti enumerabili. La raccolta di oggetti enumerabili che già conosciamo è l'array
- Il tipo è il tipo degli oggetti nella collezione. Per un array, questo sarebbe il tipo degli elementi dell'array
- variable è una variabile locale al ciclo che assumerà successivamente come proprio valore tutti i valori presenti nella collezione.
Quindi il codice seguente:
string[] amis = { "paul", "hélène", "jacques", "sylvie" };
foreach (string nom in amis) {
Console.WriteLine(nom);
}
verrebbe visualizzato:
3.4.4.2. Numero di ripetizioni sconosciuto
In C# esistono molte strutture per questo caso.
Struttura tantque (while)
while(condition){
actions;
}
Il ciclo viene eseguito finché la condizione è vera. Il ciclo potrebbe non essere mai eseguito.
Note:
- la condizione è racchiusa tra parentesi.
- Ogni azione è terminata 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 è tantque :
Ripeti la struttura fino a quando (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 è terminata 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 è repeat ... until :
Struttura per "in generale" (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_inizio vengono eseguite prima di entrare nel ciclo per la prima volta. Le istruzioni_fine_ciclo vengono eseguite dopo ogni iterazione.
note
- le varie 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 di gestione dei cicli
Uscite per loop, while, do ... while. | |
fa avanzare i cicli for, while, do ... alla successiva iterazione. while |
3.5. Gestione delle eccezioni
Molte funzioni C# possono generare eccezioni, ovvero errori. Quando una funzione può generare un'eccezione, il programmatore dovrebbe gestirla con l'obiettivo di ottenere programmi più resistenti agli errori: bisogna sempre evitare il "crash" improvviso di un'applicazione.
Un'eccezione viene gestita come segue:
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 eccezione. Scrivendo catch (Exception e), si indica che si desidera gestire tutti i tipi di eccezione. Se il codice nel try è suscettibile di generare diversi tipi di eccezione, si potrebbe voler essere più precisi gestendo l'eccezione con diversi 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 ci sia o meno un'eccezione, il codice della clausola finally verrà sempre eseguito.
- Nel catch, potresti non voler utilizzare l'Exception disponibile. Invece di scrivere catch (Exception e){..}, scriviamo catch(Exception){...} o semplicemente catch {...}.
- La classe Exception ha una proprietà Message che contiene un messaggio che descrive in dettaglio l'errore verificatosi. Quindi, se vogliamo visualizzarlo, scriveremo:
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 indicante il tipo di eccezione e il valore del Message. Possiamo quindi scrivere:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
...
}//catch
Possiamo anche scrivere:
Il compilatore assegnerà al parametro {0} il valore ex.ToString().
L'esempio seguente mostra un'eccezione generata dall'uso di un elemento di array inesistente:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// declaring & initializing an array
int[] tab = { 0, 1, 2, 3 };
int i;
// table display with for
for (i = 0; i < tab.Length; i++)
Console.WriteLine("tab[{0}]={1}", i, tab[i]);
// table display with a for each
foreach (int élmt in tab) {
Console.WriteLine(élmt);
}
// generating an exception
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, anche se la riga 21 conteneva un'istruzione return per uscire dal metodo. Si noti che il finally viene sempre eseguito.
Ecco un altro esempio di come gestire l'eccezione causata dall'assegnazione di una stringa a una variabile intera quando la stringa non rappresenta un numero intero:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// example 2
// We ask for the name
Console.Write("Nom : ");
// reading response
string nom = Console.ReadLine();
// age requested
int age = 0;
bool ageOK = false;
while (!ageOK) {
// question
Console.Write("âge : ");
// read-verify answer
try {
age = int.Parse(Console.ReadLine());
ageOK = age>=1;
} catch {
}//try-catch
if (!ageOK) {
Console.WriteLine("Age incorrect, recommencez...");
}
}//while
// final display
Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
}
}
}
- righe 15-27: il ciclo per l'inserimento dell'età di una persona
- riga 20: la riga digitata sulla tastiera viene trasformata in un numero intero utilizzando il metodo int.Parse. Questo metodo genera un'eccezione se la conversione non è possibile. Questo è il motivo per cui l'operazione è stata inserita in un try / catch.
- righe 22-23: se viene generata un'eccezione, si passa al catch dove non viene fatto nulla. Pertanto, la variabile booleana ageOK impostata su false alla riga 14 rimarrà false.
- riga 21: se si raggiunge questa riga, la conversione stringa -> int è andata a buon fine. Tuttavia, si verifica che il numero intero ottenuto sia maggiore o uguale a 1.
- righe 24-26: viene visualizzato un messaggio di errore se l'età non è corretta.
Alcuni risultati delle prestazioni:
3.6. Esempio di applicazione - V1
Proponiamo di scrivere un programma per calcolare l'imposta sul reddito di un contribuente. Il caso semplificato è quello di un contribuente che deve dichiarare solo il proprio stipendio (dati del 2004 relativi al reddito del 2003):
- il numero di quote di dipendente viene calcolato come nbParts=nbEnfants/2 +1 se non sposato, nbEnfants/2+2 se sposato, dove nbEnfants è il numero di figli.
- se ha almeno tre figli, ottiene mezza quota in più
- Calcola il tuo reddito imponibile: R = 0,72 × S, dove S è il suo stipendio annuale
- calcola il tuo coefficiente familiare QF=R/nbParts
- calcola la tua imposta I. Considera la seguente tabella:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23888 | 0,2826 | 2668,39 |
38868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Ogni riga ha 3 campi. Per calcolare l'imposta I, cerca la prima riga in cui QF<=champ1. Ad esempio, se QF=5000 troviamo la riga
L'imposta I è quindi pari a 0,0683*R - 291,09*nbParts. Se QF è tale che la relazione QF<=champ1 non viene mai verificata, vengono utilizzati i coefficienti dell'ultima riga. Qui:
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) {
// data tables required for tax calculation
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 };
// marital status is restored
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";
// number of children
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
// salary
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
// calculating the number of shares
decimal nbParts;
if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// taxable income
decimal revenu = 0.72M * salaire;
// family quotient
decimal QF = revenu / nbParts;
// search for tax bracket corresponding to QF
int i;
int nbTranches = limites.Length;
limites[nbTranches - 1] = QF;
i = 0;
while (QF > limites[i]) i++;
// tax
int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
// the result is displayed
Console.WriteLine("Impôt à payer : {0} euros", impots);
}
}
}
- righe 7-9: i valori numerici sono seguiti dal suffisso M (Money) per indicare che sono di tipo decimale.
- riga 16:
- Console.ReadLine() rende la stringa C1 tipizzata
- C1.Trim() rimuove gli spazi iniziali e finali C1 - restituisce una stringa C2
- C2.ToLower() crea la stringa C3, che è la stringa C2 trasformata in minuscolo.
- riga 21: la variabile booleana marie riceve il valore true o false in base alla relazione answer=="o"
- riga 29: la stringa digitata sulla tastiera viene trasformata in un int. Se la trasformazione fallisce, viene generata un'eccezione.
- riga 30: il booleano OK riceve il valore true o false in base alla relazione nbEnfants>=0
- righe 55-56: non è possibile scrivere semplicemente nbEnfants/2. Se nbEnfants fosse uguale a 3, avremmo 3/2, una divisione intera che darebbe 1 e non 1,5. Quindi scriviamo (decimal)nbEnfants per rendere uno degli operandi della divisione reale e avere così una divisione tra reali.
Ecco alcuni esempi:
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 principali del programma
La funzione principale Main può accettare come parametro un array di stringhe: String[] (o string[]). Questa tabella contiene gli argomenti della riga di comando utilizzati per avviare l'applicazione. Ad esempio, se eseguiamo il programma P con il seguente comando (Dos):
P arg0 arg1 … argn
e se Main è dichiarata come segue:
args[0]="arg0", args[1]="arg1" ... Ecco un esempio:
using System;
namespace Chap1 {
class P10 {
static void Main(string[] args) {
// list parameters received
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, procedere come segue:
![]() |
- in [1]: clicca con il tasto destro del mouse sul progetto / Proprietà
- in [2]: scheda [Debug]
- in [3]: inserire gli argomenti
L'esecuzione dà i seguenti risultati:
Si noti che il
è valido se Main non richiede parametri.
3.8. Enumerazioni
Un'enumerazione è un tipo di dati il cui dominio dei valori è un insieme di costanti intere. Consideriamo un programma che deve gestire i voti di un esame. Ce ne sarebbero cinque: Fair, AssezBien, Fine, TrèsBien, Excellent.
Potremmo 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 a partire da 0 per la prima costante, 1 per la successiva, ecc... Una variabile può essere dichiarata in modo da assumere questi valori nell'enumerazione:
// une variable qui prend ses valeurs dans l'énumération Mentions
Mentions maMention = Mentions.Passable;
Una variabile può essere confrontata con i vari valori possibili nell'enumerazione:
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
È possibile ottenere tutti i valori dell'enumerazione:
// list of statements in string form
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
Allo stesso modo in cui il tipo semplice int è equivalente a System.Int32, il tipo semplice enum è equivalente a System.Enum. Questa struttura dispone di un metodo statico GetValues che consente di ottenere tutti i valori di un tipo enumerato passato come parametro. Questo deve essere un oggetto di tipo Type, che è una classe di informazioni sul tipo di dati. Il tipo di una variabile v si ottiene con v.GetType(). Il tipo di tipo T si ottiene con typeof(T). Quindi qui maMention.GetType() restituisce l'oggetto Type dell'enumerazione Mentions e Enum.GetValues(maMention.GetType()) restituisce l'elenco dei valori dell'enumerazione Mentions.
Se ora scriviamo
//list of mentions in integer form
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
Riga 2: la variabile del ciclo è di tipo intero. Otteniamo quindi l'elenco dei valori di enumerazione in forma intera. L'oggetto di tipo System.Type corrispondente al tipo di dati Mentions viene ottenuto tramite typeof(Mentions). Avremmo potuto scrivere maMention.GetType().
Il programma seguente illustra quanto appena scritto:
using System;
namespace Chap1 {
class P11 {
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
static void Main(string[] args) {
// a variable that takes its values from the Mentions enumeration
Mentions maMention = Mentions.Passable;
// variable value display
Console.WriteLine("mention=" + maMention);
// test with enumeration value
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
// list of statements in string form
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
//list of mentions in integer form
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
}
}
}
I risultati sono i seguenti:
3.9. Passaggio dei parametri a una funzione
Qui ci interessa il modo in cui i parametri vengono passati 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, alla riga 1, a è chiamato parametro formale. È presente solo allo scopo di definire la 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 alla riga 3, ChangeInt(età), età è il parametro effettivo che trasmetterà il suo valore al parametro formale a. Ci interessa capire come un parametro formale recuperi il valore di un parametro effettivo.
3.9.1. Passaggio per valore
L'esempio seguente mostra che, per impostazione predefinita, i parametri delle funzioni vengono passati per valore, ovvero il valore del parametro effettivo viene copiato nel parametro formale corrispondente. Abbiamo due entità distinte. Se la funzione modifica il parametro formale, il parametro effettivo rimane invariato.
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 sono i seguenti:
Il valore 20 del parametro effettivo age è stato copiato nel parametro formale a (riga 10). Questo è stato poi modificato (riga 11). Il parametro effettivo è rimasto invariato. Questa modalità è adatta per i parametri di input delle funzioni.
3.9.2. Passaggio per riferimento
In un'esecuzione per riferimento, il parametro effettivo e il parametro formale sono la stessa entità. Se la funzione modifica il parametro formale, viene modificato anche il parametro effettivo. In C#, entrambi devono essere preceduti dalla parola chiave ref :
Ecco un esempio:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// example 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 risultati delle prestazioni:
Il parametro effettivo ha seguito la modifica del parametro formale. Questa modalità è adatta per i parametri di output delle funzioni.
3.9.3. Passaggio per riferimento con la parola chiave out
Consideriamo l'esempio precedente in cui la variabile age2 non verrebbe inizializzata prima di chiamare la funzione changeInt:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// example 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 compiliamo questo programma, otteniamo un errore:
Possiamo aggirare questo ostacolo assegnando un valore iniziale a age2. È anche possibile sostituire la parola chiave ref con la parola chiave out. In questo modo indichiamo che il parametro è solo un parametro di output e quindi non necessita di un valore iniziale:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// example 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 sono i seguenti:





