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
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 informações (e.g. inteiro →
ponto flutuante, nas expressões do tipo f + i).
Expressões sem sentido não são permitidas (eg. usar um
float como subscrito de um vetor).
Expressões que perdem informações, e.g. atribuir um long a
um short int, ou um float a um int, podem gerar um aviso do
compilador, porém, em geral, são válidas.
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).
Ver prog19-chap02-pg40.c - função lower (não é
válida para o conjunto de caracteres EBCDIC)
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 ou positivo.
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.
d) Otherwise, convert char and short to int.
e) Then, if either operand is long, convert the other to long.

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 signed
long.
-1L > 1UL porque -1L é promovido a unsigned long e
aparenta ser um número positivo grande.
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
informação.
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
expression será convertida para o tipo especificado,
segundo as regras já mencionadas.
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.
double sqrt(double) /* protótipo de sqrt */
raiz2 = sqrt(2); /* chamada de sqrt */

Força a conversão do inteiro 2 para um double sem precisar do
cast.
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.

Ver exemplos: prog21-chap02-pg44.c e prog22- chap02pg44.c
Operadores Lógicos Bit-a-bit







Podem ser aplicados a operandos integrais →
char, short, int e long, sinalizados ou não.
& - AND bit-a-bit
| - inclusive OR bit-a-bit
^ - exclusive OR bit-a-bit
<< - left shift
>> - right shift
~ - one's complement (unary)
Operador AND Bit-a-bit &

O operador AND bit-a-bit & é muito usado para
mascarar algum conjunto de bits:
n = n &0177;
zera todos os bits de de n, exceto os 7 menos
significativos.
n1n2n3n4n5n6n7n8|n9n10n11n12n13n14n15n16
01020304050607080|099111010111111111212111313111414111515111616
------------------------------------------01020304050607080|099nn1010nn1111nn1212nn1313nn1414nn1515nn1616
Operador OU Bit-a-bit |

O operator OU bit-a-bit | é usado para ativar bits:
x = x | ativo;
torna 1, em x, os bits que são 1 em ativo.
n1n2n3n4n5n6n7n8|n9n10n11n12n13n14n15n16
1112031405060708|09110011112013114115116 → ativo
-----------------------------------1112n314n5n6n7n8|n9110n11112n13114115116
Operador OU exclusivo Bit-a-bit ^



O operator OU exclusivo bit-a-bit ^ torna 1 cada
posição de bit onde seus operandos possuem bits
diferentes e 0 onde forem iguais.
Deve-se distingüir os operadores bit-a-bit & e |
dos operadores lógicos && e ||, os quais
implicam em uma avaliação da esquerda para a
direita de umvalor booleano.
P. ex., se x==1 e y==2, então x & y é zero,
enquanto x && y é 1.
Operadores de deslocamento (1)



<< → executa deslocamento à esquerda do
operando à esquerda, pelo número de bits
informado pelo operando à direita.
>> → executa deslocamento à direita do
operando à esquerda, pelo número de bits
informado pelo operando à direita. O operando
à direita deve ser um inteiro positivo.
x << 2 desloca os bits de x para a esquerda de
duas posições (equivalente a multiplicar por
quatro).
Operadores de deslocamento (2)


Um deslocamento à direita de uma quantidade
sem sinal, sempre preenche os bits vagos com
zero.
Um deslocamento à direita de uma quantidade
com sinal preenche os bits vagos com o bit de
sinal (“deslocamento aritmético”) em algumas
máquinas e com bits zero (“deslocamento
lógico”) em outras.
Operador complemento de um ~
O operador unário ~ gera o complemento a 1 de
um inteiro, isto é, converte cada bit 1 em 0 e
vice-versa.
 x = x & ~ 077; (077 == 0000000000111111)
zera os seis últimos bits de x.
 x & ~ 077 é independente do tamanho da
palavra, e é preferível a x & 0177700, que
assume que x possui 16 bits.
(0177700 == 1111111111000000)

Exemplo de uso de ops bit-a-bit







