Mostrando postagens com marcador pc. Mostrar todas as postagens
Mostrando postagens com marcador pc. Mostrar todas as postagens

domingo, 7 de abril de 2013

Pré processador


Seguinte. Tem coisas que o programa faz antes de abrir a telinha que a gente vê. Os comandos que são interpretados pelo pré processador são aqueles iniciados em #: includes, macros e condicionais.

Os includes:
#include <stdio.h> //<> já reconhece bibliotecas “defaults”
#include “minhalib.h” //se for personalizada é entre aspas

As macros:
#define MAX 100
#define nome_macro texto_equivalente
Se uma macro ficar muito grande pra caber numa linha só, é só colocar uma barra invertida ( \ ) que o programa continua lendo. Ele só para de ler aquela macro quando acha uma linha sem a \.

É possível ter funções feitas com macros, assim:
#define MILHAS_KM(m) m*(8.0/5.0)
#define MULTI(a,b) ((a)*(b))

você chama a função escrevendo MILHAS_KM(sua_var) ou MULTI(v1,v2) e ele faz aquelas continhas! E pode ter comandos mais complexos:
#define IMPRIME_MSG(msg) \
printf ("Mensagem: %s\n", msg);
uso: IMPRIME_MSG("erro na fase y");

OBSERVAÇÃO IMPORTANTE: entre o nome da sua macro e a definição dela não pode ter espaço! Se eu colocasse #define MULTI (a, b) ← espaço entre a palavra e o parêntesis – daria errado porque no lugar de MULTI ele escreveria (a, b). O espaço só pode vir depois do nome inteiro.

As condicionais:
#if SISTEMA_OPERACIONAL== 1
  #include "windows.h"
#elif SISTEMA_OPERACIONAL == 4
  #include "hpux.h"
#else
  #error Sistema operacional não definido ou inválido
#endif
Só vai incluir “windows.h” se o SO definido for 1. A estrutura tem que ser essa chata aí mesmo

O resto:
#ifdef EXEMPLO //se tiver a define EXEMPLO
#ifndef EXEMPLO //se NÃO tiver a define EXEMPLO
#undef EXEMPLO //apaga a define EXEMPLO

Operações bit a bit


Então. Lembra que 1byte = 8bits? Existe jeito de manipular 1bit só. Tem vários operadores pra bits que até lembram alguns que a gente usa:
  • & AND operador lógico E
  • | OR operador lógico OU
  • ^ OR exclusivo operador lógico OU exclusivo (XOR)
  • << left shift deslocamento à esquerda
  • >> right shift deslocamento à direita
  • ~ complemento de um,  negação (NOT)
É só pensar que ele mexe com os bits das variáveis. Completarei mais tarde.

Listas encadeadas


Lembra que eu falei que podia colocar um struct dentro do mesmo struct? Pois é, assim que funcionam as listas encadeadas. De certa forma elas são como um vetor, tem vários objetos do mesmo tipo, mas no caso das listas pode ter quantos quiser (não só o tamanho do vetor) e é bem mais manipulável.
A lógica das listas encadeadas é uma struct conter um ponteiro para o mesmo tipo. Dessa forma, um objeto vai se ligando do outro, porque ele aponta quem é o próximo da lista. Assim:

struct Nodo {
int dado;
struct Nodo *proximo;
} *raiz; //declara um ponteiro da struct Nodo chamado raiz

raiz = (Nodo *) malloc (sizeof(Nodo)); //aloca o tamanho da raiz
raiz->dado = 7; //preenche o dado
raiz->proximo = NULL; //fala que não tem próximo (ou seja, é o fim da lista)

Nodo *pnodo; //declara outro ponteiro, dessa vez chamado pnodo
pnodo = (Nodo *) malloc(sizeof(Nodo)); //aloca pnodo
pnodo->dado = 11; //preenche o dado
pnodo->proximo = NULL; //declara que é esse o fim da lista
raiz->proximo = pnodo; //fala que raiz agora deixa de ser o fim e aponta pra pnodo

Dessa forma, vai tendo uma continuidade, já que um vai apontando pro outro. Esse tipo de lista é bem mais prático que um vetor, porque não tem um tamanho fixo e não precisa ser um logo seguido do outro (em termos de alocação de memória). Agora vamos aos procedimentos básicos:
  • Como percorrer uma lista encadeada utilizando um ponteiro
Nodo *pnodo;
pnodo = raiz;
while (pnodo != NULL) {
  printf(“%d “, pnodo->dado);
  pnodo = pnodo->proximo;
}
  • Como imprimir uma lista encadeada usando um ponteiro
void print_list (struct Nodo *pi) {
  printf("Lista = ");
  while (pi) {
    printf (" %d ", pi->dado);
    pi = pi->proximo;
  }
}

Alocação dinâmica de memória


É tipo assim. Quando você declara um int ou um char, por exemplo, essas variáveis já tem um número de bytes pré definido pra ocupar. A alocação dinâmica de memória aloca só exatamente o que você precisa.
Acaba que você não tem um vetor muito grande nem muito pequeno, porque sua variável vai ocupar exatamente o que você mandar.
São 4 funções: considerando int *p;

