Posts na categoria ‘C’

As funções sprintf e sscanf

Quarta-feira, Maio 5th, 2010

O objetivo deste post é estudar essas duas funções que operam sobre strings, ambas as funções estão na biblioteca padrão stdio.h.

Todo programador em C deve saber pra que serve e como utilizar as funções printf e scanf. Pois bem, sprintf e sscanf funcionam praticamente da mesma forma, só que em vez de operarem sobre os streams de entrada e saída padrão (stdin e stdout, respectivamente) operam sobre strings (por isso o s na frente do nome).

Definições:

int sprintf(char *string, char *formato, lista de argumentos);
int sscanf(char *string, char *formato, lista de argumentos);

Ambas as funções operam sobre o parâmetro char *string. Assim como printf escreve na tela, sprintf escreve na string. Assim como scanf lê informações do teclado, sscanf lê informações da string.

A função sscanf retorna quantos parâmetros foram lidos com sucesso da string. Uma boa prática de programação é verificar se este número é o esperado.

A função sprintf retorna o tamanho (sem contar com o ‘/0′) da nova string. Repare que fica a cargo do programador verificar se a nova string cabe no espaço alocado para ela.

Veja um exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
int main(void)
{
    char s[] = "31/05/1989";
 
    int dia, mes, ano;
 
    sscanf(s,"%d/%d/%d",&dia,&mes,&ano); /* Tenta ler três inteiros da string e armazenar nas variáveis */
 
    printf("%d %d %d\n",dia,mes,ano);
 
    dia = 4;
    mes = 5;
    ano = 2010;
 
    sprintf(s,"%d/%d/%d",dia,mes,ano); /* Escreve uma nova string em s */
 
    printf("%s",s);
 
    return 0;
}

Struct e typedef

Terça-feira, Maio 4th, 2010

Neste post veremos como utilizar e ver alguns exemplos de struct (Geralmente livros em português chamam de registro) e como utilizar o typedef .

Registros (struct):

Os tipos de dados fundamentais do C são dados homogêneos(int, float, char, etc), com struct o programador pode combinar esses tipos fundamentais para criar novos tipos. Sua forma de utilização é a seguinte:

struct IDENTIFICADOR{
    lista de dados fundamentais;
}Nome_da_variavel;

IDENTIFICADOR: Não da pra ser mais claro, é um nome que identifica o tipo de registro que você está criando. É isso.

Nome_da_variavel: Este campo é opcional, ele associa uma variável ao tipo de registro que você criou.

Exemplos de struct:

struct nome{
    int a;
    float b;
    char c;
};
/* Neste caso struct nome passa a ser um tipo de dados, abaixo vemos a declaração de uma variável deste tipo */
 
struct nome var;
struct {
    int a;
    float b;
    char c;
}var;
 
/* Neste caso a struct não tem identificação, no entanto a variável var já está associada a este tipo, a desvantagem deste tipo de declaração é não poder ter mais variáveis deste tipo */
struct nome{
    int a;
    int b;
    int c;
}var1
 
/* Neste caso a variável var1 é associada a struct, mas como a struct possui identificador é possível declarar mais variáveis deste tipo */
 
struct nome var2;

Acessando campos de uma struct:

Existem duas formas de acessar campos de uma struct, cada uma é usada em uma situação diferente. A primeira delas é o “.”, este operador é usado quando a variável em questão é alocada pelo compilador. Vamos supor a seguinte struct:

struct nome{
    int a;
    float b;
};

Agora vamos declarar uma variável associada a ela:

struct nome var;

Como var foi alocada pelo compilador podemos acessar seus elementos assim:

var.a = 2;
var.b = 3.2;

Aqui vai um pequeno exemplo do uso do operador “.”:

#include <stdio.h>
 
struct nome{
    int a;
    float b;
};
 
int main(void)
{
    struct nome var;
 
    var.a = 2;
    var.b = 3.5;
 
    printf("%d\n",var.a);
    printf("%f\n",var.b);
 
    return 0;
}

Quando há um ponteiro apontando para um registro se usa o operador “->”. Exemplo:

#include <stdio.h>
 
struct nome{
    int a;
    float b;
};
 
