Introdução ao C
A linguagem C é uma das mais influentes e amplamente utilizadas na história da programação. Criada por Dennis Ritchie no início da década de 1970 nos laboratórios Bell, C foi projetada para desenvolvimento de sistemas operacionais, sendo usada para escrever o UNIX. Sua popularidade se deve à combinação de eficiência, flexibilidade e poder de abstração moderado, tornando-a ideal tanto para programação de baixo nível, próxima ao hardware, quanto para desenvolvimento de aplicações de alto desempenho.
C é uma linguagem compilada, estruturada e fortemente tipada, o que significa que o código-fonte precisa ser traduzido para código de máquina antes da execução e que as variáveis devem ser declaradas com tipos específicos. Sua sintaxe influenciou muitas outras linguagens modernas, como C++, Java, C# e Python. Uma característica importante de C é a manipulação direta de memória por meio de ponteiros, permitindo um controle refinado sobre o hardware, algo essencial para sistemas operacionais, drivers e softwares embarcados.
O modelo de programação em C é baseado em funções, permitindo a modularização do código e a reutilização eficiente de trechos de código. Além disso, a linguagem oferece uma rica biblioteca padrão, que inclui funções para manipulação de strings, entrada e saída, operações matemáticas e gerenciamento de arquivos. No entanto, diferentemente de linguagens mais modernas, C não possui gerenciamento automático de memória, exigindo que o programador aloque e libere memória manualmente para evitar vazamentos.
Orientação a Objeto
A linguagem C não é orientada a objetos. Ela é uma linguagem estruturada e procedural, onde o foco está em funções e no fluxo sequencial do programa. Em C, os dados e as funções são separados, e não há suporte nativo para conceitos como classes, herança e polimorfismo, que são fundamentais na programação orientada a objetos (POO).
No entanto, é possível simular alguns aspectos da POO em C, usando structs para organizar dados e ponteiros para funções para criar um comportamento semelhante a métodos. Essa abordagem é usada, por exemplo, na implementação de bibliotecas e sistemas complexos, como o kernel do Linux. Mas, diferentemente de C++, que possui suporte completo a POO, C exige mais esforço para alcançar um modelo orientado a objetos.
Compilador C
Para compilar programas em C no Linux, o compilador mais utilizado é o GCC (GNU Compiler Collection). A instalação pode ser feita facilmente pelo gerenciador de pacotes da sua distribuição. No Ubuntu, Debian e derivados, use o seguinte comando no terminal:
sudo apt update && sudo apt install gcc -y
O comando acima instalará o GCC e todas as dependências necessárias para compilar programas em C.
Após a instalação, verifique se o GCC está instalado corretamente com:
gcc --version
Se o comando retornar a versão do compilador, significa que a instalação foi concluída com sucesso. Agora você já pode compilar seus programas em C usando o comando abaixo:
# Para compilar o código faça:
gcc nome_do_arquivo.c -o nome_do_executavel
# Para executar o código, faça:
./nome_do_executavel
Primeiro programa em C
Como de costume vamos criar um Hello Word como nosso primeiro programa.
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
# Para compilar o código faça:
gcc helloword.c -o helloword
# Para executar o código, faça:
./helloword
O código acima inclui a biblioteca stdio.h, que contém a função printf, usada para exibir a mensagem na tela. O main() é a função principal do programa, e return 0 indica que o programa terminou com sucesso.
Hello, World!
A biblioteca stdio.h (Standard Input/Output Header) é um cabeçalho da linguagem C que contém funções para manipulação de entrada e saída padrão, como leitura e escrita no terminal e em arquivos.
Ela fornece funções essenciais, como:
printf()para exibir texto na tela.scanf()para ler entrada do usuário.putchar()eputs()para imprimir caracteres e strings.getchar()egets()(emboragets()seja insegura e obsoleta) para capturar entrada.- Funções de manipulação de arquivos, como
fopen(),fclose(),fgets(),fprintf(), entre outras.
Sempre que um programa em C precisar lidar com entrada ou saída de dados, a biblioteca stdio.h será necessária. Ela é incluída no código usando #include <stdio.h>, permitindo o uso dessas funções.
Variáveis
As variáveis são espaços na memória usados para armazenar valores que podem ser modificados durante a execução do programa. Cada variável tem um tipo, um nome e pode armazenar diferentes tipos de dados.
Para declarar uma variável, especificamos o tipo seguido do nome:
int idade; // Declaração
idade = 25; // Atribuição
Também podemos declarar e inicializar ao mesmo tempo:
int idade = 25;
Tipos de Variáveis
Em C, devemos declarar o tipo da variável porque a linguagem é fortemente e estaticamente tipada. Isso permite ao compilador reservar memória corretamente, detectar erros antes da execução, gerar código mais eficiente e garantir controle preciso sobre a memória, algo essencial em sistemas embarcados e no kernel Linux. Isso torna o C mais rápido e seguro, evitando verificações desnecessárias em tempo de execução.
Os tipos básicos de variáveis são:
- int: Números inteiros (ex: 10, -3, 1000)
- float: Números de ponto flutuante (ex: 3.14, -0.5)
- double: Similar ao
float, mas com mais precisão - char: Armazena um único caractere (ex: 'A', 'z')
- void: Usado para indicar ausência de retorno em funções ou ausência de valor (para deixar mais simples).
O que significa Fortemente Tipada e Estaticamente Tipada?
Fortemente Tipada
Fortemente Tipada significa que C não converte automaticamente tipos incompatíveis. Se você tentar atribuir um número com casas decimais para uma variável do tipo int sem conversão explícita, pode perder dados. Exemplo:
int x = 3.5; // Perde a parte decimal, pois 3.5 vira 3
Se quiser armazenar um float corretamente em um int, precisa converter explicitamente:
int x = (int)3.5; // Conversão explícita (type casting)
Estaticamente Tipada
Estaticamente Tipada significa que o tipo das variáveis é definido em tempo de compilação e não pode mudar durante a execução. Uma variável declarada como int sempre será um int. Por exemplo:
int x = 10;
x = "teste"; // Erro: Não pode atribuir string a um int
O tipo void em C representa a ausência de valor. Ele é usado principalmente em três contextos:
- Funções que não retornam valor
Se uma função não precisa retornar nada, ela é declarada comvoid.Aqui,void mensagem() {
printf("Olá, mundo!\n");
}mensagem()não retorna nada, então usamosvoidno lugar de um tipo de retorno comointoufloat.
- Ponteiros genéricos (
void *)
Umvoid *pode apontar para qualquer tipo de dado. Isso é útil para alocação dinâmica de memória:Como umvoid *ptr;
int x = 10;
ptr = &x; // O ponteiro void pode armazenar qualquer endereçovoid *não tem um tipo específico, ao acessá-lo precisamos fazer um type casting:printf("%d\n", *(int *)ptr); // Convertendo para int* antes de acessar o valor
- Parâmetros vazios em funções
Em versões antigas do C,int funcao();significava que a função poderia receber qualquer número de argumentos. Para indicar que uma função não aceita parâmetros, usamosvoid:int funcao(void); // Garante que a função não aceita argumentos
Modificadores de Tipo
Podemos modificar os tipos de variáveis para alterar sua capacidade e comportamento:
- short: Reduz o tamanho de um inteiro (
short int x;) - long: Aumenta o tamanho do inteiro (
long int y;) - unsigned: Apenas valores positivos (
unsigned int z;)
Exemplo de Uso:
#include <stdio.h>
int main() {
int numero = 10;
float pi = 3.14;
char letra = 'A';
printf("Numero: %d\n", numero);
printf("Valor de Pi: %.2f\n", pi);
printf("Letra: %c\n", letra);
return 0;
}
Numero: 10 Valor de Pi: 3.14 Letra: A
Tipo bool
O tipo bool não existe nativamente no C padrão (C89/C90). No entanto, a partir do C99, foi introduzido na biblioteca stdbool.h.
Para usá-lo, basta incluir:
#include <stdbool.h>
Isso define bool como um alias para int, onde:
trueé representado por1falseé representado por0
Como C trata qualquer valor diferente de 0 como verdadeiro (true), o uso de bool ajuda a tornar o código mais legível.
Especificadores de Formato
Os especificadores de formato são usados para exibir e ler valores formatados em C. Eles servem principalmente para exibir valores na tela, ler valores do usuário e trabalhar com formatação de números.
Os principais especificadores de formato são:
| Especificador | Tipo de dado | Exemplo |
|---|---|---|
%d | Inteiro decimal (int) | printf("Número: %d", 10); |
%i | Inteiro (equivalente a %d) | printf("Número: %i", 10); |
%f | Número de ponto flutuante (float/double) | printf("Valor: %f", 3.14); |
%.xf | Define casas decimais (x indica quantas) | printf("%.2f", 3.14159); // Exibe: 3.14 |
%c | Caractere (char) | printf("Letra: %c", 'A'); |
%s | String (char array) | printf("Nome: %s", "Testando"); |
%u | Inteiro sem sinal (unsigned int) | printf("%u", 3000000000); |
%p | Ponteiro (endereço de memória) | printf("Endereço: %p", ptr); |
%x / %X | Inteiro em hexadecimal (minúsculo/maiúsculo) | printf("%x", 255); // Saída: ff |
%o | Inteiro em octal | printf("%o", 255); // Saída: 377 |
Estruturas de decisão
No C, existem três principais estruturas de decisão, que permitem executar diferentes blocos de código com base em condições. São elas:
IF
Usado para avaliar condições booleanas e executar diferentes blocos de código com base no resultado.? :
Forma reduzida de umif-elsequando há apenas uma operação a ser realizada.switch
Alternativa aoif-elsequando há múltiplas possibilidades baseadas em um único valor.
IF, ELSE e ELSE IF
No C, as estruturas condicionais if, else if e else são usadas para tomar decisões com base em condições booleanas. A sintaxe básica é:
if (condição) {
// Bloco de código executado se a condição for verdadeira
} else if (outra_condição) {
// Bloco de código executado se a primeira condição for falsa e esta for verdadeira
} else {
// Bloco de código executado se nenhuma das condições anteriores for verdadeira
}
Abaixo podemos conferir um exemplo prático.
#include <stdio.h>
int main() {
int num = 10;
if (num > 10) {
printf("O número é maior que 10.\n");
} else if (num == 10) {
printf("O número é igual a 10.\n");
} else {
printf("O número é menor que 10.\n");
}
return 0;
}
if-else reduzido
#include <stdio.h>
int main() {
int numero = 5;
printf("%s\n", (numero % 2 == 0) ? "Par" : "Ímpar");
return 0;
}
Switch
A estrutura switch é usada quando há múltiplas condições a serem avaliadas para um mesmo valor. É uma alternativa ao uso excessivo de if-else if-else.
switch (expressão) {
case valor1:
// Código a ser executado se expressão == valor1
break;
case valor2:
// Código a ser executado se expressão == valor2
break;
default:
// Código executado se nenhum dos casos acima for verdadeiro
}
Abaixo podemos conferir um exemplo prático.
#include <stdio.h>
int main() {
int opcao = 2;
switch (opcao) {
case 1:
printf("Opção 1 selecionada.\n");
break;
case 2:
printf("Opção 2 selecionada.\n");
break;
case 3:
printf("Opção 3 selecionada.\n");
break;
default:
printf("Opção inválida.\n");
}
return 0;
}
O break evita que os casos seguintes sejam executados em efeito "cascata". O default funciona como um "else", executando caso nenhum case seja atendido.
Cuidado com a queda (fall-through)
Se um case não tiver um break, a execução continuará nos próximos case, mesmo que a condição não seja verdadeira. Exemplo:
switch (2) {
case 1:
printf("Caso 1\n");
case 2:
printf("Caso 2\n");
case 3:
printf("Caso 3\n");
}
Caso 2 Caso 3
Para evitar isso, sempre use break, a menos que queira o efeito intencionalmente.