Skip to content

3. Noções básicas da linguagem C#

3.1. Introduction

Vamos tratar o C#, inicialmente, como uma linguagem de programação clássica. Abordaremos as classes posteriormente. Em um programa, encontramos duas coisas:

  • dados
  • as instruções que os manipulam

Geralmente, procuramos separar os dados das instruções:

3.2. Os dados do C#

O C# utiliza os seguintes tipos de dados:

  1. números inteiros
  1. números reais
  2. números decimais
  3. caracteres e cadeias de caracteres
  4. booleanos
  5. os objetos

3.2.1. Tipos de dados predefinidos

Tipo C#
Tipo .NET
Dado representado
Sufixo
dos valores literais
Codificação
Intervalo de valores
char
Caractere(s)
caractere
 
2 bytes
caractere Unicode (UTF-16)
string
String (C)
sequência de caracteres
  
referência a uma sequência de caracteres Unicode
int
Int32 (S)
número inteiro
 
4 bytes
[-231, 231-1] [–2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 bytes
[0, 232-1] [0, 4294967295]
long
Int64 (S)
..
L
8 bytes
[-263, 263 -1] [–9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 bytes
[0, 264 -1] [0, 18446744073709551615]
sbyte
 
..
 
1 byte
[-27 , 27 -1] [-128,+127]
byte
Byte(s)
..
 
1 byte
[0 , 28 -1] [0,255]
short
Int16 (S)
..
 
2 bytes
[-215, 215-1] [-32768, 32767]
ushort
UInt16 (S)
..
 
2 bytes
[0, 216-1] [0,65535]
float
Único (S)
número real
F
4 bytes
[1.5 10-45, 3.4 10+38] em valor absoluto
double
Duplo (S)
..
D
8 bytes
[-1.7 10+308, 1.7 10+308] em valor absoluto
decimal
Decimal (S)
número decimal
M
16 bytes
[1.0 10-28,7.9 10+28] em valor absoluto com 28 dígitos significativos
bool
Booleano (S)
..
 
1 byte
verdadeiro, falso
object
Objeto (C)
referência de objeto
  
referência de objeto

Acima, colocamos ao lado dos tipos C# seus tipos equivalentes em .NET, com o comentário (S) se esse tipo for uma estrutura e (C) se for uma classe. Percebe-se que existem dois tipos possíveis para um inteiro de 32 bits: int e Int32. O tipo int é um tipo C#. Int32 é uma estrutura pertencente ao namespace System. Seu nome completo é, portanto, System.Int32. O tipo int é um alias em C# que designa a estrutura .NET System.Int32. Da mesma forma, o tipo C# string é um alias para o tipo .NET System.String. System.String é uma classe e não uma estrutura. Os dois conceitos são semelhantes, mas apresentam a seguinte diferença fundamental:

  • uma variável do tipo Structure é manipulada por meio de seu valor
  • uma variável do tipo Classe é manipulada por meio de seu endereço (referência na linguagem orientada a objetos).

Tanto um structure quanto um classe são tipos complexos que possuem atributos e métodos. Assim, é possível escrever:

string nomDuType=3.GetType().FullName;

No exemplo acima, o literal 3 é, por padrão, do tipo C# int e, portanto, do tipo .NET System.Int32. Essa estrutura possui um método GetType() que retorna um objeto que encapsula as características do tipo de dados System.Int32. Entre elas, a propriedade FullName retorna o nome completo do tipo. Vemos, portanto, que o literal 3 é um objeto mais complexo do que parece à primeira vista.

Aqui está um programa que ilustra esses diferentes pontos:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            // exemplo 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));
        }
    }
}
  • linha 7: declaração de um inteiro ent
  • linha 19: o tipo de uma variável v pode ser obtido por v.GetType().FullName. O tamanho de uma estrutura S pode ser obtido por sizeof(S). A instrução Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) exibe na tela o texto que é seu primeiro parâmetro, substituindo cada notação {i} pelo valor da expressão parami.
  • linha 22: como o tipo string designa uma classe e não uma estrutura, não é possível utilizar o operador sizeof.

Aqui está o resultado da execução:

Type de ent[2] : [System.Int32,4]
Type de fl[10,5]: [System.Single,4]
Type de d[-4,6] : [System.Double,8]
Type de s[essai] : [System.String]
Type de ui[5] : [System.UInt32,4]
Type de l[1000] : [System.Int64,8]
Type de ul[1001] : [System.UInt64,8]
Type de b[5] : [System.Byte,1]
Type de sh[-4] : [System.Int16,2]
Type de ush[10] : [System.UInt16,2]
Type de dec[10,67] : [System.Decimal,16]
Type de b[True] : [System.Boolean,1]

A exibição mostra os tipos .NET e não os aliases do C#.

3.2.2. Notação de dados literais

entier int (32 bits)
145, -7, 0xFF (hexadecimal)
entier long (64 bits) - suffixe L
100000L
réel double
134,789, -45E-18 (-45 × 10⁻¹⁸)
réel float (suffixe F)
134.789F, -45E-18F (-45 10⁻¹⁸)
réel decimal (suffixe M)
100000M
caractère char
'A', 'b'
chaîne de caractères string
"hoje" "c:\\cap1\\parágrafo3" @"c:\cap1\parágrafo3"
booléen bool
true, false
date
new DateTime(1954,10,13) (ano, mês, dia) para 13/10/1954

Observe as duas cadeias literais: "c:\\chap1\\paragraph3" e @"c:\chap1\paragraph3". Nas cadeias literais, o caractere \ é interpretado. Assim, “\n” representa o caractere de fim de linha e não a sucessão dos dois caracteres \ e n. Se quiséssemos essa sucessão, seria necessário escrever “\\n”, onde a sequência \\ é interpretada como um único caractere \. Também seria possível escrever @“\n” para obter o mesmo resultado. A sintaxe @“texto” exige que o texto seja interpretado exatamente como está escrito. Às vezes, isso é chamado de cadeia verbatim.

3.2.3. Declaração de dados

3.2.3.1. Função das declarações

Um programa manipula dados caracterizados por um nome e um tipo. Esses dados são armazenados na memória. No momento da compilação do programa, o compilador atribui a cada dado um local na memória caracterizado por um endereço e um tamanho. Ele faz isso com base nas declarações feitas pelo programador.

Além disso, essas declarações permitem que o compilador detecte erros de programação. Assim, a operação

x = x * 2;

será declarada como errada se x for uma sequência de caracteres, por exemplo.

3.2.3.2. Declaração de constantes

A sintaxe para declarar uma constante é a seguinte:

    const type nom=valeur;          //define constante nome=valor

Por exemplo:

const float myPI=3.141592F;    

Por que declarar constantes?

  1. A leitura do programa será mais fácil se dermos à constante um nome significativo:
    const  float taux_tva=0.186F;
  1. A modificação do programa será mais fácil caso a “constante” venha a mudar. Assim, no caso anterior, se a alíquota do IVA passar para 33%, a única alteração a ser feita será modificar a instrução que define seu valor:
    const float taux_tva=0.33F;

Se tivéssemos usado 0,186 explicitamente no programa, seria necessário alterar várias instruções.

3.2.3.3. Declaração de variáveis

Uma variável é identificada por um nome e está associada a um tipo de dados. O C# diferencia maiúsculas de minúsculas. Assim, as variáveis FIN e fin são diferentes.

As variáveis podem ser inicializadas no momento da sua declaração. A sintaxe para declarar uma ou mais variáveis é:

 Identificateur_de_type variable1[=valeur1],variable2=[valeur2],...;

onde Identificateur_de_type é um tipo predefinido ou um tipo definido pelo programador. Opcionalmente, uma variável pode ser inicializada ao mesmo tempo em que é declarada.

Também é possível não especificar o tipo exato de uma variável usando a palavra-chave var em vez de Identificateur_de_type:

 var variable1=valeur1,variable2=valeur2,...;

A palavra-chave var não significa que as variáveis não tenham um tipo específico. A variável variablei possui o tipo dos dados valeuri que lhe são atribuídos. A inicialização é obrigatória neste caso para que o compilador possa deduzir o tipo da variável.

Veja um exemplo:


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);
        }
    }
}
  • linha 6: um dado explicitamente tipado
  • linha 7: (dado).GetType().Name é o nome abreviado de (dado), (dado).GetType().FullName é o nome completo de (dado)
  • linha 8: um dado tipado implicitamente. Como 3 é do tipo int, j será do tipo int.
  • linha 10: um dado tipado implicitamente. Como DateTime.Now é do tipo DateTime, aujourdhui será do tipo DateTime.

Na execução, obtém-se o seguinte resultado:

1
2
3
Type de int i=2 : Int32,System.Int32
Type de var j=3 : Int32,System.Int32
Type de var aujourdhui : DateTime,System.DateTime

Uma variável cujo tipo é definido implicitamente pela palavra-chave var não pode, posteriormente, mudar de tipo. Assim, não seria possível escrever, após a linha 10 do código, a linha:


            var aujourdhui = "aujourd'hui";

Veremos mais adiante que é possível declarar um tipo “na hora” em uma expressão. Trata-se, nesse caso, de um tipo anônimo, ou seja, um tipo ao qual o usuário não atribuiu um nome. É o compilador que atribuirá um nome a esse novo tipo. Se um valor de tipo anônimo precisar ser atribuído a uma variável, a única maneira de declarar essa variável é usando a palavra-chave var.

3.2.4. Conversões entre números e cadeias de caracteres

nombre -> chaîne
nombre.ToString()
chaine -> int
int.Parse(cadeia) ou System.Int32.Parse
chaîne -> long
long.Parse(cadeia) ou System.Int64.Parse
chaîne -> double
double.Parse (cadeia) ou System.Double.Parse (cadeia)
chaîne -> float
float.Parse (cadeia) ou System.Float.Parse (cadeia)

A conversão de uma string em um número pode falhar se a string não representar um número válido. Nesse caso, é gerado um erro fatal chamado exceção. Esse erro pode ser tratado pela seguinte cláusula try/catch:


try{
    appel de la fonction susceptible de générer l'exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Se a função não gerar uma exceção, passa-se para a instrução seguinte; caso contrário, passa-se para o corpo da cláusula catch e, em seguida, para a instrução seguinte. Voltaremos mais tarde ao gerenciamento de exceções. Aqui está um programa que apresenta algumas técnicas de conversão entre números e cadeias de caracteres. Neste exemplo, a função affiche exibe na tela o valor de seu parâmetro. Assim, affiche(S) exibe o valor de S na tela, sendo que S é do tipo string.


using System;

namespace Chap1 {
    class P01 {
        static void Main(string[] args) {

            // dados
            const int i = 10;
            const long l = 100000;
            const float f = 45.78F;
            double d = -14.98;

            // número --> string
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());

            //booleano --> string
            const bool b = false;
            affiche(b.ToString());

            // cadeia de caracteres --> inteiro
            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);
            }

            // cadeia de caracteres --> inteiro longo
            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);
            }

            // cadeia de caracteres --> 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);
            }

            // cadeia de caracteres --> 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);
            }

        }// fim main

        public static void affiche(string S) {
            Console.Out.WriteLine("S={0}",S);
        }
    }// fim da classe
}

Nas linhas 30 a 32, trata-se a eventual exceção que possa ocorrer. e.Message é a mensagem de erro associada à exceção e.

Os resultados obtidos são os seguintes:

S=10
S=100000
S=45,78
S=-14,98
S=False
S=10
S=Erreur : Input string was not in a correct format.
S=100
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.

Observe-se que os números reais na forma de cadeia de caracteres devem utilizar a vírgula e não o ponto decimal. Assim, escrever-se-á

double d1=10.7; 

mas

double d2=int.Parse("10,7");

3.2.5. Tabelas de dados

Uma tabela em C# é um objeto que permite agrupar, sob um mesmo identificador, dados do mesmo tipo. Sua declaração é a seguinte:

Type[] tableau[]=new Type[n]

n é o número de dados que a matriz pode conter. A sintaxe Matriz[i] designa o dado nº i, onde i pertence ao intervalo [0,n-1]. Qualquer referência ao dado Tableau[i] em que i não pertença ao intervalo [0,n-1] provocará uma exceção. Uma matriz pode ser inicializada ao mesmo tempo em que é declarada:

    int[] entiers=new int[] {0,10,20,30};

ou, de forma mais simples:

    int[] entiers={0,10,20,30};

As matrizes possuem uma propriedade Length, que corresponde ao número de elementos da matriz.

Um array bidimensional pode ser declarado da seguinte forma:

Type[,] array = new Type[n,m];