int main(void)
{
    struct nome *var; /* Ponteiro para o registro */
 
    var = (struct nome *)malloc(sizeof(struct nome)); /* Aloca espaço para a estrutura e retorna o endereço de memória para o ponteiro */
 
    var->a = 2;
    var->b = 3.5;
 
    printf("%d\n",var->a);
    printf("%f\n",var->b);
 
    return 0;
}

Obviamente as duas formas são importantes, aqui vai um trecho de código em que uma estrutura é tratada de ambas as formas:

#include <stdio.h>
 
struct nome{
    int a;
    float b;
};
 
void mudaValores(struct nome *v, int a, float b)
{
    v->a = a;
    v->b = b;
 
    printf("%d\n",v->a);
    printf("%.2f\n",v->b);
}
 
int main(void)
{
    struct nome var; /* Ponteiro para o registro */
 
    var.a = 2;
    var.b = 3.5;
 
    printf("%d\n",var.a);
    printf("%.2f\n\n",var.b);
 
    mudaValores(&var,21,43.2);
 
    return 0;
}

Uma estrutura pode conter qualquer tipo de dados, inclusive outra estrutura. Veja um exemplo:

struct nome1{
    int a;
    int b;
};
 
struct nome2{
    int c;
    struct nome1 d;
};

Para acessar os elementos da estrutura interna usam-se os mesmos operadores, exemplos:

struct nome1 var1;
struct nome1 *var2
 
var1.d.a = 2;
var2->d.a = 2; /* Supondo que var2 já esteja alocada */

Uma estrutura não pode conter um elemento do próprio tipo, no entanto ela pode conter um ponteiro para um elemento do seu próprio tipo.
Permitido:

struct nome{
    int a;
    struct nome *p;
};

Não permitido:

struct nome{
    int a;
    struct nome p;
};

Typedef:

O comando typedef serve para renomear um tipo de dado, seja ele fundamental (int, float, etc) ou criado por você (uma estrutura ou um tipo criado por outro typedef).
Sua utilização é bastante simples:

typedef tipo novo_nome;

Um exemplo bem simples é a tradução dos tipos para o portugûes:

typedef int inteiro;
typedef float real;
 
inteiro a;
real b;

Typedef é muito utilizado em conjunto com struct:

#include <stdio.h>
 
struct _Data
{
    int dia;
    int mes;
    int ano;
};
 
typedef struct _Data data;
 
int main(void)
{
    data nasc;
 
    nasc.dia = 31;
    nasc.mes = 5;
    nasc.ano = 1989;
 
    return 0;
}

Outra forma de utilizar typedef e struct em conjunto:

#include <stdio.h>
 
typedef struct
{
    int dia;
    int mes;
    int ano;
}data;
 
int main(void)
{
    data nasc;
 
    nasc.dia = 31;
    nasc.mes = 5;
    nasc.ano = 1989;
 
    return 0;
}

Code::Blocks e ponteiros

Segunda-feira, Maio 3rd, 2010

Em todos os tutoriais usarei a IDE Code::Blocks. É mil vezes melhor do que o popular Dev C/C++ e é igualmente gratuito, além de ter a enorme vantagem de ser “cross-platform”, ou seja, você pode instalar ele em mais de um sistema operacional. No caso do Code::Blocks ele pode ser instalado no Windows, no Linux e no Mac OS X.

Link para download: http://www.codeblocks.org/downloads

Não vou dar detalhes sobre a instalação, talvez em um post futuro. Também está no projeto uma explicação de como usar o debugger, que no Code::Blocks funciona efetivamente.

Criando um projeto no Code::Blocks:

Clique no botão New e em seguida em Project…:

Em seguida aparece uma janela para a seleção do tipo de projeto, na maioria dos tutoriais usaremos “Console Application”, selecione esta opção e clique em Go. Também pretendo fazer tutoriais sobre a biblioteca gráfica Gtk+.

Um assistente para criação do projeto irá abrir, clique em Next. Na tela de seleção de linguagem, selecione C. No próximo passo coloque o nome do projeto e escolha o local onde os arquivos serão salvos, recomendo criar uma pasta só para o projeto.

Na próxima tela deixa as opções como na imagem:

Clique em Finish e pronto, o projeto está criado e já com um arquivo main.c.

Ponteiros

Como quase todo tutorial aqui vai ser na linguagem C, é de fundamental importância o pleno entendimento dos ponteiros, tão detestados pelos iniciantes, e tão fundamentais.

