Programar em C/Mais sobre variáveis

Origem: Wikilivros, livros abertos por um mundo aberto.

<< Tipos de dados definidos pelo usuário Índice Gerenciamento de memória >>


Tabela de conteúdo

[editar] typedef

A instrução typedef serve para definir um novo nome para um certo tipo de dados ― intrínseco da linguagem ou definido pelo usuário. Por exemplo, se fizéssemos a seguinte declaração:

typedef unsigned int uint;

poderíamos declarar variáveis inteiras sem sinal (unsigned int) da seguinte maneira:

uint numero;
// equivalente a "unsigned int numero;"

Como se vê, typedef cria uma espécie de "apelido" para um tipo de dados, permitindo que esse tipo seja referenciado através desse apelido em vez de seu identificador normal.

Um dos usos mais comuns de typedef é abreviar a declaração de structs ou estruturas. Veja este exemplo:

struct pessoa {
   char nome[40];
   int idade;
};

struct pessoa joao;

Observe que, para declarar a variável joao, precisamos escrever a palavra struct. Podemos usar typedef para abreviar essa escrita:

typedef struct _pessoa {
   char nome[40];
   int idade;
} Pessoa;

Pessoa joao;

Um "apelido" de tipo é utilizado com bastante freqüência, embora não costumemos dar por isso: é o tipo FILE, usado nas funções de entrada/saída de arquivos.

typedef struct _iobuf
{
   char* _ptr;
   int	  _cnt;
   char* _base;
   int   _flag;
   int   _file;
   int   _charbuf;
   int   _bufsiz;
   char* _tmpfname;
} FILE;

Então, quando declaramos algo como

FILE *fp;

na verdade estamos a declarar um ponteiro para uma estrutura, que será preenchida mais tarde pela função fopen.

Atenção! Você não deve tentar manipular uma estrutura do tipo FILE; sua composição foi apresentada apenas como exemplo ou ilustração.

[editar] sizeof

sizeof é uma macro substituída em tempo de compilação por um valor representando o tamanho de um tipo.

O erro mais comum com os iniciantes é achar que sizeof retorna o tamanho de variáveis em tempo de execução, portanto é importante ressaltar sizeof() só funciona em termpo de compilação

Exemplo de uso:


Correto:


#include <string.h>
#include <stdio.h>
int
main(void)
{
   char *nome;
   nome = malloc(sizeof(char) * 9);
   sprintf(nome, "wikibooks");
   printf("Site: http://pt.%s.org/", nome);
   /*
      Imprime:
        Site: http://pt.wikibooks.org/
   */
}

Incorreto:

const char *FRASE;
FRASE = "Wikibooks eh legal";
printf("Eu acho que o tamanho da string FRASE é %d", sizeof(FRASE));

A sentença acima NÃO funciona, pois sizeof é substituido pelo tamanho de um tipo em tempo de compilação.

Um exemplo mais interessante do uso de sizeof:

/* Criando um tipo estruturado de dados: */

typedef struct {
   const char *nome;
   const char *sobrenome;
   int idade;
} Pessoa;



int 
main(void)
{
   Pessoa *joaquim;
   joaquim = malloc(sizeof(Pessoa));
   joaquim->nome = "Joaquim";
   joaquim->sobrenome = "Silva";
   joaquim->idade = 15;
}

O sizeof acima foi útil pra saber a soma do tamanho de todos os tipos da estrutura Pessoa.

[editar] Conversão de tipos

[editar] Casting: conversão manual

Em C, cada tipo básico ocupa uma determinada porção de bits na memória, por exemplo:

* char possui 8 bits, vai de -127 a 127
* int possui 16 bits, vai de -32.767 a 32.767
* short int possui 8 bits, vai de 0 a 65.535
* float possui 32 bits e seis dígitos de precisão
* double possui 64 bits e dez dígitos de precisão
* long double possui 128 bits e dez dígitos de precisão

Logo, a conversão entre tipos nem sempre é algo nativo da linguagem, por assim dizer. Há funções como atol e atof que convertem string em inteiro longo (long int) e string em double, respectivamente.

Mas em muitos casos é possível usar o casting.

O casting é feito para "cortar" bits de tipos, podendo convertê-los a tipos de tamanho menor.

Vamos entender melhor tomando os seguintes tópicos:

* float possui 32 bits
* int possui 16 bits

Mas qual a diferença de float pra int ?

Um número float: 43.023 ao ser convertido para int deverá ser "cortado", ficando inteiro: 43

O que será feito é ignorar os bits extras, usados nos campos decimais.


int i;
float f;
f = 43.023;
/* A conversão com casting é: */
i = (int) f;

Em poucas palavras: Casting é colocar um tipo entre parênteses antes da atribuição de uma variável.


variavel_destino = (tipo) variavel_origem;

Isso também pode ser utilizado com ponteiros e estruturas de dados, mas lembre-se:

O casting fala ao compilador pra utilizar o tipo entre parênteses como "forma" para cortar bits do dado antes de atribuí-lo à variável.

[editar] Modificadores de acesso

Estes modificadores, como o próprio nome indica, mudam a maneira com a qual a variável é acessada e modificada.Alguns dos exemplos usam conceitos que só serão abordados nas seções seguintes, então você pode deixar esta seção para depois se assim o desejar.

[editar] const

O modificador const faz com que a variável não possa ser modificada no programa. Como o nome já sugere é útil para se declarar constantes. Poderíamos ter, por exemplo:

const float PI = 3.1415;

