03. Tipos de Dados e Operadores
Aprende os tipos elementares em C, modificadores de tipo, constantes inteiras e reais, representação ASCII e sequências de escape, conversões implícitas e explícitas, e domina todos os operadores: aritméticos, relacionais, lógicos, bitwise, ternário e atribuições reduzidas.
Tipos de Dados e Operadores em C
Nesta aula vamos aprofundar a estrutura dos dados na linguagem C com base nos conteúdos da apresentação Aula 3 — Tipos e Operadores. Vais compreender como a memória armazena valores numéricos e textuais, quais os modificadores de tipo disponíveis, como funcionam as conversões implícitas e explícitas (type casting) e dominar todo o ecossistema de operadores: desde a aritmética elementar à manipulação direta de bits ao nível do hardware.
1. Tipos Elementares Predefinidos
Cada dado em C tem associado um determinado tipo, que define:
- O tamanho ocupado em memória (número de bytes).
- A forma de representação interna dos bits.
- O intervalo de valores admissíveis.
Estes atributos dependem da arquitetura do processador e do compilador utilizado.
Existem quatro tipos básicos de dados em C, a partir dos quais todos os outros tipos se constroem:
int(inteiro)float(vírgula flutuante / precisão simples)double(vírgula flutuante com dupla precisão)char(caractere)
2. O Tipo Inteiro (int) e Modificadores
O tipo int representa o conjunto dos números inteiros (positivos e negativos). Consoante a arquitetura, ocupa habitualmente 2 ou 4 bytes:
| Bytes | Menor Valor | Maior Valor |
|---|---|---|
| 2 Bytes | -32 768 |
32 767 |
| 4 Bytes | -2 147 483 648 |
2 147 483 647 |
Modificadores de Tipo
Para garantir que uma variável tenha uma dimensão previsível independentemente da arquitetura da máquina onde corre, o C disponibiliza quatro modificadores de tipo:
short: Inteiro pequeno (sempre 2 bytes).long: Inteiro grande (sempre 4 bytes ou mais).signed: Inteiro com sinal (valores positivos e negativos).unsigned: Inteiro sem sinal (apenas valores positivos ou zero).
Se quisermos assegurar um inteiro de 4 bytes independentemente da máquina, devemos declarar explicitamente:
long int n;
Combinações de Modificadores
Podemos combinar dois modificadores para ajustar rigorosamente o intervalo de dados:
| Tipo | Bytes | Menor Valor | Maior Valor |
|---|---|---|---|
short int |
2 | -32 768 |
32 767 |
long int |
4 | -2 147 483 648 |
2 147 483 647 |
unsigned short int |
2 | 0 |
65 535 |
unsigned long int |
4 | 0 |
4 294 967 295 |
3. Tipos Associados a Constantes
Em C, o tipo associado aos valores constantes encontra-se implícito na forma como cada valor é graficamente representado no código-fonte.
Constantes Inteiras
- Decimal:
123→ tipoint1234567lou1234567L→ tipolong12345uou12345U→ tipounsigned1234567ulou1234567UL→ tipounsigned long
- Hexadecimal (prefixo
0xou0X):0x1f→ valor decimal correspondente:310xFUL→ valor decimal correspondente:15(tipounsigned long)
- Octal (prefixo
0):037→ valor decimal correspondente:31(3 × 8¹ + 7 × 8⁰ = 24 + 7 = 31)
O Tipo Real (float e double) e Constantes Reais
As variáveis de tipo float e double armazenam valores reais com parte fracionária (vírgula flutuante).
- Em ponto flutuante, os valores reais são guardados de forma não exata (aproximação binária).
- Um
floatocupa habitualmente 4 bytes, enquanto umdoubleocupa habitualmente 8 bytes (o dobro da precisão).
Formas de representação de constantes reais:
123.4→ tipodouble1e-2→ tipodouble(notação científica equivalente a0.01)123.4f→ tipofloat(sufixofouF)1E-2f→ tipofloat(equivalente a0.01f)
4. O Tipo Caractere (char), Tabela ASCII e Escapes
Uma variável do tipo char destina-se a armazenar um caractere.
- Ocupa sempre 1 único Byte em qualquer arquitetura, possibilitando 2⁸ = 256 valores possíveis.
- Na prática, o C armazena não o desenho do símbolo, mas sim o seu código numérico na Tabela ASCII.
- Portanto, em C um caractere não é mais do que um inteiro de 1 Byte! As grandezas
'A'e o número65são essencialmente a mesma coisa. - Por esta razão, podemos efetuar operações aritméticas sobre caracteres (ex.:
'a' - 'A' + 'b'). - Os valores constantes do tipo caractere são delimitados por plicas simples (ex.:
'x','5','+','A').
Sequências de Escape
Caracteres especiais que não têm representação gráfica visível ou não são diretamente acessíveis no teclado são escritos através de sequências de escape iniciadas pela barra invertida (\):
| Sequência | Significado / Caractere Associado |
|---|---|
\a |
Alert (sinal sonoro / campainha do terminal) |
\b |
Backspace (retrocesso de uma posição) |
\f |
Formfeed (avanço de página) |
\n |
Newline (mudança de linha) |
\r |
Carriage return (retorno ao início da linha) |
\t |
Horizontal tab (tabulação horizontal) |
\v |
Vertical tab (tabulação vertical) |
\\ |
Barra invertida (\) |
\? |
Ponto de interrogação (?) |
\' |
Plica simples (') |
\" |
Aspas duplas (") |
Também é possível representar qualquer caractere através do seu código numérico:
\ooo: código ASCII em base octal (ex.:\101produz a letra'A').\xhh: código ASCII em base hexadecimal (ex.:\x5Aproduz a letra'Z').
Caractere vs. String: O Caractere Nulo '\0'
Existe um caractere especial crucial: o '\0' (caractere nulo), cujo código ASCII é o valor 0.
- É utilizado para sinalizar formalmente o fim de uma string na memória.
- As strings constantes são cadeias de caracteres delimitadas por aspas (ex.:
"Sou uma string"). - Atenção à diferença:
'x'→ É uma constante de 1 único caractere (ocupa 1 Byte)."x"→ É uma string (vetor de caracteres) constituída por dois elementos:'x'e'\0'(ocupa 2 Bytes).
5. Conversões de Tipo
Por vezes, os dados manipulados sofrem alterações no seu tipo. Essas conversões podem ser implícitas (automáticas) ou explícitas (type casting).
Conversões Implícitas
Ocorrem automaticamente sem qualquer intervenção do programador:
-
Conversões Aritméticas:
- Quando os dois operandos de um operador têm tipos diferentes, o operando de tipo mais restrito é promovido automaticamente para o de maior capacidade.
- Exemplo:
float + int→ ointé convertido internamente parafloat.
-
Conversão nas Atribuições:
- O valor do lado direito do operador de atribuição é convertido para o tipo da variável de destino antes de ser guardado.
- Atribuir um valor para uma variável de tipo mais restrito pode originar perda de informação ou truncagem:
int→char: os bits mais significativos em excesso são ignorados.char c; int i = 256; // 256 em binário de 16 bits = 0000 0001 0000 0000 c = i; // c fica com 0 (apenas os 8 bits mais à direita: 0000 0000)float→int: a parte decimal é simplesmente ignorada (truncada).int n; float f = 3.6f; n = f; // n fica com 3double→float: ocorre arredondamento ou truncagem dependendo da plataforma.
Conversões Explícitas (Cast)
Permitem forçar uma expressão a assumir um determinado tipo através do operador cast:
(tipo) valor
(float) n→ Trata o valor da variável inteirancomo vírgula flutuante naquela expressão.(int) x→ Extrai a parte inteira de um valor realx.
6. Operadores da Linguagem C
Os operadores em C classificam-se quanto ao número de operandos em unários (1 operando), binários (2 operandos) ou ternários (3 operandos).
6.1. Operador de Atribuição (=)
Permite atribuir um valor a uma variável:
variavel = valor;
É possível encadear atribuições em sequência (avaliadas da direita para a esquerda):
variavel1 = variavel2 = variavel3 = valor; // As três variáveis recebem o mesmo valor
6.2. Operadores Aritméticos
| Operador | Operação | Tipo |
|---|---|---|
+ |
Adição | Binário |
- |
Subtração | Binário |
* |
Multiplicação | Binário |
/ |
Divisão | Binário |
% |
Módulo (Resto da divisão inteira) | Binário |
++ |
Incremento (soma 1) | Unário |
-- |
Decremento (subtrai 1) | Unário |
Os Operadores / e %
- O resultado da divisão
/segue o tipo dos operandos: a divisão entre inteiros resulta sempre num número inteiro!5 / 2 = 2 - Para obter a parte decimal, pelo menos um dos operandos deve ser de tipo real:
(float)5 / 2 // 2.5 5 / (float)2 // 2.5 5.0 / 2 // 2.5 5 / 2.0 // 2.5 5.0 / 2.0 // 2.5 - O operador módulo
%calcula o resto da divisão inteira e só pode ser aplicado a operandos inteiros:11 % 4 = 3 // pois 11 = 4 * 2 + 3
Incremento e Decremento (++ e --)
O operando deve ser obrigatoriamente uma variável (pois o seu conteúdo é alterado).
- Isoladamente,
++x;,x++;ex = x + 1;são equivalentes. - Porém, quando inseridos numa expressão, a posição do operador é crítica:
- Pré-incremento (
++x): O valor da variável é incrementado antes de ser utilizado na expressão. - Pós-incremento (
x++): O valor original da variável é utilizado na expressão e o incremento ocorre apenas depois.
Comparação Prática:
// Caso 1: Pós-incremento
int x = 10, y;
y = x++;
// Operações: y recebe 10; de seguida x passa a 11.
// Caso 2: Pré-incremento
int x = 10, y;
y = ++x;
// Operações: x passa a 11; de seguida y recebe 11.
Comportamento em printf:
int x = 10;
printf("%d ", x++); // Imprime 10, depois x fica 11
printf("%d\n", x); // Imprime 11
int a = 10;
printf("%d ", ++a); // a passa a 11 e imprime 11
printf("%d\n", a); // Imprime 11
Em expressões compostas:
int x = 10, y;
y = 10 * x++ + 5; // y = 10 * 10 + 5 = 105; depois x fica 11
int x = 10, y;
y = 10 * ++x + 5; // x passa a 11; y = 10 * 11 + 5 = 115
O operador -- segue rigorosamente a mesma regra de precedência e momento de execução.
Precedência dos Operadores Aritméticos:
- Maior:
++,-- - Intermédia:
*,/,% - Menor:
+,-
6.3. Operadores Relacionais
Permitem comparar dois operandos:
| Operador | Descrição |
|---|---|
> |
Maior |
>= |
Maior ou igual |
< |
Menor |
<= |
Menor ou igual |
== |
Igual a |
!= |
Diferente de |
- Todos os operadores relacionais partilham a mesma ordem de precedência entre si e são avaliados da esquerda para a direita.
- Têm precedência inferior aos operadores aritméticos:
x < y - 1 // Equivale a: x < (y - 1) - Valores de Verdade em C: A linguagem C não dispunha originalmente de um tipo booleano nativo. O valor falso é representado pelo número inteiro
0e o valor verdadeiro por qualquer inteiro diferente de zero. As expressões relacionais devolvem0se falsas e1se verdadeiras.
6.4. Operadores Lógicos
Combinam condições lógicas:
| Operador | Descrição |
|---|---|
&& |
E lógico (AND) |
|| |
OU lógico (OR) |
! |
Negação (NOT — unário) |
Exemplo Clássico — OR Exclusivo (XOR) entre a e b:
(a || b) && !(a && b)
Precedência Global entre Aritméticos, Relacionais e Lógicos:
!(Negação — mais alta)*,/,%+,->,>=,<,<===,!=&&||(Mais baixa)
Exemplo: x > 0 && x < 10 equivale a (x > 0) && (x < 10).
Exemplo de Avaliação de Condições:
int a = 20;
int b = 0;
a && b // 0 (pois b é falso/zero)
a || b // 1 (pois a é verdadeiro/não zero)
!a // 0
!b // 1
6.5. Operadores para Manipulação de Bits (Bitwise)
Atuam diretamente nos bits individuais de operandos inteiros:
| Operador | Operação |
|---|---|
& |
E lógico bit a bit (AND) |
| |
OU inclusivo bit a bit (OR) |
^ |
OU exclusivo bit a bit (XOR) |
~ |
Complemento / Negação bit a bit (NOT) |
>> |
Deslocamento de bits à direita (Right shift) |
<< |
Deslocamento de bits à esquerda (Left shift) |
Nos operadores de deslocamento (<< e >>), os bits vazios são alimentados por zeros de um lado, descartando-se os bits excedentes do lado oposto.
Exemplo Detalhado com a = 19 e b = 42:
a = 19 --> 0000 0000 0001 0011
b = 42 --> 0000 0000 0010 1010
-----------------------------------------
a & b --> 0000 0000 0000 0010 (Valor: 2)
a | b --> 0000 0000 0011 1011 (Valor: 59)
a ^ b --> 0000 0000 0011 1001 (Valor: 57)
~a --> 1111 1111 1110 1100 (Valor unsigned 16-bit: 65516)
a >> 1 --> 0000 0000 0000 1001 (Valor: 9 -> divisão inteira por 2)
a << 2 --> 0000 0000 0100 1100 (Valor: 76 -> multiplicação por 4)
6.6. O Operador Condicional Ternário (? :)
É um operador de 3 operandos capaz de substituir estruturas simples de decisão:
Condicao ? Expressao1 : Expressao2
Avalia a condição: se for verdadeira devolve Expressao1; se for falsa devolve Expressao2.
int x = 5, y = 30, z;
z = x > y ? x : y; // Como x > y é falso, z recebe y (30)
6.7. Atribuições Reduzidas (Compound Assignment)
Combinam uma operação aritmética ou de manipulação de bits com a atribuição:
x op= y // Equivale exatamente a: x = x op (y)
Operadores válidos: +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=.
| Atribuição Reduzida | Operação Equivalente |
|---|---|
a += 2; |
a = a + 2; |
j <<= 2; |
j = j << 2; |
q /= 7 + 2; |
q = q / (7 + 2); (Note-se que avalia primeiro a soma!) |
7. Rastreio Completo de Expressões em Sequência
Eis o exercício de síntese apresentado no Slide 34, resolvido linha a linha com as variáveis int a = 10, b;:
int a = 10, b;
2 + 3 + 7 * 5 // Resultado: 40 (Multiplicação tem precedência: 2 + 3 + 35)
(5 / 2 + 8 * 10) // Resultado: 82 (5/2 é divisão inteira = 2; 2 + 80 = 82)
a * 5 - a * 2 // Resultado: 30 (10*5 - 10*2 = 50 - 20 = 30)
a++ * 7 + 3 // Resultado: 73 (Usa a=10: 10*7+3 = 73; no fim, a passa a 11)
--a // Resultado: 10 (Pré-decremento: a volta a valer 10)
b = 5 // Resultado: 5 (b fica com o valor 5)
a > 0 && a < 20 // Resultado: 1 (Verdadeiro: 10>0 é 1 e 10<20 é 1 -> 1 && 1 = 1)
a > b ? a - b : b - a// Resultado: 5 (10 > 5 é verdadeiro, calcula 10 - 5 = 5)
a && b // Resultado: 1 (10 e 5 são ambos diferentes de zero -> 1)
a & b // Resultado: 0 (00001010 & 00000101 = 00000000 -> 0)
a += 9 // Resultado: 19 (a = 10 + 9 = 19)
📝 Nota sobre os Exercícios Práticos:
Os enunciados práticos e problemas de consolidação associados a esta aula (Tipos de Dados, Operadores e Expressões) serão disponibilizados na respetiva folha de exercícios / ficha de trabalho autónomo da disciplina. Recomenda-se a resolução manual prévia do rastreio de expressões da Secção 7 antes da verificação com o compilador GCC.