\documentclass[apostila.tex]{subfiles} \begin{document} \chapter{Especificadores e modificadores de tipos} \section{Casting} Na linguagem C existem basicamente duas maneiras de converter uma variável de um tipo para outro: implicitamente ou explicitamente. Uma conversão implícita é aquela que ocorre quando é realizada a atribuição de uma variável de um determinado tipo para outra variável de um tipo diferente. O exemplo abaixo ilustra uma conversão implícita. \begin{lstlisting} int x; float y=10.53; x=y; // aqui ocorre uma conversão implícita \end{lstlisting} Nem sempre a linguagem C aceita conversões implícitas, principalmente se os tipos de dados envolvidos forem muito diferentes. \begin{lstlisting} struct prod { int code; char nome[15]; }; struct prod var b; int i; i = (var) b; // atribuição inválida! \end{lstlisting} Uma conversão explícita é quando o programador especifica qual o tipo que determinada variável deve assumir, por exemplo, através de um casting. \begin{lstlisting} int x; float y = 10.53; x = (int) y; // o parênteses com o tipo é um casting \end{lstlisting} Assim como a conversão implícita, conversões explícitas entre tipos de dados muito diferentes também não são válidas. Basicamente, a única vantagem de um casting sobre uma conversão implícita é para determinar exatamente qual o tipo de dado que uma determinada variável deve assumir. Casting mudando resultados \begin{lstlisting} int a, b; float c,d; a=5; b=2; c=a/b; // c divisão entre inteiros d=(float)a/(float)b; // d divisão entre floats \end{lstlisting} No exemplo anterior, embora a variável $c$ seja do tipo float, as variáveis $a$ e $b$ são inteiras, portanto o resultado da divisão será $2$ e não $2,5$. Isso acontece porque operações que envolvem dois números inteiros sempre resultam em um valor inteiro. Por outro lado, a variável d assumirá o valor 2.5 porque foi especificado, através de casting, que as variáveis a e b deveriam ser consideradas como float, o que obriga o programa a calcular a divisão entre dois float's ao invés de dois números inteiros. \section{Variáveis Static} Variáveis static são variáveis cujo valor é preservado entre sucessivas chamadas à função em que foi declarada. Elas diferem das variáveis locais porque o seu valor não é destruído quando a função termina e diferem das variáveis globais porque somente são visíveis dentro do contexto da função em que foram declaradas. Observe o código abaixo: \begin{lstlisting} #include int prox_par(void) { static int pares = -2; pares += 2; return (pares); } void main(void) { int i; printf ("Os numeros pares menores que 100 sao: "); for(i=0; i < 100; i++) printf ("%d ", prox_par()); } \end{lstlisting} Este programa imprime na tela números pares, iniciando por zero, enquanto a variável i for menor que 100, utilizando apenas a sucessiva chamada da função \verb|prox_par|. O segredo está na variável static declarada. Ela é inicializada com o valor $-2$ quando o programa inicia a execução. Na primeira chamada à função, essa variável é acrescida de $2$, resultando no valor $0$, que é devolvido pela função. Na próxima chamada à função, o valor antigo da variável (zero) é preservado. Ela é então acrescida de $2$, o valor $2$ $(0+2=2)$ é retornado e assim sucessivamente. Observe que se a variável não fosse declarada como static, a função retornaria zero sempre. \subsubsection{Exercício} Escreva uma função capaz de armazenar uma string e imprimir seu conteúdo. A função deverá ter apenas um parâmetro (int p) e funcionará de modo que se $p=0$ a função obtêm e armazena a string e se $p=1$ a função deverá apenas imprimir a string armazenada. \subsection{Variáveis Register} Este modificador de tipo é tradicionalmente aplicado a variáveis do tipo int e char, embora possa ser aplicado a outros tipos de dados também. O modificador register requisita ao compilador que mantenha, se possível, o valor das variáveis declaradas como register em registradores da CPU, ao invés de mantê-las na memória. Isso significa que as operações com tais variáveis são muito mais rápidas, pois o valor já está na própria CPU, economizando assim acessos à memória. É importante observar que o ganho de desempenho só será significativo quando aplicado a variáveis muito utilizadas, como índices de matrizes e controladores de repetições. Atualmente este modificador não é muito utilizado e tende a se tornar obsoleto, pois os compiladores modernos vêm com recursos que analisam e otimizam automaticamente o código, de forma muito mais eficiente (e rápida) que qualquer ser humano. Exemplo de declaração: \begin{lstlisting} register int contador; \end{lstlisting} \subsection{typedef} A linguagem C permite definir explicitamente novos nomes de tipos de dados usando-se a palavra reservada typedef. Não é criado um novo tipo de dado, apenas definido um novo nome (apelido) para um tipo de dado existente. A forma geral da declaração typedef é: \begin{lstlisting} typedef tipo novo_nome; \end{lstlisting} onde tipo é qualquer tipo de dado válido e identificador é o novo nome para esse tipo. O novo nome definido não substitui o nome do tipo, apenas é usado como um apelido para o mesmo. Por exemplo: \begin{lstlisting} typedef float flutuante; ... flutuante atraso; \end{lstlisting} O typedef é extremamente útil para simplificar nomes muito longos e complexos, como por exemplo, nomes de estruturas. Por exemplo: \begin{lstlisting} typedef struct cliente { float divida; int atraso; char nome[40]; } cliente; cliente lista[50]; \end{lstlisting} Nesse exemplo, cliente não é uma variável do tipo struct cliente, mas um outro nome para struct cliente. O código a seguir é equivalente ao anterior. \begin{lstlisting} struct cliente { float divida; int atraso; char nome[40]; }; typedef struct cliente cliente; cliente lista[50]; \end{lstlisting} \subsection{Campos de bit (bit fields)} A linguagem C permite que sejam acessados os bits individuais dentro de um tipo de dados maior como, por exemplo, um byte. Campos de bit são campos de uma estrutura formatados de modo a ocuparem uma quantidade de bits definível pelo programador. Éssa propriedade é muito útil quando se deseja definir estruturas que vão conter dados que, normalmente, precisariam de menos que 8 bits (menor tamanho possível para um tipo de dado comum), ou mesmo um dado que ocuparia uma quantidade de bits que não seja múltiplo de 8. A forma genérica para se definir um campo de bit é: \begin{lstlisting} struct nome estrutura{ tipo var var : nbits; ... }; \end{lstlisting} Onde, além dos elementos tradicionais de uma estrutura, existe um novo elemento denominado nbits, que determina o tamanho, em bits, que a variável deve ocupar. Observe que nbits não pode ser maior que o tamanho normal do tipo de dado tipo var. \begin{lstlisting} struct pessoa { char nome[30]; int sexo: 1; // 0-> feminino , 1-> masculino int idade: 7; int estado civil: 2; // 0 -> solteiro // 1 -> casado // 2 -> desquitado // 3 -> viuvo } pessoas[2]; int main () { pessoas[0].nome = "Jose Maria da Silva"; pessoas[0].sexo = 1; // masculino pessoas[0].idade = 34; pessoas[0].estado civil = 0; // solteiro } \end{lstlisting} \subsection{Exercícios} 1. Defina uma estrutura para armazenar os seguintes campos: \begin{itemize} \item produto: [10 caracteres] \item pago: (valores que pode assumir: s/n) \item código: (valores que pode assumir: 0-31) \item setor: (valores que pode assumir: 0-3) \end{itemize} Defina duas estruturas, uma sem campos de bits e outra com campos de bits de modo a ocupar o menor tamanho possível. Baseado na tabela abaixo, compare a diferença entre os tamanhos de uma estrutura normal (sem campos de bits) e uma estrutura com campos de bits. \begin{center} \begin{tabular}{|l|l|} \hline char & 1 \\ \hline int & 4 \\ \hline \end{tabular} \end{center} \end{document}