Mudanças entre as edições de "PRG29002 - Programação I - Eng.Telecom 2016-2"
Linha 2 110: | Linha 2 110: | ||
==Requisitos mínimos== | ==Requisitos mínimos== | ||
− | |||
*Realizar acesso a arquivo, lendo e escrevendo informações (escrever em arquivo de log e em arquivo de configuração); | *Realizar acesso a arquivo, lendo e escrevendo informações (escrever em arquivo de log e em arquivo de configuração); | ||
*Utilizar funções (ao menos duas além do main, sendo que devem receber argumentos e possuem retorno); | *Utilizar funções (ao menos duas além do main, sendo que devem receber argumentos e possuem retorno); | ||
Linha 2 116: | Linha 2 115: | ||
*Utilizar Structs ou Unions; | *Utilizar Structs ou Unions; | ||
*Utilizar alguma biblioteca (além da stdio.h); | *Utilizar alguma biblioteca (além da stdio.h); | ||
− | |||
*Utilizar diretivas de pré-compilação; | *Utilizar diretivas de pré-compilação; | ||
*Utilizar comentários; | *Utilizar comentários; |
Edição das 11h56min de 16 de dezembro de 2016
Professor da Disciplina: Cleber Jorge Amaral
e-mail: cleber.amaral@ifsc.edu.br
Monitoria: Programa_de_monitoria_dos_cursos_superiores_de_Telecomunicações
Ementa de PRG29002
- Ementa da disciplina na wiki: Engenharia de Telecomunicações 2ª Fase
Critérios e instrumentos de avaliação
- Conceitos numéricos entre 0 e 10. 0 é reservado para alunos com frequência insuficiente
- N1 = Prova teórica sobre pseudocodigo e fluxograma (sem apoio de computador)
- N2 = Prova prática sobre C
- N3 = Apresentação de projeto de desenvolvimento em C (Avaliação do projeto = 30% e da performance do aluno na apresentação = 70%)
- Recuperação realizada após cada prova e do trabalho uma reapresentação que valerá no máximo conceito 7.
- Média = (N1+2*N2+N3)/4
- Ter 75% de frequência.
Datas importantes
- 19/09 - Prova 1: Lógica de algoritmos em fluxograma e pseudocódigo
- 03/10 - Recuperação da prova 1
- 21/11 - Prova 2: Prática em linguagem C
- 05/12 - Recuperação da prova 2
- 12/12 e 16/12 - Apresentação do projeto final
- 19/12 - Possivel recuperação do projeto final
Eventos da área de desenvolvimento
- Outubro de 2016 - QCon Rio de Janeiro QConRio
- Outubro de 2016 - The Developers Conference Porto Alegre TDC
- Março de 2017 (previsto) - Arduino Day São Paulo ArduinoDay
- Abril de 2017 - Qcon São Paulo QConSP
- Maio de 2017 (previsto) - The Developers Conference Florianópolis TDC
- Junho de 2017 (previsto) - JavaOne São Paulo JavaOne
- Julho de 2017 (previsto)- The Developers Conference São Paulo TDC
Material de aula
Diário de aula
Introdução aos algoritmos utilizando fluxograma
Aula inaugural e introdução aos algoritmos |
---|
|
Algoritmos - fluxogramas |
---|
- - - - - - - - - - - - - - - - - - - - - - - Estado atual das chaves: R1 = Emprestada (João) R2 = Disponível CAD2 = Emprestada (Pedro) CAD3 = Disponível - - - - - - - - - - - - - - - - - - - - - - - 1: pegar/devolver uma chave 2: cadastro de chaves 3: cadastro de pessoas 4: ver histórico de empréstimos 5: sair do programa Digite a opção ______
Digite sua identificação: ___ Digite o nome da chave a pegar ou devolver: ____
As chaves atualmente cadastradas são: R1, R2, CAD2, CAD3 Digite um nome existente para deletar ou um novo nome para criar uma nova: ____
|
Fluxogramas |
---|
|
Pseudo-código
Pseudo-código utilizando Portugol - repetições |
---|
Exercícios - série 1
|
Pseudo-código utilizando Portugol - sub-rotinas e registros |
---|
Exercícios
Parte da implementação do problema das funções trigonométricas
|
Introdução ao C
Introdução ao C e funções de saída e entrada de dados |
---|
|
Controle de fluxo em C
Condicionais em C |
---|
|
Estruturas de repetição em C |
---|
|
Funções
Funções |
---|
|
A função main |
---|
O programa inicia pela primeira instrução contida na função main() e também se encerra na última instrução. O retorno padrão da função main é um int que representa um código de erros reconhecidos por muitos sistemas operacionais. Se o programa terminou sua execução corretamente o retorno deverá ser 0 (zero). int main(void)
{
//Programa
return 0;
}
|
A função exit |
---|
Uma alternativa a terminação do programa chegando ao fim da função main é a função exit da biblioteca <stdlib.h>. Para esta função deve-se passar um argumento inteiro que tem o mesmo significado do código de retorno da função main, portanto exit(0) representa uma terminação normal, alternativamente exit(EXIT_SUCCESS). Para representar uma terminação anormal pode-se utilizar exit(EXIT_FAILURE). |
Vetores e matrizes em C
Vetores e matrizes em C |
---|
char nome_da_string [tamanho];
|
Execícios de vetores em C |
---|
|
Gerando números pseudo-aleatórios |
---|
|
Tabela ASCII |
---|
|
Vetor de tamanho variável |
---|
O vetor de tamanho variável (variable lenght array) é um recurso do C que permite que o tamanho do vetor seja definido em tempo de execução. Na prática o C irá alocar uma quantidade de memória que não precisa estar definida antes da execução. variable-lenght |
Operadores e precedência
Operadores e precedência |
---|
Tipos de dados compostos
Estruturas |
---|
Assim como o vetor a estrutura é um conjunto de dados, mas traz uma vantagem: a possibilidade de possuir "campos" de diferentes tipos de variáveis. Por exemplo, a struct TPessoa poderia ter os campos nome (char[40]) e idade (int). A declaração genérica da estrutura é: struct TNome_do_tipo { //variável 1 //variável 2 //variável N } nome_instancia;
#include <stdio.h>
struct TUsuario /* struct TUsuario é o nome do tipo que está sendo criado */
{
char userID[20];
char senha[20];
} Usuario; /* aqui é definida uma variável do tipo struct TUsuario */
struct TUsuario TabelaUsuario[20];
main()
{
scanf("%s", Usuario.userID);
scanf("%s", Usuario.senha);
scanf("%s", TabelaUsuario[10].userID);
scanf("%s", TabelaUsuario[10].senha);
}
Neste exemplo, foi definido um tipo (modelo) para o registro (struct TUsuario) e foi criada uma variável chamada Usuario a partir deste tipo. Na sequência foi criada mais uma variável (um vetor de estruturas) chamada TabelaUsuario. Note que basta usar as palavras chave struct Usuario para criar novas variáveis. O tipo completo é definido uma única vez no início.
#include <stdio.h>
#define NUM_MAX 3
struct TAluno {
char nome[30];
char matricula[11];
float b1,b2,b3,b4;
} Turma[NUM_MAX];
void print_aluno(struct TAluno aux)
{
printf("Nome -> %s\n", aux.nome);
printf("Matrícula -> %s\n", aux.matricula);
printf("Bimestre 1 -> %f\n", aux.b1);
printf("Bimestre 2 -> %f\n", aux.b2);
printf("Bimestre 3 -> %f\n", aux.b3);
printf("Bimestre 4 -> %f\n", aux.b4);
}
main()
{
int i;
for(i=0;i<NUM_MAX;i++) {
printf("Entre com o nome do aluno\n");
scanf("%s", Turma[i].nome);
printf("Entre com a matrícula do aluno\n");
scanf("%s", Turma[i].matricula);
printf("Entre com a nota do bimestre 1\n");
scanf("%f", &Turma[i].b1);
printf("Entre com a nota do bimestre 2\n");
scanf("%f", &Turma[i].b2);
printf("Entre com a nota do bimestre 3\n");
scanf("%f", &Turma[i].b3);
printf("Entre com a nota do bimestre 4\n");
scanf("%f", &Turma[i].b4);
}
for(i=0;i<NUM_MAX;i++) {
printf("=========== Aluno %d ============\n", i);
print_aluno(Turma[i]);
}
}
O exemplo a seguir demonstra como se pode copiar uma variável struct para outra do mesmo tipo. #include <stdio.h>
struct THoras{
int hora;
int minuto;
int segundo;
};
struct THoras Ontem = {2,10,57};
void main()
{
struct THoras Hoje;
Hoje = Ontem;
printf("Hora hoje = %d, Minuto hoje = %d e Segundo hoje %d\n", Hoje.hora, Hoje.minuto, Hoje.segundo);
}
Vamos ver um exemplo com estruturas definidas dentro de estruturas: #include <stdio.h>
struct TEndereco{
char rua[50];
char numero[10];
};
struct TCidadao{
char nome[50];
char cpf[20];
struct TEndereco endereco;
int num_filhos;
};
void main()
{
struct TCidadao Cidadao;
printf("Entre com o nome\n");
scanf ("%s",Cidadao.nome);
printf("Entre com o cpf\n");
scanf ("%s",Cidadao.cpf);
printf("Entre a rua\n");
scanf ("%s",Cidadao.endereco.rua);
printf("Entre a numero\n");
scanf ("%s",Cidadao.endereco.numero);
printf("Entre com o número de filhos\n");
scanf ("%d",&Cidadao.num_filhos);
}
Como toda variável, é possível dar valores para uma variável do tipo struct definida no programa: #include <stdio.h>
struct TEndereco {
char rua[50];
int numero;
};
struct TCidadao{
char nome[50];
char cpf[20];
struct TEndereco endereco;
};
int main(void)
{
//Inicializando com parâmetros em sequencia (ordem tem que ser respeitada)
struct TCidadao CidadaoMaria = {"Maria","42342342234",{"Rua AlfaBeta",145}};
//Inicializando com parâmetros via campo (não é necessário respeitar qualquer ordem)
struct TCidadao CidadaoJose = {.cpf = "1234567890", .endereco.numero = 541,.nome = "Jose",.endereco.rua = "Rua GamaDelta"};
printf("Rua do cidadao %s = %s\n", CidadaoMaria.nome, CidadaoMaria.endereco.rua);
printf("Rua do cidadao %s = %s\n", CidadaoJose.nome, CidadaoJose.endereco.rua);
}
Se não for usado o operador "&" , um parâmetro que é estrutura será passado por cópia. Não apresentaremos agora a passagem por endereço pois necessita do conceita de ponteiro. Observe o exercício abaixo. #include <stdio.h>
struct TEndereco{
char rua[50];
char numero[10];
};
struct TCidadao{
char nome[50];
char cpf[20];
struct TEndereco endereco;
int num_filhos;
};
void print_struct (struct TCidadao aux)
{
printf("nome=%s cpf=%s\n", aux.nome, aux.cpf);
printf("endereço inicial do aux %p\n", &aux);
}
void main()
{
struct TCidadao Cidadao;
printf("Entre com o nome\n");
scanf ("%s",Cidadao.nome);
printf("Entre com o cpf\n");
scanf ("%s",Cidadao.cpf);
printf("Entre a rua\n");
scanf ("%s",Cidadao.endereco.rua);
printf("Entre a numero\n");
scanf ("%s",Cidadao.endereco.numero);
printf("Entre com o número de filhos\n");
scanf ("%d",&Cidadao.num_filhos);
print_struct(Cidadao);
printf("endereço inicial do Cidadao %p\n", &Cidadao);
}
|
Unions |
---|
Union é um recurso do C que permite declarar um conjunto de dados que irá ocupar um mesmo espaço. É bastante empregado quando se deseja economizar espaço ou não se tem certeza sobre qual tipo de dado deve ser armazenado para determinada instancia. No exemplo a seguir é criada uma struct chamada TProduto e dentro destra estrutura há uma área de detalhamento do produto que é de uso genérico, para alguns produtos há campos específicos para preenchimento e outros não se tem ao certo os detalhes, portanto fica um campo de uso geral.
#include <stdio.h>
struct TRoupeiro{
char cor[20];
int volume;
float peso;
};
struct TProduto{
int id;
char nome[20];
union {
struct TRoupeiro roupeiro;
char descricao_generica[sizeof(int)+sizeof(float)+20];
};
};
int main(void)
{
struct TProduto vaso_decorativo = {
.id = 2,.nome = "Vaso decorativo 1",
.descricao_generica = "em vidro - peça única"
};
struct TProduto guarda_roupas_solteiro = {
.id = 1,.nome = "Roupeiro 3 portas",
.roupeiro.cor = "CZ", .roupeiro.volume = 304,.roupeiro.peso = 50.0
};
printf("nome = %s, \ndescrição = %s, \ncor = %s, \nvolume = %d, \npeso = %f\n\n\n",
guarda_roupas_solteiro.nome,
guarda_roupas_solteiro.descricao_generica,
guarda_roupas_solteiro.roupeiro.cor,
guarda_roupas_solteiro.roupeiro.volume,
guarda_roupas_solteiro.roupeiro.peso
);
printf("nome = %s, \ndescrição = %s, \ncor = %s, \nvolume = %d, \npeso = %f\n\n\n",
vaso_decorativo.nome,
vaso_decorativo.descricao_generica,
vaso_decorativo.roupeiro.cor,
vaso_decorativo.roupeiro.volume,
vaso_decorativo.roupeiro.peso
);
}
|
Ponteiros
Ponteiros |
---|
A memória de um computador pode ser vista como um vetor de bytes. Neste espaço vimos a utilização de variáveis diversas que podem armazenar valores que podem ser obtidos do usuários, serem resultados de ariméticas e muitas outras operações. O ponteiro nada mais é que um tipo de dado igualmente armazenado em memória, porém este dado se refere a um endereço da memória, ou seja, a um outro objeto. Este recurso é muito útil para diversos propósitos, basta pensar na própria aplicação do conceito "endereço", imagine como seria localizar uma casa em uma cidade sem haver uma forma de endereçar e armazenar os endereços das casas. Explorando esta analogia, cada lote possui um endereço e pode ter um conteúdo de diferentes tipos como uma casa, um prédio ou um conjunto de lojas, enfim, trazendo para o C seria como os tipos int, char, vetores diversos, etc. Assim é a memória, cada byte possui um endereço. O tamanho da memória é definido pelo tamanho do barramento de endereços usado para acessá-la. Uma variável ocupa uma área da memória. Tipicamente uma variável to tipo char se utiliza de um byte. Já uma variável do tipo int pode (dependendo do sistema) usar 4 bytes contíguos.
#include <stdio.h>
int main(void)
{
int i = 10;
int *p;
long int li;
p = &i;
printf("Conteúdo de i: i = %d\n",i);
printf("Endereço de i: &i = %p\n",&i);
printf("Conteúdo de p: p = %p\n",p);
printf("Endereço de p: &p = %p\n",&p);
printf("Conteúdo apontado: *p = %d (conteúdo do endereço apontado por p)\n",*p);
printf("Tamanho do ponteiro = %li bytes\n",sizeof(p));
printf("Tamanho do lont int = %li bytes\n",sizeof(li));
printf("Tamanho do int = %li bytes\n",sizeof(i));
return 0;
}
Resposta obtida através do gcc em uma máquina Linux Ubuntu: Conteúdo de i: i = 10 Endereço de i: &i = 0x7ffeb25859e4 Conteúdo de p: p = 0x7ffeb25859e4 Endereço de p: &p = 0x7ffeb25859e8 Conteúdo apontado: *p = 10 (conteúdo do endereço apontado por p) Tamanho do ponteiro = 8 bytes Tamanho do lont int = 8 bytes Tamanho do int = 4 bytes
Conteúdo de i: i = 10 Endereço de i: &i = 0xbfadc2b8 Conteúdo de p: p = 0xbfadc2b8 Endereço de p: &p = 0xbfadc2bc Conteúdo apontado: *p = 10 (conteúdo do endereço apontado por p) Tamanho do ponteiro = 4 bytes Tamanho do lont int = 4 bytes Tamanho do int = 4 bytes
Observe o programa abaixo. A variável p é um ponteiro para inteiro. Isto significa que ela pode armazenar um endereço de um inteiro. #include <stdio.h>
main()
{
int x;
int *p;
x=5;
printf("Valor de x antes = %d\n", x);
p = &x;
*p=10;
printf("Valor de x depois = %d\n", x);
printf("Valor de p = %p\n", p);
}
Observe que para se referenciar o conteúdo da posição de memória apontada por p deve-se usar o asterisco: *p
main()
{
int x=10;
int y, *p;
}
Complete o código para copiar o conteúdo de x para y, sem que qualquer variável apareçam no lado esquerdo de um sinal de atribuição. Ou seja, sem envolver diretamente x e y.
main()
{
int x,y,w,*p1,*p2;
x = 20;
w = 30;
p1 = &x;
p2 = &w;
y = *p1 + *p2;
}
main()
{
int x,y,w,*p1,*p2, *p3;
x = 20;
w = 30;
p1 = &x;
p2 = &w;
y = *p1 + w;
p3 = &y;
*p3 = *p3 + 10;
y = *p1 + *p2 + *p3;
}
#include <stdio.h>
void main()
{
int x,y;
int *p;
y=0;
p=&y;
x=*p;
x=4;
(*p)++;
x--;
(*p) += x;
printf("\ny=%d x=%d\n",y,x);
}
Os ponteiro para char são muito utilizados pois permitem apontar para strings. A ideia é que ele aponte para o primeiro caracter (char) da string. Veja o exemplo abaixo. #include <stdio.h>
main()
{
char x[10]="ifsc";
char *p;
p = &x[2];
printf("x[2] = %c\n", *p);
p = x;
printf("string %s\n", p);
while (*p!=0) {
printf("Endereco %p conteúdo %c\n", p,*p);
p++;
}
}
Neste foi usado o incremento de um ponteiro, o que implica em adicionar ao endereço armazenado em p uma quantidade relativa ao tamanho do tipo apontado. No caso é 1 (tamanho de um char é um byte).
main()
{
char x[10]="ifsc";
char *p, y;
p = x + 2;
y= *p;
}
#include <stdio.h>
main()
{
int x[10]= {0,1,2,3,4,5,6,7,8,9};
int *p;
int i;
p = x;
i=0;
while (i<10) {
printf(" endereco %p e conteudo %d\n", p, *p);
p++;
i++;
}
}
OBSERVE que p++ incrementa em 4 unidades.
Ponteiros podem apontar para qualquer "objeto" de qualquer tipo. Vamos verificar como é possível apontar para uma estrutura: #include <stdio.h>
struct TRegistro {
char nome[20];
int idade;
} Tabela[4] = {
{"joao",18,},
{"maria",18,},
{"jose",19,},
{"lara",17,},
};
struct TRegistro *p;
main()
{
p = &Tabela[3]; /*p aponta para o registro 3 da tabela */
printf("O nome na posição 3 é %s e idade = %d\n", p->nome,p->idade);
}
NOTE que o uso de p->nome é uma alternativa ao uso de (*p).nome No primeiro caso pode-se ler: o campo nome do objeto que é apontado por p.
#include <stdio.h>
struct TRegistro {
char nome[20];
int idade;
} Tabela[4];
struct TRegistro *p;
main()
{
int i;
p = &Tabela[0]; /*p aponta para o registro 0 da tabela */
for (i=0;i<4;i++,p++)
{
printf("Digite o nome e idade da pessoa %d\n", i+1);
scanf("%s %d",p->nome,&(p->idade));
printf("\n\n>>>> O nome e idade da pessoa %d é: %s, %d \n\n\n", i+1, p->nome, p->idade);
}
}
No exemplo a abaixo a função RetornarStruct() retorna um ponteiro para uma estrutura. O cuidadado que se deve ter é que a função não deveria apontar para uma estrutura que foi criada localmente na função! #include <stdio.h>
struct TRegistro {
char nome[20];
int idade;
} Tabela[4] = {
{"joao",18,},
{"maria",18,},
{"jose",19,},
{"lara",17,},
};
struct TRegistro *p;
struct TRegistro * RetornarStruct(int indice)
{
return &Tabela[indice];
}
main()
{
p = RetornarStruct(2); /*p aponta para o registro 3 da tabela */
printf("O nome na posição 2 é %s e idade = %d\n", p->nome,p->idade);
}
#include <stdio.h>
struct TRegistro {
char nome[20];
int idade;
} Tabela[4] = {
{"joao",18,},
{"maria",18,},
{"jose",19,},
{"lara",17,},
};
struct TRegistro *p;
void MudarStruct(struct TRegistro *p1, int indice)
{
Tabela[indice] = *p1;
}
main()
{
struct TRegistro aux = {"luisa",16};
MudarStruct(&aux,2);
p = &Tabela[2];
printf("O nome na posição 2 é %s e idade = %d\n", p->nome,p->idade);
}
A função main() pode ter parâmetros formais, mas o programador não pode escolhores quais serão eles. A declaração que se pode ter para a função main() é: int main (int argc, char *argv[]); Exemplo: Escreva um programa que faça uso dos parâmentros argv e argc. O programa deverá receber da linha de comando o dia, mês e ano correntes (dd/mm/aaaa), e imprimir a data em formato apropriado. Veja o exemplo, supondo que o executável se chame data: $ data 04 11 2016 O programa deverá imprimir: $ 04 de novembro de 2016#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[])
{
int mes;
char *nomemes [] = {"janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"};
if(argc == 4) /* Testa se o numero de parametros fornecidos esta' (nome do programa, o dia, o mes e os dois ultimos algarismos do ano */
{
/* argv contem strings. A string referente ao mes deve ser
* transformada em um numero inteiro. A funcao atoi esta sendo
* usada para isto: recebe a string e transforma no inteiro equivalente
*/
mes = atoi(argv[2]);
if (mes<1 || mes>12) /* Testa se o mes e' valido */
printf("Erro!\nUso mes: mm, deve ser de 1 a 12.\n");
else
printf("\n%s de %s de %s\n\n", argv[1], nomemes[mes-1],argv[3]);
}
else
printf("Erro!\nUso: dd/mm/aaaa, devem ser inteiros, ou estão faltando.\n");
}
|
Usando ponteiros como parâmetros de entrada saída de funções |
---|
Enviar um ponteiro a uma função tem diversas aplicações, uma delas é a de evitar redundância de dados e realizar a leitura de informações "diretas da fonte". Estas informações quando são de grande volume também poderiam requisitar um grande volume de memória para copiar, então mais um motivo para se passar a referência (ponteiro). Observe como podemos usar ponteiros na passagem de parâmetros: #include <stdio.h>
void str_cpy(char *pdest, char *pfonte)
{
while (*pfonte!=0) {
*pdest++ = *pfonte++;
}
*pdest = 0;
}
int str_len (char *p)
{
int i=0;
while (*p++!=0)
i++;
return i;
}
main()
{
char fonte[10]="ifsc";
char destino[10];
str_cpy(destino, fonte);
printf("string destino = %s\n", destino);
printf("tamanho de dest = %d\n", str_len(destino));
}
#include <stdio.h>
void alfa(int *p)
{
*p=10;
}
main()
{
int x;
x =5;
printf("Valor de x antes da chamada de alfa = %d\n", x);
alfa(&x);
printf("Valor de x depois da chamada de alfa = %d\n", x);
}
|
Alocação dinâmica de memória
Alocação dinâmica de memória |
---|
As estruturas de dados em C são normalmente de tamanho fixo. Mesmo num vetor de tamanho variável, seu tamanho apesar de ser determinado em tempo de execução, seu tamanho se mantém fixo até que seja destruído. Nos exemplos que trabalhamos a maioria tinha número de registros fixo, esta situação é bastante restringente. Em um programa de registros de notas de alunos, por exemplo, se determinado um número de registros pequeno pode-se deparar com a necessidade de aumentar este vetor, havendo necessidade de recompilação. Se por outro lado for alocado um grande espaço de memória, pode-se estar desperdiçando bastante espaço caso fiquem obsoletos. Para resolver este problema há funções de alocação dinâmica de memória, que permitem que seja alocada e desalocada memória conforme crescimento ou redução da necessidade de espaço. As funções mais conhecidas são (http://en.wikipedia.org/wiki/C_dynamic_memory_allocation):
#include <stdlib.h>
main()
{
int *px, *py;
int resultado;
px = (int *) malloc(sizeof(int));
*px = 5;
py = (int *) malloc(sizeof(int));
*py = 2;
resultado = *px + *py;
free (px);
px = NULL;
free (py);
py = NULL;
}
Observe que há duas chamadas de alocação utilizando "malloc", onde são alocados a quantidade de bytes que uma variável "int" ocupa (normalmente 4 bytes). Conforme consta na documentação lincada acima, o malloc retorna um void*, que é um ponteiro genérico. Para que este ponteiro seja tratado como um ponteiro de int (que é nossa necessidade neste código) é realizado um typedef de (int*). Após as alocações são escritos e lidos valores destes espaços de memória através do apontamento de "px" e "py". Por fim, é chamada a função "free" que recebe como argumento um ponteiro e libera o bloco de memória apontado por e este ponteiro. A função free deve ser chamada sempre que a memória alocada (por malloc, calloc ou realloc) não for mais necessária. A não desalocação correta de memória pode causar "vazamentos de memória" (ou memory leak). Uma chamada de free em um espaço de memória já desalocado poderá também causar falha, trata-se de um comportamento não previsível, possivelmente uma falha de segmentação. Após desalocar o bloco de memória apontado por um ponteiro, setar este ponteiro para NULL é uma boa prática. Esta ação evita que acidentalmente será tentado utilizar ou desalocar um espaço de memória já livre o que poderia causar falhas graves. Esta prática de "anular" o ponteiro transforma este em um "ponteiro nulo" (ou NULL pointer), sendo um tipo especial que não aponta para lugar nenhum (nulo não é um endereço de memória válido), isso significa que uma desalocação em um ponteiro nulo não deverá ter nenhum efeito. As funções malloc e calloc são muito parecidas, o efeito prático é que o calloc além de alocar a memória também "zera" este espaço. O ato de "zerar" a memória faz do calloc uma função menos performática, isso ocorre por duas razões: o simples processo de escrever "zeros" é dispendioso e também o ato de escrever algo na memória obriga o sistema operacional a "tocar" na memória e realizar possíveis ações de swap de outros processos. No caso do malloc estas ações só ocorrerão quando houver real necessidade de escrever naquele determinado espaço de memória.
Diferente da função malloc, calloc além de inicializar os espaços de memória ainda atribui o valor 0 (zero) para cada um deles.
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *valores, *aux;
int qtd, i, j, vlr;
printf("\nEntre com a quantidade de números: ");
scanf("%d", &qtd);
if(qtd == 0) exit(0);
for (j=0;j<2;j++)
{
//Aloca um vetor com a quantidade recebida em qtd
valores = (int *) calloc(qtd, sizeof (int));
printf("Rodada %d:\n", j);
//Imprime o endereco e valor (zero) para a quantidade de números informada
aux = valores;
for(i = 1; i <= qtd; i++){
printf("Situação inicial da memória: %p\t%d\n", aux, *aux);
aux++;
}
aux = valores; //Ponteiro para o primeiro espaço de memória
for(i = 1; i <= qtd; i++){
printf("Digite o número %d ->: ", i);
scanf("%d", &vlr);
*aux = vlr;
aux++;
}
aux = valores; //Retorna à posição inicial no mapa de memória
for(i = 1; i <= qtd; i++){
printf("Situação final da memória : %p\t%d\n", aux, *aux);
aux++;
}
printf("\n");
free(valores);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *valores, *aux;
int qtd, i, j, vlr;
printf("\nEntre com a quantidade de números: ");
scanf("%d", &qtd);
if(qtd == 0) exit(0);
for (j=0;j<2;j++)
{
//Aloca um vetor com a quantidade recebida em qtd
valores = (int *) malloc(qtd * sizeof (int));
printf("Rodada %d:\n", j);
//Imprime o endereco e valor (zero) para a quantidade de números informada
aux = valores;
for(i = 1; i <= qtd; i++){
printf("Situação inicial da memória: %p\t%d\n", aux, *aux);
aux++;
}
aux = valores; //Ponteiro para o primeiro espaço de memória
for(i = 1; i <= qtd; i++){
printf("Digite o número %d ->: ", i);
scanf("%d", &vlr);
*aux = vlr;
aux++;
}
aux = valores; //Retorna à posição inicial no mapa de memória
for(i = 1; i <= qtd; i++){
printf("Situação final da memória : %p\t%d\n", aux, *aux);
aux++;
}
printf("\n");
free(valores);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#define QT_INSTANCIAS 100000
void main()
{
struct TTeste{
long int x;
long int y;
char st[50];
};
struct TTeste *teste;
teste = (struct TTeste *) malloc (QT_INSTANCIAS*sizeof(struct TTeste));
//Boa pratica: se o retorno do malloc é NULL houve erro de alocação
if (teste!=NULL) {
printf("%li MBytes de memória alocados com sucesso!\n",QT_INSTANCIAS*sizeof(struct TTeste)/(1024*1024));
printf("Digite qualquer tecla para prosseguir: ");
getchar();
} else {
printf("Erro ao tentar alocar %li MBytes de memória!\n",QT_INSTANCIAS*sizeof(struct TTeste)/(1024*1024));
exit(1);
}
long int i;
struct TTeste *aux;
aux = teste;
for (i=0;i<QT_INSTANCIAS;i++,aux++)
{
aux->x=i+1;
aux->y=aux->x*2;
sprintf(aux->st,"%li",i*1000000);
printf("%li: teste.x=%li, teste.y=%li e teste.st=%s\n",i,aux->x,aux->y,aux->st);
}
aux=NULL;
free(teste);
teste=NULL;
}
Observe agora o uso da função "realloc". Como argumentos além do tamanho em bytes (idem ao malloc), também é necessário setar o ponteiro que será redimensionado. Este tamanho poderá ser maior ou menor ao tamanho anteriormente alocado, fazendo com que seja liberada memória ou alocado um bloco ainda maior. O retorno da função é o próprio ponteiro do bloco realocado. Se o retorno for nulo, a operação de alocação não foi bem sucedida. #include <stdio.h>
#include <stdlib.h>
void main()
{
struct TTeste{
int x;
int y;
} *teste;
if ((teste = (struct TTeste *) malloc (100*sizeof(struct TTeste)))==NULL) {
printf("erro de alocação");
exit(1);
}
teste[10].x= 5;
if ((teste = realloc(teste, 10000*sizeof(struct TTeste)))==NULL) {
printf("erro de alocação");
exit(1);
}
teste[9000].x=20;
free(teste);
}
|
Diretivas de pré-compilação
Definição de diretiva de pré-compilação |
---|
Uma diretiva de pré-compilação é um código processado pelo pré-compilador que "prepara", então, o código que será efetivamente compilado. Nesta execução pode haver definições, mudanças de comportamento do compilador e mesmo blocos lógicos que decidirão o que será ou não compilado. As diretivas de pré-compilação são úteis para configurações diversas que o compilador precisa conhecer para poder gerra o código objeto como também são úteis para criar "macros" diversas que podem tornar processos mais simples ou tornar o código mais inteligível. |
#include |
---|
O pré-processador busca o conteúdo de um outro arquivo e traz este código para que o compilador possa realizar a compilação e posterior link. #include <stdio.h>
int main(void) {
int i = 20;
printf("i = %d\n",i);
return 0;
}
No exemplo acima a função "printf" está definida na biblioteca "stdio", portanto, é necessário incluir este código para que o compilador passe a "conhecer" o que é a função printf e possa então chamar este código quando esta função é evocada. Quando a biblioteca é incluída com sinais de maior e menor (< e >) envolvando o nome do arquivo significa que o pré-compilador deve buscar esta biblioteca nos diretórios listados como endereços de biblioteca do compilador. Portanto precisa estar definido nos arquivos de configuração do compilador. O diretório onde se encontra a libc (biblioteca padrão do C) já vem por padrão listada nestes endereços. Quando a biblioteca é incuída entre aspas duplas (" ") envolvendo o nome do arquivo significa que o pré-compilador deve procurar em um endereço específico. Se o caminho não está todo definido será o endereço relativo, havendo apenas o nome do arquivo da biblioteca será buscado no diretório em que se encontra o código c que está sendo compilado. |
#define |
---|
A diretiva "define" cria uma macro que é uma substituição de um valor por um código ou lista de códigos de programa. Há algumas vantagens de se utilizar macros:
#include <stdio.h>
#define qtnotas 5
int main(void)
{
float notas[qtnotas], soma=0;
int i;
for(i = 0;i < qtnotas;i++)
{
printf("Digite a nota = %d\n",i+1);
scanf("%f",¬as[i]);
soma += notas[i];
}
printf("A média é: %.1f\n",soma/qtnotas);
}
Observe acima como o uso da macro qtnotas traz benefícios como os listados anteriormente. |
Acessando arquivos em C
Introdução |
---|
Arquivos em C são tratados como "streams" que literalmente significa córrego (riacho), em computação significa dados em fluxo. Utilizado para tratar quantidade desconhecida de informações (potencialmente infinita). Por esta característica arquivos são acessados através de ponteiros do tipo FILE * (um tipo de dados declarado na biblioteca <stdio.h>). Ainda na stdio, há três streams padrão (stdin, stdout e stderror) que não precisam ser declarados e estão prontos para uso. Funcões como scanf e getchar, por exemplo, na prática estão obtendo dados do teclado (stdin) e funções como printf e putchar estão escrevendo da tela (stdout). Eventuais falhas serão também enviadas para a tela (stderr). A quantidade de streams que um programa mantém aberto pode ser limitado pelo sistema operacional. A biblioteca stdio.h suporta dois tipos de arquivos: binários ou texto. Os arquivos tipo texto são compostos por caracteres humanamente compreensíveis. Já os arquivos binários são codificados. Arquivos binários podem armazenar mais informações em menor espaço e são normalmente mais fáceis de serem lidos por programas tornando-os também mais performáticos. |
Operações de abertura e fechamento de arquivos |
---|
A primeira ação será a de abertura de uma arquivo. É realizado com a função fopen que deve receber como parâmetros o nome do arquivo a ser aberto e o modo de abertura que especifica se será para leitura ou escrita. A diretiva restrict que aparece em ambos os argumentos não é muito relevante para o momento, basicamente está dizendo que estes espaços de memória não podem ser compartilhados (C99). FILE *fopen(const char * restrict filename, const char * restrict mode)
FILE * arquivo;
arquivo = fopen("IFSC.txt", "r");
Onde "r" significa somente leitura
arquivo = fopen("c:\\temp\\temp.txt", "w");
Onde "w" significa que o arquivo está sendo aberto para escrita Observe também o uso de "\\" e não apenas "\". Isto de deve pois algo como "C:\temp\temp.txt" teria então um "\t" que significa TAB. O uso de "\\" previne esta má interpretação. Uma outra alternativa é escrever "C:/temp/temp.txt", ou seja, com as barras invertidas. O operação fopen deve retornar um endereço válido se for bem sucedida, se falhar retornará NULL. Razões para falhas estão listadas aqui.
O caractere '+' representa o modo atualização (update mode). Para arquivos binários devem ter a letra "b" associada ("rb", "wb", "ab", "r+b"/"rb+", "w+b"/"wb+", "a+b"/"ab+"). No sistema operacional Linux a especificação de abertura de arquivo como binário não trará mudança pois é utilizado apenas um caractere para representar nova linha (\n). Porém, para manter portabilidade com o Windows que utiliza dois caracteres de nova linha '\r\n' é importante utilizar o modo 'b' para arquivos binários.
Permite o programa fechar um arquivo que não está mais sendo utilizado. Deve receber como parâmetro o ponteiro (FILE *) para o arquivo. Se fechar com sucesso retornará zero, caso ocorre erro retornará EOF (uma macro definida na stdio.h. int fclose(FILE *stream);
fclose(arquivo);
|
Operações de escrita de arquivos |
---|
Escreve uma saída em um stream apontado. A string apontada no segundo argumento se parece as utilizadas na função "printf". int fprintf(FILE * restrict stream, const char * restrict format, ...);
int fputc(int c, FILE *stream); //Escreve um caracter
int fputs(const char * restrict s, FILE restrict *stream); //Escreve uma string
#include <stdio.h>
int main(void)
{
FILE *p_arq;
int i;
if ((p_arq=fopen("IFSC.txt", "w")) == NULL) {
printf("Problemas na abertura do arquivo\n");
return 0;
} else {
printf("Arquivo aberto com sucesso. Iniciando escrita no arquivo...\n");
}
for (i = 0; i<10;i++) {
/* A funcao fprintf devolve o número de bytes gravados ou EOF se houve erro na gravação */
if((fprintf(p_arq,"Linha %d\n",i))==EOF) {
printf("Erro ao escrever no arquivo!\n");
return -1;
}
}
printf("Fim da escrita, observe o arquivo 'IFSC.txt' criado na mesma pasta deste executável!\n");
fclose(p_arq);
return 0;
}
Note que se o arquivo IFSC.txt não existir, ele será criado.
|
Operações de leitura de arquivos |
---|
Lê informações de um stream apontado em um formato definido. int fscanf(FILE *stream, const char *format, ...);
int fgetc(FILE *stream); //Lê um caracter e avança o cursor
char *fgets(char *str, int n, FILE *stream); //Lê um conjunto de caracteres limitado a n de tamanho, nova linha ou fim do arquivo
#include <stdio.h>
int main()
{
FILE *p_arq;
int i,j;
char buff[100];
if ((p_arq=fopen("IFSC.txt", "r")) == NULL) {
printf("Problemas na abertura do arquivo, o arquivo existe?\n");
return 0;
} else {
printf("Arquivo aberto com sucesso. Inicio da leitura do arquivo...\n");
}
while(1) {
if((fscanf(p_arq,"%s %d",buff,&j))==EOF) {
printf("Fim de leitura\n");
break;
}
printf("%s %d\n",buff,j);
}
fclose(p_arq);
return 0;
}
Note que o fscanf se comporta de forma similar ao scanf. A função retorna o caractere EOF (end-of-file) quando não existe mais dados a serem lidos.
|
Adicionando uma linha no final de um arquivo |
---|
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t ltime;
FILE *fp;
if ((fp=fopen("leituras.log", "a")) == NULL) {
printf("Problemas na abertura do arquivo\n");
return;
}
time(<ime);
if ((fputs(ctime(<ime), fp)) != EOF ) {
fclose(fp);
} else {
printf("Erro na escrita do arquivo!\n");
}
}
Tipicamente, quando se abre uma arquivo para leitura/escrita, o "cursor" de acesso fica na posição 0 (início do arquivo). Se o arquivo for aberto em modo append ('a'), o "cursor" é posicionado no final. A cada acesso (leitura ou escrita), este cursor é incrementado conforme o número de dados lidos ou escritos. Execute este código algumas vezes e vá observando o que ocorre com o arquivo 'leituras.log'.
#include <stdio.h>
#include <time.h>
#include <string.h>
int main(void)
{
time_t ltime;
FILE *fp;
struct tm *info;
char buffer[100],b2[100];
if ((fp=fopen("tst.log", "a+")) == NULL) {
printf("Problemas na abertura do arquivo\n");
return;
}
time(<ime);
info = localtime( <ime );
strftime(buffer,sizeof(buffer),"%x-%I:%M%p", info);
printf("Formatted date & time : |%s|\n", buffer );
strcat(buffer," Joao\n");
if ((fputs(buffer, fp)) == EOF ) {
printf("Erro na escrita do arquivo!\n");
}
fseek(fp,0,SEEK_SET);
while(1) {
if((fscanf(fp,"%s %s",buffer,b2))==EOF) {
printf("Fim de leitura\n");
break;
}
printf("Marcação lida: %s %s\n",buffer,b2);
}
fclose(fp);
return 0;
}
|
Alterando o cursor com fseek |
---|
Observe este exemplo que escreve em um arquivo e depois lê o conteúdo escrito. Neste caso é utilizado fseek para voltar o cursor para o início do arquivo. #include <stdio.h>
int main(int argc, char *argv[])
{
FILE *p_arq;
int i,j;
char buff[100];
if ((p_arq=fopen("IFSC.txt", "w+")) == NULL) {
printf("Problemas na abertura do arquivo\n");
return 0;
} else {
printf("Aberto com sucesso. Iniciando escrita no arquivo...\n");
}
for (i = 0; i<10;i++) {
if((fprintf(p_arq,"LINHA %d\n",i))==EOF) {
printf("Erro ao escrever no arquivo!\n");
return -1;
}
}
printf("Escrito! 'IFSC.txt' criado na pasta deste executável!\n");
/*Muda a posição do cursor para o início do arquivo*/
fseek(p_arq,0,SEEK_SET);
while(1) {
if((fscanf(p_arq,"%s %d",buff,&j))==EOF) {
printf("Fim de leitura\n");
break;
}
printf("%s %d\n",buff,j);
}
fclose(p_arq);
return 0;
}
|
Tratando erros de abertura de arquivo |
---|
Quando não é possível abrir um arquivo a função retorna NULL, porém para se saber o real motivo da falha é necessário consultar o "errno" que fica setado na biblioteca <errno.h>. Há diversos códigos de erro para váriadas funções, no caso da fopen, consulte aqui os erros possíveis. #include <stdio.h>
#include <errno.h>
int main()
{
FILE *fp;
printf("Para simular o erro certifique-se de não haver um arquivo 'testeErro.txt' na pasta\n");
if ((fp = fopen("testeErro.txt","r")) == NULL) {
if (errno == ENOENT)
printf("Código de erro %d, arquivo não existe!\n",errno);
else
printf("Erro não previsto (código: %d)!\n",errno);
return 0;
}
fclose(fp);
return 0;
}
|
Utilizando typedef
Typedef introdução |
---|
O compilador possui uma lista de tipos definidos como 'char', 'int', 'float' e outros. O C permite ainda que outros tipos sejam adicionados por código através da diretiva 'typedef'. A vantagem é principalmente tornar o programa mias inteligível, utilizando-se nomes intuitivos. Um exemplo, em um programa que processa cálculos financeiros poderia-se criar um tipo chamado para representar valores monetários, o tipo "Real", por exemplo. O comando seria conforme segue: typedef float Real; Então, declarar variáveis como salário, comissão e outras sendo do tipo "Real" tornaria o programa mais fácil de compreender. Real salario, comissao; //Seria equivalente a declarar "float salario, comissao", porem com 'Real' fica mais inteligível Outra vantagem de uso do typedef é a portabilidade entre plataformas. Imaginando um programa que roda em uma máquina 64 bits e é portado para uma 32 bits, ou 16 bits. Neste exemplo, a tratativa de números 64 bits é bastante diferente entre plataformas. Imaginando um campo 'quantidade' que pode armazenar números inteiros que podem variar até 2 bilhões (positivo ou negativo). Em uma máquina em que o inteiro ocupa 32 bits este variação é possível em um 'int'. Porém em uma plataforma cujo o inteiro ocupa 16 bits (short) já não será possível tal variação. Uma possível solução: typedef int quantidade; //Plataformas 32 bits ou superior Ou: typedef long quantidade; //Plataformas 16 bits |
Utilizando typedef em estruturas |
---|
#include <stdio.h>
#include <stdlib.h>
void main()
{
typedef struct {
int x;
int y;
} TTeste;
TTeste *teste;
teste = (TTeste *) malloc (sizeof(TTeste));
if (teste==NULL) {
printf("erro de alocação");
exit(1);
}
teste->x=10;
free(teste);
teste=NULL;
}
|
Recursividade
Introdução a Recursividade |
---|
A recursividade ocorre quando uma função chama a si própria. O retorno da função é sempre dado a função que a chamou, então se a função chama a si própria ela mesma recebe o retorno de uma segunda chamada, a segunda chamada receberá o retorno se esta se chamar uma terceira vez e assim por diante. Esta característica é possível pois o C quando inicia a execução de uma função, salva o contexto da anterior em uma pilha, esta função anterior oportunamente receberá o retorno de uma função que chamou. É importante que a função quando projetada para trabalhar recursivamente possui um teste seguro para que não se chame infinitamente. Há ainda dois modos de operação, simples ou múltiplo. Na recursividade simples a função se chama apenas uma vezes dentro de uma execução. Na recursividade múltipla em um ciclo de execução a função se chama mais de uma vez (na mesma recursão). O modo simples tende a ser mais eficiente, o tempo e espaço tendem a aumentar linearmente conforme a quantidade de recursões. No modo múltiplo o tempo de execução e o espaço de memória que a função precisa aumentam exponencialmente. Fontes:
|
Exemplo de recursividade simples |
---|
O fatorial de um número natural n, representado por n!, é o produto de todos os inteiros positivos menores ou iguais a n fonte. <stdio.h>
int main()
{
int in, out = 0;
printf("Entre com o valor de entrada da fatorial: ");
scanf("%d",&in);
out = fact(in);
printf("Resultado: %d\n",out);
return 0;
}
int fact(int n)
{
if (n <= 1)
{
printf("Finalmente n = 1, a função para se se chamar e retorna o resultado final.\n");
return 1;
}
else
{
printf("Nesta chamada n = %d, esta função se chamará recursivamente se for maior que 1.\n",n);
return n * fact(n-1);
}
}
No exemplo acima, se entrarmos com o valor 3 (para realizar o fatorial de 3), a seguinte sequencia será processada: fact(3) Como 3 não é menor ou igual a 1, realiza chamada de si mesma porém com n-1 que será 2 fact(2) Como 2 não é menor ou igual a 1, realiza chamada de si mesma com n-1 que será 1 fact(1) Finalmente 1 é menor ou igual a 1, então o retorno será 1, este retorno será entregue a chamada anterior, quando era fact(2) fact(2) retornará n * 1 (retorno recebido), resultado 2 fact(3) retornará n * 2 (2 foi o retorno que ela recebeu), resultando 6 Este modo é chamado de recursividade simples pois a função fact() é chamada apenas uma vez em uma recursão.
|
Exemplo de recursividade múltipla |
---|
Na matemática os números de Fibonacci são definidos pela equação F(n) = F(n-1) + F(n-2) onde F(0) = 0 e F(1) = 1 fonte. #include <stdio.h>
int main()
{
int in, out = 0, i;
printf("Entre com o sequencial que deseja saber o fibonacci: ");
scanf("%d",&in);
out = fib(in);
printf("Resultado: %d\n",out);
printf("A sequencia fica assim:\n");
for (i=1;i<=in;i++) printf("%d ",fib(i));
printf("\n");
return 0;
}
int fib(int n)
{
if (n-1 <= 0)
return 0;
else if (n-1 == 1)
return 1;
else {
return fib(n-1) + fib(n-2);
}
}
|
Referências
Referências bibliográficas
- Araújo, Everton Coimbra de. Algoritmos: fundamento e prática; 3ª ed. [S.l]:Visual Books, 2007. 414p. ISBN 9788575022092.
- KERNIGHAN, Brian W.; RITCHIE, Dennis M C: a linguagem de programação padrão ANSI; 1ª ed.[S.l]:Campus, 1989. 304p. ISBN 9788570015860.
- SCHILDT, Herbert C Completo e Total; 3ª ed. [S.l]:Makron Books, 2009. 827p. ISBN 9788534605953.
- FORBELLONE, Andre L. Lógica de Programação; 3ª ed. [S.l]:Makron Books, 2005. 197p. ISBN 9788576050247.
- KING, K.N. C Programming: A Modern Approach; 2ª ed. [S.l]:W. W. Norton & Company, 2008. 832p. ISBN 9780393979503.
- MANZANO, Jose Augusto Navarro Garcia Estudo Dirigido em Linguagem C. ; 16ª ed. [S.l]:Erica, 2012. 216p. ISBN 9788571948877.
- NEVES, Júlio Cézar Programação Shell Linux; 5a ed. Rio de Janeiro:Brasport, 2005. 408p. ISBN 8574522031.
- VEIGA, Roberto G. A. Comandos do Linux: guia de consulta rápida; ed. São Paulo:Novatec, 2004. 144p. ISBN 85-7522-060-8.
Referências adicionais
Ferramentas úteis
- VisualG3: Uma IDE para desenvolvimento de programas em pseudocódigo (freeware), permite editar e compilar programas utilizando uma sintaxe própria de pseudocódigo muito parecida com a que trabalhamos em sala. Muito útil para verificar o funcionamento real dos algoritmos. Ver exemplos de códigos visualG3 em Exemplos VisualG3
- LibreOffice: O LibreOffice é um programa gratuito (freeware) e de código aberto (opensource). Além de editor de textos, planilhas e apresentações tem a ferramenta Draw que permite a criação de fluxogramas.
- VirtualBox: O Oracle VirtualBox é um programa gratuito (freeware) que permite criar e instanciar máquinas virtuais. O uso de máquinas virtuais é bastante interessante quando desejamos ter diferentes sistemas operacionais em um computador bem como quando se está realizando ensaios e deseja-se isolar estes experimentos do sistema principal.
- Debian: O Debian, é umas das distribuições Linux mais estáveis existentes, suportando atualmente 12 arquiteturas de processador. É software livre e de código aberto e mantido por uma ampla comunidade com mais de 18000 desenvolvedores. Sua versão atual é a 8.5 (codinome Jessie, do filme Toy Story).
- Ubuntu: O Ubuntu é uma distribuição linux (freeware e opensource) bastante estável e com uma comunidade bastante ativa que está sempre atualizando o sistema e presente nos foruns e redes sociais para dirimir dúvidas.
- LinuxMint: O LinuxMint é uma distribuição linux (freeware e opensource) bastante estável e confortável aos usuários windows, pois traz um gerenciador de janelas configurado de uma forma mais natural para estes usuários e vem com um conjunto de programas pré-instalados que consegue atender a maior parte das demandas inicias.
- dbDesigner4: O dbDesigner é uma ferramenta gratuita para elaboração de diagramas de bancos de dados relacionais. Não trabalhamos com bancos na disciplina PRG29002, porém trabalhamos com dados, esta ferramenta é útil para organizá-los em diagramas.
Projeto final
O aluno deve propor ao professor um projeto de sua preferência que respeite os requisitos mínimos. Sendo aceito deverá desenvolver o projeto e apresentá-lo.
Requisitos mínimos
- Realizar acesso a arquivo, lendo e escrevendo informações (escrever em arquivo de log e em arquivo de configuração);
- Utilizar funções (ao menos duas além do main, sendo que devem receber argumentos e possuem retorno);
- Apresentar menu utilizando switch case e conter laço infinito;
- Utilizar Structs ou Unions;
- Utilizar alguma biblioteca (além da stdio.h);
- Utilizar diretivas de pré-compilação;
- Utilizar comentários;
- Utilizar Ponteiros;
- Utilizar alocação dinâmica de memória.
- Aceitar argumento de entrada no programa;
Modelo
- Trabalho individual
Metodologia
- Apresentar a proposta de projeto ao professor
- Opcional: Documentar o escopo do projeto utilizando descrição narrativa (descrição simples)
- Cenário
- Problema
- Dados de entrada e saída
- O planejamento do cronograma não será cobrado porém cabe ao aluno se organizar quanto ao tempo para entrega no prazo
- Desenvolver o projeto
- Apresentar individualmente ao professor
- Serão realizados testes diversos, arguido sobre o funcionamento, possibilidades de alterações, etc
Algumas ideias de projetos
- Sugestão geral: veja em outras disciplinas que processos podem ser automatizados e proponha um projeto que realiza esta tarefa como de cálculos diversos de eletrônica, de rádio transmissão, etc.
- Implementar o jogo Pedra, papel ou tesoura. Neste jogo dois ou mais jogadores em diferentes computadores devem rodar um aplicativo que fará a leitura de um arquivo compartilhado. O algoritmo deve tratar as etapas do jogo (Setup do aplicativo, entrada na sala, escolha da figura e apresentação do resultado)
- Implementar o jogo da velha escrevendo em arquivo. Neste jogo dois jogadores em diferentes computadores devem rodar um aplicativo que fará a leitura de um arquivo compartilhado. O algoritmo deve tratar as etapas do jogo (Setup do aplicativo, entrada na sala, seleção das casas e apresentação do resultado)
- Implementar controle de empréstimo de objetos. Neste software o usuário poderá digitar nomes de objetos que emprestou, a pessoa a quem emprestou e automaticamente o software guarda a data. Deve haver uma opção para gerar relatório dos itens emprestados e opção para marcar a devolução (podendo manter o registro em histórico ou apagando o registro).
- Implementar software gerador de lista de compras. Neste software o usuário poderá digitar itens de supermercado com nome e quantidade. O software escreve num arquivo que poderá depois ser impresso. O software também pode ter função de numa segunda execução já trazer a antiga listagem digitada e permitir que o usuário apenas selecione novas quantidades ou inclua novos itens.
- Implementar software para realização de cálculos de eletrônica. Neste software um menu apresenta várias opções de cálculo como de potencia através de tensão e corrente, como obtenção do valor de um resistor, como solução de equivalência de paralelo de vários resistores e outras. Num arquivo texto pode ser armazenado um histórico de operações realizadas.
- Implementação de software para apostas na mega sena. Neste software são dadas sugestões de números para apostas de acordo com o número do sorteio da mega sena. Com este histórico armazenado é possível então entrar com um número de sorteio e digitar quais foram os números verdadeiramente sorteados na loteria federal checagem os acertos.
Projetos dos alunos
- Alexandre: Plano de vôos
- Ameliza: Forca
- André: Calculos de Eletronica
- Fabio: Controle de biblioteca
- João: Controle de estoque de supermercado
- Luiza: Controle Financeiro Pessoal
- Marcone: Emprestimo de objetos
- Gabriel Santos: Jokempô
- Gabriel Martins: Jogo de corrida
- Guilherme Vieira: Controle de estoque
- Guilherme Januário: controle de comanda de eventos
- Gustavo Prim: Lista de compras
- Vinicius: Mega sena
- Yan: Controle de estacionamento
Avaliações
Conceitos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
Plano de aula
Aula | Data | Horas | Conteúdo | Recursos | |
---|---|---|---|---|---|
1 | 12/8 | 2 | Aula inaugural, apresentação do professor e turma, apresentação da disciplina e introdução aos algoritmos | ||
2 | 15/8 | 2 | Prática: Introdução ao fluxograma | ||
3 | 19/8 | 2 | Algoritmos continuação (representação por pseudo-codigo, apresentacao o portugol) | ||
4 | 22/8 | 2 | Prática: Resolução de problemas em pseudocódigo e fluxogramas (declaração de variáveis, leia e escreva, uso de condicionais e biblioteca portugol) | ||
5 | 26/8 | 2 | Pseudocódigo: Estruturas de repetição e sub-rotinas | ||
6 | 29/8 | 2 | Prática: Resolução de problemas em pseudocódigo e fluxogramas | ||
7 | 2/9 | 2 | Pseudocódigo: Uso de vetores e sub-rotinas | ||
8 | 5/9 | 2 | Prática: Resolução de problemas com vetores e sub-rotinas | ||
9 | 9/9 | 2 | Introdução ao C, primeiros conceitos de compilação, variáveis, if… else, printf e scanf | ||
10 | 12/9 | 2 | Prática: Aula de exercicios de C | ||
11 | 16/9 | 2 | Continuação C, condicionais, operadores relacionais, operadores lógicos | ||
12 | 19/9 | 2 | Prática: Estruturas de repetição em C | ||
13 | 23/9 | 2 | Revisão de algoritmos para preparação para prova e Funções no C | ||
14 | 26/9 | 2 | Prática: Exercicios de funções | ||
15 | 30/9 | 2 | Avaliação 1 - Algoritmos, resolução de problemas e C básico | ||
16 | 3/10 | 2 | Prática: Correção da Avaliação | ||
17 | 7/10 | 2 | Resolução de exercícios de vetores | ||
18 | 10/10 | 2 | Prática: Resolução de exercícios de fixação de vetores. | ||
19 | 14/10 | 2 | Estruturas | ||
20 | 17/10 | 2 | Prática: Exercícios adicionais de preparação para avaliação 1 de Laboratório | ||
21 | 21/10 | 2 | Exercícios adicionais de preparação para avaliação 1 de Laboratório | ||
22 | 24/10 | 2 | Prática: Avaliação de Laboratório | ||
23 | 28/10 | 2 | FERIADO: DIA DO SERVIDOR PUBLICO | ||
24 | 31/10 | 2 | Prática: Correção da avaliação | ||
25 | 4/11 | 2 | Ponteiros | ||
26 | 7/11 | 2 | Prática: Exercicios ponteiros | ||
27 | 11/11 | 2 | Vetor de Ponteiros e Ponteiro Para Estruturas | ||
28 | 14/11 | 2 | PROVAVEL RECESSO | ||
29 | 18/11 | 2 | Desenvolvimento do Projeto | ||
30 | 21/11 | 2 | Prática: Desenvolvimento do Projeto | ||
31 | 25/11 | 2 | Avaliação II de Laboratório | ||
32 | 28/11 | 2 | Prática: Desenvolvimento do Projeto | ||
33 | 2/12 | 2 | Desenvolvimento do Projeto | ||
34 | 5/12 | 2 | Prática: Desenvolvimento do Projeto | ||
35 | 9/12 | 2 | Desenvolvimento do Projeto | ||
36 | 12/12 | 2 | Prática: Desenvolvimento do Projeto | ||
37 | 16/12 | 2 | Desenvolvimento do Projeto | ||
38 | 19/12 | 2 | Prática: Recuperação | ||
TOTAL | 76 |
Tópicos avançados
- C++ para sistemas embarcados - mitos e verdades, comparação C e C++ (parte 1/2)
- C++ para sistemas embarcados - mitos e verdades, comparação C e C++ (parte 2/2)
- Dynamic Memory Allocation
- Debugging Memory on Linux
- memory layout in c..data segment,bss, code segment, stack, heap segment
Calculo de tempo recursividade simples |
---|
Na recursividade simples o tempo aumenta linearmente como mostra o código abaixo #include <stdio.h>
#include <stdint.h>
#include <time.h>
int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}
int fact(int n)
{
if (n <= 1)
{
printf("Finalmente n = 1, a função para se se chamar e retorna o resultado final.\n");
return 1;
}
else
{
printf("Nesta chamada n = %d, esta função se chamará recursivamente se for maior que 1.\n",n);
return n * fact(n-1);
}
}
int main()
{
uint64_t tempoFact10 = 0;
uint64_t tempoFact20 = 0;
uint64_t tempoFact30 = 0;
int i;
int in, out = 0;
struct timespec start, end;
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
// Some code I am interested in measuring
in = 10;
out = fact(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFact10 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
// Some code I am interested in measuring
in = 20;
out = fact(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFact20 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
// Some code I am interested in measuring
in = 30;
out = fact(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFact30 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
printf("\nMédia de tempo Fact(10): %li\n",tempoFact10);
printf("\nMédia de tempo Fact(20): %li\n",tempoFact20);
printf("\nMédia de tempo Fact(30): %li\n",tempoFact30);
}
|
Calculo de tempo recursividade multipla |
---|
Na recursividade simples o tempo aumenta linearmente como mostra o código abaixo #include <stdio.h>
#include <stdint.h>
#include <time.h>
int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}
int main()
{
uint64_t tempoFib10 = 0;
uint64_t tempoFib20 = 0;
uint64_t tempoFib30 = 0;
uint64_t tempoFib40 = 0;
int i;
int in, out = 0;
struct timespec start, end;
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
in = 10;
out = fib(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFib10 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
in = 20;
out = fib(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFib20 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
in = 30;
out = fib(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFib30 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
for (i=0;i<10;i++)
{
clock_gettime(CLOCK_MONOTONIC, &start);
in = 40;
out = fib(in);
clock_gettime(CLOCK_MONOTONIC, &end);
uint64_t timeElapsed = timespecDiff(&end, &start);
tempoFib40 += timeElapsed / 10;
}
printf("Resultado: %d\n",out);
printf("\nMédia de tempo Fib(10): %li\n",tempoFib10);
printf("\nMédia de tempo Fib(20): %li\n",tempoFib20);
printf("\nMédia de tempo Fib(30): %li\n",tempoFib30);
printf("\nMédia de tempo Fib(40): %li\n",tempoFib40);
}
int fib(int n)
{
if (n-1 <= 0)
return 0;
else if (n-1 == 1)
return 1;
else {
return fib(n-1) + fib(n-2);
}
}
Exemplo de resultado: Média de tempo Fib(10): 1113 Média de tempo Fib(20): 113658 Média de tempo Fib(30): 9817635 Média de tempo Fib(40): 427706133 Ou seja, há um aumento exponencial no tempo |
Verificando o código de retorno de um programa |
---|
|