malloc: p = (int*) malloc(N * sizeof(int)); //aloca memória (N inteiros)
calloc: p = calloc(MAX_TAM, sizeof(int)); //aloca memória de tamanho MAX_TAM
realloc: p = (int *) realloc(x, 8000*sizeof(int)); //realoca memória de antes pro tamanho de 8000 inteiros
free: free(p); //libera a memória

É importante lembrar que quando se usa a alocação dinâmica é da NOSSA responsabilidade desalocar. Senão nem quando fechar o programa aquela alocação vai embora.
Outra coisa a acrescentar é sobre a função sizeof: ela retorna o tamanho em bytes daquele tipo de (ou do tipo daquela) variável.

Arquivos


Arquivos. São. Um. Porre. Vou até ser menos legal com eles. Declaração:
FILE *arquivo; //declarei um ponteiro que vai ser a representação do meu arquivo no programa
arquivo = fopen(nome, modo); //nome vem entre aspas e é o nome do arquivo que eu vou querer abrir, modo também vem entre aspas e diz o que eu vou fazer.



Esses são os modos. Agora as principais funções:
Abrir: arquivo = fopen(“arquivo.txt”, “wb+”);
Fim do arquivo: feof(arquivo);
Ler: fread (variável que vai receber, tamanho a ser lido, quantas vezes vai ler, arquivo);
Escrever: fwrite (var que vai escrever, tamanho a ser escrito, quantas vezes vai escrever, arquivo);
Posição do cursor: ftell(arquivo);
Volta o cursor pro começo: rewind(arquivo);
Posicionar o cursor: fseek(arquivo, tamanho a pular, de onde começa) //SEEK_SET = início, SEEK_END = final, SEEK_CUR = de onde está agora
Fechar: fclose(arquivo);
Também tem outras funções, como as de manipular diretamente char ou String (putc(caracter, arquivo) e puts(string, arquivo) para escrever, por exemplo). Mas com as que eu coloquei já dá pra trabalhar, portanto não vou aprofundar no assunto por agora.
Vou colocar um códigozinho aqui pra demonstrar algumas funções:

FILE *arq, *cop; //declarei meus dois arquivos
if (arq = fopen("arquivo.txt", "wb+" && cop = fopen("copia.txt", "wb+")) { //verifiquei se foi possível abrir os dois
   fseek(arq, 0, SEEK_SET); //coloquei o cursor no início
   fseek(cop, 0, SEEK_SET);
   while(!feof(arq)) { //enquanto não acabar o fim do arquivo arq
      char c;
      fread(c, 1, 1, arq); //eu leio 1 char de arq
      fwrite(c, 1, 1, cop); //e escrevo esse char em cop
   }
   fclose(arq); //fecho os dois ao final da cópia
   fclose(cop); //sempre importante fechar
} //fim

Escrevi aqui e agora, não compilei esse trem. Mas se não funcionar, o código correto é algo bem parecido. Esse está bem rudimentar porque eu não tratei muitos dos possíveis erros, só o da abertura de arquivo (se a abertura falhar, a função retorna NULL). Porém dá pra pegar mais ou menos o uso;

Structs


Structs são simples e práticas, coisas fofas da mamãe, eu curto. É tipo assim. Vamos supor que a gente várias coisas pra guardar e cada coisa tem várias coisas. Por exemplo, numa agenda uma pessoa tem nome, telefone, e-mail e endereço. A coisa1 é a pessoa, as coisas da coisa são os dados que eu pus ali. A declaração de uma struct fica assim:

typedef struct nome_struct { //typedef serve pra definir um tipo de variável
int campo1;
char campo2;
struct nome_struct campo3; //é possível ter um elemento com a mesma estrutura
} NOME_TIPO //nome do novo tipo, não pode ser usado dentro da struct porque ainda “não existe” quando ela é criada

Então que que a gente tem: um novo tipo de variável que chama NOME_TIPO. Agora eu posso declarar isso que nem eu declaro int (int é um tipo de variável). Não é obrigatório ter uma variável struct dentro da struct, eu só tava mostrando isso aí porque mais tarde a gente vai usar isso.
Pra acessar um campo de uma variável do meu tipo fica assim:

NOME_TIPO n1;
n1.campo1 = 0; //uso . (ponto) pra me referir ao campo1 dentro de n1
n1.campo2 = 'a';

Se for vetor o acesso é da mesma forma, só que você coloca qual posição você quer. Por exemplo, n1[0].campo1 = 1 faz com que o campo1 da primeira posição do vetor (fictício) n1 seja 1.

Números aleatórios


TÓPICO BONUS! Então, números aleatórios são úteis. A gente usa eles pra um tanto de coisas, mesmo que eu não esteja lembrando de nenhuma agora. Pra declarar um número aleatório usamos a função rand(); assim:

int i = rand() % x;

Em que x é o valor limite que eu quero chegar. Isso me dá resultados de 0 a x – 1. Quando a gente randomiza uma vez e roda o programa de novo, os números são os mesmos. Isso porque usamos a mesma raiz para gerar nossos números aleatórios, que por default é 1. Para usar outra raiz, é só colocar outro número como parâmetro da função rand (tipo rand(2)). Só que sempre vai gerar os mesmos também. Para gerar números REALMENTE aleatórios, a gente precisa de colocar na raiz uma coisa que sempre mude. Aí a gente chama a função que nos retorna a hora atual em (acho que) milissegundos, que sempre vai ser diferente. Fica assim:

int i = srand(time(NULL));

Pronto, aleatório.