onde n é o número de linhas e m, o número de colunas. A sintaxe Tableau[i,j] designa o elemento j da linha i da matriz. A matriz bidimensional também pode ser inicializada ao mesmo tempo em que é declarada:

    double[,] réels=new double[,] { {0.5, 1.7}, {8.4, -6}};

ou, de forma mais simples:

    double[,] réels={ {0.5, 1.7}, {8.4, -6}};

O número de elementos em cada uma das dimensões pode ser obtido pelo método GetLength(i), em que i=0 representa a dimensão correspondente ao primeiro índice, i=1 a dimensão correspondente ao segundo índice, …

O número total de dimensões é obtido com a propriedade Rank, e o número total de elementos com a propriedade Length.

Uma matriz de matrizes é declarada da seguinte forma:

Type[][] array = new Type[n][];

A declaração acima cria uma matriz com n linhas. Cada elemento matriz[i] é uma referência de matriz unidimensional. Essas referências matriz[i] não são inicializadas na declaração acima. Elas têm como valor a referência nula.

O exemplo abaixo ilustra a criação de uma matriz de matrizes:


            // uma matriz de matrizes
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // inicialização
            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
  • linha 2: um array noms com 3 elementos do tipo string[][]. Cada elemento é um ponteiro de array (uma referência de objeto) cujos elementos são do tipo string[].
  • linhas 3-5: os 3 elementos da matriz noms são inicializados. Cada um deles agora “aponta” para uma matriz de elementos do tipo string[]. noms[i][j] é o elemento j da matriz do tipo string [] referenciada por noms[i].
  • linha 9: inicialização do elemento nomes[i][j] dentro de um ciclo duplo. Aqui, noms[i] é um array de i+1 elementos. Como noms[i] é um array, noms[i].Length representa o número de elementos.

Aqui está um exemplo que reúne os três tipos de matrizes que acabamos de apresentar:


using System;

namespace Chap1 {
    // matrizes

    using System;

    // classe de teste
    public class P02 {
        public static void Main() {
            // um array unidimensional inicializado
            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]);
            }//para

            // um array bidimensional inicializado
            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

            // uma matriz de matrizes
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//para
            // inicialização
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//para j
            }//para i
            // exibição
            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]);
                }//para j
            }//para i
        }//Principal
    }//classe
}//namespace

Ao executar, obtemos os seguintes resultados:

entiers[0]=0
entiers[1]=10
entiers[2]=20
entiers[3]=30
réels[0,0]=0,5
réels[0,1]=1,7
réels[1,0]=8,4
réels[1,1]=-6
noms[0][0]=nom00
noms[1][0]=nom10
noms[1][1]=nom11
noms[2][0]=nom20
noms[2][1]=nom21
noms[2][2]=nom22

3.3. As instruções básicas do C#

Distinguem-se

1 as instruções elementares executadas pelo computador.

2 as instruções de controle do andamento do programa.

As instruções elementares ficam claras quando se analisa a estrutura de um microcomputador e de seus periféricos.

  1. leitura de informações provenientes do teclado

  2. processamento de informações

  3. exibição de informações na tela

3.3.1. Gravação na tela

Existem diferentes instruções para exibição na tela:

Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)

onde expression representa qualquer tipo de dado que possa ser convertido em uma sequência de caracteres para ser exibido na tela. Todos os objetos em C# ou .NET possuem um método ToString() que é utilizado para realizar essa conversão.

A classe System.Console fornece acesso às operações de gravação na tela (Write, WriteLine). A classe Console possui duas propriedades, Out e Error, que são fluxos de gravação do tipo TextWriter:

  • Console.WriteLine() é equivalente a Console.Out.WriteLine() e grava no fluxo Out normalmente associado à tela.
  • Console.Error.WriteLine() grava no fluxo Error, que normalmente também está associado à tela.

Os fluxos Out e Error podem ser redirecionados para arquivos de texto durante a execução do programa, como veremos em breve.

3.3.2. Leitura de dados digitados no teclado

O fluxo de dados proveniente do teclado é designado pelo objeto Console.In, do tipo TextReader. Esse tipo de objeto permite ler uma linha de texto com o método ReadLine:

    string ligne=Console.In.ReadLine();

A classe Console oferece um método ReadLine associado por padrão ao fluxo In. Portanto, é possível escrever:

    string ligne=Console.ReadLine();

A linha digitada no teclado é armazenada na variável ligne e pode, em seguida, ser utilizada pelo programa. O fluxo In pode ser redirecionado para um arquivo, assim como os fluxos Out e Error.

3.3.3. Exemplo de entradas e saídas

Aqui está um pequeno programa para ilustrar as operações de entrada e saída do teclado/tela:


using System;

namespace Chap1 {
    // classe de teste
    public class P03 {
        public static void Main() {

            // gravação no fluxo Out
            object obj = new object();
            Console.Out.WriteLine(obj);

            // gravação no fluxo Error
            int i = 10;
            Console.Error.WriteLine("i=" + i);

            // leitura de uma linha digitada no teclado
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
        }//fim da função main
    }//fim da classe
}
  • linha 9: obj é uma referência de objeto
  • linha 10: obj é exibido na tela. Por padrão, é chamado o método obj.ToString().
  • linha 14: também é possível escrever:

            Console.Error.WriteLine("i={0}",i);

O primeiro parâmetro “i={0}” é o formato de exibição; os demais parâmetros são as expressões a serem exibidas. Os elementos {n} são parâmetros “posicionais”. Na execução, o parâmetro {n} é substituído pelo valor da expressão nº n.

O resultado da execução é o seguinte:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • linha 1: a exibição gerada pela linha 10 do código. O método obj.ToString() exibiu o nome do tipo da variável obj: System.Object. O tipo object é um alias em C# do tipo .NET System.Object.

3.3.4. Redirecionamento de E/S

Existem, sob DOS e UNIX, três dispositivos padrão denominados:

  1. dispositivo de entrada padrão — designa, por padrão, o teclado e possui o nº 0
  2. dispositivo de saída padrão — designa, por padrão, a tela e possui o nº 1
  3. dispositivo de erro padrão — designa, por padrão, a tela e possui o número 2

Em C#, o fluxo de gravação Console.Out grava no dispositivo 1, o fluxo de gravação Console.Error grava no dispositivo 2 e o fluxo de leitura Console.In lê os dados provenientes do dispositivo 0.

Ao executar um programa no DOS ou no Unix, é possível definir quais serão os dispositivos 0, 1 e 2 para o programa em execução. Consideremos a seguinte linha de comando:

pg arg1 arg2 .. argn

