Aula 01: Introdução às linguagens de programação, estrutura básica, tipos e operadores
Introdução às linguagens de programação
Após pensar em um algoritmo para seu problema, você é capaz de formaliza-lo em uma linguagem de programação. A priori, um arquivo com código de uma linguagem de programação qualquer parece só com um monte de texto, mas como de fato o computador entende suas instruções? Vejamos
A grande imagem


A linguagem C
C é uma linguagem de propósito geral, imperativa e estruturada desenvolvida em 1972 no Bell Labs por Dennis Ritchie. Inicialmente desenvolvida para o sistema operacional UNIX.
Estrutura básica de um programa em C
int main() {
return 0;
}
#include <stdio.h>
int main() {
printf("Hello, World!");
return(0);
}
Onde programar ?
A princípio você é capaz de escrever o seu arquivo com código C em qualquer lugar, os mais comuns são editores de texto como o Bloco de notas, Vim, Neovim, Emacs, Helix, VSCode e outros. Mas aqui utilizaremos uma IDE chamada CodeBlocks.
Compilando
gcc arquivo.c -o nome_que_voce_quiser
Executando
./nome_que_voce_quiser
Variáveis em C
Tipos
Tipos, entre muitas coisas, definem as operações à qual uma variável é capaz de respeitar. Os chamados tipos primitivos em C, são os tipos mais elementares, que não podem ser decompostos em outros.
| Tipo | Memória | Especificador | O que armazena | Exemplo |
char |
1 byte | %c |
Um único caractere ASCII | ‘1’ ‘c’ ‘A’ ‘&’ |
int |
4 bytes | %d %i |
Números inteiros num intervalo de representação | -10 42 |
float |
4 bytes | %f |
Números de ponto flutuante num intervalo de representação | 3.14 |
double |
8 bytes | %lf |
Número de ponto flutuante num intervalo de representação com maior precisão | 3.14159265 |
O tipo int tem 4 bytes \(=\) 32 bits. Com \(n\) bits, o sistema admite \(2^n\) combinações binárias. Para \(n = 32\) int representa números inteiros no intervalo \([-2^{31}, 2^{31}-1]\).
Tipos de ponto flutuante como o float e o double são construídos de uma forma um pouco mais complicada que você pode ler aqui.
É possível alterar a faixa de representação através de modificadores como signed, unsigned, short, long e long long.
| Modificador | Efeito | Exemplo |
signed |
Reserva o bit mais significativo para indicar o sinal, e utiliza complemento de dois. | signed int e int tem o mesmo efeito, pois int é assinalado por padrão. |
unsigned |
O bit outrora usado para sinal, dobra a magnitude da representação positiva, eliminando a representação de números negativos. | unsigned int utiliza todos os bits para valores não negativos \([0, 2^{32}-1]\). |
short |
Reduz a quantidade de bytes de representação. | short int ou short reduz um inteiro para 2 bytes, num intervalo \([-32768, 32767]\). |
long |
Aumenta a quantidade de bytes de representação. | long int expande para Linux e macOS e long double aumenta a precisão decimal. |
long long |
Aumenta a quantidade de bytes de representação, mas mais que long. |
long long permite armazenar inteiros muito grandes, até \(2^{63} - 1\). |
A norma ISO da linguagem C não define o tamanho exato em bytes para esses tipos, apenas garante uma regra de hierarquia tal que short \(\leq\) long \(\leq\) long long. Em projetos onde o tamanho exato do inteiro, por exemplo, importa, como em estruturas de arquivos ou dados de rede, incluir o cabeçalho <stdint.h> é uma boa prática pois define os tamanhos explicitamente como int32_t, uint32_t, int64_t.
Casting
Casting é o mecanismo que faz a conversão de tipos e altera a interpretação do tipo de uma expressão. Em C existem dois tipos, o casting implícito (coerção de tipo) e o casting explícito (typecasting).
Coerção de tipo
#include <stdio.h>
int main() {
char c = 'a';
printf("%d\n", c);
return(0);
}
O printf desse programa imprime 97, que é o valor inteiro da representação ASCII de ‘a’.
Typecasting
#include <stdio.h>
int main() {
int a = 5, b = 2;
float c = (float)(a/b);
return(0);
}
Se (float) não fosse utilizado no lado direito da atribuição a divisão seria inteira e c seria 2.0, no programa acima c é 2.5.
C é uma linguagem com tipos estáticos, i.e. os tipos são atribuidos no momento da compilação. Isso não ocorre em linguagens como Ruby ou Lua que têm tipos dinâmicos, ou seja, atribuidos no momento da execução do programa.
x = 42
puts "#{x.class}"
x = "C eh maneiro"
puts "#{x.class}"
#include <stdio.h>
int main() {
int x = 42
printf("%d\n", x);
x = "C eh maneiro";
return(0);
}
Embora C (e C++) possua tipos estáticos, um programa pode passar na análise semântica do compilador e mesmo assim apresentar comportamento indefinido, em outras linguagens com tipos estáticos como Rust, isso não é possível.
fn main() {
let a: i32 = 2;
let b: i64 = 40;
let c = a + b;
println!("{}", c);
}
#include <stdio.h>
#include <stdint.h>
int main() {
int32_t a = 2;
int64_t b = 40;
int64_t c = a + b;
printf("%lld\n", c);
return(0);
}
Lendo e imprimindo variáveis
Utilizaremos as funções printf() e scanf() de <stdio.h> para ler e imprimir informações. Os especificadores de formato são utilizados para dizer os tipos na leitura e na escrita.
#include <stdio.h>
int main() {
int i;
float f;
double d;
char c;
scanf("%d", &i);
scanf("%f", &f);
scanf("%lf", &d);
scanf("%c", &c);
printf("%d\n", i);
printf("%f\n", f);
printf("%lf\n", d);
printf("%c\n", c);
return(0);
}
As variáveis ocupam espaço na memória, scanf() escreve nesse endereço, para indicá-lo, utiliza-se &. De fato, podemos analisar em qual endereço a variável se encontra imprimindo-o com printf(). O typecast para (*void) indica um endereço de memória para um dado de tipo arbitrário.
#include <stdio.h>
int main() {
int x = 42;
printf("%p\n", (void*)&x);
return(0);
}
Para formatar saídas \n' indica uma nova linha e \t indica tabulação. Também podemos formatar a impressão de um número flutuante com %.Nf onde \(N\) é o número de casas decimais desejadas.
#include <stdio.h>
int main() {
printf("NOME\t\tIDADE\tNOTA\n");
printf("Augusto\t\t23\t%.1f\n", 8.5);
printf("Carolina\t\t24\t%.1f\n", 9.8);
return(0);
}
Operadores
Operadores aritiméticos
| Operador | Operação | Exemplo | Resultado |
+ |
Adição. a + b \(a + b\). |
int c = 40 + 2. |
c é 42. |
- |
Subtração. a - b \(a - b\). |
int c = 40 - 2. |
c é 38. |
* |
Multiplicação. a * b \(a \times b\). |
int c = 40 * 2. |
c é 80. |
/ |
Divisão inteira. a / b \(\frac{a}{b}\). |
int c = 40 / 2. |
c é 20. |
% |
Módulo (resto da divisão inteira). a % b \(a \pmod b\). |
int c = 40 % 2. |
c é 0. |
Operadores relacionais
| Operador | Operação | Exemplo | Resultado |
== |
Igual a. a == b \(a = b\). |
int c = (40 == 2). |
c é 0 (false). |
!= |
Diferente de. a != b \(a \neq b\). |
int c = (40 != 2). |
c é 1 (true). |
> |
Maior que. a > b \(a > b\). |
int c = (40 > 2). |
c é 1 (true). |
< |
Menor que. a < b \(a < b\). |
int c = (40 < 2). |
c é 0 (false). |
>= |
Maior ou igual a. a >= b \(a \ge b\). |
int c = (40 >= 2). |
c é 1 (true). |
<= |
Menor ou igual a. a <= b \(a \le b\). |
int c = (40 <= 2). |
c é 0 (false). |
Atenção, escrever x = y é uma atribuição, a qual x recebe o valor de y, i.e. \(x \leftarrow y\). x == y é um julgamento sobre os valores de x e y, verdadeiro se x e y têm o mesmo valor e falso caso contrário. Vale notar também que 0 é falso e qualquer coisa diferente de 0 é verdadeiro em C.
Operadores lógicos
Os operadores lógicos talvez sejam maior novidade. Operamos sobre valores de verdade, ou seja, 0 e qualquer coisa diferente de 0, significando falso e verdadeiro respectivamente.
O operador && é o E lógico, sendo seu resultado verdadeiro se e somente se os dois operando forem diferente de 0, ou seja, ambos verdadeiros.
a |
b |
a && b |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
O operador || é o OU lógico inclusivo, sendo seu resultado verdadeiro se e somente se pelo menos um dos operandos for diferente de 0, verdadeiro.
a |
b |
a || b |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
O operador ! é a NEGAÇÃO lógica, é um operador unário, ou seja, é aplicado a apenas um operando e sua aplicação apenas inverte o valor de verdade.
a |
!a |
| 0 | 1 |
| 1 | 0 |
Operações bit a bit
Como vimos, variáveis ocupam espaço em bytes na memória. De fato, essas variáveis também são representadas por uma base binária. O núemro \(5\) por exemplo pode ser escrito como
\[ 5 = 4 + 1 = 2^2 + 2^0 = \dots 0101_2.\]
Ou o número \(12\)
\[ 12 = 2^3 + 2^2 = 0\dots1100_2.\]
As operações bit a bit aplicam operadores lógicos (e outras operações) nas representações binárias. Considere 5 e 12 em suas representações binárias, i.e. \(0101_2\) e \(1100_2\).
| Operador | Operação | Exemplo | Resultado |
& |
E lógico bit a bit. | int c = 5 & 12. |
c é \(2^2\), o único bit em comum, sendo 4. |
| |
OU lógico inclusivo bit a bit. | int c = 5 | 12. |
c é \(2^3 + 2^2 + 2^1\) que é 13. |
~ |
NEGAÇÃO lógica bit a bit. | int c = !5. |
c é \(\neg 0101_2 = 1010_2\) que é 10. |
^ |
OU lógico exclusivo bit a bit. | int c = 5 ^ 12. |
c é \(2^3 + 2^0\) que é 9. |
<< |
Deslocamento de bits para a esquerda. \(\times 2\). | int c = 5 << 1. |
c é 10. |
>> |
Deslocamento de bits para a direita. \(\div 2\). | int c = 12 >> 1. |
c é 6. |
Para mostrar que << e >> é como multiplicar e dividir por 2, note que um número \(N\) pode ser representado na base 2 por uma sequência de bits finita \(b_n b_{n-1} \dots b_1 b_0 \in \{0,1\}^{n+1}\).\Pela definição do sistema posicional em base 2, o valor decimal do número \(N\) é dado pela soma
\[N = \sum_{i = 0}^n b_i \times 2^i = b_n \times 2^n + b_{n-1} \times 2^{n-1} + \dots + b_1 \times 2 + b_0.\]
A operação N << 1 desloca todos os bits uma posição para a esquerda e adiciona um bit na nova posição \(b_0'\). O novo valor então é \(N'\) tal que
\[ N’ = (\sum_{i = 0}^n b_i \times 2^{i+1}) + 0 = 2 \times (\sum_{i=0}^n b_i \times 2^i) = 2 N. \]
A operação N >> 1 desloca todos os bits uma posição para a direita e descarta o bit menos significativo \(b_0\). O novo valor então é \(N''\) tal que
\[ N’’ = \sum_{i=1}^n b_i \times 2^{i-1} = \sum_{j = 0}^{n-1} b_{j+1} \times 2^j\]
\[ N = b_0 + \sum_{i=1}^n b_i \times 2^i = b_0 + 2 \times \sum_{i=1}^n b_i \times 2^{i - 1} = b_0 + 2 N’’ \]
\[ N’’ = \frac{N - b_0}{2}. \]
Sendo o resto da divisão inteira de \(N'\) por \(2\).
Incrementos e decrementos em atribuições
A linguagem C fornece alguns açúcares sintáticos para incrementar ou decrementar valores de variáveis. Por exemplo, ao ínves de se escrever c = c + 1 – o novo valor de c é o antigo mais 1 – podemos simplesmente escrever c++ :)
Da mesma forma, ao ínves de escrever x = x * 10, pode-se escrever x *= 10. Algumas das possíbilidades são mostradas
| Operação | Açúcar sintático para |
c++ |
c = c + 1 |
c-- |
c = c - 1 |
c += a |
c = c + a |
c -= a |
c = c - a |
c *= a |
c = c * a |
c /= a |
c = c / a |
c %= a |
c = c % a |
c <<= a |
c = c << a |
c >>= a |
c = c >> a |
c &= a |
c = c & a |
c |= a |
c = c | a |
c ^= a |
c = c ^ a |
Precedência de operadores
| Precedência | Operador |
| 1 | ++ -- |
| 2 | * / % |
| 3 | + - |
| Precedência | Operador |
| 1 | ! |
| 2 | && |
| 3 | || |
Exercícios
1: O que são as linguagens de programação no seu computador? Indique ao menos um motivo para a existência de linguagens de programação.
2: Descreva a função da IDE, do compilador, do sistema operacional e do hardware no desenvolvimento de programas.
3: Escreva um programa em C que imprime o seu número inteiro favorito na tela e depois imprima o resto da divisão dele pelo seu segundo número favorito.
4: Escreva um programa em C que pergunta e lê a sua idade, sua altura, seu número favorito e a inicial do seu nome e depois imprime esses valores com a devida formatação. Dica: você pode usar \t, \n e %.Nf como vimos.
5: Escreva um código que lê um peso e uma altura e imprime o IMC. O IMC é obtido pela divisão do peso em quilogramas pelo quadrado da altura em metros. Dica: Guarde esse programa, vamos evoluir ele nas próximas aulas.
6: Escreva um programa que lê um código ASCII e imprime o caractere que aquele código representa. Depois aumente seu programa lendo também um caractere e imprimindo seu código ASCII.
7: A partir dos operadores lógicos básicos, &&, || e ! construa uma operação que lê dois valores de verdade a e b e atribua o novo valor de verdade à c a partir do OU lógico exclusivo, também chamado XOR. O XOR de dois valores de verdade é verdadeiro se e somente se os valores são diferentes, confira a tabela
a |
b |
c |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
8: Escreva um programa em C que lê um inteiro de 32 bits assinalado int, e apenas utilizando operadores bit a bit ou aritiméticos (ou seja nada de operadores lógicos, condicionais, ternários etc., note que não vimos isso ainda nas aulas), determina se ele é negativo. O programa deve imprimir 1 se o número for negativo e 0 se for não-negativo.
9: Observe que int c = a / b dá a divisão inteira arredondada para baixo. Escreva um programa que arredonda pra cima.
10: A nota de um aluno da UFMG na disciplina de Análise I é determinada pela média aritimética ponderada das 3 provas, arredondada pra cima. Todas as provas valem 100, a primeira tem peso 1 a segunda peso 4 e a terceira peso 2. Escreva o programa que lê as 3 notas e compute a nota final do aluno. Dica: guarde o programa pras próximas aulas.
📄 Gabarito