Linguagem de
programac
¸˜
ao C
Universidade do Estado do Rio de Janeiro Centro de Tecnologia e Ciˆencias
Instituto de F´ısica
Sum´
ario
1 Introduc¸˜ao 1
1.1 Aspectos b´asicos do c´odigo-fonte em C . . . 1
1.2 Compiladores C e C++ . . . 4
1.3 Compilac¸˜ao dos c´odigos-fonte . . . 5
2 Tipos de dados 7 2.1 Representac¸˜ao de Dados na Mem´oria . . . 8
2.1.1 Representac¸˜ao de N´umeros Inteiros. . . 9
2.1.2 Representac¸˜ao de N´umeros Reais . . . 11
2.1.3 Tipos de Dados Representados . . . 11
2.1.4 Organizac¸˜ao dos Dados na Mem´oria . . . 12
2.2 Tipos Definidos de Dado . . . 14
2.2.1 Tipo Literal: char . . . 14
2.2.2 Tipo Inteiro: char e int . . . 17
2.2.3 Tipo Real: float e double . . . 18
2.2.4 Tipo Indefinido: void . . . 20
2.2.5 Tipo L´ogico . . . 20
2.2.6 Modificadores de Tipo signed e unsigned . . . 21
2.2.7 Modificadores de Tipo short e long . . . 22
2.2.8 Type Casting . . . 23
2.2.9 Resumo dos Tipos intr´ınsecos . . . 24
2.3 Vetores, Matrizes e Strings . . . 24
2.3.1 Declarac¸˜ao de Vetores e Matrizes . . . 24
2.3.2 Cadeia de Caracteres (Strings) . . . 25
2.4 Tipos Abstratos de Dado . . . 27
2.4.1 Estruturas de Dados: struct . . . 27
2.4.2 Enumerac¸˜oes: enum . . . 30
2.4.3 Uni˜oes: union . . . 31
2.4.4 Campo de Bits . . . 33
2.4.5 Declarac¸˜ao typedef . . . 35
2.5 Ponteiros . . . 36
2.5.1 Ponteiros e Enderec¸o de Mem´oria . . . 36
2.5.2 Declarac¸˜ao de Ponteiros . . . 38
2.5.3 Operador de Enderec¸amento de Dado(&) . . . 39
2.5.4 Operador de Referenciamento de Dado (*). . . 39
2.5.5 Operador de Referenciamento de Campo de Estrutura (->) . . . . 40
2.5.6 Aritm´etica de Ponteiros . . . 41
2.5.7 Um Cuidado Mais Especial. . . 41
2.5.8 Relac¸˜ao entre Ponteiros, Vetores e Matrizes . . . 42
2.5.9 Alocac¸˜ao de Mem´oria para Ponteiros . . . 46
3 Operadores Matem´aticos, L´ogicos e Bin´arios 47 3.1 Convers˜ao de Tipo e Operador de Atribuic¸˜ao . . . 47
3.2 Operadores Aritm´eticos . . . 50
3.3 Operadores Relacionais . . . 53
3.3.1 Operadores de Igualdade (==) e de Diferenc¸a (!=) . . . 53
3.3.2 Operadores Maior (>) e Maior que (>=) . . . 53
3.3.3 Operadores Menor (<) e Menor que (<=) . . . 53
3.4 Operadores L´ogicos . . . 53
3.4.1 Operador de Conjunc¸˜ao: E L´ogico (&&) . . . 53
3.4.2 Operador de Disjunc¸˜ao: OU L´ogico (||) . . . 53
3.4.3 Operador de Negac¸˜ao (!) . . . 53
3.5 Operadores Bin´arios . . . 53
3.5.1 Operador E Bin´ario (&) . . . 53
3.5.2 Operador OU Bin´ario (|) . . . 53
3.5.3 Operador OU EXCLUSIVO Bin´ario (ˆ) . . . 54
3.5.5 Operadores de Deslocamento Bin´ario (<< e >>) . . . 54
3.6 Operadores de Atribuic¸˜ao Concatenados . . . 54
3.6.1 Operador de Adic¸˜ao (+=) . . . 54
3.6.2 Operador de Subtrac¸˜ao (-=) . . . 54
3.6.3 Operador de Multiplicac¸˜ao (*=) . . . 54
3.6.4 Operador de Divis˜ao (/=) . . . 54
3.6.5 Operador L´ogico E Bin´ario (&=) . . . 54
3.6.6 Operador L´ogico OU Bin´ario (|=) . . . 54
3.6.7 Operador L´ogico OU EXCLUSIVO Bin´ario (ˆ=) . . . 55
3.6.8 Operadores de Deslocamento Bin´ario (<<= e >>=) . . . 55
4 Estruturas de Controle de Execuc¸˜ao 57 4.1 Estruturas de Condic¸˜ao. . . 57
4.1.1 Estrutura do Se . . . 57
4.1.2 Estrutura do Se Tern´ario . . . 59
4.1.3 Estrutura de Selec¸˜ao de Caso . . . 60
4.2 Estruturas de Repetic¸˜ao . . . 61
4.2.1 Estrutura de Lac¸o Definido. . . 61
4.2.2 Estrutura de Lac¸o Condicional. . . 63
5 Func¸˜oes 65 5.1 Argumentos por Valor e por Referˆencia . . . 67
5.2 Prot´otipo de uma Func¸˜ao . . . 69
5.3 Relac¸˜ao das Func¸˜oes Intr´ınsecas . . . 70
5.4 Func¸˜oes de Entrada e Sa´ıda Padr˜oes . . . 79
6 Diretivas de Compilac¸˜ao 81 6.1 Diretiva #include . . . 81
6.2 Diretivas #define|#undef . . . 83
6.3 Diretivas #if –#elif –#else–#endif . . . 86
6.4 Diretivas #ifdef |#ifndef –#endif . . . 87
Lista de Figuras
2.1 Tabela ASCII de valores literais. . . 14
Introduc
¸˜
ao
1.1
Aspectos b´
asicos do c´
odigo-fonte em C
Para comec¸ar, vejamos um exemplo de c´odigo-fonte simples em C para, depois, apresentarmos cada elemento componente dele.
C´odigo 1.1: exemplo1.c # i n c l u d e < s t d l i b . h > # i n c l u d e < s t d i o . h > # i n c l u d e < s t r i n g . h > # i n c l u d e < m a t h . h > int m a i n (v o i d) { int a ; f l o a t b , c ; p r i n t f (" d i g i t e um v a l o r i n t e i r o : \ n "); s c a n f (" % d ",& a ); b = 0 . 5 ; c = a + b ; p r i n t f (" a s o m a d e s t e v a l o r com 0.5 eh : % f \ n ", c ); 1
r e t u r n 0; }
Um c´odigo-fonte em C ´e composto por instruc¸˜oes de compilac¸˜ao e instruc¸˜oes de programac¸˜ao.
Asinstruc¸˜oes de compilac¸˜ao (ou diretivas de compilac¸˜ao) iniciam com o s´ımbolo “#” e s˜ao direcionadas ao compilador. Elas n˜ao geram c´odigo execut´avel e somente tˆem efeito durante o processo de compilac¸˜ao. Indicam ac¸˜oes que o compilador deve executar ou modificam um comportamento espec´ıfico do compilador.
Asinstruc¸˜oes de programac¸˜ao (tudo que n˜ao comec¸a com “#”) podem ser clas-sificadas como comandos de declarac¸˜ao e comandos de execuc¸˜ao. Os comandos de declarac¸˜ao s˜ao usados para definir vari´aveis, tipos de vari´aveis, estruturas de dados e func¸˜oes. Os comandos de execuc¸˜ao s˜ao as instruc¸˜oes que ser˜ao efetivamente executadas pelo processador. Durante o processo de compilac¸˜ao, estes comandos s˜ao traduzidos em uma linguagem intermedi´aria, chamada de linguagem objeto (que n˜ao tem nada a ver com orientac¸˜ao a objeto) para depois, durante o processo de link-edic¸˜ao, ser convertida em linguagem de m´aquina.
Neste exemplo inicial, vocˆe pode ver todos estes tipos de instruc¸˜ao. As quatro primeiras linhas de c´odigo (quatro diretivas #include) informam ao compilador que ele deve incluir, no processo de compilac¸˜ao, quatro arquivos com extens˜ao “.h” (chamados de arquivos de cabec¸alho, do inglˆes header files). Estes arquivos contˆem os prot´otipos de func¸˜oes intr´ınsecas da linguagem C e s˜ao usadas basicamente, durante o processo de compilac¸˜ao, para verificar se as func¸˜oes intr´ınsecas usadas no programa est˜ao de-claradas corretamente (sintaxe da linguagem). Os arquivos de cabec¸alho n˜ao cont´em o c´odigo-fonte das func¸˜oes, mas apenas as declarac¸˜oes (prot´otipos). As func¸˜oes em si est˜ao dispon´ıveis na forma de bibliotecas pr´e-compiladas que acompanham a instalac¸˜ao do compilador. Os arquivos de cabec¸alho, por assim dizer, funcionam como uma lista condensada com o nome das func¸˜oes e seus respectivos argumentos.
• O arquivo stdlib.h cont´em os prot´otipos das func¸˜oes intr´ınsecas b´asicas.
• O arquivo stdio.h cont´em os prot´otipos das func¸˜oes b´asicas de entrada e sa´ıda de dados.
strings (cadeia de caracteres).
• O arquivo math.h cont´em os prot´otipos das func¸˜oes b´asicas de matem´atica. Depois das diretivas de compilac¸˜ao, encontramos as instruc¸˜oes de programac¸˜ao. A primeira instruc¸˜ao ´e uma declarac¸˜ao de func¸˜ao: a func¸˜ao int main(void). Esta ´
e a func¸˜ao principal das linguagens C e C++. Ela desempenha um papel similar ao comando program do Fortran e do Pascal, isto ´e, define o ponto de entrada do programa execut´avel. Pode faltar quase tudo num c´odigo-fonte em C e C++, mas n˜ao pode faltar a func¸˜ao main().
Dentro da func¸˜ao main(), encontramos as declarac¸˜oes de vari´aveis e os comandos de execuc¸˜ao. As vari´aveis s˜ao declaradas sempre no in´ıcio da codificac¸˜ao de um bloco de instruc¸˜oes (delimitado pelos s´ımbolos “{” e “}”). Por que? Porque, sendo a linguagem C uma linguagem de programac¸˜ao estruturada, as vari´aveis devem ser declaradas antes de serem usadas. Durante o processo de compilac¸˜ao, as declarac¸˜oes de vari´aveis s˜ao usa-das para montar uma tabela de identificadores que dever˜ao armazenar dados. Se uma vari´avel ´e declarada duas vezes, o compilador tem como detectar o erro, pois o processo de compilac¸˜ao tentar´a criar dois identificadores com os mesmos nomes. Se isso fosse feito depois de processar os comandos de execuc¸˜ao, o compilador perderia o controle na identificac¸˜ao de que vari´avel est´a sendo referenciada numa instruc¸˜ao espec´ıfica. Ent˜ao, declarac¸˜ao de vari´avel vem sempre antes dos comandos de execuc¸˜ao, iniciando os blocos de instruc¸˜oes.
Depois das declarac¸˜oes de vari´aveis, vemos comandos de atribuic¸˜ao, operac¸˜ao aritm´etica, chamadas de func¸˜oes e de retorno de dado.
Uma informac¸˜ao muito importante que deve ser levantada aqui, no in´ıcio do mate-rial sobre as linguagens C e C++, ´e que, em C e C++, n˜ao existe a declarac¸˜ao formal de sub-rotina ou procedimento. Tudo em C e C++ ´e func¸˜ao. O que ir´a definir se a func¸˜ao se comportar´a como uma func¸˜ao tradicional (que retorna um dado) ou como uma sub-rotina (que n˜ao possui retorno de informac¸˜ao atrav´es de operac¸˜ao de atribuic¸˜ao) ´e o tipo da func¸˜ao. Como ser´a visto no cap´ıtulo2sobre tipos de dados, existe um tipo cha-mado de void que indica a ausˆencia de tipo definido. Veja bem, n˜ao ´e “tipo indefinido”, ´e “ausˆencia de tipo definido”. Ent˜ao, uma func¸˜ao declarada como sendo do tipo void n˜ao retorna dado, ou seja, comporta-se como uma sub-rotina. Outro uso do tipo void est´a na declarac¸˜ao de func¸˜oes que n˜ao precisam de argumentos (no nosso exemplo, a func¸˜ao
main() ´e declarada com a palavra reservada void no lugar dos argumento. Isto significa que a func¸˜ao main n˜ao recebe qualquer informac¸˜ao de fora do programa. Poderia ser diferente? Sim, poderia. Se o programador precisar passar dados para dentro da func¸˜ao
main, ele declara a lista de argumentos da forma tradicional: tipo do argumento e nome
do argumento.
Agora que vocˆe j´a teve um primeiro contato com a linguagem, vamos falar um pouco do compilador e da compilac¸˜ao.
1.2
Compiladores C e C++
Existem diversos compiladores C e C++. Pensando em compiladores de acesso
livre, os mais usados s˜ao os compilares GNU gcc para a linguagem C e GNU g++
para a linguagem C++. Ambos os compiladores possuem vers˜oes para Windows,
Ma-cOS e Linux. No ambiente Linux, estes compiladores s˜ao padr˜ao e j´a vem com a instalac¸˜ao da distribuic¸˜ao do sistema operacional. Caso n˜ao estejam instalados, ´e s´o acessar o reposit´orio de programas da distribuic¸˜ao e instal´a-los. Para Windows, existem as distribuic¸˜oesMinGW e CygWin, ambos gratuitos.
Estes compiladores n˜ao possuem interfaces de desenvolvimento (as famosas IDE’s, do inglˆes Integrated Development Environment), mas aceitam que sejam instaladas a parte. As IDE’s para Windows mais famosas s˜ao oVisualC da Microsoft e o C++Buider da CodeGear, ambos n˜ao gratuitos. No campo das IDE’s de c´odigo aberto, tanto para Windows como para Linux, existem alguns programas bons. Destacaria oCode::Blocks que tem vers˜oes para Windows e Linux. Outras duas IDE’s s˜ao:Eclipse e NetBeans, que originalmente foram concebidas para a linguagem Java, mas possuem pacotes para as linguagens C e C++.
Para todos os efeitos, este material n˜ao tem a intenc¸˜ao de forc¸ar nenhuma das IDE’s mencionadas, apenas determinar que os exemplos apresentados ser˜ao todos tes-tados nos compiladores gcc e g++. Fica ao cargo de cada um decidir se instala ou n˜ao uma IDE.
1.3
Compilac
¸˜
ao dos c´
odigos-fonte
As linhas de comando dos compiladores s˜ao exatamente iguais `as usadas no For-tran (com o f95 ou g95 ou gforFor-tran):
• gcc:
– por etapas:
gcc -c <lista c´odigos fontes>
gcc -o <programa execut´avel><lista c´odigos objeto>
– forma resumida:
gcc -o <programa execut´avel><lista c´odigos fontes> • g++:
– por etapas:
g++ -c <lista c´odigos fontes>
g++ -o <programa execut´avel><lista c´odigos objeto>
– forma resumida:
g++ -o <programa execut´avel><lista c´odigos fontes>
Os arquivos de c´odigo fonte em C usam a extens˜ao “.c” e os arquivos de c´odigo fonte em C++ usam a extens˜ao “.cpp” ou “.C”. No processo de compilac¸˜ao (gcc -c ou
g++ -c), os c´odigos fontes d˜ao origem aos arquivos objeto cujas extens˜oes s˜ao “.o”. No processo de link-edic¸˜ao (gcc -o ou g++ -o), todos os arquivos objeto gerados na compilac¸˜ao s˜ao processados para gerar o programa execut´avel.
Existe a forma reduzida de comando onde a compilac¸˜ao e a link-edic¸˜ao s˜ao execu-tadas sequencialmente. O resultado final ´e o mesmo em qualquer uma das duas opc¸˜oes. A vantagem de se compilar por etapas se destaca quando o projeto que est´a sendo de-senvolvido cont´em muitos arquivos de c´odigo. ´E sempre interessante compilar os arqui-vos separadamente para se ter um controle melhor sobre os erros. Quando se compila v´arios arquivos simultaneamente, a lista de erros pode ser t˜ao grande que a manutenc¸˜ao e correc¸˜ao dos erros fica prejudicada, al´em de desestimular.
Ent˜ao, como a linguagem est´a montada sobre esta estrutura, vale olharmos para cada uma delas separadamente e com mais detalhes.
Outro detalhe ´e a codificac¸˜ao dos exemplos. Vai acontecer dos exemplos conterem elementos (instruc¸˜oes) que n˜ao tenham sido apresentados formalmente ainda, em espe-cial as diretivas de compilac¸˜ao. Mas, a intenc¸˜ao ´e que os exemplos possam ser copiados e testados. No tempo certo, as d´uvidas ser˜ao sanadas. Portanto, m˜aos `a obra.
Tipos de dados
Como em toda linguagem formal, a linguagem C possui tipos pr´e-definidos de dados, ou tipos intr´ınsecos, que s˜ao definidos como:
• tipo literal (que armazena caracteres alfanum´ericos);
• tipo num´erico inteiro (para dados num´ericos dentro do conjunto dos n´umeros naturais positivos e negativos);
• tipo num´erico real (pr´oprio para os n´umeros racionais); • tipo l´ogico (para o “falso” e “verdadeiro”).
• tipo ponteiro (relacionado aos enderec¸os de dados na mem´oria).
Cada um destes tipos intr´ınsecos pode ser usado para a declarac¸˜ao de dados escala-res (um ´unico valor por dado) ou dados ordenados (v´arios valores por dado, ordenados atrav´es de ´ındices).
E para que o programador tenha uma liberdade de criac¸˜ao, a linguagem C permite tamb´em a definic¸˜ao de novos tipos de dados a partir da abstrac¸˜ao dos tipos intr´ınsecos que s˜ao os tipos derivados. Dentre eles, encontram-se:
• as estruturas de dados; • as enumerac¸˜oes; • as uni˜oes.
Um t´opico particular da linguagem C versa sobre osponteiros, elemento este que distingui o C de todas as outras linguagens. Os ponteiros s˜ao uma ferramenta muito poderosa que permite ao programador trabalhar diretamente na mem´oria do computa-dor e descer a um n´ıvel mais baixo de programac¸˜ao (o que nem sempre ´e necess´ario). Para tanto, se faz necess´ario relembrar algumas caracter´ısticas de representac¸˜ao bin´aria e organizac¸˜ao de dados na mem´oria do computador. Isso facilitar´a a compreens˜ao de recursos de programac¸˜ao em C tais como passagem de parˆametros por referˆencia, declarac¸˜ao de vetores e matrizes e alocac¸˜ao dinˆamica de mem´oria.
2.1
Representac
¸˜
ao de Dados na Mem´
oria
Todo dado deve estar armazenado em algum lugar no computador. O local mais prov´avel ´e a mem´oria RAM (do inglˆes random access memory). A arquitetura dos com-putadores n˜ao permite que os dados sejam armazenados usando a mesma representac¸˜ao gr´afica que n´os, humanos, usamos. Como todos devem recordar, os computadores s´o re-conhecem dois tipos de informac¸˜ao: “ligado” e “desligado”. Para maior conforto nosso, as informac¸˜oes “ligado” e “desligado” podem ser representados como “falso” e “verda-deiro” ou 1 e 0, respectivamente. Ainda assim, representar uma informac¸˜ao complexa na forma de 0’s e 1’s n˜ao se traduz numa forma completamente confort´avel de leitura, pois n˜ao estamos acostumados a ver as coisas desta forma. Mas ´e muito importante que sejamos capazes de entender como um dado ´e armazenado na mem´oria, pois isso interfere diretamente naquilo que estamos tentando fazer, ou seja, na programac¸˜ao.
Ent˜ao, de in´ıcio, vamos lembrar dos termos mais usuais, quais sejam: os bits, os
bytes e seus prefixos de grandeza (quilo, mega, giga, tera, etc.).
bit (b): O bit ´e a menor informac¸˜ao representada no computador. Pode assumir dois va-lores distintos: 0 e 1. Eletronicamente corresponde `as situac¸˜oes de: tem corrente, n˜ao tem corrente, ou tens˜ao diferente de zero, tens˜ao igual a zero. Da´ı a noc¸˜ao de “ligado” e “desligado”.
byte (B): O byte ´e o agrupamento de 8 bits. Forma a menor “palavra” em computadores. Sua decodificac¸˜ao, ou seja, sua interpretac¸˜ao ´e obtida usando a base matem´atica bin´aria. Os bytes podem ser concatenados formando palavras de 2 bytes (16 bits),
4 bytes (32 bits), 8 bytes (64 bits) e assim por diante. Repare que os agrupamentos s˜ao sempre em potˆencia de 2 (uma vez que estamos usando notac¸˜ao bin´aria). kilobyte (kB): um kilobyte ´e o agrupamento de 1024 bytes. Este valor n˜ao ´e m´agico;
corresponde ao n´umero 210; ´e a potˆencia de 2 mais pr´oxima do valor decimal 1000
ou 103.
megabyte (MB): um megabyte ´e o agrupamento de 10242 bytes. Segue o racioc´ınio de
potˆencia de 2 mais pr´oximo a 106.
gigabyte (GB): um gigabyte ´e o agrupamento de 10243bytes.
terabyte (TB): um terabyte ´e o agrupamento de 10244 bytes.
petabyte (PB): um pentabyte ´e o agrupamento de 10245 bytes.
exabyte (EB): um exabyte ´e o agrupamento de 10246 bytes.
e por a´ı vai...
2.1.1 Representac¸˜ao de N´umeros Inteiros
O conjunto de n´umeros naturais incorporam n´umeros positivos, negativos e nulo. Em bin´ario, tamb´em se faz necess´ario representar este mesmo conjunto. At´e aqui, o byte ´
e apenas um agrupamento de 8 bits, sendo cada d´ıgito, 0 ou 1, valores compreendidos como positivos. Ent˜ao, como representar um valor negativo?
Uma forma muito natural seria admitir o sinal “+” e “−” prefixando os n´umeros bin´arios. Mas o computador s´o entende 0 e 1. Ent˜ao, o jeito foi criar um padr˜ao (inter-nacional e aceito pela maioria das ind´ustrias de componentes eletrˆonicos) que definisse a representac¸˜ao de n´umeros positivos e negativos em bin´ario. Uma da agˆencias interna-cionais que recomendam padr˜oes ´e a IEEE (do inglˆes, Institute of Electrical and Electronic
Engineering). Nas recomendac¸˜oes de representac¸˜ao de n´umeros bin´arias, consta que o
bit mais significativo (abreviado em inglˆes para msb) do agrupamento de bytes, o bit mais a esquerda, pode ser interpretado como o bit de sinal, seguindo a seguinte codificac¸˜ao:
• se o msb ´e 0, o n´umero representado ´e positivo; • se o msb ´e 1, o n´umero representado ´e negativo;
Neste padr˜ao, pensando em um n´umero com 1 byte, o bit mais a esquerda, o
msb, se torna o indicador de sinal. Sobram 7 bits ent˜ao para representar os n´umeros propriamente ditos.
msb
b7 b6 b5 b4 b3 b2 b1 b0
Usando aritm´etica bin´aria, 27 vale 128. Se pusermos o bit de sinal na frente,
ter´ıamos 128 n´umeros positivos (desde o +0 at´e o +127) e 128 negativos (dede o −0 at´e o −127). Surge um problema: temos dois zeros, o +0 e o −0. N˜ao faz sentido representarmos duas vezes o mesmo valor, at´e porque estar´ıamos desperdic¸ando capaci-dade computacional. Ent˜ao, para superar esta dificuldade, propˆos-se um mecanismo de c´alculo de n´umeros negativos chamado dec´alculo por complemento a 2. Este meca-nismo funciona assim: pegue a representac¸˜ao bin´ario do n´umero positivo; retire o bit de sinal; inverta cada bit da representac¸˜ao, isto ´e, troque os 0’s por 1’s e vice-versa (isto se chamac´alculo de complemento a 1); adicione 1 ao resultado das invers˜oes; acrescente o digito 1 como msb, o bit de sinal; este novo resultado ´e a representac¸˜ao negativa do n´umero positivo inicial.
Um exemplo: o n´umero +9 em bin´ario ´e 0|0001001(b). Colocamos o sufixo “(b)” para lembrarmos que o n´umero est´a em bin´ario e o car´acter “|” para separar o bit de sinal – isso ajuda na visualizac¸˜ao da representac¸˜ao bin´aria. A representac¸˜ao bin´aria do n´umero −9 ser´a 1|1110111(b). O c´alculo do n´umero −9 ´e apresentado a seguir:
|0001001(b) → |1110110(b) /* complemento a 1 */
+ 1(b)
|1110111(b) /* complemento a 2 */
Ent˜ao, o n´umero −9 ´e 1|1110111(b). Este mecanismo pode ser aplicado a qual-quer agrupamento de bytes, sempre lembrando que o bit de sinal ´e o msb, o bit mais a esquerda do agrupamento.
Outros exemplos: o n´umero bin´ario 0|0000000(b) ´e o 0 decimal, 1|1111111(b) ´e o −1 e 1|0000000(b) ´e o n´umero −128.
2.1.2 Representac¸˜ao de N´umeros Reais
Um n´umero real se difere do n´umero inteiro por conta da parte fracion´aria. Os computadores, que s´o trabalham com 0’s e 1’s, precisam de alguma outra forma de padronizac¸˜ao de representac¸˜ao para n´umeros reais. Da´ı que, novamente, a IEEE1gerou uma outra recomendac¸˜ao. Os n´umeros reais s˜ao organizados na forma de mantissa
e expoente. A mantissa do n´umero real deve estar no intervalo 0,0 (fechado) e 1,0
(aberto). O expoente se refere a base 2. Assim, um n´umero real, em bin´ario, deve ser organizado na forma: mantissa×2expoente. A IEEE sugere uma representac¸˜ao m´ınima
para n´umeros reais com 4 bytes de comprimento. Este ´e o chamado “n´umero real de precis˜ao simples”. Dos 4 bytes, o byte mais significativo ´e o expoente e os demais s˜ao a mantissa. O expoente e a mantissa possuem, cada um, um bit de sinal.
O formato bin´ario de um n´umero real usa o ponto decimal e cada bit ap´os o ponto corresponde `a uma potˆencia de 2 com expoente negativo. Por exemplo: o n´umero bin´ario .1(b) corresponde ao n´umero 1 × 2−1, isto ´e, 0, 5 reais. O n´umero 1|.11(b) vale
−(1 × 2−1+ 1 × 2−2), ou seja, −0, 5 − 0, 25 = −0, 75 reais. O n´umero 0|0000001 0|.1100000 00000000 00000000 vale 21× (1 × 2−1+ 1 × 2−2) = 2 × 0, 75 = 1, 5, ou
0|0000000 0|1.100000 00000000 00000000
O d´ıgito 1 antes do ponto ´e uma unidade e o d´ıgito 1 ap´os o ponto corresponde a 0, 5. Logo, este numer˜ao tamb´em vale 1, 5 reais e o expoente corresponde, na pr´atica, ao deslocamento de todos os bits para a direita ou esquerda, dependendo o sinal do expoente. Neste caso, como o expoente ´e positivo, o deslocamento ´e para a esquerda.
Por fim, a IEEE tamb´em recomenda um formato de dupla precis˜ao para os n´umeros reais. Nele, o expoente tem 2 bytes e a mantissa tem 6 bytes. E se pode chegar at´e precis˜ao qu´adrupla com 4 bytes para o expoente e 12 bytes para a mantissa. Na pr´atica, o maior formato para n´umeros reais usa 4 bytes para o expoente e 6 bytes para a mantissa.
2.1.3 Tipos de Dados Representados
Como foi visto, os n´umeros inteiros podem ser representados agrupando um ou mais bytes. Cada agrupamento pode conter n´umeros com limites distintos. Um n´umero
inteiro representado por 1 byte pode armazenar valores entre 0 e 28− 1 (= 255) se for
sem sinal e entre −27 (= −128) e 27− 1 (= 127) se for com sinal. Se a representac¸˜ao
do n´umero inteiro utilizar 2 bytes, as faixas de valores aumentam: entre 0 e 216− 1
(= 65.535) para n´umeros sem sinal e entre −215 (= −32.768) e 215− 1 (= 32.767) para
n´umeros com sinal.
Admitindo diferentes comprimentos em bytes para representac¸˜ao de n´umeros in-teiros, cada linguagem de programac¸˜ao nomeia seus tipos de dados. Na linguagem C, os n´umeros inteiros s˜ao representados com tipos de dados que usam 1, 2 e 4 bytes de comprimento. Cada agrupamento recebe um nome diferente (um tipo diferente de in-teiro). Al´em disso, existe a possibilidade de se “tipar” explicitamente, representac¸˜oes de n´umeros com e sem sinal. A linguagem C tamb´em permite essa situac¸˜ao.
Quanto aos n´umeros reais, as linguagem “tipificam” a precis˜ao segundo a quanti-dade de d´ıgitos poss´ıveis para a parte fracion´aria em decimal. Existe a precis˜ao simples, dupla e qu´adrupla. A linguagem C implementa estes trˆes tipos de n´umeros reais.
Sobram, no rol dos tipos de dados que poderiam ser representados em computador por uma linguagem de programac¸˜ao, os dados l´ogicos e os literais. Algumas linguagens implementam os valores l´ogicosfalso e verdadeiro. N˜ao ´e o caso da linguagem C. Em C, “falso” ´e tudo que ´e igual a nulo e “verdadeiro” ´e tudo que for diferente de nulo. Como a base dos computadores ´e bin´aria, 0 ´e falso e 1 ´e verdadeiro; 00000000(b) ´e falso e qualquer coisa diferente disso ´e verdadeiro.
E os dados literais (caracteres), estes s˜ao codificados segundo um padr˜ao inter-nacional chamado ASCII (que ´e a abreviatura de uma organizac¸˜ao de padronizac¸˜ao americana). Cada car´acter ´e indexado na tabela ASCII. Logo, quando a linguagem C (e provavelmente muitas outras) precisa armazenar um car´acter, ela armazena o ´ındice do car´acter. Como a tabela ASCII tem 256 caracteres, uma palavra de 8 bits ´e suficiente para acess´a-la completamente. Ent˜ao, os caracteres em C s˜ao representados por um tipo de dado com 1 byte de comprimento.
2.1.4 Organizac¸˜ao dos Dados na Mem´oria
Por fim, e n˜ao menos importante, quando um agrupamento de bytes represen-tando um n´umero inteiro ou real ´e armazenado na mem´oria do computador, o mesmo precisa reservar uma sequˆencia cont´ınua de bytes para esta tarefa. A ordem com que os
bytes componentes destes agrupamento s˜ao arranjados tamb´em precisa ser definido de alguma forma.
Durante muito tempo, esta ordem era definida pelo fabricante do dispositivo (fosse ele um computador, um videogame, uma calculadora, um rel´ogio digital ou qualquer outra coisa que usasse dados digitais). Com a popularizac¸˜ao dos computadores, sua miniaturizac¸˜ao, seu barateamento e o aumento da complexidade das redes de compu-tadores, fez-se necess´ario uma padronizac¸˜ao (que ainda n˜ao ´e admitida por todos). O padr˜ao mais comum ´e armazenar dados a partir do byte menos significativo (abreviado em inglˆes como lsb) para o mais significativo (msb). Cada byte de um agrupamento (de um tipo de dado) ´e armazenado em um enderec¸o de mem´oria e o enderec¸o do byte menos significativo ´e o que se chama de enderec¸o base. A partir do enderec¸o base, os demais bytes do dado s˜ao arranjados.
Numa linguagem de alto n´ıvel, quando o programador declara uma vari´avel, ele est´a na verdade solicitando ao computador que localize um espac¸o de mem´oria com um determinado comprimento em bytes (suficiente para armazenar o dado) e que associe o nome da vari´avel ao enderec¸o de mem´oria onde o dado ser´a armazenado. Quando o programador atribui um dado `a vari´avel, ele est´a na verdade solicitando ao computador para copiar o dado no enderec¸o referente `a vari´avel declarada. O computador pega o dado, vˆe o nome da vari´avel (que ´e um identificador), recupera o enderec¸o associado `a vari´avel e transfere o dado para este enderec¸o.
Na linguagem C, existe um “tipo” de dado chamado ponteiro que o diferencia de praticamente todas as outras linguagens (pelo menos as mais antigas). O ponteiro nada mais ´e do que o enderec¸o de mem´oria onde o dado est´a ou o enderec¸o de mem´oria re-ferente a uma vari´avel. Como enderec¸o ´e um n´umero inteiro e precisa ficar armazenado em algum lugar, a vari´avel que armazena enderec¸os ´e dita ser do tipo “ponteiro”. Por que? Porque eleaponta para um enderec¸o espec´ıfico da mem´oria. Simples assim.
Ser˜ao apresentados, nas sec¸˜oes seguintes, os nomes formais dos tipos de dados definidos pela linguagem C. Vocˆe deve reparar que cada tipo de dado tem um compri-mento diferente em bytes. Ser˜ao apresentados os tipos intr´ınsecos, ou seja, pr´e-definidos, os ponteiros, os vetores e matrizes e os tipos de dados que o usu´ario pode elaborar. A linguagem C ´e muito vers´atil, permitindo ao programador uma liberdade de trabalho muito grande como poder´a ser constatado.
2.2
Tipos Definidos de Dado
2.2.1 Tipo Literal: char
O tipo de vari´avel em C usado para armazenar caracteres se chama char. Este tipo tem comprimento de 1 byte e ´e equivalente ao tipo character do Fortran. Os caracteres v´alidos que podem ser associados as vari´aveis char s˜ao os caracteres da tabela ASCII (fig.2.1).
Figura 2.1: Tabela ASCII de valores literais.
O fragmento de c´odigo abaixo mostra a declarac¸˜ao de duas vari´aveis char cha-madas ch e letra. Estas vari´aveis ser˜ao preenchidas com as constantes literais ‘a’ e
‘+’. C´odigo 2.1: exemplo2.c v o i d m a i n (v o i d) { c h a r ch ; c h a r l e t r a ; ch = ’ a ’; l e t r a = ’ + ’; }
Como no Fortran, as vari´aveis podem ser inicializadas diretamente na declarac¸˜ao de vari´avel como a seguir:
C´odigo 2.2: exemplo3.c v o i d m a i n (v o i d) {
c h a r ch = ’ a ’; c h a r l e t r a = ’ + ’; }
Reparar que a constante literal ´e formada por um ´unico car´acter e que este ´e digi-tado entre ap´ostrofos.
A linguagem C reserva algumas constantes literais especiais para controle de edic¸˜ao e exibic¸˜ao de caracteres tais como os exibidos na tabela a seguir.
constante ASCII
literal hexadecimal car´acter significado
\a 0x07 BEL beep
\b 0x08 BS backspace
\f 0x0c FF alimentador de folha
\n 0x0a LF alimentador de linha
\r 0x0d CR retorno de carrilh˜ao \t 0x09 HT tabulac¸˜ao horizontal \v 0x0b VT tabulac¸˜ao vertical \\ 0x5c \ backslash \’ 0x27 ’ ap´ostrofo \” 0x22 ” aspas \? 0x3f ? interrogac¸˜ao
Fora desta tabela, existe um outro car´acter especial chamado NULL que ´e ‘\0’. Como seu nome diz, ele ´e um car´acter nulo que corresponde ao valor zero. Ele ser´a apresentado no t´opico sobre ponteiros.
Para imprimir na tela o conte´udo de vari´aveis do tipo char quando o conte´udo ´e uma constante literal, usamos o seguinte comando:
printf("%c",/*variavel*/);
onde o c´odigo de formatac¸˜ao “%c” indica que um car´acter dever´a ser impresso. E para que a func¸˜ao printf() funcione corretamente, ´e necess´ario incluir a diretiva “#include <stdio.h>” no in´ıcio do c´odigo. Repetindo o exemplo2:
C´odigo 2.3: exemplo4a.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r ch = ’ a ’; c h a r l e t r a = ’ + ’; p r i n t f (" % c ", ch ); p r i n t f (" % c ", l e t r a ); }
Para “quebrar” a linha ap´os a impress˜ao do conte´udo, pode-se incluir a constante literal “\n” na string de formatac¸˜ao:
C´odigo 2.4: exemplo4b.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r ch = ’ a ’; c h a r l e t r a = ’ + ’; p r i n t f (" % c % c \ n ", ch , l e t r a ); }
2.2.2 Tipo Inteiro: char e int
Em C, vari´aveis que armazenam valores inteiros s˜ao do tipo char e int. O tipo char ´
e o mesmo usado para armazenar caracteres e, neste caso, o tipo character do Fortran n˜ao possui correspondˆencia. Quando usado para armazenar valores inteiros, o tipo char pode assumir valores entre -128 e 127, que s˜ao os valores poss´ıveis para um n´umero inteiro de 8 bits, sendo um deles o bit de sinal.
C´odigo 2.5: exemplo5.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r x = -100; c h a r i = 54; p r i n t f (" % d % d \ n ",x , i ); }
O c´odigo de controle de formatac¸˜ao para impress˜ao de um n´umero inteiro ´e “%d”. Repare que mesmo a vari´avel sendo do tipo char, o que ser´a impresso na tela ´e o n´umero inteiro armazenado na vari´avel, mesmo que a vari´avel tenha sido inicializada com uma constante literal:
# i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r x = ’ a ’; c h a r i = ’ 1 ’; p r i n t f (" % d % d \ n ",x , i ); }
Neste exemplo, o compilador substitui as constantes literais pelos seus respectivos ´ındices na tabela ASCII: o ´ındice do car´acter ‘a’ ´e 97 e do car´acter ‘1’ ´e 49.
O tipo int tem comprimento de 32 bits (4 bytes) e pode armazenar n´umeros entre -2.147.483.648 e 2.147.483.647, ou seja, 31 bits para representar o n´umero e 1 bit de sinal. Este tipo equivale ao tipo integer do Fortran.
C´odigo 2.7: exemplo6.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { int k = 10000 , a , h ; a = - 1 2 3 4 5 6 7 ; h = 8 5 7 4 8 4 0 3 ; p r i n t f (" % d \ t % d \ t % d \ n ",k , a , h ); }
A constante literal “\t” ´e respons´avel pela tabulac¸˜ao da impress˜ao. O padr˜ao de tabulac¸˜ao em C s˜ao 8 caracteres.
2.2.3 Tipo Real: float e double
No Fortran, um n´umero real de precis˜ao simples recebe o nome de real. Em C, o n´umero real de precis˜ao simples ´e o float, que possui precis˜ao de 7 d´ıgitos e 4 bytes de comprimento. Os valores limites do tipo float s˜ao ±3, 4 × 10±38.
# i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { f l o a t f = 0 . 0 3 4 5 3 ; f l o a t x = 1.2 e - 1 0 ; f l o a t y = -0.4356 e23 ; f l o a t z = 10; p r i n t f (" % f , % f , % f , % f \ n ",f , x , y , z ); }
O c´odigo de controle de formatac¸˜ao para o tipo float ´e “%f”. O n´umero real ´e impresso na forma decimal. A quest˜ao ´e que nem sempre este estilo de formatac¸˜ao ´
e adequado `a magnitude do n´umero. O resultado do exemplo anterior ´e prova disto. Ent˜ao, uma alternativa ´e imprimir o n´umero real no formato de notac¸˜ao cient´ıfica. O c´odigo de formatac¸˜ao ´e “%e”. Experimente trocar a formatac¸˜ao no exemplo anterior para ver o efeito.
Al´em do tipo float, a linguagem C possui outro tipo de vari´avel para armazenar um n´umero real com um n´umero maior de d´ıgitos que ´e o double. Sua precis˜ao ´e de 15 d´ıgitos e possui 64 bits (ou 8 bytes) de comprimento. Seus valores limites s˜ao ±1, 7×10±308. O double n˜ao possui equivalente direto no Fortran. ´E necess´ario modificar
o tipo de precis˜ao atrav´es da instruc¸˜ao select kind precision. C´odigo 2.9: exemplo8.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { d o u b l e G , k , g , Na ; G = 6 . 6 7 2 e - 1 1 ; /* c o n s t a n t e g r a v i t a c i o n a l */ k = 1 . 3 8 0 7 e - 2 3 ; /* c o n s t a n t e de B o l t z m a n n */ g = 9 . 8 0 6 6 5 ; /* g r a v i d a d e p a d r a o */ Na = 6 . 0 2 2 0 e23 ; /* n u m e r o de A v o g a d r o */ p r i n t f (" n u m e r o de A v o g a d r o = % lf \ n " " c o n s t a n t e de B o l t z m a n n = % lf \ n " " c o n s t a n t e g r a v i t a c i o n a l = % lf \ n "
" g r a v i d a d e p a d r a o = % lf \ n ", Na , k , G , g ); }
O c´odigo de controle de formatac¸˜ao para o tipo double ´e “%lf” (long float). Nova-mente, a formatac¸˜ao pode n˜ao ser apropriada para a impress˜ao do n´umero em func¸˜ao de sua magnitude. Experimente trocar “%lf” para “%g” que ajusta a formatac¸˜ao auto-maticamente.
Repare que existem duas vari´aveis que usam a letragˆe, uma delas mai´uscula G e a outra min´uscula g. A linguagem C diferencia estes nomes de vari´aveis (identificadores), pois ela ´e case sensitive, ou seja, “sens´ıvel `a caixa”. Portanto, as vari´aveis G e g s˜ao distintas.
2.2.4 Tipo Indefinido: void
O tipo void ´e algo que s´o existe na linguagem C. Ele representa a ausˆencia de tipo pr´e-definido. Possui um comprimento de 4 bytes e pode ser usado para armazenar enderec¸os de mem´oria se associado a um ponteiro (que ser´a apresentado mais adiante). Sua aplicac¸˜ao mais intensa se refere `a definic¸˜ao de sub-rotinas que, em C, s˜ao func¸˜oes que n˜ao retornam valores.
2.2.5 Tipo L´ogico
Um dado do tipo l´ogico deve, por definic¸˜ao, assumir dois valores poss´ıveis: falso e verdadeiro. Em C, n˜ao existe um tipo l´ogico pr´e-definido como em Fortran (logical). Para reproduzir as caracter´ısticas de um tipo l´ogico, a linguagem C usa a seguinte regra: qualquer dado igual a zero ´e interpretado como o valor “falso” e, por oposic¸˜ao, qualquer coisa diferente de zero ´e considerado “verdadeiro”.
O tipo l´ogico ´e muito ´util no caso de tomada de decis˜ao. Dependendo do conte´udo de uma vari´avel ou do resultado de uma express˜ao l´ogica, o algoritmo que est´a sendo executado pode ser desviado para uma posic¸˜ao espec´ıfica dentro do c´odigo. O teste l´ogico tamb´em est´a presente nas instruc¸˜oes de repetic¸˜ao. O teste de parada pode usar uma vari´avel l´ogica ou o resultado de uma express˜ao l´ogica para determinar se a iterac¸˜ao prossegue ou para.
C´odigo 2.10: exemplo9.c v o i d m a i n (v o i d) { c h a r a = 0 , b = 1; c h a r c = ’ \0 ’, d = ’ z ’; int g = 0 , h = -100; f l o a t f = 0 , r = 0.1 e - 1 0 ; d o u b l e x = 0 , y = 1 e - 3 0 ; }
Se os testes l´ogicos fossem realizados com as vari´aveis declaradas acima, os re-sultados dos testes para as vari´aveis a, c, g, f e x seriam “falso”. As demais vari´aveis retornariam “verdadeiro”, pois seus conte´udos s˜ao diferentes de zero (nulo). Isso ficar´a mais claro quando for tratado o tema sobre comandos condicionais.
2.2.6 Modificadores de Tipo signed e unsigned
Duas palavras reservadas em C s˜ao usadas para controlar o uso ou n˜ao do bit de sinal em um n´umero inteiro. Estas palavras s˜ao signed e unsigned. Toda vari´avel inteira ´
e, a princ´ıpio, uma vari´avel inteira com sinal (usando-se ou n˜ao a palavra signed). Se h´a a necessidade de se declarar uma vari´avel inteira sem sinal, deve-se usar a palavra reservada unsigned antes da declarac¸˜ao do tipo (somente os tipos char e int aceitam o prefixo signed e unsigned). Em Fortran, n˜ao h´a equivalˆencia para este mecanismo.
Uma vari´avel declarada como unsigned char aceita valores entre 0 e 255 e uma vari´avel do tipo unsigned int assume valores entre 0 e 4.294.967.295. Se uma vari´avel for declarada como unsigned (sem a declarac¸˜ao int), o compilador entende que a vari´avel declarada ´e do tipo unsigned int. Exemplo:
C´odigo 2.11: exemplo10.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r r ; /* 1 b y t e com s i n a l */ u n s i g n e d c h a r t ; /* 1 b y t e sem s i n a l */ int i ; /* 4 b y t e s com s i n a l */ u n s i g n e d j ; /* 4 b y t e s sem s i n a l */
p r i n t f (" s i z e o f ( c h a r )=% d \ n s i z e o f ( u n s i g n e d c h a r )=% d \ n " " s i z e o f ( int )=% d \ n s i z e o f ( u n s i g n e d )=% d \ n ", s i z e o f(c h a r) ,s i z e o f(u n s i g n e d c h a r) ,
s i z e o f(int) ,s i z e o f(u n s i g n e d)); }
A func¸˜ao sizeof(tipo) retorna o comprimento em bytes do tipo passado como argumento.
Caberia uma pergunta aqui que seria a seguinte: o que acontece quando se declara uma vari´avel char, por exemplo, e associa-se um valor maior que 127 a ela?
C´odigo 2.12: exemplo11.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r ch = 1 2 9 ; /* ? */ p r i n t f (" % d \ n ", ch ); }
Para responder isto, ´e necess´ario analisar o byte que representa a vari´avel ch. O n´umero 129(d) em bin´ario ´e 10000001(b). Mas o bit mais significativo (msb: most
signi-ficant bit), que ´e o bit mais a esquerda, ´e o bit de sinal. Portanto, o processador entende que este n´umero ´e um n´umero negativo. Para descobrir que n´umero negativo ´e este, ´e necess´ario calcular-se o complemento a dois dele.
|0000001(b) → |1111110(b) /* complemento a 1 */
+ 1(b)
|1111111(b) /* complemento a 2 */
O n´umero bin´ario |1111111(b) ´e o decimal 127(d). Portanto, o computador en-tender´a internamente que “char ch = 129;” ´e, na verdade, “char ch = -127;”.
2.2.7 Modificadores de Tipo short e long
Outras duas palavra que interferem na declarac¸˜ao de vari´aveis s˜ao as palavras
um comprimento em bytes menor ou maior respectivamente. O tipo short int possui 2
bytes e, como tem sinal, pode assumir valores desde -32.768 at´e 32.767. Se o tipo da vari´avel for declarado como unsigned short int, ent˜ao a vari´avel possuir´a os mesmos 2
bytes de comprimento, mas aceitar´a valores entre 0 e 65.535. Uma vari´avel do tipo long
int mant´em o mesmo n´umero de bytes de comprimento que o tipo int, isto ´e, 4 bytes. Portanto, pode armazenar valores entre -2.147.483.648 e 2.147.483.647. A linguagem C aceita a declarac¸˜ao de vari´aveis usando-se somente as palavras reservadas short e long. Ela entende que as vari´aveis ser˜ao do tipo short int e long int respectivamente, mas esta n˜ao ´e uma boa regra de programac¸˜ao.
O tipo double tamb´em admite o modificador long. Uma vari´avel do tipo long double pode assumir valores com precis˜ao ou amplitudes muito grandes (±3, 37 × 10±4932). Ela
possui um comprimento de 80 bits (ou 10 bytes) com 18 d´ıgitos de precis˜ao.
Os compiladores Fortran possuem um mecanismo que permite definir o compri-mento de uma vari´avel inteira, mas este mecanismo n˜ao ´e padronizado. Alguns com-piladores aceitam a declarac¸˜ao integer*8 como um inteiro de 8 bytes, outros declaram
integer 8, e h´a aqueles que definem integer(8). Substituindo o n´umero 8 por 4 ou 2, seria poss´ıvel declarar-se vari´aveis inteiras com 4 ou 2 bytes respectivamente.
2.2.8 Type Casting
Type casting ´e um mecanismo de convers˜ao de tipos que nada mais ´e que colocar um dos tipos predefinidos (char, int, float, double, short, long, unsigned e assim por diante) entre parˆenteses na frente da vari´avel a ter o tipo convertido. Veja que esta operac¸˜ao n˜ao muda o tipo da vari´avel, mas somente o seu conte´udo no momento de uma associac¸˜ao. Veja o seguinte exemplo:
C´odigo 2.13: exemplo12.c # i n c l u d e < s t d i o . h > v o i d m a i n (v o i d) { c h a r c ; int i ; f l o a t f = 2 0 0 . 0 ; i = (int) f ; /* C o n v e r t e r 2 0 0 . 0 p a r a i n t e i r o
s i g n i f i c a t r u n c a r a p a r t e d e c i m a l . */ c = (c h a r) f ; /* Aqui , a l e m do n u m e r o 2 0 0 . 0 ser t r u n c a d o , p o i s o t i p o c h a r so a c e i t a n u m e r o s i n t e i r o s , o n u m e r o 200 u l t r a p a s s a o l i m i t e de r e p r e s e n t a c a o do c h a r . Logo , o n u m e r o 200 s e r a i n t e r p r e t a d o c o m o -56. */ p r i n t f (" % f \ t % d \ t % d \ n ",f , i , c ); }
2.2.9 Resumo dos Tipos intr´ınsecos
Tipo bits Faixa de valores
unsigned char 8 0 : 255
char 8 −128 : 127
short int 16 −32.768 : 32.767
unsigned short int 16 0 : 65.535
unsigned int 32 0 : 4.294.967.295
int 32 −2.147.483.648 : 2.147.483.647
unsigned long int 32 0 : 4.294.967.295
long int 32 −2.147.483.648 : 2.147.483.647
float 32 ±3, 4 × 10±38
double 64 ±1, 7 × 10±308
long double 80 ±3, 4 × 10±4932
2.3
Vetores, Matrizes e Strings
2.3.1 Declarac¸˜ao de Vetores e Matrizes
Os vetores e matrizes s˜ao sequˆencias cont´ınuas de um mesmo tipo de vari´avel cujos elementos individuais podem ser acessados atrav´es de ´ındices. Em C, um vetor ´
e declarado definindo-se o seu tipo de vari´avel e o nome do vetor seguido de sua di-mens˜ao:
C´odigo 2.14: exemplo13.c v o i d m a i n (v o i d) { u n s i g n e d v _ i n t [ 1 0 0 ] ; /* T i p o : u n s i g n e d int N o m e : v _ i n t D i m e n s a o : 100 e l e m e n t o s . */ }
Uma matriz ´e declarada da mesma forma: tipo, nome da vari´avel e suas di-mens˜oes. Mas cada dimens˜ao ´e apresentada individualmente entre colchetes:
C´odigo 2.15: exemplo14.c v o i d m a i n (v o i d) { d o u b l e d m a t [ 1 0 ] [ 1 0 ] ; /* T i p o : d o u b l e N o m e : d m a t D i m e n s a o : 10 x10 . */ }
Como mencionado, elementos individuais nos vetores e matrizes s˜ao acessados atrav´es de ´ındices. ´E importante destacar que os vetores e matrizes em C comec¸am com o ´ındice zero. Portanto, v int[0] ´e o primeiro elemento do vetor v int e dmat[0][0] ´e o elemento inicial da matriz. O ´ındice final do vetor e da matriz ´e sua dimens˜ao menos 1. Para os exemplos apresentados, os elementos v int[99] e dmat[9][9] s˜ao os elementos finais do vetor e da matriz, respectivamente.
Teoricamente, n˜ao h´a limite no dimensionamento das matrizes. Isto significa que se poderia criar matrizes N dimensionais com N tendendo a infinito. L´ogico que isso ´e um exagero, mas fica a ideia de poder-se criar matrizes com dimens˜oes muito grandes. O maior limitante ´e a quantidade de mem´oria dispon´ıvel. Para se calcular a quantidade de mem´oria ocupada por um vetor ou matriz, basta multiplicar as dimens˜oes da estru-tura (vetor ou matriz) pelo total de bytes correspondente ao tipo de dado que define a estrutura. Ent˜ao, o vetor v int com 100 elementos ocupa 100 × 4 bytes ou 400 bytes. A matriz dmat ocupa 10 × 10 × 8 bytes ou 800 bytes de mem´oria.
2.3.2 Cadeia de Caracteres (Strings)
As cadeias de caracteres (strings) em Fortran s˜ao declaradas usando-se o tipo
cadeia pode assumir. Em C, o tipo char ´e usado para declarar a cadeira de caracteres, que n˜ao ´e mais que um vetor de caracteres. E como vetor, sua declarac¸˜ao em C ´e igual `a usada em qualquer outra situac¸˜ao:
C´odigo 2.16: exemplo15.c v o i d m a i n (v o i d) {
/* d u a s s t r i n g s : f r a s e e p a l a v r a . */
c h a r f r a s e [100] , p a l a v r a [ 3 0 ] ; }
A string frase pode conter at´e 100 caracteres e palavra, 30; O que diferencia o C do Fortran ´e a utilizac¸˜ao do car´acter NULL como terminador do vetor. Em Fortran, se uma string chamada “palavra” for declarada com comprimento 30 e contiver a pa-lavra “paralelepipedo” (14 caracteres), os 16 caracteres restantes continuam fazendo parte da string. Caso ela seja impressa na tela do computador, atrav´es do comando “write(unit=*,fmt=“(a)”) palavra”, os 30 caracteres ser˜ao impressos. Para eliminar os 6 caracteres restantes, ´e preciso usar a func¸˜ao “trim(palavra)”.
Em C, o car´acter que indica o fim da string ´e o NULL. No exemplo acima, se o vetor palavra[30] contiver a palavra “paralelepipedo”, o d´ecimo quinto car´acter, ou seja, palavra[14], ser´a o car´acter NULL.
Quando o programador usar a func¸˜ao “printf(“%s”,palavra);” que imprime na tela (idˆentico ao comando “write” do Fortran), ser˜ao impressos somente os 14 caracteres da palavra “paralelepipedo”. Se n˜ao houver o terminador NULL, ser´a impresso a palavra “paralelepipedo” e um monte de outros caracteres. A sequˆencia de “lixos” s´o ir´a parar quando o computador encontrar um car´acter NULL perdido na mem´oria.
Veja que o nome da string (que ´e um vetor) ´e um ponteiro. Portanto, a func¸˜ao “printf( )” recebe um ponteiro contendo o in´ıcio da string e comec¸a imprimindo na tela a sequˆencia de caracteres at´e encontrar o NULL. Enquanto a func¸˜ao n˜ao encontrar o
NULL, ela continuar´a imprimindo. Mas o importante aqui n˜ao ´e a func¸˜ao “printf()”, mas a importˆancia do car´acter terminador NULL e que o programador deve sempre prever que um dos caracteres de sua string ser´a ele, ou seja, ele ter´a que somar 1 no comprimento da string. Sempre. Para armazenar a palavra “paralelepipedo”, ele deve usar no m´ınimo 15 caracteres (14 letras mais o NULL).
das duas dever´a ter, no m´ınimo, a soma dos comprimentos delas mais 1. Uma string nula, isto ´e, sem caracteres (“”), deve ter pelo menos um byte de comprimento, para acomodar o car´acter NULL.
Duas func¸˜oes em C que s˜ao muito ´uteis na manipulac¸˜ao de strings s˜ao: “strcpy( )” e “strlen()”. A func¸˜ao “strcpy( )” usa dois argumentos: um ponteiro que aponta para a ´
area de mem´oria que cont´em a string e outro que aponta para o enderec¸o de destino. A func¸˜ao “strlen()” retorna o total de caracteres que comp˜oem uma string, passada como argumento, exclusive o terminador NULL. O exemplo a seguir mostra como copiar a
string “paralelepipedo” para o vetor palavra:
strcpy(palavra,‘‘paralelepipedo’’);
2.4
Tipos Abstratos de Dado
2.4.1 Estruturas de Dados: struct
O Fortran e o C estabelecem um mecanismo de construc¸˜ao de tipos mais com-plexos que os intr´ınsecos atrav´es de agrupamentos (campos) em estruturas de dados. Uma situac¸˜ao t´ıpica de aplicac¸˜ao de estrutura de dados ´e a criac¸˜ao de bancos de dados. Normalmente, deseja-se cadastrar pessoas agrupando, de alguma forma, seus dados de identificac¸˜ao, tais como: nome completo, identidade, enderec¸o, profiss˜ao, etc.
A palavra reservada em C que define uma estrutura ´e struct. Por exemplo, se o programador deseja criar uma estrutura chamada “tDadosPessoais” e que contenha os campos nome, identidade e enderec¸o, ele deveria escrever o seguinte fragmento de c´odigo: struct tDadosPessoais { char nome[256]; char endereco[256]; int identidade; };
Repare que a declarac¸˜ao struct termina com o ponto e v´ırgula. Para se definir uma vari´avel deste novo tipo de dado, o procedimento ´e similar a definic¸˜ao de vari´aveis de qualquer outro tipo:
struct tDadosPessoais Usuario;
/* variavel: Usu´ario
tipo : struct tDadosPessoais */
struct tDadosPessoais Funcionario; /* variavel: Funcionario
tipo : struct tDadosPessoais */
struct tDadosPessoais Biblioteca; /* variavel: Biblioteca
tipo : struct tDadosPessoais */
Para preencher qualquer um dos campos de uma estrutura em C, ´e necess´ario utilizar o operador ‘.’ (ponto). Este operador indica o acesso a um determinado campo da estrutura. Por exemplo: um determinado funcion´ario tem identidade 9871234. O c´odigo completo seria:
C´odigo 2.17: exemplo16.c v o i d m a i n (v o i d) { s t r u c t t D a d o s P e s s o a i s { c h a r n o m e [ 2 5 6 ] ; c h a r e n d e r e c o [ 2 5 6 ] ; int i d e n t i d a d e ; }; s t r u c t t D a d o s P e s s o a i s F u n c i o n a r i o ; /* t i p o : s t r u c t t D a d o s P e s s o a i s v a r i a v e l : F u n c i o n a r i o */ F u n c i o n a r i o . i d e n t i d a d e = 9 8 7 1 2 3 4 ; /* le - se : c a m p o i d e n t i d a d e da v a r i a v e l F u n c i o n a r i o */ }
Pode-se criar um vetor de estruturas simplesmente adicionando-se a dimens˜ao do vetor ap´os o nome da vari´avel estrutura:
C´odigo 2.18: exemplo17.c v o i d m a i n (v o i d) {
s t r u c t t D a d o s P e s s o a i s { c h a r n o m e [ 2 5 6 ] ; c h a r e n d e r e c o [ 2 5 6 ] ; int i d e n t i d a d e ; }; /* d e c l a r a c a o do v e t o r de e s t r u t u r a U s u a r i o com 1 0 0 0 e n t r a d a s */ s t r u c t t D a d o s P e s s o a i s U s u a r i o [ 1 0 0 0 ] ; s t r c p y ( U s u a r i o [ 0 ] . nome ," J o a o das N e v e s "); s t r c p y ( U s u a r i o [ 0 ] . e n d e r e c o ," Av . A t l a n t i d a , 1 0 0 / 1 0 1 "); U s u a r i o [ 0 ] . i d e n t i d a d e = 1 2 3 4 ; s t r c p y ( U s u a r i o [ 1 0 0 ] . nome ," P a t r i c i a A r a u j o "); s t r c p y ( U s u a r i o [ 1 0 0 ] . e n d e r e c o ," R . Xavier , 3 1 2 / 7 0 8 "); U s u a r i o [ 1 0 0 ] . i d e n t i d a d e = 2 3 4 5 ; s t r c p y ( U s u a r i o [ 1 2 ] . nome ," C a r l o s P a r r e i r a "); s t r c p y ( U s u a r i o [ 1 2 ] . e n d e r e c o ," R . Da Cruz , c a s a 100 "); U s u a r i o [ 1 2 ] . i d e n t i d a d e = 6 3 4 3 ; /* u l t i m a e n t r a d a de um v e t o r de 1 0 0 0 p o s i c o e s */ s t r c p y ( U s u a r i o [ 9 9 9 ] . nome ," R a q u e l de Q u e i r o z "); s t r c p y ( U s u a r i o [ 9 9 9 ] . e n d e r e c o ," R . Paiva , 3 9 8 / 1 1 0 2 "); U s u a r i o [ 9 9 9 ] . i d e n t i d a d e = 4 4 4 4 ; }
A linguagem C permite algumas simplificac¸˜oes muito ´uteis para o programador no que se refere a declarac¸˜ao de vari´aveis de estrutura. A principal ´e a declarac¸˜ao da estrutura propriamente dita combinada `a declarac¸˜ao das vari´aveis. Por exemplo:
C´odigo 2.19: exemplo18.c v o i d m a i n (v o i d) { /* d e c l a r a c a o da e s t r u t u r a c o m b i n a d a a d e c l a r a c a o das v a r i a v e i s */ s t r u c t t D a d o s P e s s o a i s { c h a r n o m e [ 2 5 6 ] ;
c h a r e n d e r e c o [ 2 5 6 ] ; int i d e n t i d a d e ; } U s u a r i o [1000] , F u n c i o n a r i o ; s t r c p y ( U s u a r i o [ 0 ] . nome ," J o a o das N e v e s "); s t r c p y ( U s u a r i o [ 0 ] . e n d e r e c o ," Av . A t l a n t i d a , 1 0 0 / 1 0 1 "); U s u a r i o [ 0 ] . i d e n t i d a d e = 1 2 3 4 ; F u n c i o n a r i o . i d e n t i d a d e = 9 8 7 1 2 3 4 ; s t r c p y ( F u n c i o n a r i o . e n d e r e c o ," R . S . F r a n c i s c o " " Xavier , 524 "); s t r c p y ( F u n c i o n a r i o . nome ," P i q u e t C a r n e i r o Jr . "); }
A declarac¸˜ao das vari´aveis segue a declarac¸˜ao dos campos da estrutura.
2.4.2 Enumerac¸˜oes: enum
As enumerac¸˜oes s˜ao agrupamentos de “constantes” associadas `a n´umeros inteiros. Por exemplo:
enum Posicao { PARA_CIMA, PARA_BAIXO,
PARA_ESQUERDA, PARA_DIREITA };
Nesta declarac¸˜ao de enumerac¸˜ao, a constante PARA CIMA ´e vista pelo
compila-dor como o n´umero 0. As constantes PARA BAIXO, PARA ES-QUERDA e PARA DIREITA
s˜ao interpretadas como os n´umeros 1, 2 e 3 respectivamente. O papel principal das enumerac¸˜oes ´e facilitar a rotulac¸˜ao de determinados n´umeros que tenham um signifi-cado especial. E a enumerac¸˜ao impede que uma vari´avel do tipo enumerac¸˜ao assuma outros valores que n˜ao tenham sido declarados na enumerac¸˜ao.
O mecanismo para declarar uma vari´avel do tipo enum Posicao ´e similar ao de uma estrutura:
enum Posicao posicao;
A vari´avel posicao pode assumir qualquer um dos valores pr´e-definidos para o enum Posicao. Veja o trecho de c´odigo a seguir:
C´odigo 2.20: exemplo19.c v o i d m a i n (v o i d) { /* d e c l a r a c a o da e n u m e r a c a o */ e n u m P o s i c a o { P A R A _ C I M A , P A R A _ B A I X O , P A R A _ E S Q U E R D A , P A R A _ D I R E I T A }; /* d e c l a r a c a o das v a r i a v e i s do t i p o e n u m e r a c a o */ e n u m P o s i c a o posicao , s i t u a c a o , c o m a n d o ; p o s i c a o = P A R A _ C I M A ; s i t u a c a o = P A R A _ D I R E I T A ; c o m a n d o = P A R A _ B A I X O ; }
2.4.3 Uni˜oes: union
As uni˜oes (unions) s˜ao estruturas onde os campos compartilham o mesmo espac¸o da mem´oria, isto ´e, os campos que comp˜oem a uni˜ao est˜ao “superpostos”. Por exemplo, uma uni˜ao que define dois campos: uma vari´avel do tipo int e um vetor de 4 elementos do tipo unsigned char.
union char4int {
unsigned char c[4]; int i;
};
A declarac¸˜ao de vari´aveis do tipo uni˜ao segue o mesmo modelo das declarac¸˜oes de vari´aveis de estruturas. No exemplo a seguir, a vari´avel Byte4 est´a sendo declarada
como sendo do tipo union char4int. Para preencher o campo i de vari´avel Byte4,
usa-se o operador ‘.’.
C´odigo 2.21: exemplo20.c # i n c l u d e < s t d i o . h >
v o i d m a i n (v o i d) {
u n i o n c h a r 4 i n t { u n s i g n e d c h a r c [ 4 ] ; int i ; }; /* d e c l a r a c a o da v a r i a v e l */ u n i o n c h a r 4 i n t B y t e 4 ; /* a c e s s a n d o o c a m p o ’ i ’ da u n i a o */ B y t e 4 . i = 0 x 0 1 2 0 0 8 0 3 ; /* h e x a d e c i m a l */ /* i m p r e s s a o do c o n t e u d o do v e t o r ’ c ’ c o m o n u m e r o s h e x a d e c i m a i s */ p r i n t f (" % x \ t % x \ t % x \ t % x \ n ", B y t e 4 . c [0] , B y t e 4 . c [1] , B y t e 4 . c [2] , B y t e 4 . c [ 3 ] ) ; }
O c´odigo de formatac¸˜ao “%x” imprime um n´umero inteiro na forma de um n´umero hexadecimal. Desta forma, fica mais f´acil conferir o conte´udo de cada byte de dado da estrutura.
O vetor c e a vari´avel i ocupam o mesmo espac¸o na mem´oria. O esquema repre-sentando a mem´oria ajudar´a a visualizar o que se passa no programa.
Enderec¸o Mem´oria Vari´avel
0x1a3c20 0x03 c[0] 0x08 c[1] 0x20 c[2] 0x01 c[3] i 0x1a3c24
Na mem´oria, o byte menos significativo ´e o primeiro a ser escrito: 0x03. O pr´oximo
byte ´e 0x08, o terceiro, 0x20 e o quarto, o mais significativo, 0x01. Repare que c[0] coincide com o byte menos significativo. c[1] coincide com o segundo, c[2] com o terceiro e c[3] com o quarto.
2.4.4 Campo de Bits
O campo de bits ´e um recurso provavelmente exclusivo da linguagem C. Tem por sintaxe a forma de uma estrutura, mas cada campo declarado dentro dela refere-se a uma sequˆencia de bits que pode variar de 1 at´e o limite de 32 bits. O tipo de cada entrada na estrutura de campo de bits deve ser do tipo unsigned, pois o elemento bit n˜ao tem sinal. Por exemplo:
struct CampoBits {
/* bit identificado por b0 tem 1 bit de comprimento. */ unsigned b0:1;
/* o mesmo vale para o bit declarado como b1. */
unsigned b1:1;
/* o campo b2_3 tem comprimento de 2 bits. */
unsigned b2_3:2;
/* e o campo b4_7 tem comprimento de 4 bits. */
unsigned b4_7:4; };
No exemplo acima, a estrutura CampoBits declara quatro agrupamentos de bits: dois com 1 bit de comprimento (b0 e b1), um com dois bits (b2 3) e um com quatro (b4 7). Repare que o total de bits da estrutura ´e 8 que equivale a uma vari´avel char. O acesso a cada bit ´e tratado de forma natural como de qualquer outra estrutura:
C´odigo 2.22: exemplo21.c v o i d m a i n (v o i d) { s t r u c t C a m p o B i t s { u n s i g n e d b0 :1; /* 0 ,1 */ u n s i g n e d b1 :1; /* 0 ,1 */ u n s i g n e d b 2 _ 3 :2; /* 0 . . 3 */ u n s i g n e d b 4 _ 7 :4; /* 0 . . 1 5 */ }; s t r u c t C a m p o B i t s b i t s ; b i t s . b0 = 0;
b i t s . b1 = 1;
b i t s . b 2 _ 3 = 2; /* 2 d e c i m a l em b i n a r i o eh 10. */
b i t s . b 4 _ 7 = 6; /* 6 d e c i m a l em b i n a r i o eh 0 1 1 0 . */
Se o campo de bits for utilizado dentro de uma uni˜ao, cria-se a possibilidade de se converter n´umeros declarados “binariamente” em decimais e vice-versa. ´E interessante perceber que o campo de bits ´e muito apropriado para gerac¸˜ao de “m´ascaras” (muito utilizado quando se precisa acessar o hardware e testar/acionar bits individualmente).
C´odigo 2.23: exemplo22.c v o i d m a i n (v o i d) { s t r u c t C a m p o B i t s { u n s i g n e d b0 :1; u n s i g n e d b1 :1; u n s i g n e d b 2 _ 3 :2; u n s i g n e d b 4 _ 7 :4; }; u n i o n S t a t u s M o u s e { /* o c a m p o de b i t s e a v a r i a v e l u n s i g n e d c h a r */ /* c o m p a r t i l h a m a m e s m a a r e a da m e m o r i a . */ s t r u c t C a m p o B i t s b i t s ; u n s i g n e d c h a r ch ; }; u n i o n S t a t u s M o u s e sm ; sm . ch = 12; /* 12 em b i n a r i o eh 0 0 0 0 1 1 0 0 . O bit b0 eh o */ /* m a i s a d i r e i t a e os b i t s de b4_7 , os m a i s */ /* a e s q u e r d a . P o r t a n t o , b0 eh 0 , b1 eh 0 , */ /* b 2 _ 3 eh 3 (11 em b i n a r i o eh 3 d e c i m a l ) , e */ /* b 4 _ 7 eh 0 ( 0 0 0 0 b i n a r i o ). */ }
Por exemplo: se o bit b0 corresponde ao bot˜ao esquerdo do mouse e o bit b1 ´e o bot˜ao direito, para testar se o usu´ario est´a pressionando o bot˜ao esquerdo, bastaria
verificar se o bit b0 ´e 1; para testar o bot˜ao direito, ´e s´o verificar o bit b1 (o mecanismo que liga o bit b0 e o bit b1 ao status do mouse n˜ao est´a mostrado; assuma que exista um mecanismo que fac¸a isso).
2.4.5 Declarac¸˜ao typedef
typedef ´e uma palavra reservada da linguagem C que simplifica a declarac¸˜ao de estruturas, uni˜oes, enumerac¸˜oes e campos de bits. Atrav´es do typedef, declara-se formal-mente o nome de novos tipos. A sintaxe do typedef ´e simples:
C´odigo 2.24: exemplo23.c /* cria - se a e s t r u t u r a M e u C a d a s t r o . */ s t r u c t M e u C a d a s t r o { c h a r n o m e [ 2 5 6 ] ; c h a r e n d e r e c o [ 2 5 6 ] ; u n s i g n e d t e l e f o n e ; }; /* defini - se o n o v o t i p o p a r a s t r u c t M e u C a d a s t r o c o m o s e n d o s i m p l e s m e n t e C a d a s t r o . */ t y p e d e f s t r u c t M e u C a d a s t r o C a d a s t r o ; v o i d m a i n (v o i d) { /* v e t o r de 100 e l e m e n t o s do t i p o C a d a s t r o ( que eh , na verdade , s t r u c t M e u C a d a s t r o ). */ C a d a s t r o cad [ 1 0 0 ] ; }
Sem o typedef, a linha struct MeuCadastro Cadastro estaria criando uma vari´avel Cadastro do tipo struct MeuCadastro. Com o typedef, o compilador entende que Ca-dastro ´e o novo nome de struct MeuCadastro. Cadastro ´e muito mais compacto que struct MeuCadastro.
2.5
Ponteiros
Dada a importˆancia e a frequˆencia com que os ponteiros s˜ao utilizados em C, este “tipo” ´unico de dado, que ´e t´ıpico do C e de umas poucas outras linguagens, ser´a apresentado de forma cuidadosa nesta sec¸˜ao. Antecipando uma informac¸˜ao crucial, os ponteiros est˜ao intimamente relacionados aos vetores e matrizes.
2.5.1 Ponteiros e Enderec¸o de Mem´oria
Resgatando o que j´a foi apresentado na subsec¸˜ao2.1.4, quando o programador de-clara uma vari´avel, compila o c´odigo-fonte e o programa ´e executado, ele sabe que a sua vari´avel ser´a alocada em algum enderec¸o na mem´oria do computador. O programador n˜ao precisa, a princ´ıpio, saber o enderec¸o da vari´avel para fazer sua l´ogica funcionar ou armazenar um dado; o computador ´e que faz o papel de relacionar o nome da vari´avel com o enderec¸o no qual ela foi alocada, e “copiar para” ou “ler de” l´a os dados.
Para facilitar a visualizac¸˜ao do mecanismo de funcionamento dos ponteiros, ima-gine a mem´oria do computador como uma grande pilha de caixas onde cada uma possui um enderec¸o espec´ıfico e um byte de comprimento. Quando o programador declara uma vari´avel e executa o programa (depois da compilac¸˜ao), o computador associa uma dessas caixas com o como da vari´avel; ´e como se o nome da vari´avel e o enderec¸o na mem´oria fossem sinˆonimos. Quando o programador acessa uma vari´avel, ´e o enderec¸o dela que o computador enxerga. Quando o programador lˆe ou escreve um dado na vari´avel, o computador lˆe ou escreve este dado na caixa correspondente `a vari´avel. A figura abaixo ir´a ajudar.
char ch1, ch2;
ch1 = 10; ch2 = 2*ch1;
Enderec¸o Mem´oria Vari´avel
0x1100 10 ch1
0x1101 20 ch2
ch2 no enderec¸o 0x1101. Quando a linha de instruc¸˜ao “ch1 = 10;” ´e executada, o com-putador copia o n´umero 10 no enderec¸o da vari´avel ch1, ou seja, no enderec¸o 0x1100.
A linha de instruc¸˜ao seguinte ´e “ch2 = 2*ch1;”. O computador ir´a ler o dado no enderec¸o da vari´avel ch1, ir´a multiplicar este dado por 2 e, depois, escrever´a o resultado desta multiplicac¸˜ao no enderec¸o da vari´avel ch2. Note que, em nenhum momento, o programador precisou saber o enderec¸o das suas vari´aveis.
int i1, i2;
Enderec¸o Mem´oria Vari´avel
0x1100 i1
0x1104 i2
0x1107 0x1108
Vejamos um outro exemplo agora usando vari´aveis do tipo int que tem 4 bytes de comprimento. Quando o programador declara uma vari´avel do tipo int, ele est´a solicitando ao computador que reserve 4 bytes cont´ıguos na mem´oria para serem usados no armazenamento de n´umeros inteiros. O computador ir´a, novamente, associar um enderec¸o de mem´oria ao nome da vari´avel. O enderec¸o associado ´e o enderec¸o do primeiro byte dos quatro que formam o n´umero inteiro, o enderec¸o base (veja a figura anterior).
Se o programador declara duas vari´aveis int, i1 e i2, o computador reserva 4
bytes para cada uma delas. A vari´avel i1 ´e alocada no enderec¸o 0x1100. A vari´avel i2 s´o poder´a ser alocada 4 bytes depois. Isto significa que seu enderec¸o de mem´oria ser´a 0x1104. Al´em disso, qualquer outra vari´avel que tiver de ser alocada na mem´oria, s´o poder´a estar a partir do enderec¸o 0x1108, uma vez que o byte do enderec¸o 0x1107 ainda faz parte da vari´avel i2.
A relac¸˜ao entre uma vari´avel e seu enderec¸o ´e biun´ıvoca, de um para um: toda vari´avel possui um enderec¸o espec´ıfico, assim como todo enderec¸o corresponde a uma
vari´avel.
2.5.2 Declarac¸˜ao de Ponteiros
A vari´avel ponteiro ´e declarada a partir de um dos tipos v´alidos em C, isto ´e, ´e v´alido declarar ponteiros para: char, unsigned char, short, unsigned short, int, unsigned,
long, unsigned long, float, double e long double. O compilador reconhece como definic¸˜ao de ponteiro a declarac¸˜ao de uma vari´avel de qualquer um destes tipos v´alidos prece-dido de um asterisco ‘*’. Por exemplo, o fragmento de c´odigo abaixo declara vari´aveis ponteiro para cada um dos tipos v´alidos (os nomes das vari´aveis foram escolhidos arbi-trariamente).
char *ch;
unsigned char *uch;
short *si; /* mesmo que ‘short int’ */
unsigned short *usi; /* mesmo que ‘unsigned short int’ */
int *i;
unsigned *ui; /* mesmo que ‘unsigned int’ */
long *li; /* mesmo que ‘long int’ */
unsigned long *uli; /* mesmo que ‘unsigned long int’ */
float *flt;
double *dbl;
long double *ldbl;
Duas vari´aveis ponteiro do mesmo tipo podem ser declaradas na mesma linha:
unsigned char *ch1, *ch2;
As vari´aveis ponteiros ocupam 4 bytes de mem´oria, independente do tipo de dado apontado. O conte´udo da vari´avel ponteiro ´e um enderec¸o de mem´oria. Qualquer enderec¸o de mem´oria ´e um n´umero inteiro, positivo e sem sinal. Se o computador ´e de 32 bits, a vari´avel ponteiro tem 32 bits de comprimento (4 bytes). Caso o computador seja de 64 bits, as vari´aveis ponteiros ocupar˜ao 8 bytes cada. Nos exemplos apresentados nesta sec¸˜ao, ser´a assumido um computador de 32 bits.
2.5.3 Operador de Enderec¸amento de Dado(&)
Um ponteiro pode receber um enderec¸o de mem´oria explicitamente (digitado pelo programador ou declarado como constante num´erica) ou receber o enderec¸o de uma vari´avel atrav´es do operador ‘&’. Este operador ´e usado na frente da vari´avel que se deseja extrair o enderec¸o. Por exemplo:
int *iptr, i; /* iptr ´e um ponteiro para int e
i ´e uma vari´avel do tipo int. */
iptr = &i; /* iptr recebe o endere¸co
da vari´avel i. */
´
E importante manter a coerˆencia entre tipos de ponteiros e tipos de vari´aveis que est˜ao retornando enderec¸o. Ponteiros do tipo int recebem enderec¸os de vari´aveis do tipo
int; ponteiros do tipo double recebem enderec¸os de vari´aveis do tipo double e assim por diante.
2.5.4 Operador de Referenciamento de Dado (*)
Mostrou-se at´e agora o mecanismo de extrac¸˜ao de enderec¸o de uma vari´avel e sub-sequente armazenamento em um ponteiro. Para acessar o dado apontado pela vari´avel ponteiro, ´e necess´ario usar-se o operador ‘*’ antes do ponteiro. O fragmento de c´odigo a seguir mostra o procedimento e a figura auxilia na visualizac¸˜ao da mem´oria:
char i1, *iptr, i2;
i1 = 20; iptr = &i1; i2 = *iptr;
Enderec¸o Mem´oria Vari´avel
0x1a3c22 20 i1 0x1a3c23 0x22 iptr 0x3c 0x1a 0x0 0x1a3c27 20 i2
Quando o c´odigo ´e executado, o computador encontra inicialmente a declarac¸˜ao de trˆes vari´aveis: i1, iptr e i2. Ele aloca as vari´aveis, na ordem de declarac¸˜ao, em espac¸os da mem´oria. Suponha que a vari´avel i1 seja alocada no enderec¸o 0x1a3c22, a