Atrás dos argumentos argi do programa pg, é possível redirecionar os dispositivos de E/S padrão para arquivos:

0<in.txt
o fluxo de entrada padrão nº 0 é redirecionado para o arquivo in.txt. No programa, o fluxo Console.In, portanto, obterá seus dados do arquivo in.txt.
1>out.txt
redireciona a saída nº 1 para o arquivo out.txt. Isso faz com que, no programa, o fluxo Console.Out grave seus dados no arquivo out.txt
1>>out.txt
o mesmo, mas os dados gravados são adicionados ao conteúdo atual do arquivo out.txt.
2>error.txt
redireciona a saída nº 2 para o arquivo error.txt. Isso faz com que, no programa, o fluxo Console.Error grave seus dados no arquivo error.txt
2>>error.txt
O mesmo, mas os dados gravados são adicionados ao conteúdo atual do arquivo error.txt.
1>out.txt 2>error.txt
Os dispositivos 1 e 2 são ambos redirecionados para arquivos

Observe-se que, para redirecionar os fluxos de E/S do programa pg para arquivos, o programa pg não precisa ser modificado. É o sistema operacional que define a natureza dos dispositivos 0, 1 e 2. Consideremos o seguinte programa:


using System;

namespace Chap1 {

    // redirecionamentos
    public class P04 {
        public static void Main(string[] args) {
            // leitura do fluxo In
            string data = Console.In.ReadLine();
            // gravação no fluxo de saída
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // gravação no fluxo de erros
            Console.Error.WriteLine("écriture dans flux Error : " + data);
        }//Principal
    }//classe
}

Vamos gerar o executável a partir desse código-fonte:

  • em [1]: o executável é criado clicando com o botão direito do mouse no projeto / Build
  • em [2]: em uma janela do DOS, o executável 04.exe foi criado na pasta bin/Release do projeto.

Digite os seguintes comandos na janela do DOS [2]:

1
2
3
4
5
6
7
8
...\04\bin\Release>echo test >in.txt
...\04\bin\Release>more in.txt
test
...\04\bin\Release>04 0<in.txt 1>out.txt 2>err.txt
...\04\bin\Release>more out.txt
écriture dans flux Out : test
...\04\bin\Release>more err.txt
écriture dans flux Error : test
  • linha 1: inserimos a string test no arquivo in.txt
  • linhas 2-3: exibimos o conteúdo do arquivo in.txt para verificação
  • linha 4: execução do programa 04.exe. O fluxo In é redirecionado para o arquivo in.txt, o fluxo Out para o arquivo out.txt, o fluxo Error para o arquivo err.txt. A execução não gera nenhuma exibição.
  • linhas 5-6: conteúdo do arquivo out.txt. Esse conteúdo nos mostra que:
  • o arquivo in.txt foi lido
  • a saída para a tela foi redirecionada para o arquivo out.txt
  • linhas 7-8: verificação semelhante para o arquivo err.txt

Vemos claramente que os fluxos Out e In não gravam nos mesmos dispositivos, já que foi possível redirecioná-los separadamente.

3.3.5. Atribuição do valor de uma expressão a uma variável

Aqui, estamos interessados na operação variable=expression;

A expressão pode ser do tipo: aritmética, relacional, booleana, de caracteres

3.3.5.1. Interpretação da operação de atribuição

A operação variable=expression;

é, por si só, uma expressão cuja avaliação ocorre da seguinte maneira:

  • a parte direita da atribuição é avaliada: o resultado é um valor V.
  • o valor V é atribuído à variável
  • o valor V é também o valor da atribuição, vista agora como uma expressão.

É assim que a operação

    V1=V2=expression

é válida. Devido à prioridade, o operador = mais à direita será avaliado. Portanto, temos

    V1=(V2=expression)

A expressão V2=expressão é avaliada e tem como valor V. A avaliação dessa expressão provocou a atribuição de V a V2. O operador = seguinte é então avaliado na forma:

    V1=V

O valor dessa expressão ainda é V. Sua avaliação resulta na atribuição de V a V1.

Assim, a operação V1=V2=expressão

é uma expressão cuja avaliação

  • provoca a atribuição do valor de expression às variáveis V1 e V2
  • retorna como resultado o valor expression.

É possível generalizar para uma expressão do tipo:

V1=V2=....=Vn=expression

3.3.5.2. Expressão aritmética

Os operadores das expressões aritméticas são os seguintes:

  • adição

  • subtração

* multiplicação

/ divisão: o resultado é o quociente exato se pelo menos um dos operandos for real. Se ambos os operandos forem inteiros, o resultado é o quociente inteiro. Assim, 5/2 → 2 e 5,0/2 → 2,5.

% divisão: o resultado é o resto, independentemente da natureza dos operandos, sendo o quociente inteiro. Trata-se, portanto, da operação módulo.

Existem diversas funções matemáticas. Aqui estão algumas delas:

double Sqrt(double x)
raiz quadrada
double Cos(double x)
Cosseno
double Sin(double x)
Senus
double Tan(double x)
Tangente
double Pow(double x,double y)
x elevado a y (x > 0)
double Exp(double x)
Exponencial
double Log(double x)
Logaritmo natural
double Abs(double x)
valor absoluto

etc...

Todas essas funções estão definidas em uma classe C# chamada Math. Ao utilizá-las, é necessário antepor o nome da classe em que estão definidas. Assim, escreveremos:

double x, y=4;
x=Math.Sqrt(y);

A definição completa da classe Math é a seguinte:

Image

Image

Image

Image

Image

3.3.5.3. Prioridades na avaliação de expressões aritméticas

A prioridade dos operadores na avaliação de uma expressão aritmética é a seguinte (da mais alta à mais baixa):

[fonctions], [ ( )],[ *, /, %], [+, -]

Os operadores de um mesmo bloco [ ] têm a mesma prioridade.

3.3.5.4. Expressões relacionais

Os operadores são os seguintes:

       <, <=, ==, !=, >, >=

prioridades dos operadores

>, >=, <, <=
==, !=

O resultado de uma expressão relacional é o valor booleano false se a expressão for falsa; caso contrário, é true.

      bool fin;
      int x=...;
      fin=x>4;

Comparação de dois caracteres

Sejam dois caracteres C1 e C2. É possível compará-los com os operadores

    <, <=, ==, !=, >, >=

São, portanto, seus códigos Unicode — que são números — que são comparados. De acordo com a ordem Unicode, temos as seguintes relações:

espace < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. <'z'

Comparação de duas cadeias de caracteres

Elas são comparadas caractere por caractere. A primeira desigualdade encontrada entre dois caracteres determina uma desigualdade no mesmo sentido nas cadeias.

Exemplos:

Suponhamos que se queira comparar as sequências “Gato” e “Cão”

Essa última desigualdade permite afirmar que “Gato” < “Cão”.

Suponha que se queira comparar as cadeias “Gato” e “Gatinho”. Há igualdade o tempo todo até que a cadeia “Gato” seja esgotada. Nesse caso, a cadeia esgotada é declarada como a mais “pequena”. Temos, portanto, a relação

    "Chat" < "Chaton".

Funções de comparação de duas cadeias de caracteres

É possível usar os operadores relacionais == e != para verificar se duas cadeias de caracteres são iguais ou não, ou ainda o método Equals da classe System.String. Para as relações < <= > >=, é necessário utilizar o método CompareTo da 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);
        }
    }
}

Na linha 7, a variável i terá o valor:

0 se as duas cadeias forem iguais

1 se a sequência n.º 1 for maior que a sequência n.º 2

-1 se a sequência n.º 1 for menor que a sequência n.º 2

Na linha 8, a variável egal terá o valor true se as duas cadeias forem iguais; caso contrário, terá o valor false. Na linha 10, utilizam-se os operadores == e != para verificar se duas cadeias são iguais ou não.

Resultados da execução:

i=-1, egal=False
chien==chaine1:False,chien!=chaine2:False

3.3.5.5. Expressões booleanas

Os operadores que podem ser utilizados são AND (&&) OR (||) NOT (!). O resultado de uma expressão booleana é um valor booleano.

Prioridades dos operadores:

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valide = x > 2 && x < 4;

Os operadores relacionais têm prioridade sobre os operadores && e ||.

3.3.5.6. Operações em bits

Os operadores

Sejam i e j dois inteiros.

i<<n
desloca i de n bits para a esquerda. Os bits de entrada são zeros.
i>>n
desloca i n bits para a direita. Se i for um inteiro com sinal (signed char, int, long), o bit de sinal é preservado.
i & j
realiza a operação lógica ET entre i e j, bit a bit.
i | j
realiza a operação lógica bit a bit entre i e j, resultando em OU.
~i
completa i com 1
i^j
gera o OU a partir do EXCLUSIF de i e j

Seja o seguinte código:


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));
  • o formato {0:x4} exibe o parâmetro nº 0 no formato hexadecimal (x) com 4 caracteres (4).

Os resultados da execução são os seguintes:

i=0x0064, j=0xfff3, k=0xf123
i<<4=0x0640, i>>4=0x0006,k>>4=0x0f12,i&j=0x0060,i|j=0xfff7,~i=0xff9b,j<<2=0xffcc,j>>2=0xfffc

3.3.5.7. Combinação de operadores

a=a+b pode ser escrito como a+=b

a=a-b pode ser escrito como a-=b

O mesmo se aplica aos operadores /, %, *, <<, >>, &, |, ^. Assim, a=a/2; pode ser escrito como a/=2;

3.3.5.8. Operadores de incremento e decremento

A notação variable++ significa variable=variable+1 ou ainda variable+=1

A notação variable-- significa variable=variable-1 ou ainda variable-=1.

3.3.5.9. O operador ternário?

A expressão

    expr_cond ? expr1:expr2

é avaliada da seguinte forma:

1 a expressão expr_cond é avaliada. Trata-se de uma expressão condicional cujo valor é vrai ou faux

2 Se for verdadeira, o valor da expressão é o de expr1, e expr2 não é avaliada.

3 Se for falsa, ocorre o contrário: o valor da expressão é o de expr2 e expr1 não é avaliado.

A operação i=(j>4 ? j+1:j-1); atribuirá à variável i: j+1 se j>4, j-1 caso contrário. É o mesmo que escrever if(j>4) i=j+1; else i=j-1;, mas é mais conciso.

3.3.5.10. Prioridade geral dos operadores

() []  fonction                  
gd
! ~ ++ --                        
dg
new (type) opérateurs cast       
dg
*  /  %                          
gd
+  -                             
gd
<<  >>                           
gd
< <=  > >= instanceof            
gd
==    !=                         
gd
&                                
gd
^                                
gd
|                                
gd
&&                               
gd
||                               
gd
?   :                            
dg
= += -= etc. .                   
dg

gd indica que, em caso de igualdade de prioridade, é observada a regra de prioridade da esquerda para a direita. Isso significa que, quando em uma expressão há operadores com a mesma prioridade, o operador mais à esquerda na expressão é avaliado primeiro. dg indica a regra de prioridade da direita para a esquerda.

3.3.5.11. Mudanças de tipo

É possível, em uma expressão, alterar temporariamente a codificação de um valor. Isso é chamado de alteração do tipo de um dado ou, em inglês, “type casting”. A sintaxe para alterar o tipo de um valor em uma expressão é a seguinte:

    (type) valeur

O valor assume, então, o tipo indicado. Isso resulta em uma alteração na codificação do valor.


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);
        }
    }
}
  • na linha 7, f1 terá o valor 0,0. A divisão 3/4 é uma divisão inteira, uma vez que ambos os operandos são do tipo int.
  • linha 8, (float)i é o valor de i convertido para float. Agora, temos uma divisão entre um número real do tipo float e um inteiro do tipo int. É a divisão entre números reais que é então realizada. O valor de j também será convertido para o tipo float, e então será feita a divisão entre os dois reais. f2 terá, então, o valor 0,75.

Aqui estão os resultados da execução:

f1=0, f2=0,75

Na operação (float)i:

  • i é um valor codificado exatamente em 2 bytes
  • (float); i é o mesmo valor codificado de forma aproximada como número real em 4 bytes

Portanto, ocorre uma transcodificação do valor de i. Essa transcodificação ocorre apenas durante o cálculo, já que a variável i mantém sempre seu tipo int.

3.4. As instruções de controle do andamento do programa

3.4.1. Parada

O método Exit, definido na classe Environment, permite interromper a execução de um programa.

syntaxe        void Exit(int status)
action        arrête le processus en cours et rend la valeur status au processus père

Exit encerra o processo em andamento e devolve o controle ao processo chamador. O valor de status pode ser utilizado por este. Em DOS, essa variável de status é retornada na variável de sistema ERRORLEVEL, cujo valor pode ser verificado em um arquivo em lote. No Unix, com o interpretador de comandos Shell Bourne, é a variável $? que recupera o valor de status.

    Environment.Exit(0);

interromperá a execução do programa com um valor de status igual a 0.

3.4.2. Estrutura de escolha simples

 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

observações:

  • a condição está entre parênteses.
  • cada ação é terminada por ponto-e-vírgula.
  • As chaves não são encerradas por ponto-e-vírgula.
  • As chaves só são necessárias se houver mais de uma ação.
  • a cláusula else pode estar ausente.
  • Não há cláusula then.

O equivalente algorítmico dessa estrutura é a estrutura “se... então... senão”:

Exemplo


    if (x>0)  { nx=nx+1;sx=sx+x;} else dx=dx-x;

É possível aninhar as estruturas de escolha:

if(condition1)
if (condition2)
        {......}
      else         //condição2
         {......}
else         //condição1
     {.......}

Às vezes surge o seguinte 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");
        }
    }
}

No exemplo anterior, a linha else da linha 10 se refere a qual if? A regra é que um else sempre se refere ao if mais próximo: if(n>6), na linha 8, no exemplo. Consideremos outro exemplo:


            if (n2 > 1) {
                if (n2 > 6) Console.Out.WriteLine(">6");
       } else Console.Out.WriteLine("<=1");    

Aqui, queríamos associar um else ao if(n2>1), e não um else ao if(n2>6). Devido à observação anterior, somos obrigados a colocar chaves no if(n2>1) {...} else ...

3.4.3. Estrutura de casos

A sintaxe é a seguinte:

switch(expression) {
    case v1:     
            actions1;
            break;
    case v2:     
            actions2;
            break;
         . .. .. .. .. ..
    default:     
            actions_sinon;
            break;
}

notas

  • o valor da expressão de controle do switch pode ser um número inteiro, um caractere ou uma sequência de caracteres
  • A expressão de controle está entre parênteses.
  • A cláusula default pode estar ausente.
  • Os valores vi são valores possíveis da expressão. Se a expressão tiver o valor vi, as ações associadas à cláusula case vi serão executadas.
  • A instrução break faz com que se saia da estrutura de caso.
  • Cada bloco de instruções associado a um valor vi deve terminar com uma instrução de ramificação (break, goto, return, ...); caso contrário, o compilador sinaliza um erro.

Exemplo

Em algoritmos

                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

Em C#

int choix = 2; 
bool erreur = false;
switch (choix) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: erreur = true; break;
}
}// fim do Main

static void M1() {
    Console.WriteLine("M1");
}

static void M2() {
    Console.WriteLine("M2");
}
}

3.4.4. Estruturas de repetição

3.4.4.1. Número conhecido de repetições

Estrutura for

A sintaxe é a seguinte:


    for (i=id;i<=if;i=i+ip){ 
       actions; 
      } 

Notas

  • os três argumentos do for estão entre parênteses e separados por ponto-e-vírgula.
  • Cada ação do for é terminada por um ponto-e-vírgula.
  • A chave só é necessária se houver mais de uma ação.
  • A chave não é seguida de ponto-e-vírgula.

O equivalente algorítmico é a estrutura pour:

pour i variant de id à if avec un pas de ip
    actions
finpour

que pode ser traduzida por uma estrutura tantque:

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

Estrutura foreach

A sintaxe é a seguinte:


foreach (Type variable in collection)
    instructions; 
}

Notas

  • collection é uma coleção de objetos enumerável. A coleção de objetos enumerável que já conhecemos é o array
  • Type é o tipo dos objetos da coleção. No caso de um array, seria o tipo dos elementos do array
  • variable é uma variável local do loop que assumirá sucessivamente todos os valores da coleção.

Assim, o código a seguir:


            string[] amis = { "paul", "hélène", "jacques", "sylvie" };
            foreach (string nom in amis) {
                Console.WriteLine(nom);
         }

exibiria:

paul
hélène
jacques
sylvie

3.4.4.2. Número de repetições desconhecido

Existem diversas estruturas em C# para esse caso.

Estrutura while


    while(condition){
          actions;
        } 

O loop é executado enquanto a condição for verdadeira. O loop pode nunca ser executado.

Notas:

  • a condição está entre parênteses.
  • cada ação é terminada por ponto-e-vírgula.
  • A chave só é necessária se houver mais de uma ação.
  • A chave não é seguida de ponto-e-vírgula.

A estrutura algorítmica correspondente é a estrutura “enquanto”:

tantque condition
    actions
fintantque

Estrutura de repetição até (do while)

A sintaxe é a seguinte:


    do{
       instructions;
    }while(condition); 

O ciclo é repetido até que a condição se torne falsa. Aqui, o ciclo é executado pelo menos uma vez.

Notas

  • a condição está entre parênteses.
  • Cada ação é terminada por ponto-e-vírgula.
  • A chave só é necessária se houver mais de uma ação.
  • A chave não é seguida de ponto-e-vírgula.

A estrutura algorítmica correspondente é a estrutura “repetir … até”:

répéter
    actions
jusqu'à condition

Estrutura para “for”

A sintaxe é a seguinte:


for(instructions_départ;condition;instructions_fin_boucle){
    instructions; 
}

O loop é executado enquanto a condição for verdadeira (avaliada antes de cada iteração do loop). Instructions_départ são executadas antes de entrar no loop pela primeira vez. Instructions_fin_boucle são executadas após cada iteração do loop.

notas

  • as diferentes instruções em instructions_depart e instructions_fin_boucle são separadas por vírgulas.

A estrutura algorítmica correspondente é a seguinte:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

Exemplos

Todos os trechos de código a seguir calculam a soma dos primeiros 10 números inteiros.

            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. Instruções para gerenciamento de loop

break
sai do loop for, while, do ... while.
continue
avança para a próxima iteração dos loops for, while, do ... while

3.5. Gerenciamento de exceções

Muitas funções em C# podem gerar exceções, ou seja, erros. Quando uma função pode gerar uma exceção, o programador deve gerenciá-la com o objetivo de obter programas mais resistentes a erros: é preciso sempre evitar que um aplicativo “trave” de forma inesperada.

O tratamento de uma exceção é feito de acordo com o esquema a seguir:

try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Se a função não gerar uma exceção, passa-se para a instrução seguinte; caso contrário, passa-se para o corpo da cláusula catch e, em seguida, para a instrução seguinte. Observe os seguintes pontos:

  • e é um objeto do tipo Exception ou derivado. É possível ser mais preciso utilizando tipos como IndexOutOfRangeException, FormatException, SystemException, etc.: existem vários tipos de exceções. Ao escrever catch (Exception e), indicamos que queremos tratar todos os tipos de exceções. Se o código da cláusula try for suscetível de gerar vários tipos de exceções, podemos querer ser mais precisos, tratando a exceção com várias cláusulas 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
  • É possível adicionar uma cláusula `finally` às cláusulas try/catch:
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

Haja ou não exceção, o código da cláusula finally será sempre executado.

  • Na cláusula catch, talvez não se queira utilizar o objeto Exception disponível. Em vez de escrever catch (Exception e){..}, escreve-se então catch(Exception){...} ou, mais simplesmente, catch {...}.
  • A classe Exception possui uma propriedade Message, que é uma mensagem detalhando o erro ocorrido. Portanto, se quisermos exibi-la, escreveremos:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • A classe Exception possui um método ToString que retorna uma sequência de caracteres indicando o tipo da exceção, bem como o valor da propriedade Message. Assim, pode-se escrever:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

Também é possível escrever:

catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex);
    ...
}//catch

O compilador atribuirá ao parâmetro {0} o valor ex.ToString().

O exemplo a seguir mostra uma exceção gerada pelo uso de um elemento de tabela inexistente:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
            // declaração e inicialização de uma matriz
            int[] tab = { 0, 1, 2, 3 };
            int i;
            // exibição da matriz com um for
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
            // exibição de tabela com um for each
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
            // geração de uma exceção
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
                return;
            }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

Na linha 18 acima, será gerada uma exceção porque a matriz tab não possui o elemento nº 100. A execução do programa produz os seguintes resultados:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
L'erreur suivante s'est produite : System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.
   à Chap1.P08.Main(String[] args) dans C:\data\travail\2007-2008\c# 2008\poly\Chap1\08\Program.cs:linha 7
finally ...
  • linha 9: ocorreu a exceção [System.IndexOutOfRangeException]
  • linha 11: a cláusula finally (linhas 23-25) do código foi executada, embora na linha 21 houvesse uma instrução return para sair do método. Vale ressaltar que a cláusula finally é sempre executada.

Aqui está outro exemplo em que se lida com a exceção provocada pela atribuição de uma sequência de caracteres a uma variável do tipo inteiro, quando a sequência não representa um número inteiro:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {

            // exemplo 2
            // Solicita-se o nome
            Console.Write("Nom : ");
            // leitura da resposta
            string nom = Console.ReadLine();
            // pergunta-se a idade
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                // pergunta
                Console.Write("âge : ");
                // leitura e verificação da resposta
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
            }//while
            // exibição final
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • linhas 15-27: o loop de entrada da idade de uma pessoa
  • linha 20: o texto digitado no teclado é convertido em número inteiro pelo método int.Parse. Esse método lança uma exceção se a conversão não for possível. Por isso, a operação foi colocada em um try/catch.
  • linhas 22-23: se uma exceção for lançada, entramos no `catch`, onde nada é feito. Assim, a variável booleana ageOK, definida como false na linha 14, permanecerá como false.
  • linha 21: se chegarmos a esta linha, significa que a conversão de string para int foi bem-sucedida. Verifica-se, no entanto, se o inteiro obtido é realmente maior ou igual a 1.
  • linhas 24-26: uma mensagem de erro é exibida se a idade estiver incorreta.

Alguns resultados da execução:

1
2
3
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 an(s)
1
2
3
4
5
6
7
Nom : durand
âge : x
Age incorrect, recommencez...
âge : -4
Age incorrect, recommencez...
âge : 12
Vous vous appelez durand et vous avez 12 an(s)

3.6. Aplicativo de exemplo - V1

Propõe-se escrever um programa que permita calcular o imposto de um contribuinte. Consideramos o caso simplificado de um contribuinte que tenha apenas seu salário a declarar (dados de 2004 referentes aos rendimentos de 2003):

  • calcula-se o número de cotas do empregado nbParts = nbEnfants/2 + 1 se ele for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
  • se ele tiver pelo menos três filhos, recebe mais meia parcela
  • calcula-se sua renda tributável R = 0,72 * S, sendo que S é seu salário anual
  • calcula-se seu coeficiente familiar QF = R/nbParts
  • calcula-se seu imposto I. Consideremos a tabela a seguir:
4262
0
0
8382
0,0683
291,09
14.753
0,1914
1.322,92
23.888
0,2826
2.668,39
38.868
0,3738
4.846,98
47.932
0,4262
6.883,66
0
0,4809
9505,54

Cada linha possui 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF ≤ campo1. Por exemplo, se QF = 5000, será encontrada a linha

    8382        0.0683        291.09

O imposto I é, então, igual a 0,0683*R - 291,09*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então serão utilizados os coeficientes da última linha. Aqui:

0 0,4809 9505,54

o que resulta no imposto I = 0,4809 * R - 9505,54 * nbParts.

O programa em C# correspondente é o seguinte:


using System;

namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
            // tabelas de dados necessárias para o cálculo do imposto
            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 };

            // recuperação do estado civil
            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";

            // número de filhos
            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");
                }
            }// enquanto

            // salário
            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

            // cálculo do número de cotas
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;

            // renda tributável
            decimal revenu = 0.72M * salaire;

            // quociente familiar
            decimal QF = revenu / nbParts;

            // busca da faixa de imposto correspondente a QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
            // o imposto
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);

            // exibe-se o resultado
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • linhas 7-9: os valores numéricos são precedidos pelo sufixo M (Money) para que sejam do tipo decimal.
  • linha 16:
  • Console.ReadLine() transforma a string C1 digitada no teclado
  • C1.Trim() remove os espaços no início e no final de C1 — retorna a string C2
  • C2.ToLower() retorna a string C3, que é a string C2 convertida para letras minúsculas.
  • linha 21: o booleano marie recebe o valor true ou false da relação reponse=="o"
  • linha 29: a string digitada no teclado é convertida para o tipo int. Se a conversão falhar, uma exceção é lançada.
  • linha 30: o booleano OK recebe o valor true ou false da relação nbEnfants>=0
  • linhas 55-56: não é possível escrever simplesmente nbEnfants/2. Se nbEnfants fosse igual a 3, teríamos 3/2, uma divisão inteira que resultaria em 1 e não em 1,5. Portanto, escreve-se (decimal)nbEnfants para tornar real um dos operandos da divisão e, assim, obter uma divisão entre números reais.