Como o próprio nome diz, um ponteiro é um tipo de dado que aponta para uma posição da memória, ou seja, a informação que ele guarda é um endereço de memória, não um dado em si.

Falando grosseiramente sobre arquitetura de computadores, cada posição de memória tem um número que a identifica, por exemplo, se você está trabalhando com uma memória de 1 gb, a primeira posição será a 1 e a última será a 1.073.741.824 (muita coisa hein?), e é exatamente essa identificação que um ponteiro guarda.

Notação:

Um ponteiro pode ser de qualquer tipo, abaixo estão algumas declarações de ponteiros:

int *p;
float *f;
char *s;

Essa é a forma de declarar um ponteiro, que não tem nada haver com a forma de se usar um ponteiro. Vamos supor que declaramos um ponteiro para inteiro (int *p).

Observação: Em C quando declaramos uma variável, seja ela de que tipo for, ela não é zerada, o que significa que a posição de memória que foi alocada para ela tem o que chamamos de lixo, uma informação que não foi o seu programa que criou, simplesmente foi largada na memória por outro programa.

O operador & e o operador *:

Quando você aplica o operador & sobre uma variável, você recebe o endereço de memória onde ela está armazenada, vejamos o seguinte trecho de código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
int main(void)
{
    int *p=NULL; /* Declaramos p inicialmente apontando para nada */
    int a=10;
 
    printf("Valor do ponteiro: %p",p); /* Aqui o valor do ponteiro será NULL */
    printf("\nEndereço de a: %p",&a);
 
    p = &a; /* O ponteiro p recebe o endereço de memória de a */
 
    printf("\nValor do ponteiro: %p",p);
    printf("\nConteudo do ponteiro: %d",*p); /* O operador * recupera a informação armazenada no endereço de memória apontado por p */
 
    return 0;
}

Como comentado no código, o operador * recupera a informação armazenada no endereço de memória apontado por p. No exemplo acima, p aponta para a, que armazena o valor 10, portando *p será 10.

Agora vamos analisar o seguinte código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
 
int main(void)
{
    int *p=NULL; /* Declaramos p inicialmente apontando para nada */
    int a=100,b=200,c=300,d=400,e=500;
 
    p = &a; /* p aponta para a */
    printf("\n*p = %d",*p);
 
    *p = c; /* O conteudo de p recebe c, mas como p aponta para a, a recebe c */
    printf("\n*p = %d",*p);
    printf("\n a = %d\n",a);
 
    p = &b; /* p aponta para b */
    printf("\n*p = %d",*p);
 
    *p = d; /* O conteudo de p recebe d, mas como p aponta para b, b recebe c */
    printf("\n*p = %d",*p);
    printf("\n b = %d\n",b);
 
    p = &e; /* p aponta para e */
    printf("\n*p = %d",*p);
 
    *p = 600; /* O conteudo de p recebe o valor 600, mas como p aponta para e, e recebe c */
    printf("\n*p = %d",*p);
    printf("\n e = %d\n",e);
 
    e = 700; /* e recebe 700, como p aponta para e, *p será 700 */
    printf("\n*p = %d",*p);
    return 0;
}

Na linha 8 p recebe o endereço de a, ou seja, p aponta para a:

Na linha 11 o conteúdo de p recebe c, mas como p aponta para a, a recebe c:

Na linha 15 p recebe o endereço de b, ou seja, p aponta para b:

Na linha 18 o conteúdo de p recebe d, mas como p aponta para b, b recebe d:

Na linha 22 p recebe o endereço de e, ou seja, p aponta para e:

Na linha 25 o conteúdo de p recebe o valor 600, mas como p aponta para e, e recebe o valor 600:

Na linha 29 e recebe o valor 700, portanto *p será 700 também.

Observação: Veja como o modo de declarar ponteiros é diferente do modo de usá-los. Quando declaramos um ponteiro, se fizermos int *p = 2341, significa que o ponteiro está apontando para a posição 2341 da memória, mas se fizermos *p = 2341 em outra parte, significa que o conteúdo de p recebe 2341.

Curiosidade: Quando um vetor é declarado, por exemplo, int a[10], “a” contem o endereço do primeiro elemento do vetor, logo a == &a[0].

Em breve postarei algumas aplicações práticas de ponteiros, como listas encadeadas.