Ver prog23-chap02-pg45.c
função getbits(x,p,n) que retorna (ajustado à direita) o campo de
n-bits de x que se inicia na posição p.
Assume-se que 0 é a posição mais à direita e que n e p são valores
positivos e com sentido.
getbits(x, 4, 3) → retorna os tres bits referentes às posições 4, 3 e
2, ajustados à direita.
A expressão x >> p + 1 – n move o campo desejado para a direita
da palavra.
~0 → todos os bits 1; ~0 << n → coloca 0 nos n bits mais à
direita. ~ (~0 << n) → transforma os n bits mais à direita em 1.
ex.: .|.|.|.|.|.|.|.|.|.|.|4|3|2|1|0
p=4, n =3
Ops e exps de atribuição
i =i + 2 é equivalente a i += 2
 += é um operador de atribuição
 A maioria dos operadores binários possui um
operador de atribuição op=, onde op pode ser:
+ - * / % << >> & ^ |.
 Se exp1 e exp2 são expressões, então
exp1 op= exp2 é equivalente a
exp1 = (exp1) op (exp2)
 x *= y + 1 é equivalente a x = x * (y +1)

Operador de atribuição - exemplo




Ver prog24-chap02-pg46.c
A função bitcount conta o número de bits 1 no seu
argumento inteiro.
Declarar o argumento x como unsigned asssegura que, ao
ser deslocado à direita, os bits vagos serão preenchidos com
zeros, e não com bits de sinal, não importando a máquina
em que o programa está sendo executado.
O comando de atribuição tem um valor, e pode ocorrer em
expressões. Ex.:
while ((c = getchar()) != EOF)

O tipo de uma expressão de atribuição é o tipo do seu
operando à esquerda, e o valor é o valor após a atribuição.
Expressões condicionais (1)
if (a > b)
/* z = max(a, b) */
z = a;
else
z = b;
/* é equivalente a: */
-----------------------------------z = (a > b) ? a : b; /* z = max(a, b) */

exp1 ? exp2 : exp3
/* ?: → operador ternário */
Primeiramente a expressão exp1 é avaliada. Se for diferente de zero
(verdadeira), então exp2 é avaliada e este é o valor da expressão
condicional. Caso contrário exp3 é avaliada e este é o valor.

Se exp2 e exp3 são de tipos diferentes, aplicam-se as regras de de
conversão de tipos.
Expressões condicionais (2)



Obs.: a precedência do operador ternário ?: é muito
baixa, logo acima da atribuição.
A expressão condicional leva a códigos compactos.
Ex: O loop a seguir imprime n elementos de um vetor,
10 por linha, com cada coluna separada por um espaço,
e com cada linha, inclusive a última, terminada por um
caracter de new line (\n).
for (i = 0; i < n; i++)
printf("%6d%c", a[i], (i%10==9 || i==n-1) ? '\n' : ' ');
Precedência e ordem de avaliação(1)
Operadores
Associatividade
() [] -> .
E→ D
! ~ ++ -- + - * (type) sizeof
D→E
*/%
E→ D
+-
E→ D
<< >>
E→ D
< <= > >=
E→ D
== !=
E→ D
&
E→ D
^
E→ D
|
E→ D
&&
E→ D
||
E→ D
?:
D→E
= += -= *= /= %= &= ^= |= <<= >>=
D→E
,
E→ D
Precedência e ordem de avaliação(2)





() refere-se à invocação (chamada) de uma
função.
-> e . são usados para se ter acesso a membros
de estruturas (capítulo 6).
sizeof (tamanho de um objeto) - (capítulo 6).
* (indireção através de pointer) - (capítulo 5).
Operador , (capítulo 3).
Precedência e ordem de avaliação(3)
A precedência dos operadores bit-a-bit &, ^ e | fica
abaixo de == e !=.
if ((x & MASK) == 0) ...
 C, como a maioria das linguagens, não especifica a
ordem em que os operandos de um operador são
avaliados (exceções: &&, ||, ?: e ',')
em x = f() + g(); f pode ser avaliado antes de g e viceversa.
Se f ou g alterar uma variável do qual a outra depende, o
valor de x dependerá da ordem de avaliação.

Precedência e ordem de avaliação(4)

A ordem em que os argumentos de uma função são avaliados não
é especificada. Em
printf("%d %d\n", ++n, power(2, n)); /* errado */
poderá produzir resultados diferentes em compiladores diferentes.
Certo seria:
++n;
printf("%d %d\n", n, power(2, n));


Chamadas de funções, comandos de atribuição aninhados e operadores
de incremento e decremento podem produzir efeitos colaterais (alguma
variável é alterada, como resultado da avaliação de uma expressão).
Moral da história: o código dos programas deve independer, sempre,
da ordem de avaliação de expressões.
Download

int