Aqui estão alguns exemplos de execução:

Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 2
Salaire annuel : 60000
Impôt à payer : 4282 euros
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. Argumentos do programa principal

A função principal Main pode aceitar como parâmetro uma matriz de strings: String[] (ou string[]). Essa matriz contém os argumentos da linha de comando utilizada para executar o aplicativo. Assim, se executarmos o programa P com o seguinte comando (DOS):


        P arg0 arg1 … argn

e se a função Main for declarada da seguinte forma:

public static void Main(string[] args)

teremos args[0]="arg0", args[1]="arg1" … Veja um exemplo:


using System;

namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
            // listam-se os parâmetros recebidos
            Console.WriteLine("Il y a  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

Para passar argumentos ao código executado, proceda da seguinte forma:

  • em [1]: clique com o botão direito do mouse no projeto / Propriedades
  • em [2]: guia [Debug]
  • em [3]: inserir os argumentos

A execução produz os seguintes resultados:

1
2
3
4
5
Il y a  4 arguments
arguments[0]=a0
arguments[1]=a1
arguments[2]=a2
arguments[3]=a3

Observe-se que a assinatura

public static void Main()

é válida se a função Main não esperar parâmetros.

3.8. As enumerações

Uma enumeração é um tipo de dados cujo domínio de valores é um conjunto de constantes inteiras. Consideremos um programa que precisa gerenciar notas de uma prova. Seria um total de cinco: Passable, AssezBien, Bien, TrèsBien, Excellent.

Seria possível, então, definir uma enumeração para essas cinco constantes:


        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };

Internamente, essas cinco constantes são codificadas por números inteiros consecutivos, começando por 0 para a primeira constante, 1 para a seguinte, e assim por diante. Uma variável pode ser declarada para assumir esses valores na enumeração:


            // uma variável que obtém seus valores na enumeração “Mentions”
Mentions maMention = Mentions.Passable;

É possível comparar uma variável com os diferentes valores possíveis da enumeração:


            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
}

É possível obter todos os valores da enumeração:


            // lista de menções na forma de cadeias de caracteres
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

Da mesma forma que o tipo simples int é equivalente à estrutura System.Int32, o tipo simples enum é equivalente à estrutura System.Enum. Essa estrutura possui um método estático GetValues que permite obter todos os valores de um tipo enumerado passado como parâmetro. Esse parâmetro deve ser um objeto do tipo **Type, que é uma classe de informações sobre o tipo de um dado. O tipo de uma variável v é obtido por meio de v.GetType(). O tipo de um tipo T é obtido por meio de typeof(T). Portanto, aqui maMention.GetType() retorna o objeto Type da enumeração Mentions e Enum.GetValues(maMention.GetType()) a lista de valores da enumeração Mentions.

Se escrevermos agora


            //lista das menções na forma de números inteiros
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

Linha 2, a variável do loop é do tipo inteiro. Obtém-se, então, a lista de valores da enumeração na forma de inteiros. O objeto do tipo System.Type, correspondente ao tipo de dados Mentions, é obtido por typeof(Mentions). Poderíamos ter escrito, como anteriormente, maMention.GetType().

O programa a seguir ilustra o que acabou de ser explicado:


using System;

namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
            // uma variável cujos valores são obtidos da enumeração “Mentions”
            Mentions maMention = Mentions.Passable;
            // exibição do valor da variável
            Console.WriteLine("mention=" + maMention);
            // teste com valor da enumeração
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
            // lista de menções na forma de cadeias de caracteres
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
            //lista de menções na forma de números inteiros
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

Os resultados da execução são os seguintes:

mention=Passable
Peut mieux faire
Passable
AssezBien
Bien
TrèsBien
Excellent
0
1
2
3
4

3.9. Passagem de parâmetros para uma função

Aqui, vamos nos concentrar na forma como os parâmetros são passados para uma função. Consideremos a seguinte função estática:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
}

Na definição da função, linha 1, a é chamado de parâmetro formal. Ele existe apenas para fins da definição da função changeInt. Ele poderia muito bem ter sido chamado de b. Consideremos agora uma utilização dessa função:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
}

Aqui, na instrução da linha 3, ChangeInt(idade), age é o parâmetro efetivo que transmitirá seu valor ao parâmetro formal a. Estamos interessados em saber como um parâmetro formal recupera o valor de um parâmetro efetivo.

3.9.1. Passagem por valor

O exemplo a seguir mostra que os parâmetros de uma função são, por padrão, passados por valor, ou seja, o valor do parâmetro efetivo é copiado para o parâmetro formal correspondente. Temos duas entidades distintas. Se a função modificar o parâmetro formal, o parâmetro efetivo não sofre nenhuma alteração.


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);
        }
    }
}

Os resultados obtidos são os seguintes:

Paramètre formel a=30
Paramètre effectif age=20

O valor 20 do parâmetro efetivo age foi copiado para o parâmetro formal a (linha 10). Este foi, em seguida, modificado (linha 11). O parâmetro efetivo, por sua vez, permaneceu inalterado. Esse modo de passagem é adequado para parâmetros de entrada de uma função.

3.9.2. Passagem por referência

Em uma passagem por referência, o parâmetro efetivo e o parâmetro formal são uma única e mesma entidade. Se a função modificar o parâmetro formal, o parâmetro efetivo também será modificado. Em C#, ambos devem ser precedidos pela palavra-chave ref:

Veja um exemplo:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // exemplo 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 os resultados da execução:

Paramètre formel a2=30
Paramètre effectif age2=30

O parâmetro efetivo acompanhou a modificação do parâmetro formal. Esse modo de passagem é adequado para parâmetros de saída de uma função.

3.9.3. Passagem por referência com a palavra-chave out

Consideremos o exemplo anterior, no qual a variável age2 não seria inicializada antes da chamada à função changeInt:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // exemplo 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);
        }
    }
}

Ao compilar esse programa, ocorre um erro:

    Use of unassigned local variable 'age2'

É possível contornar esse problema atribuindo um valor inicial a age2. Também é possível substituir a palavra-chave ref pela palavra-chave out. Assim, indica-se que o parâmetro é exclusivamente um parâmetro de saída e, portanto, não precisa de valor inicial:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // exemplo 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);
        }
    }
}

Os resultados da execução são os seguintes:

Paramètre formel a3=30
Paramètre effectif age3=30