3. Noções básicas da linguagem C#
3.1. Introduction
Vamos abordar o C#, em primeiro lugar, como uma linguagem de programação clássica. Abordaremos as classes mais tarde. Num programa, encontramos duas coisas:
- dados
- as instruções que os manipulam
Normalmente, procuramos separar os dados das instruções:
![]() |
3.2. Os dados do C#
O C# utiliza os seguintes tipos de dados:
- números inteiros
- números reais
- números decimais
- caracteres e cadeias de caracteres
- valores booleanos
- os objetos
3.2.1. Os tipos de dados predefinidos
Tipo C# | Tipo .NET | Dado representado | Sufixo dos valores literais | Codificação | Intervalo de valores |
Caractere(s) | carácter | 2 bytes | carácter Unicode (UTF-16) | ||
String (C) | cadeia de caracteres | referência a uma sequência de caracteres Unicode | |||
Int32 (S) | número inteiro | 4 bytes | [-231, 231-1] [–2147483648, 2147483647] | ||
UInt32 (S) | .. | U | 4 octetos | [0, 232-1] [0, 4294967295] | |
Int64 (S) | .. | L | 8 octetos | [-263, 263 -1] [–9223372036854775808, 9223372036854775807] | |
UInt64 (S) | .. | UL | 8 octetos | [0, 264 -1] [0, 18446744073709551615] | |
.. | 1 byte | [-27 , 27 -1] [-128,+127] | |||
Byte(s) | .. | 1 byte | [0 , 28 -1] [0,255] | ||
Int16 (S) | .. | 2 bytes | [-215, 215-1] [-32768, 32767] | ||
UInt16 (S) | .. | 2 bytes | [0, 216-1] [0,65535] | ||
Individual (S) | número real | F | 4 bytes | [1.5 10-45, 3.4 10+38] em valor absoluto | |
Duplo (S) | .. | D | 8 octetos | [-1.7 10+308, 1.7 10+308] em valor absoluto | |
Decimal (S) | número decimal | M | 16 bytes | [1.0 10-28,7.9 10+28] em valor absoluto com 28 algarismos significativos | |
Booleano (S) | .. | 1 byte | true, false | ||
Objeto (C) | referência do objeto | referência de objeto |
Acima, colocámos em paralelo os tipos C# e os seus tipos .NET equivalentes, com o comentário (S) se esse tipo for uma estrutura e (C) se for uma classe. Verifica-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 espaço de nomes System. O 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 através do seu valor
- uma variável do tipo Classe é manipulada através do seu endereço (referência na linguagem orientada para objetos).
Tanto um structure como um classe são tipos complexos com atributos e métodos. Assim, poderemos escrever:
No exemplo acima, o literal 3 é, por predefinição, do tipo C# int, ou seja, do tipo .NET System.Int32. Esta estrutura possui um método GetType() que devolve um objeto que encapsula as características do tipo de dados System.Int32. Entre estas, a propriedade FullName devolve o nome completo do tipo. Vemos, portanto, que o literal 3 é um objeto mais complexo do que parece à primeira vista.
Eis um programa que ilustra estes 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 através de v.GetType().FullName. O tamanho de uma estrutura S pode ser obtido através de sizeof(S). A instrução Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) exibe no ecrã o texto que constitui o 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.
Eis o resultado da execução:
A exibição apresenta os tipos .NET e não os aliases C#.
3.2.2. Notação dos dados literais
145, -7, 0xFF (hexadecimal) | |
100000L | |
134,789, -45E-18 (-45 × 10⁻¹⁸) | |
134.789F, -45E-18F (-45 10-18) | |
100000M | |
'A', 'b' | |
"hoje" "c:\\capítulo1\\parágrafo3" @"c:\capítulo1\parágrafo3" | |
true, false | |
new DateTime(1954,10,13) (ano, mês, dia) para 13/10/1954 |
Note-se as duas cadeias literais: "c:\\chap1\\paragraph3" e @"c:\chap1\paragraph3". Nas cadeias literais, o carácter \ é interpretado. Assim, «\n» representa o símbolo de fim de linha e não a sucessão dos dois caracteres \ e n. Se se quisesse essa sucessão, seria necessário escrever «\\n», em que a sequência \\ é interpretada como um único caractere \. Também se poderia escrever @"\n" para obter o mesmo resultado. A sintaxe @"texto" exige que o texto seja interpretado exatamente como está escrito. Por vezes, chama-se a isto uma 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. Estes 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. Faz-o com base nas declarações feitas pelo programador.
Além disso, estas permitem ao compilador detetar erros de programação. Assim, a operação
x=x*2;
será declarada como errada se x for, por exemplo, uma cadeia de caracteres.
3.2.3.2. Declaração de constantes
A sintaxe para declarar uma constante é a seguinte:
Por exemplo:
Por que declarar constantes?
- A leitura do programa será mais fácil se se atribuir à constante um nome significativo:
- A modificação do programa será mais fácil se a «constante» vier a mudar. Assim, no caso anterior, se a taxa de IVA passar para 33%, a única alteração a fazer será modificar a instrução que define o seu valor:
Se tivéssemos utilizado explicitamente 0,186 no programa, seria necessário alterar inúmeras 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# distingue entre maiúsculas e 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 é:
onde Identificateur_de_type é um tipo predefinido ou um tipo definido pelo programador. Opcionalmente, uma variável pode ser inicializada ao mesmo tempo que é declarada.
Também é possível não especificar o tipo exato de uma variável, utilizando a palavra-chave var em vez de Identificateur_de_type:
A palavra-chave var não significa que as variáveis não tenham um tipo específico. A variável variablei tem o tipo dos dados valeuri que lhe são atribuídos. A inicialização é aqui obrigatória para que o compilador possa deduzir o tipo da variável.
Eis 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:
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 «em tempo real» numa expressão. Trata-se, nesse caso, de um tipo anónimo, ou seja, um tipo ao qual o utilizador não atribuiu um nome. É o compilador que atribuirá um nome a esse novo tipo. Se um valor de tipo anónimo tiver de ser atribuído a uma variável, a única forma de declarar essa variável é utilizar a palavra-chave var.
3.2.4. Conversões entre números e cadeias de caracteres
nombre.ToString() | |
int.Parse(cadeia) ou System.Int32.Parse | |
long.Parse(cadeia) ou System.Int64.Parse | |
double.Parse (cadeia) ou System.Double.Parse (cadeia) | |
float.Parse (cadeia) ou System.Float.Parse (cadeia) |
A conversão de uma cadeia de caracteres num número pode falhar se a cadeia não representar um número válido. Nesse caso, é gerado um erro fatal denominado «exceção». Este 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 então 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 à gestão de exceções. Eis um programa que apresenta algumas técnicas de conversão entre números e cadeias de caracteres. Neste exemplo, a função affiche exibe no ecrã o valor do seu parâmetro. Assim, affiche(S) exibe o valor de S no ecrã, 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 --> cadeia
affiche(i.ToString());
affiche(l.ToString());
affiche(f.ToString());
affiche(d.ToString());
//booleano --> cadeia
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-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:
Note-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-á
mas
3.2.5. As tabelas de dados
Uma tabela em C# é um objeto que permite agrupar, sob um mesmo identificador, dados do mesmo tipo. A sua declaração é a seguinte:
n é o número de dados que a matriz pode conter. A sintaxe Matriz[i] designa o dado n.º i, em que 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 que é declarada:
ou, mais simplesmente:
Os tabuletos têm uma propriedade Length, que corresponde ao número de elementos do tabuleto.
Um tabuleiro bidimensional pode ser declarado da seguinte forma:
Type[,] tabela = 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 que é declarada:
ou, mais simplesmente:
O número de elementos em cada uma das dimensões pode ser obtido através do 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 através da propriedade Rank, e o número total de elementos através da 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 a uma matriz unidimensional. Estas referências matriz[i] não são inicializadas na declaração acima. O seu valor é a referência nula.
O exemplo abaixo ilustra a criação de um tabuleiro de tabuleiros:
// um tabuleiro de tabuleiros
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 tabuleiro noms com 3 elementos do tipo string[][]. Cada elemento é um ponteiro de tabuleiro (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 «aponta» agora 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]» no interior de um ciclo duplo. Aqui, «noms[i]» é um tabuleiro com i+1 elementos. Como «noms[i]» é um tabuleiro, «noms[i].Length» corresponde ao seu número de elementos.
Eis um exemplo que reúne os três tipos de matrizes que acabámos de apresentar:
using System;
namespace Chap1 {
// matrizes
using System;
// classe de teste
public class P02 {
public static void Main() {
// um tabuleiro 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 tabuleiro de duas dimensões 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
// um tabuleiro de tabuleiros
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 o código, obtemos os seguintes resultados:
3.3. As instruções básicas do C#
Distinguem-se
1 as instruções elementares executadas pelo computador.
2 as instruções de controlo do andamento do programa.
As instruções elementares tornam-se evidentes quando se analisa a estrutura de um microcomputador e dos seus periféricos.
![]() |
-
leitura de informações provenientes do teclado
-
processamento de informações
-
Gravação de informações no ecrã
3.3.1. Gravação no ecrã
Existem várias instruções para escrever no ecrã:
Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)
onde expression é qualquer tipo de dados que possa ser convertido numa cadeia de caracteres para ser apresentado no ecrã. Todos os objetos em C# ou .NET têm um método ToString() que é utilizado para efetuar essa conversão.
A classe System.Console dá acesso às operações de escrita no ecrã (Write, WriteLine). A classe Console tem duas propriedades, Out e Error, que são fluxos de escrita do tipo TextWriter:
- Console.WriteLine() é equivalente a Console.Out.WriteLine() e escreve no fluxo Out normalmente associado ao ecrã.
- Console.Error.WriteLine() escreve no fluxo Error, que normalmente também está associado ao ecrã.
Os fluxos Out e Error podem ser redirecionados para ficheiros de texto durante a execução do programa, como veremos em breve.
3.3.2. Leitura de dados introduzidos pelo teclado
O fluxo de dados proveniente do teclado é designado pelo objeto Console.In do tipo TextReader. Este tipo de objeto permite ler uma linha de texto com o método ReadLine:
A classe Console disponibiliza um método ReadLine associado, por predefinição, ao fluxo In. Assim, pode escrever-se:
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 ficheiro, tal como os fluxos Out e Error.
3.3.3. Exemplo de entradas e saídas
Eis um pequeno programa que ilustra as operações de entrada e saída do teclado/ecrã:
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 introduzida pelo 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 no ecrã. Por predefinição, é chamado o método obj.ToString().
- linha 14: também se pode escrever:
Console.Error.WriteLine("i={0}",i);
O primeiro parâmetro «i={0}» é o formato de exibição; os restantes parâmetros são as expressões a exibir. Os elementos {n} são parâmetros «posicionais». Durante a execução, o parâmetro {n} é substituído pelo valor da expressão n.º n.
O resultado da execução é o seguinte:
- linha 1: a exibição produzida 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, em DOS e UNIX, três dispositivos padrão denominados:
- dispositivo de entrada padrão — designa, por predefinição, o teclado e tem o n.º 0
- periférico de saída padrão — designa, por predefinição, o ecrã e tem o n.º 1
- dispositivo de erro padrão — designa, por predefinição, o ecrã e tem o n.º 2
Em C#, o fluxo de escrita Console.Out escreve no dispositivo 1, o fluxo de escrita Console.Error escreve no dispositivo 2 e o fluxo de leitura Console.In lê os dados provenientes do dispositivo 0.
Ao iniciar 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:
Através dos argumentos argi do programa pg, é possível redirecionar os dispositivos de E/S padrão para ficheiros:
o fluxo de entrada padrão n.º 0 é redirecionado para o ficheiro in.txt. No programa, o fluxo Console.In irá, portanto, obter os seus dados do ficheiro in.txt. | |
redireciona a saída n.º 1 para o ficheiro out.txt. Isto significa que, no programa, o fluxo Console.Out gravará os seus dados no ficheiro out.txt | |
o mesmo, mas os dados gravados são adicionados ao conteúdo atual do ficheiro out.txt. | |
redireciona a saída n.º 2 para o ficheiro error.txt. Isto faz com que, no programa, o fluxo Console.Error grave os seus dados no ficheiro error.txt | |
O mesmo se aplica, mas os dados gravados são adicionados ao conteúdo atual do ficheiro error.txt. | |
Os dispositivos 1 e 2 são ambos redirecionados para ficheiros |
Note-se que, para redirecionar os fluxos de E/S do programa pg para ficheiros, o programa pg não precisa de ser alterado. É o sistema operativo que determina 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 do fluxo de saída
Console.Out.WriteLine("écriture dans flux Out : " + data);
// gravação do fluxo de erros
Console.Error.WriteLine("écriture dans flux Error : " + data);
}//Principal
}//classe
}
Vamos gerar o executável a partir deste código-fonte:
![]() |
- em [1]: o executável é criado clicando com o botão direito do rato no projeto / Build
- em [2]: numa janela do DOS, o executável 04.exe foi criado na pasta bin/Release do projeto.
Executemos os seguintes comandos na janela do DOS [2]:
- linha 1: insere-se a cadeia test no ficheiro in.txt
- linhas 2-3: exibimos o conteúdo do ficheiro in.txt para verificação
- linha 4: execução do programa 04.exe. O fluxo In é redirecionado para o ficheiro in.txt, o fluxo Out para o ficheiro out.txt, o fluxo Error para o ficheiro err.txt. A execução não provoca qualquer exibição.
- linhas 5-6: conteúdo do ficheiro out.txt. Este conteúdo mostra-nos que:
- o ficheiro in.txt foi lido
- a saída para o ecrã foi redirecionada para o ficheiro out.txt
- linhas 7-8: verificação semelhante para o ficheiro err.txt
Vê-se claramente que os fluxos Out e In não escrevem nos mesmos dispositivos, uma vez que foi possível redirecioná-los separadamente.
3.3.5. Atribuição do valor de uma expressão a uma variável
Vamos analisar aqui a operação variable=expression;
A expressão pode ser do tipo: aritmética, relacional, booleana ou de caracteres
3.3.5.1. Interpretação da operação de atribuição
A operação variable=expression;
é, ela própria, uma expressão cuja avaliação decorre da seguinte forma:
- 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, agora considerada como uma expressão.
É assim que a operação
é válida. Devido à prioridade, é o operador = mais à direita que será avaliado. Temos, portanto,
A expressão V2=expressão é avaliada e tem como valor V. A avaliação desta expressão provocou a atribuição de V a V2. O operador = seguinte é então avaliado na forma:
O valor desta expressão continua a ser V. A sua avaliação provoca a 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:
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 um número inteiro. Trata-se, portanto, da operação módulo.
Existem várias funções matemáticas. Aqui estão algumas delas:
raiz quadrada | |
Cosseno | |
Senus | |
Tangente | |
x elevado a y (x>0) | |
Exponencial | |
Logaritmo natural | |
valor absoluto |
etc...
Todas estas funções estão definidas numa classe C# chamada Math. Ao utilizá-las, é necessário antepor-lhes o nome da classe onde estão definidas. Assim, escrever-se-á:
A definição completa da classe Math é a seguinte:





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 elevada à mais baixa):
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.
Comparação de dois caracteres
Sejam dois caracteres C1 e C2. É possível compará-los com os operadores
São então os seus códigos Unicode, que são números, que são comparados. De acordo com a ordem Unicode, temos as seguintes relações:
Comparação de duas cadeias de caracteres
São comparadas caractere a caractere. A primeira desigualdade encontrada entre dois caracteres implica uma desigualdade no mesmo sentido nas cadeias de caracteres.
Exemplos:
Suponhamos que se queiram comparar as cadeias «Gato» e «Cão»
![]() |
Esta última desigualdade permite concluir que «Gato» < «Cão».
Suponhamos que se queiram comparar as cadeias «Gato» e «Gatinho». Há igualdade durante todo o tempo até que a cadeia «Gato» se esgote. Neste caso, a cadeia esgotada é declarada como a mais «pequena». Temos, portanto, a relação
Funções de comparação de duas cadeias de caracteres
É possível utilizar 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 < <= > >=, deve utilizar-se 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 cadeia n.º 1 for maior que a cadeia n.º 2
-1 se a cadeia n.º 1 for menor que a cadeia 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:
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:
- !
- &&
- ||
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 bit a bit
Os operadores
Sejam i e j dois números inteiros.
desloca i de n bits para a esquerda. Os bits de entrada são zeros. | |
desloca i n bits para a direita. Se i for um inteiro com sinal (signed char, int, long), o bit de sinal é preservado. | |
realiza a operação lógica ET entre i e j, bit a bit. | |
realiza a operação lógica bit a bit entre i e j, como em OU. | |
completa i com 1 | |
realiza a operação OU EXCLUSIF entre 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} apresenta o parâmetro n.º 0 no formato hexadecimal (x) com 4 caracteres (4).
Os resultados da execução são os seguintes:
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
é 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 inverso: 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
gd | |
dg | |
dg | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
dg | |
dg |
gd indica que, em caso de igualdade de prioridade, é seguida a regra de prioridade da esquerda para a direita. Isto significa que, quando numa expressão existem operadores com a mesma prioridade, é o operador mais à esquerda na expressão que é avaliado em primeiro lugar. dg indica uma prioridade da direita para a esquerda.
3.3.5.11. As conversões de tipo
É possível, numa expressão, alterar momentaneamente a codificação de um valor. A isto chama-se alteração do tipo de um dado ou, em inglês, «type casting». A sintaxe para alterar o tipo de um valor numa expressão é a seguinte:
O valor assume então o tipo indicado. Isto implica 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.
- na linha 8, (float)i é o valor de i transformado em float. Agora, temos uma divisão entre um número real do tipo float e um número inteiro do tipo int. É então efetuada a divisão entre números reais. O valor de j será também convertido para o tipo float, após o que será efetuada a divisão entre os dois reais. f2 terá então o valor 0,75.
Eis os resultados da execução:
Na operação (float)i:
- i é um valor codificado de forma exata em 2 bytes
- (float); i é o mesmo valor codificado de forma aproximada como número real em 4 bytes
Verifica-se, portanto, uma transcodificação do valor de i. Esta transcodificação ocorre apenas durante o cálculo, mantendo a variável i sempre o seu tipo int.
3.4. As instruções de controlo do andamento do programa
3.4.1. Paragem
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 provoca o fim do processo em curso e devolve o controlo ao processo chamador. O valor de status pode ser utilizado por este último. Em DOS, esta variável de estado é devolvida na variável de sistema ERRORLEVEL, cujo valor pode ser verificado num ficheiro batch. No Unix, com o interpretador de comandos Shell Bourne, é a variável $? que recupera o valor de status.
interromperá a execução do programa com um valor de estado igual a 0.
3.4.2. Estrutura de escolha simples
Notas:
- a condição está entre parênteses.
- cada ação termina com um ponto-e-vírgula.
- As chaves não terminam com ponto e vírgula.
- As chaves só são necessárias se houver mais do que uma ação.
- A cláusula else pode estar ausente.
- Não existe a cláusula then.
O equivalente algorítmico desta 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:
Por 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 referência else da linha 10 a que if se refere? A regra é que um else se refere sempre 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 controlo do switch pode ser um número inteiro, um carácter ou uma cadeia de caracteres
- A expressão de controlo 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 são executadas.
- A instrução break faz com que se saia da estrutura «case».
- 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#
3.4.4. Estruturas de repetição
3.4.4.1. Número de repetições conhecido
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 termina com um ponto e vírgula.
- A chave só é necessária se houver mais do que uma ação.
- A chave não é seguida de ponto-e-vírgula.
O equivalente algorítmico é a estrutura pour:
que pode ser traduzida por uma estrutura tantque:
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 tabelo
- 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 ciclo que irá assumir sucessivamente, como valor, todos os valores da coleção.
Assim, o código seguinte:
string[] amis = { "paul", "hélène", "jacques", "sylvie" };
foreach (string nom in amis) {
Console.WriteLine(nom);
}
exibiria:
3.4.4.2. Número de repetições desconhecido
Existem várias estruturas em C# para este caso.
Estrutura «while»
while(condition){
actions;
}
O ciclo repete-se enquanto a condição for verdadeira. O ciclo pode nunca vir a ser executado.
Notas:
- a condição está entre parênteses.
- cada ação termina com um ponto-e-vírgula.
- A chave só é necessária se houver mais do que uma ação.
- A chave não é seguida de ponto-e-vírgula.
A estrutura algorítmica correspondente é a estrutura «enquanto»:
Estrutura «repetir até» (do while)
A sintaxe é a seguinte:
do{
instructions;
}while(condition);
O ciclo repete-se até que a condição se torne falsa. Neste caso, o ciclo é executado pelo menos uma vez.
Notas
- A condição está entre parênteses.
- Cada ação termina com um ponto-e-vírgula.
- A chave só é necessária se houver mais do que uma ação.
- A chave não é seguida de ponto-e-vírgula.
A estrutura algorítmica correspondente é a estrutura «repetir … até»:
Estrutura «for» (for)
A sintaxe é a seguinte:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
O ciclo repete-se enquanto a condição for verdadeira (avaliada antes de cada iteração do ciclo). As instruções Instructions_départ são executadas antes de entrar no ciclo pela primeira vez. As instruções Instructions_fin_boucle são executadas após cada iteração do ciclo.
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:
Exemplos
Os seguintes fragmentos de código calculam, todos eles, 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 de gestão de ciclo
sai do ciclo «for», «while», «do...while». | |
avança para a iteração seguinte nos ciclos «for», «while» e «do...while» |
3.5. Gestão 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 gerir essa exceção com o objetivo de obter programas mais resistentes a erros: deve-se evitar sempre que uma aplicação «trave» de forma inesperada.
A gestão de uma exceção é feita de acordo com o seguinte esquema:
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 então 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. É importante notar 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 pretendemos 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
Quer haja ou não uma exceção, o código da cláusula finally será sempre executado.
- Na cláusula catch, pode não se querer 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 contém uma mensagem detalhando o erro que ocorreu. Assim, se se pretender exibir essa mensagem, escrever-se-á:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
...
}//catch
- A classe Exception possui um método ToString que devolve uma cadeia de caracteres indicando o tipo da exceção, bem como o valor da propriedade Message. Assim, poderemos escrever:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
...
}//catch
Também se pode escrever:
O compilador atribuirá ao parâmetro {0} o valor ex.ToString().
O exemplo seguinte mostra uma exceção gerada pela utilização de um elemento de tabela inexistente:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// declaração e inicialização de um tabuleiro
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 um tabelo 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:
- linha 9: ocorreu a exceção [System.IndexOutOfRangeException]
- linha 11: a cláusula finally (linhas 23-25) do código foi executada, apesar de, na linha 21, existir uma instrução return para sair do método. É importante notar que a cláusula finally é sempre executada.
Eis outro exemplo em que se trata a exceção provocada pela atribuição de uma cadeia de caracteres a uma variável do tipo inteiro, quando a cadeia não representa um número inteiro:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// exemplo 2
// Pede-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 ciclo de introdução da idade de uma pessoa
- linha 20: o texto digitado no teclado é convertido num número inteiro pelo método int.Parse. Este método lança uma exceção se a conversão não for possível. Por isso, a operação foi colocada num bloco try/catch.
- linhas 22-23: se for lançada uma exceção, passa-se para o `catch`, onde nada é feito. Assim, a variável booleana ageOK, definida em false na linha 14, permanecerá em 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 é superior ou igual a 1.
- linhas 24-26: é emitida uma mensagem de erro se a idade estiver incorreta.
Alguns resultados da execução:
3.6. Aplicação 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 o seu salário para declarar (dados de 2004 relativos aos rendimentos de 2003):
- calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants é o número de filhos que tem.
- se tiver pelo menos três filhos, tem mais meia quota
- calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- calcula-se o seu coeficiente familiar QF = R/nbParts
- calcula-se o seu imposto I. Consideremos a seguinte tabela:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23 888 | 0,2826 | 2668,39 |
38 868 | 0,3738 | 4846,98 |
47 932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 5000, encontrar-se-á a linha
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 são utilizados os coeficientes da última linha. Neste caso:
0 0,4809 9505,54
o que dá o imposto I = 0,4809*R - 9505,54*nbParts.
O programa 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 };
// recuperar o 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 quotas
decimal nbParts;
if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// rendimento tributável
decimal revenu = 0.72M * salaire;
// quociente familiar
decimal QF = revenu / nbParts;
// procura 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 seguidos do sufixo M (Money) para que sejam do tipo decimal.
- linha 16:
- Console.ReadLine() transforma a cadeia C1 digitada no teclado
- C1.Trim() remove os espaços no início e no fim de C1 — devolve a cadeia C2
- C2.ToLower() devolve a cadeia C3, que é a cadeia C2 convertida para minúsculas.
- linha 21: o valor booleano marie recebe o valor true ou false da relação reponse=="o"
- linha 29: a cadeia de caracteres introduzida pelo teclado é convertida para o tipo int. Se a conversão falhar, é lançada uma exceção.
- linha 30: o valor booleano OK recebe o valor true ou false da relação nbEnfants>=0
- linhas 55-56: não se pode escrever simplesmente nbEnfants/2. Se nbEnfants fosse igual a 3, teríamos 3/2, uma divisão inteira que daria 1 e não 1,5. Por isso, escreve-se (decimal)nbEnfants para tornar real um dos operandos da divisão e obter, assim, uma divisão entre números reais.
Eis alguns exemplos de execução:
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 cadeias de caracteres: String[] (ou string[]). Esta matriz contém os argumentos da linha de comandos utilizada para iniciar a aplicação. Assim, se iniciarmos o programa P com o seguinte comando (DOS):
P arg0 arg1 … argn
e se a função Main estiver declarada da seguinte forma:
teremos args[0]="arg0", args[1]="arg1" … Eis 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, deve-se proceder da seguinte forma:
![]() |
- em [1]: clique com o botão direito do rato no projeto / Propriedades
- em [2]: separador [Debug]
- em [3]: introduzir os argumentos
A execução produz os seguintes resultados:
Note-se que a assinatura
é 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 tem de gerir as notas de um exame. Seria necessário ter cinco: Passable, AssezBien, Bien, TrèsBien, Excellent.
Poderíamos, então, definir uma enumeração para estas cinco constantes:
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
Internamente, estas cinco constantes são codificadas por números inteiros consecutivos, começando por 0 para a primeira constante, 1 para a seguinte, etc... Uma variável pode ser declarada para assumir estes valores na enumeração:
// uma variável cujos valores provêm da 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 sob a 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. Esta estrutura possui um método estático GetValues que permite obter todos os valores de um tipo enumerado que seja passado como parâmetro. Este 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 através de v.GetType(). O tipo de um tipo T é obtido através de typeof(T). Assim, neste caso, maMention.GetType() devolve 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 sob a forma de números inteiros
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
na linha 2, a variável do ciclo é do tipo inteiro. Obtém-se, então, a lista de valores da enumeração sob a forma de inteiros. O objeto do tipo System.Type, correspondente ao tipo de dados Mentions, é obtido por typeof(Mentions). Poderíamos ter escrito, tal como anteriormente, maMention.GetType().
O programa seguinte 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 provêm da enumeração «Mentions»
Mentions maMention = Mentions.Passable;
// exibição do valor da variável
Console.WriteLine("mention=" + maMention);
// teste com um valor da enumeração
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
// lista de menções sob a forma de cadeias de caracteres
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
//lista de menções sob a 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:
3.9. Passagem de parâmetros para uma função
Vamos analisar aqui a forma como os parâmetros são passados a 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 é designado como um parâmetro formal. Existe apenas para efeitos da definição da função changeInt. Poderia muito bem ter-se chamado b. Consideremos agora uma utilização desta 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 irá transmitir o seu valor ao parâmetro formal a. O que nos interessa é a forma como um parâmetro formal recupera o valor de um parâmetro efetivo.
3.9.1. Passagem por valor
O exemplo seguinte mostra-nos que os parâmetros de uma função são, por predefiniçã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 alterar o parâmetro formal, o parâmetro efetivo não sofre qualquer 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:
O valor 20 do parâmetro efetivo age foi copiado para o parâmetro formal a (linha 10). Este foi posteriormente alterado (linha 11). O parâmetro efetivo, por sua vez, permaneceu inalterado. Este modo de passagem é adequado para os parâmetros de entrada de uma função.
3.9.2. Passagem por referência
Numa passagem por referência, o parâmetro efetivo e o parâmetro formal são uma única e mesma entidade. Se a função alterar o parâmetro formal, o parâmetro efetivo também é alterado. Em C#, ambos devem ser precedidos pela palavra-chave ref:
Eis 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:
O parâmetro efetivo acompanhou a alteração do parâmetro formal. Este modo de passagem é adequado para os 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 este programa, surge um erro:
É possível contornar este obstáculo atribuindo um valor inicial a age2. Também é possível substituir a palavra-chave ref pela palavra-chave out. Desta forma, indica-se que o parâmetro é apenas um parâmetro de saída e, por isso, não necessita de um 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:





