Laboratório de Programação Prof. Oscar Luiz Monteiro de Farias [email protected] Capítulo 2 Tipos, Operadores e Expressões Variáveis, operadores e exps Variáveis e constantes são os objetos de dados básicos manipulados por um programa em C. As declarações listam as variáveis a serem usadas, definem os tipos assumidos pelas mesmas e eventualmente estabelecem os seus valores iniciais. Os operadores especificam as operações que devem ser realizadas com as variáveis. As expressões combinam variáveis e constantes, por meio dos operadores, a fim de produzir novos valores (que são eventualmente armazenados em variáveis). Identificadores (1) Os nomes de variáveis e constantes simbólicas são compostos por letras e dígitos; o primeiro caracter deve ser uma letra. Não inicie identificadores com um '_', pois as funções da biblioteca padrão normalmente usam nomes assim. Maiúsculas e minúsculas são distintas (x e X representam nomes distintos) Use minúsculas para as variáveis e maiúsculas para as constantes simbólicas (convenção do C). Identificadores (2) Pelo menos os 31 primeiros caracteres de um nome interno são significativos. Para os nomes externos o padrão ANSI garante que apenas os 6 primeiros caracteres são significativos, bem como um único tipo de letra (m ou M). As keywords (if, else, int, for, float, etc.) não podem ser usadas como nomes de variável. As keywords devem ser escritas em minúsculas. Tipos de dados básicos (1) char - um único byte - capaz de representar um caracter no conjunto de caracteres local int - inteiro, reflete o tamanho dos inteiros no hardware da máquina → usualmente 16bits (-32768 <= valor <= 32767) ou 32 bits float – ponto flutuante em precisão simples usualmente 32 bits - com pelo menos 6 dígitos significativos 10-38 <=valor<=10+38 double - ponto flutuante com precisão dupla Os tamanhos dos objetos em ponto flutuante são definidos pela implementação Tipos de dados básicos (2) Há qualificadores que podem ser aplicados aos tipos básicos long double - ponto flutuante com precisão estendida. short int - inteiro curto, normalmente ocupa16 bits long int - inteiro longo, normalmente ocupa 32 bits Ex.: short int counter; Cada compilador é livre para escolher os tamanhos adequados ao seu hw; Restrições: i) short e int devem ocupar pelo menos 16 bits; ii) short <=int <= long Os qualificadores signed ou unsigned podem ser aplicados a char ou a qualquer inteiro. Tipos de dados básicos (3) Números unsigned são sempre positivos ou zero. Obedecem as leis da aritmética módulo 2n, onde n é o número de bits do tipo correspondente. ex.: unsigned char x; → 0 <= x <= 255 signed char x; → -128 <= x <= 127 (em máquinas que usam o complemento a dois) Os arquivos headers standard <limits.h> e <float.h> contêm constantes simbólicas para todos os tamanhos, juntamente com soutra propriedades da máquina e do compilador. Os arquivos headers do C estão localizados em /usr/include. Ver o apêndice B do livro K&R. Constantes (1) Constante inteira → 1234 Constante long → 123456789L Um inteiro muito grande para caber em um int será considerado como long. Constantes não sinalizadas são escritas com a letra u ou U ao final O sufixo ul ou UL indica unsigned long Constantes de ponto flutuante contêm um ponto decimal → 123.4 ou um expoente → 1e-2 Constantes (2) Seu tipo é double, a menos que tenham um sufixo. F ou f → indicam uma constante float L ou l → constante long double O valor de um inteiro pode ser especificado em decimal, octal ou hexadecimal Um zero inicial em uma constante inteira significa octal → 037 (octal) = 31 em decimal 0X ou 0x inicial significam um número hexadecimal → 0x1f ou 0X1F = 31 em decimal Constantes octais ou decimais podem ser seguidas por l ou L (para torná-las long) ou por u ou U (para torná-las unsigned). Constantes (3) Uma constante de caracter é um inteiro, escrito como um caracter entre aspas → 'x' O valor de uma constante de caracter é o valor numérico do caracter no conjunto de caracteres da máquina Ex.: a constante de caracter '0' tem o valor 48 no conjunto de caracteres ASCII, que não está relacionado ao valor numérico 0. Escrevendo '0' no lugar do valor numérico 48 o programa se torna independente do valor e mais fácil de ser lido. Constantes de caracteres participam em operações aritméticas como quaisquer outros inteiros Seqüência de escape → \n - \t - \b (um único caracter) Constantes (4) Um padrão de bits arbitrário, do tamanho de um byte, pode ser especificado por '\000', onde 00 é de um a dois dígitos octais (0..7). ou por '\xhh`, onde hh é de um a dois dígitos hexadecimais (0...9, A...F). #define VTAB '\013' /* ASCII vertical tab */ #define BELL '\007' /* ASCII bell character */ #define VTAB '\xb' /* ASCII vertical tab */ #define BELL '\x7' /* ASCII bell character */ Seqüências de escape \a alert (bell) \\ backslash \b backspace \? question mark \f formfeed \' single quote \n newline \" double quote \r carriage return \ooo octal number \t horizontal tab \xhh hexadecimal number \v vertical tab Constantes (5) Uma expressão constante é uma expressão que envolve apenas constantes É avaliada em tempo de compilação Podem ser usadas como constantes também Exemplos: #define MAXLINE 1000 char line[MAXLINE+1]; #define LEAP 1 /* in leap years */ int days[31+28+LEAP+31+30+31+30+31+31+30+31+30+31]; Constantes (6) Uma constante do tipo cadeia (string) ou literal do tipo cadeia é uma seqüência de zero ou mais caracteres delimitados por aspas ''Eu sou uma cadeia de caracteres'' '''' /* cadeia vazia ou nula */ As mesmas seqüências de escape ssão usadas nas cadeias Constantes do tipo cadeia podem ser concatenadas em tempo de compilação ''Rio'' ''de'' ''Janeiro'' == ''Rio de Janeiro'' Constantes (7) Uma cadeia é um vetor cujos elementos são caracteres. A representação interna de uma cadeia possui um caracter nulo '\0' ao seu final. A área física de armazenamento é sempre um a mais do que o número de caracteres entre aspas. A função da biblioteca padrão strlen (s) retorna o tamanho da cadeia s, excluindo o '\0' final. Ver prog17-chap02-pg38.c O arquivo <string.h> possui declarações de várias funções de cadeias Constantes (8) 'x' não é o mesmo que ''x'' 'x' é uma constante de caracter, um inteiro usado para produzir o valor numérico da letra x no conjunto de caracteres da máquina. ''x'' é uma cadeia de caracteres (ou vetor) que contém o caracter x e um '\0'. Constante de enumeração (1) É uma lista de valores inteiros constantes enum boolean { NO, YES }; o primeiro nome em uma enum tem o valor 0, o seguinte 1, e assim por diante, a não ser que valores explícitos sejam especificados. Se nem todos os valores foram especificados, aqueles que não o foram continuama progressão a partir do último valor informado enum months { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; /* FEB = 2, MAR = 3, etc. */ Constante de enumeração (2) Os nomes em diferentes enumerações devem ser distintos. As enumerações fornecem uma forma conveniente de associar valores constantes a nomes. É uma alternativa ao uso de #define, com a vantagem dos valores serem gerados pelo compilador. Declarações (1) Todas as variáveis devem ser declaradas antes de serem usadas Certas declarações podem ser feitas implicitamente pelo contexto (eg. variáveis extern). Uma declaração especifica um tipo e é seguida por uma ou mais variáveis daquele tipo int lower, upper, step; char c, line[1000]; Declarações (2) Pode-se inicializar uma variável em sua declaração char esc = '\\'; int i =0; float eps = 1.0e-5; Se a variável não for automática (local à função) a inicialização é feita uma única vez, antes do início do programa e o inicializador deve ser uma expressão constante. Declarações (3) Uma variável automática explicitamente inicializada tem seu valor inicializado toda vez que a função ou bloco em que se encontra ganhar o controle do programa. Por default as variáveis externas e estáticas são inicializadas com zero. Variáveis automáticas sem inicializações explícitas assumem valores iniciais indefinidos (lixo). Declarações (4) O qualificador const pode ser aplicado à declaração de qualquer variável, indicando que a mesma não poderá ser alterada. const double e = 2.71828182845905; const char msg[] = "warning: "; const pode ser aplicado a argumentos do tipo vetor, indicando que os seus elementos não podem ser alterados int strlen(const char[]); Operadores aritméticos +, -, *, /, % (módulo) A divisão inteira trunca a parte fracionária x % y é igual a zero se x for um múltiplo de y; em caso contrário é o resto da divisão de x por y + e – possuem a mesma precedência que é menor que a dos operadores *, / e %, que por sua vez é menor que a dos operadores unários + e -. Os operadores aritméticos se associam da esquerda para a direita. Operadores relacionais e lógicos (1) Relacionais: >, >= , < , <= possuem a mesma precedência. De igualdade: == , != têm maior precedência que os operadores relacionais. Os operadores relacionais possuem menor precedência que os aritméticos. Expressões como i < lim -1 são avaliadas como i < (lim -1) Lógicos: &&, || Operadores relacionais e lógicos (2) Expressões conectadas por && e || são avaliadas da esquerda para a direita, e a avaliação pára, assim que a veracidade ou falsidade do resultado for conhecida. A maioria dos programas em C fundamenta-se nessas propriedades. for (i=0; i < lim-1 && (c = getchar()) != '\n' && c != EOF; ++i) s[i] = c; A precedência de && é maior que a de || e ambos têm menor precedência que os operadores relacionais e de igualdade. A precedência de != é maior que a da atribuição = Operadores relacionais e lógicos (3) Por definição, o valor numérico de uma expressão relacional ou lógica é 1 se a relação for verdadeira e 0 se a relação for falsa. O operador unário de negação ! converte um operando diferente de 0, ou verdadeiro, no valor 0, e um operador 0 ou falso no valor 1. if (!valid) /* ao invés de */ if (valid == 0) Conversões de tipo (1) Operador com operandos de diferentes tipos → conversão dos operandos para um tipo comum, segundo regras. Em geral, as conversões automáticas são aquelas que convertem um operando mais particular/estreito para um mais geral/largo, sem perder Conversões de tipo (2) Um char é apenas um pequeno inteiro, de modo que podem ser usados em expressões aritméticas. Esta propriedade fornece grande flexibilidade em certos tipos de transformações de caracteres. Ver prog18-chap02-pg40.c - função atoi s[i] – '0' fornece o valor numérico do caracter armazenado em s[i], pois os valores de '0', '1' … '9' formam uma seqüência continuamente crescente (vide ASCII table). Conversões de tipo (3) O arquivo header padrão <ctype.h> descrito no apêndice B do livro do K&R (/usr/include/ctype.h) fornece funções para teste e conversão de caracteres, independente do conjunto de caracteres usado. tolower(c) / isdigit(c) C não especifica se as variáveis do tipo char são quantidades signed ou unsigned. Dependendo do hardware a conversão de um char para int pode produzir um inteiro negativo Conversões de tipo (4) Em algumas máquinas um char, cujo bit mais à esquerda é 1 será convertido para um inteiro negativo (''sign extension''). Em outras, um char é promovido a um int, incluindo-se 0 nos bits mais à esquerda, e é sempre positivo. A definição de C garante que qualquer caracter pertencente ao conjunto de caracteres da máquina jamais será negativo. Conversões de tipo (5) Lembrar que o valor de uma expressão relacional ou lógica é 1 (verdadeiro) ou 0 (falso). d = c >= '0' && c <= '9' atribui 1 a d, se c for um dígito, e 0 se não for. Todavia, funções como isdigit (c) podem retornar qualquer valor não-zero, indicando um resultado verdadeiro. Na parte de teste de if, while, for, etc., verdadeiro significa não-zero. Conversões de tipo – regras (1) Ver apêndice A, seção 6, do livro do K&R. Se não houver operandos do tipo unsigned, o seguinte conjunto informal de regras basta: a) If either operand is long double, convert the other to long double. b) Otherwise, if either operand is double, convert the other to double. c) Otherwise, if either operand is float, convert the other to float. Conversões de tipo – regras (2) Em geral as funções matemáticas, como as de <math.h> usam precisão dupla. As regras de conversão são mais complicadas quando operandos unsigned estão envolvidos. A comparação entre valores signed e unsigned são dependentes da máquina, pois dependem do tamanho (# de bits) assumidos pelos inteiros. Supor int (16 bits) e long (32 bits) → -1L < 1U, porque 1U, unsigned int, é promovido a Conversões de tipo – regras (3) int i; char c; ... i = c; c = i; O valor de c é inalterado. Isto é verdade independente de se ter ou não extensão de sinal envolvida. Todavia, invertendo-se a ordem dos comandos de atribuição poderá levar à perda de Conversões de tipo – regras (4) Como os argumentos de uma função são expressões, ocorrerá uma conversão de tipos quando eles forem passados para a função (atribuídos aos parâmetros). usar um protótipo de função para forçar a coerção de tipos (será visto + adiante) Typecast – a conversão explícita de tipos poderá ser forçada em qualquer expressão através do operador unário cast (molde). Em (type name) expression Conversões de tipo – regras (5) Se n é um int deve-se ter sqrt ((double(n)) pois sqrt espera um double como argumento. O typecast converte o int para double, antes de passá-lo para sqrt. Se forem declarados argumentos por um protótipo de função (como normalmente deve ser), esta declaração causará uma coerção automática dos argumentos, quando a função for invocada. Ops de Incremento e Decremento ++ e - Podem ser usados prefixados ou pós-fixados. ++n e n++ (ambos incrementam n) ++n incrementa n antes que seu valor seja usado, n++ incrementa n depois que seu valor foi usado. Se n = 5 x = n++; /* atribui 5 a x */ x = ++n; /* atribui 6 a x */ em ambos os casos n torna-se 6.