Podemos ver pelo exemplo que as variáveis com o modificador const podem ser inicializadas. Mas PI não poderia ser alterado em qualquer outra parte do programa. Se o programador tentar modificar PI o compilador gerará um erro de compilação.

Outro uso de const, aliás muito comum que o outro, é evitar que um parâmetro de uma função seja alterado pela função. Isto é muito útil no caso de um ponteiro, pois o conteúdo de um ponteiro pode ser alterado por uma função. Para proteger o ponteiro contra alterações, basta declarar o parâmetro como const.

#include <stdio.h>

int sqr (const int *num);

int main(void)
{
   int a = 10;
   int b;
   b = sqr(&a);
}

int sqr (const int *num)
{
   return ((*num)*(*num));
}

No exemplo, num está protegido contra alterações. Isto quer dizer que, se tentássemos fazer

*num = 10;

dentro da função sqr(), o compilador daria uma mensagem de erro.

[editar] volatile

O modificador volatile diz ao compilador que a variável em questão pode ser alterada sem que este seja avisado. Isto evita "bugs" que poderiam ocorrer se o compilador tentasse fazer uma otimização no código que não é segura quando a memória é modificada externamente.

Digamos que, por exemplo, tenhamos uma variável que o BIOS do computador altera de minuto em minuto (um relógio, por exemplo). Seria importante que declarássemos esta variável como volatile.

Um uso importante de variáveis volatile é em aplicações com várias threads (linhas de execução), onde a memória é compartilhada por vários pedaços de código que são executados simultaneamente.

[editar] extern

O modificador extern diz ao compilador que a variável indicada foi declarada em outro arquivo que não podemos incluir diretamente, por exemplo o código de uma biblioteca padrão. Isso é importante pois, se não colocarmos o modificador extern, o compilador irá declarar uma nova variável com o nome especificado, "ocultando" a variável que realmente desejamos usar. E se simplesmente não declarássemos a variável, já sabemos que o compilador não saberia o tamanho da variável.

Quando o compilador encontra o modificador extern, ele marca a variável como não resolvida, e o montador se encarregará de substituir o endereço correto da variável.

extern float sum;
extern int count;

float returnSum (void)
{
   count++;
   return sum;
}

Neste exemplo, o compilador irá saber que count e sum estão sendo usados no arquivo mas que foram declarados em outro.

Uma variável externa freqüentemente usada é a variável errno (declarada no arquivo-cabeçalho errno.h), que indica o último código de erro encontrado na execução de uma função da biblioteca padrão ou do sistema.

[editar] static

O funcionamento das variáveis declaradas como static depende de se estas são globais ou locais.

  • Variáveis globais static funcionam como variáveis globais dentro de um módulo, ou seja, são variáveis globais que não são (e nem podem ser) conhecidas em outros módulos (arquivos). Isto é util se quisermos isolar pedaços de um programa para evitar mudanças acidentais em variáveis globais. Isso é um tipo de encapsulamento — que é, simplificadamente, o ato de não permitir que uma variável seja modificada diretamente, mas apenas por meio de uma função.
  • Variáveis locais estáticas são variáveis cujo valor é mantido de uma chamada da função para a outra. Veja o exemplo:
int count (void)
{
   static int num = 0;
   num++;
   return num;
}

A função count() retorna o número de vezes que ela já foi chamada. Veja que a variável local int é inicializada. Esta inicialização só vale para a primeira vez que a função é chamada pois num deve manter o seu valor de uma chamada para a outra. O que a função faz é incrementar num a cada chamada e retornar o seu valor. A melhor maneira de se entender esta variável local static é implementando. Veja por si mesmo, executando seu próprio programa que use este conceito.

[editar] register

O computador pode guardar dados na memória (RAM) e nos registradores internos do processador. As variáveis (assim como o programa como um todo) costumam ser armazenadas na memória. O modificador register diz ao compilador que a variável em questão deve ser, se possível, guardada em um registrador da CPU.

Vamos agora ressaltar vários pontos importantes:

  • Porque usar register? Variáveis nos registradores da CPU vão ser acessadas em um tempo muito menor pois os registradores são muito mais rápidos que a memória. No entanto, a maioria dos compiladores otimizantes atuais usa registradores da CPU para variáveis, então o uso de register é freqüentemente desnecessário.
  • Em que tipo de variável podemos usar o register? Antes da criação do padrão ANSI C, register aplicava-se apenas aos tipos int e char, mas o padrão atual permite o uso de register para qualquer um dos quatro tipos fundamentais. É claro que seqüências de caracteres, arrays e estruturas também não podem ser guardadas nos registradores da CPU por serem grandes demais.
  • register é um pedido que o programador faz ao compilador. Este não precisa ser atendido necessariamente, e alguns compiladores até ignoram o modificador register, o que é permitido pelo padrão C.
  • register não pode ser usado em variáveis globais, pois isto implicaria em um registrador da CPU ficar o tempo todo ocupado por essa variável.

Um exemplo do uso do register é dado a seguir:

int main (void)
{
   register int count;
   for (count = 0; count < 10; count++)
   {
      ...
   }
   return 0;
}

O loop acima, em compiladores que não guardam variáveis em registradores por padrão, deve ser executado mais rapidamente do que seria se não usássemos o register. Este é o uso mais recomendável para o register: uma variável que será usada muitas vezes em seguida.


Nuvola apps konsole.png

Esta página é um esboço de informática. Ampliando-a você ajudará a melhorar o Wikilivros.