Algoritmos e estruturas de dados:
partes centrais de qualquer software
Escolher a solução mais adequada
Entender os compromissos de diferentes
estruturas de dados
Tipos abstratos de dados Análise de algoritmos
O(n), O(log(n)), etc
Estruturas de dados
Listas, filas, pilhas e árvores
Métodos de ordenação
Quicksort, heapsort, etc
Métodos de pesquisa
Hashing, árvores balanceadas
Parte 1 Prova 1 Trabalho prático 1 Parte 2 Prova 2 Trabalho prático 2 Parte 3 Prova 3 Trabalho prático 3
Projeto de Algoritmos
Nívio Ziviani
Introduction to Algorithms
Cormen, Leiserson, Rivest, Stein
Algorithms
Robert Sedgewick e Kevin Wayne
The Art of Computer Programming
Volumes 1 e 3
3 provas (20+20+20 = 60 pontos) 1 prova suplementar 4 trabalhos práticos (4+10+10+16 = 40 pts) Implementação Documentação Teste
Página Web da disciplina
http://www.dcc.ufmg.br/~cunha/
Linguagem de programação: C
CodeBlocks
GCC
Sistema operacional recomendado: Linux
Os trabalhos práticos precisam rodar no Linux
Eficiência
Construções similares a instruções de máquina
Acesso direto à memória Portabilidade
De microcontroladores a supercomputadores
Poucos requisitos para execução
Software de sistema ou de base
Linux Gnome
Python, Perl, PHP, GCC Bibliotecas
▪ GNU Scientific Library
▪ Partes do Matlab
Inclusão de cabeçalhos Declarações globais
Definições de funções
#include <stdio.h>
char *mensagem = “hello, world!\n”; int main(void) {
puts(mensagem); return 0;
Compilador
date.h
struct date {
int day; int month; int year;
}
struct date create(void);
int week_of_year(struct date d);
data.h
#include “data.h”
struct data create(void) {
... }
int semana_do_ano(struct data d) { ... } ... data.c data.o
Compilador
date.h
struct date {
int day; int month; int year;
}
struct date create(void);
int week_of_year(struct date d);
data.h
#include “data.h”
int main(int argc, char **argv) { ... } principal.c data.o principal.o
date.o main.o
Economize tempo de depuração tratando
todos os avisos do compilador
Muitas vezes a mensagem de erro não reflete o
que está ocorrendo; observar as redondezas da linha em que o erro/warning foi indicado
[debian:~/prof/aeds2/src]% gcc –Wall –c data.c data.c: In function ‘main’:
data.c:12: warning: ‘return’ with no value, in function returning non-void data.c:11: warning: ‘hoje’ is used
Identificadores de funções e variáveis
Letras, números, e underscores Não podem começar com número
Palavras
reservadas
auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Tipos inteiros
char, short, int, long, long long
Tipos de ponto flutuante
float, double, long double
void
Arranjos Estruturas Ponteiros
type_sizes
type_limits
float_precision
Aritméticos x + y, x - y, x * y, x / y, x % y, -x Incremento e decremento x++, ++x, y--, --y Comparação x > y, x >= y, x < y, x <= y, x == y, x!= y Lógicos !x, x && y, x || y Binários x & y, x | y, x ^ y, ~x, x << y, x >> y Atribuição x = y, x += y, x |= y, x <<= y, etc. Endereçamento de memória
Conversão (int)x, (double)x Condicional x ? y : z sizeof sizeof(x), sizeof(double) Precedência 1 << 2 * 3 % 4 ^ 5 – 6 && 7
Sequência de elementos de um único tipo
Tamanho fixo
Sem checagem de limites
Opcional: inicialização durante a declaração
▪ Impossível atribuir a um arranjo depois da declaração
int primos[7];
int primos[7] = {2, 3, 5, 7, 11, 13, 17};
2 3 5 7 11 13 17
Alocação linear
Compilador converte indíces para a posição do
elemento referenciado
double identidade[4][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
identidade
identidade[x][y] = valor do elemento na posição x*4 + y
Um string termina com o caractere nulo
’\0’
Um string num arranjo de 80 caracteres
char string[80] = “hello world!”
Strings inicializados na declaração
char string[] = “hello world!”
O tamanho do arranjo é definido automaticamente pelo compilador
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 79
Copiar um string: strcpy, strncpy
Funções com n no nome recebem o tamanho do
arranjo como parâmetro
▪ Evita erros caso o string não caiba no arranjo
Concatenar strings: strcat, strncat
String de destino tem que ter espaço
Comparar strings: strcmp, strncmp
Comparar strings com == só compara a posição dos
arranjos, não o conteúdo
Tamanho de um string: strlen
Converter string para inteiros
int atoi(const char *string) long atol(const char *string)
long long atoll(const char *string)
Converter de string para ponto flutuante
array_boundaries1
array_boundaries2
string_cmp
Combinam conjunto de dados relacionados
Campos armazenados em sequência
Possível inclusão de espaçamento pelo compilador
struct cliente { char nome[48]; long long cpf; long telefone; struct endereco residencial; ... }; struct cliente cl; cl.cpf = 65423423123; sprintf(cl.residencial.rua, “Rua dos Goitacases”);
cl.residencial.numero = 1325; struct endereco end = {
Referência para um objeto na memória Vários usos:
Manipulação de dados
▪ Exemplo: ordenar um arranjo de estruturas grandes
Passagem de parâmetro por referência
Declarações:
int i = 10;
int * ponteiro = &i; int ** ppp = &ponteiro;
Variável Posição Valor
i 0x80 10
ponteiro 0x84 0x80
Operadores & e *
Endereço de uma variável é um ponteiro para
aquela variável
Acessar o valor da variável apontada
int x = 10; int y;
int * ponteiro = &x;
y = *ponteiro + 1; // y = x + 1 = 11 *ponteiro = 20; // x = 20
Nenhum ponteiro válido tem o valor NULL NULL não pode ser acessado
Falha de segmentação
Útil para denotar um ponteiro não inicializado
ou condições de erro
int * ponteiro;
/* ponteiro tem um valor * aleatório, não sabemos * para onde ele aponta. */ ponteiro = NULL;
fatores(ponteiro);
int * fatores(int * p) {
if(!p) { return NULL; } // calcula fatores primos ...
Não existe objeto com tipo void
Um ponteiro para void é como um coringa,
pode apontar para qualquer tipo de objeto
int i; int * int_ptr; void * void_ptr; double * double_ptr; int_ptr = &i; void_ptr = int_ptr; // OK double_ptr = int_ptr; // !OK double_ptr = void_ptr; // OK /* Qual o problema com
* double_ptr? */
Variável Posição Valor
i 0x80 10
int_ptr 0x84 0x80
void_ptr 0x88 0x80
Declaração e inicialização normal
Acesso aos campos
struct data { int dia; int mes; int ano; }; struct data d1;
struct data *ptr = &d1; int i = 0; (*ptr).dia = 8; (*ptr).mes = 3; (*ptr).ano = 2012; ptr->dia = 8; ptr->mes = 3; ptr->ano = 2012;
Variável Posição Valor
d1.dia 0x80 8
d1.mes 0x84 3
d1.ano 0x88 2012
ptr 0x8c 0x80
Declaração
Tipo de retorno, nome, parâmetros
▪ double pow(double x, double y)
Em cabeçalhos .h para funções externas
Em arquivos .c para funções auxiliares (internas)
Definição
Em arquivos .c
Corpo da função
double pow(double x, double y) {
... }
C passa parâmetros por valor
Modificações do valor de um parâmetro não afeta
a variável original (fora da função)
int incrementa(int x) { x = x+1;
return x; }
int main(int argc, char **argv) {
x = 1;
y = incrementa(x);
printf(“%d %d\n”, x, y); ...
Variedade de utilidades Declaração
int (*comparador)(void *e1, void *e2);
Inicialização
Nome da função é convertido em ponteiro
Passando como parâmetro
int compara_dados(void *e1, void *e2) { ... }; int main(void) {
int (*compara)(void *e1, void *e2) = compara_dados;
void ordena(void *dados, int nelem,
func_params
struct_func_params
Preferível sempre que não soubermos quanta
memória um programa utilizará
Alocação estática é fixa
Alocação dinâmica permite utilizar a quantidade necessária de memória sem desperdícios
Alocar um novo bloco de memória
malloc(), calloc()
Redimensionar um bloco já alocado
realloc()
Liberar um bloco alocado
malloc aloca um bloco de size bytes
O conteúdo do bloco alocado é indeterminado Retorna um ponteiro para void
O programador decide como usar o bloco alocado
Retorna NULL em caso de erro
int *primos = malloc(7 * sizeof(int)); if(!primos) { perror(NULL); exit(1); } char *buffer = malloc(64);
if(!buffer) { perror(NULL); exit(1); } double *fracoes = malloc(64);
if(!fracoes) { perror(NULL); exit(1); } memset(fracoes, 0, 8*sizeof(double)); int *primos = malloc(7 * sizeof(int)); if(!primos) { perror(NULL); exit(1); } char *buffer = malloc(64);
if(!buffer) { perror(NULL); exit(1); }
double *fracoes = malloc(8*sizeof(double)); if(!fracoes) { perror(NULL); exit(1); }
calloc aloca um bloco com espaço para
count objetos de tamanho size bytes
O bloco alocado é inicializado com zero
int *primos = calloc(7, sizeof(int)); if(!primos) { perror(NULL); exit(1); } char *buffer = calloc(64, sizeof(char)); if(!buffer) { perror(NULL); exit(1); }
double *fracoes = calloc(8, sizeof(double)); if(!fracoes) { perror(NULL); exit(1); }
Redimensiona o bloco de memória apontado
por ptr para size bytes
Mantém o conteúdo do bloco apontado por ptr
(limitado pelo tamanho do novo bloco)
O local do novo bloco de memória pode mudar ▪ Mesmo se o novo bloco for menor que o anterior!
int *primos = calloc(7, sizeof(int)); if(!primos) { perror(NULL); exit(1); }
realloc(primos, 5*sizeof(int)); // BUG primos = realloc(primos, 5*sizeof(int)); // OK if(!primos) { perror(NULL); exit(1); }
Libera o bloco de memória apontado por ptr
Chamar free mais de uma vez pra um mesmo
bloco de memória é um bug
free só pode ser chamada em ponteiros
retornados por malloc, calloc e realloc.
char * montar_string(struct endereco e) { char *string = malloc(128);
if(!string) { perror(NULL); exit(1); } // montar string ...
return string; }
char * exemplo(char parametro[]) {
int i; char estatico[80];
char *dinamico = malloc(80);
Variável Posição Valor
parametro 0x7c 0x480 i 0x80 ? estatico[0] 0x84 ? ... ... ? estatico[79] 0xd3 ? dinamico 0xd4 0x884
Variável Posição Valor
dinamico[0] 0x884 ? dinamico[1] 0x885 ?
... ... ?
dyn_alloc
Rotinas de entrada e saída não fazem parte
da linguagem
Disponíveis em bibliotecas que acompanham
os compiladores
Padronizadas
formato específica como os valores devem
ser impressos na saída. printf(“X = %d”, x);
printf(“Area: %f\n”, PI*r*r); printf(“Nome: %s”, aluno.nome);
Existem vários caracteres de controle
Retorna o número de conversões impressas
conversão c char d int u unsigned int x int, hexadecimal f float e float, científico g float, e ou f p ponteiro s string % sinal percentual %[opções][largura mínima][.precisão][tamanho]conversão
printf(“valor do float na posição %p = %f\n”, ptr, *ptr);
tamanho hh char h short l long ll long long L long double z size_t t ptrdiff_t j intmax_t
printf(“valor do double na posição %p = %lf\n”, ptr, *ptr);
tamanho
0 zeros à esquerda # alternativa
- alinhar à esquerda + mostrar sinal positivo
espaço para sinal positivo
‘ agrupar milhares I digitos alternativos
Caracteres especiais e reposicionamento do
cursor
printf(“barra invertida \\\n”); printf(“aspas duplas \”\n”);
scanf é o inverso do printf:
lê dados do terminal
Mesmos códigos de conversão Mesmas sequências de escape
Passar um ponteiro para a variável que você
quer inicializar
int nlados = 0; float lado = 0;
scanf(“%f %d\n”, &lado, &nlados); perimetro = lado * nlados;
Observe que scanf interrompe a leitura de
um string (%s) quando encontra um branco
Especificadores de tamanho e filtro
%[aeiou]s lê apenas vogais
Para na primeira consoante, número, espaço, pontuação, etc
%[0123456789]s lê apenas números %60s lê apenas 60 caracteres
%60[^0123456789]s lê até 60 caracteres parando
quando encontrar um número
char buffer[80];
getchar lê um único caractere do terminal putchar(int c) imprime o caractere
Mesma coisa, só precisamos passar o
manipulador do arquivo como parâmetro FILE *entrada; FILE *saida; ... fscanf(entrada, “%79s”, buffer); char c = fgetc(entrada); fputc(c, saida); fprintf(saida, “X = %d”, x);
FILE * fopen(char *nome, char *modo)
Abre o arquivo com o dado nome
modo pode ser:
▪ “r” para leitura, “w” para escrita, “rw” para leitura e escrita ▪ Se o arquivo já existir, podemos usar “a” para adicionar ao arquivo
Sempre teste se o retorno é nulo, pois podem ocorrer erros
▪ Arquivo ou caminho não existente, permissões insuficientes, etc.
int fclose(FILE *arquivo)
Fecha o arquivo apontado por arquivo
FILE *arquivo = fopen(“C:\Users\Cunha\Desktop\teste.txt”, “w”); if(!arquivo) { perror(NULL); exit(EXIT_FAILURE); }
fprintf(arquivo, “hello arquivo!\n”); fclose(arquivo);
printf e fprintf são idênticas, só operam sobre
manipuladores de arquivos diferentes
printf sempre imprime na saída padrão (terminal)
fprintf recebe o arquivo onde imprimir como parâmetro O manipulador do arquivo correspondente à saída padrão é o
stdout, e o manipulador da entrada padrão é o stdin
Cuidado ao terminar de ler um arquivo
Use int feof(FILE *arquivo)para testar
se já leu o arquivo até o fim
▪ feof retorna falso se o arquivo ainda não tiver
terminado
▪ feof só retorna verdadeiro depois que você tentar ler
Saber a posição atual do arquivo
long ftell(FILE *arquivo)
Mudar para uma dada posição no arquivo
int fseek(FILE *arquivo,
int base, int distancia)
Onde base pode ser:
▪ SEEK_SET, o começo do arquivo
▪ SEEK_CUR, a posição atual no arquivo
write_test_file
int main(int argc, char *argv[]) { ... }
argv é um arranjo de strings, um parâmetro em
cada índice do arranjo
argc é o número de parâmetros em argv argv[0] é sempre o nome do executável
▪ Logo, argc >= 1
Para processamento avançado de
parâmetros, use getopt()
Parâmetros em qualquer ordem, opcionais, etc.
cmdline_params (CodeBlocks)
Pequeno esforço, grande impacto Código mais legível
Ajuda o entendimento das idéias
Útil quando você for ler o código 6 meses depois Útil para outras pessoas
Código com menos erros
Economizar tempo e ter menos dor de cabeça
Em AEDS2: ajuda entendimento das idéias e
Realça estrutura lógica do código
Em geral, indenta-se com tabulação (tab)
Em geral um tab corresponde a 8 espaços, mas é configurável na maioria dos editores
static char * concat (char *s1, char *s2) { while (x == y) { something (); somethingelse (); } finalthing (); }
static char * concat (char *s1, char *s2) { while (x == y) { something (); somethingelse (); } finalthing (); }
static char * concat(char *s1, char *s2) { while(x == y) { something(); something_else(); } final_thing(); } static char * concat(char *s1, char *s2) { while(x == y) { something(); something_else(); } final_thing(); } static char * concat(char *s1, char *s2) { while(x == y) { something(); something_else(); } final_thing(); } GNU K&R Allman
Facilitam a compreensão do código, mais
importantes para código complexo
Código bem escrito não depende muito de
comentários
Comentário errado é pior do que nenhum
comentário
No início de um módulo
Descrever variáveis globais ou importantes Em funções para explicar os parâmetros, o
tipo de retorno, e o que a função faz
Não explicar como a função faz a tarefa, código
deve ser claro o bastante
Indicar invariantes de loops
Não comentar o óbvio DUH:i += 1; // incrementa i. OK:
Escolher bons identificadores ajudam a
compreensão do código
void ordena(int *vetor); void processa(int *vetor); double media(int *vetor);
Mesma coisa para variáveis
▪ Variáveis auxiliares podem receber nomes simples, mas sem exagerar
▪ Indices: i, j, k
▪ Variáveis tipo ponto flutuante: x, y, z
Se houver, preferir o estilo que já estiver em uso Underscore:
int num_clientes;
struct list *lista_alunos;
CamelCase:
int numClientes;
Não usar “números mágicos” no código Valores podem precisar ser modificados “Números mágicos” não têm significado
Usar #define para dar nome a constantes
Nomes em maiúsculas
#define PI 3.14159
#define TAMANHO_MAX_LINHA 256 char * le_linha(FILE *entrada) {
char *linha = malloc(TAMANHO_MAX_LINHA); ...
return linha; }
Particionamento de um programa
Um módulo geralmente é um par de arquivos
modulo.c contém a implementação das funções modulo.h contém a declaração das funções e
tipos de dados; é importado por outros módulos
Outros programadores só precisam saber o
que o módulo faz, não como ele funciona
Procurar identificar módulos independentes