#+title: Curso Básico da Linguagem C
#+subtitle: Aula 3: Tipos de dados
#+author: Blau Araujo
#+startup: show2levels
#+options: toc:3

* Aula 3: Tipos de dados

- [[https://youtu.be/iMiRzZCU7hE][Vídeo desta aula]]
- [[https://fiorix.wordpress.com/wp-content/uploads/2014/04/o-fantc3a1stico-mundo-da-linguagem-c.pdf][O Fantástico Mundo da Linguagem C - Alexandre Firori]]

** Exemplo da última aula (=quadrado.c=)

#+begin_src c
#include <stdio.h>

int quadrado(int base) {
    return base * base;
}

int main(void) {
    int num = 100000;

    printf("%d^2 = %d\n", num, quadrado(num));
    return 0;
}
#+end_src

O que há de novo:
  
~int num = 100000;~ → A base será 100.000

Compilando e executando...

#+begin_example
:~$ gcc quadrado.c
:~$ ./a.out
100000^2 = 1410065408
#+end_example

O resultado está obviamente errado!

** Tipos são tamanhos de dados na memória

- Bit: algarismos 0 ou 1;
- Byte: 8 bits;
- Tipo =int=: 32 bits == 4 bytes (arquitetura x86_64).

*** Maior número que se pode escrever com 4 bytes (32bits):

#+begin_example
(2^32) - 1 = 4.294.967.295
#+end_example

Quadrado de 100.000:

#+begin_example
100.000^2 = 10.000.000.000
#+end_example

*** O que aconteceu?

Bytes necessários para escrever 10.000.000.000:

#+begin_example
:~$ printf '0x%016x\n' 10000000000
0x00000002540be400
        ↑
        5 bytes
#+end_example

Bytes truncados pelo limite do tipo =int= (4 bytes):

#+begin_example
:~$ printf '0x%016x\n' 1410065408
0x00000000540be400
          ↑
          4 bytes
#+end_example

Precisamos de mais espaço para escrever o resultado:

| Tipo  | Tamanho | Faixa de valores                                       |
|-------+---------+--------------------------------------------------------|
| =char=  | 1 byte  | -128 a 127                                             |
| =short= | 2 bytes | -32.768 a 32.767                                       |
| =int=   | 4 bytes | -2.147.483.648 a 2.147.483.647                         |
| =long=  | 8 bytes | -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807 |

Todos esses tipos consideram o uso de valores com sinal!

** Exemplo corrigido (=quadrado-long.c=)

#+begin_src c
#include <stdio.h>

unsigned long quadrado(long base) {
    return base * base; /* Qual é o tipo dessa expressão? */
}

int main(void) {
    long num = 100000;

    printf("%ld^2 = %lu\n", num, quadrado(num));
    return 0;
}
#+end_src

## Compilando e executando...

#+begin_example
:~$ gcc quadrado-long.c
:~$ ./a.out
100000^2 = 10000000000
#+end_example

** Tipos primitivos

| Tipo   | Tamanho | Faixa de valores (x86_64)      |
|--------+---------+--------------------------------|
| =char=   | 1 byte  | -128 a 127                     |
| =short=  | 2 bytes | -32.768 a 32.767               |
| =int=    | 4 bytes | -2.147.483.648 a 2.147.483.647 |
| =float=  | 4 bytes | 1,18E-38 a 3,40E+38            |
| =double= | 8 bytes | 2,23E-308 a 1,80E+308          |

*** Qualificador =unsigned= (sem sinal):

| Tipo           | Tamanho | Faixa de valores (x86_64) |
|----------------+---------+---------------------------|
| =unsigned char=  | 1 byte  | 0 a 255                   |
| =unsigned short= | 2 bytes | 0 a 65.535                |
| =unsigned int=   | 4 bytes | 0 a 4.294.967.295         |

*** Qualificador =long=:

| Tipo          | Tamanho  | Faixa de valores (x86_64)                  |
|---------------+----------+--------------------------------------------|
| =long=          | 8 bytes  | -9223372036854775808 a 9223372036854775807 |
| =unsigned long= | 8 bytes  | 0 a 18446744073709551615                   |
| =long double=   | 16 bytes | 3,36E-4932 a 1,19E+4932                    |

** Tipos customizados

Nós podemos criar apelidos para tipos:

#+begin_example
typedef [QUALIFICADORES] TIPO NOME;
#+end_example

*** Exemplos:

#+begin_src c
typedef int i32;
typedef unsigned int u32;
typedef long int i64;
typedef unsigned long int u64;
#+end_src

Para diferenciar de nomes de variáveis, é comum utilizar o sufixo =_t= (de /type/):

#+begin_src c
typedef int i32_t;
typedef unsigned int u32_t;
typedef long int i64_t;
typedef unsigned long int u64_t;
#+end_src

** Expressões constantes (valores literais)

| EXPRESSÃO | TIPO                     |
|-----------+--------------------------|
| =42=        | =int=                      |
| =42U=       | =unsigned int=             |
| =42L=       | =long int=                 |
| =42UL=      | =unsigned long int=        |
| =42LL=      | =long long int=            |
| =42ULL=     | =unsigned long long int=   |
| ='a'=       | =int=                      |
| =12.3=      | =double=                   |
| =1e16=      | =double=                   |
| =12.3F=     | =float=                    |
| =1e16F=     | =float=                    |
| =12.3L=     | =long double=              |
| =1e16L=     | =long double=              |
| ="salve"=   | =unsigned long= (endereço) |

** O tipo =void=

No contexto de declarações de funções, =void= representa a ausência de tipo,
mas pode ter outros significados.

*** Funções sem valor de retorno:

#+begin_src c
void func(int num) {
    printf("num x 2 = %d\n", num * 2);
}
#+end_src

#+begin_quote
Retorno não é o que a função imprime, mas o valor que ela expressa!
#+end_quote

*** Funções que não recebem argumentos:

#+begin_src c
int func(void) {
    puts("Nada a fazer...");
    return 42;
}
#+end_src

*** Ponteiros de dados de tipo indeterminado:

#+begin_src c
void *ptr;
#+end_src

#+begin_quote
Ponteiros são variáveis que recebem endereços da memória (aula 5).
#+end_quote

** Modelagem de tipos (type casting)

/Type casting/ é a conversão explícita de um tipo de dado para outro, utilizando
a sintaxe =(TIPO)= antes de um valor ou de uma variável:

Exemplo:

#+begin_src c
(double)5    /* O inteiro '5' será compilado como 'double'. */
#+end_src


