• Nenhum resultado encontrado

Linguagem C

N/A
N/A
Protected

Academic year: 2021

Share "Linguagem C"

Copied!
103
0
0

Texto

(1)

Linguagem de

programac

¸˜

ao C

Universidade do Estado do Rio de Janeiro Centro de Tecnologia e Ciˆencias

Instituto de F´ısica

(2)
(3)

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

(4)

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

(5)

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

(6)
(7)

Lista de Figuras

2.1 Tabela ASCII de valores literais. . . 14

(8)
(9)

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

(10)

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.

(11)

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

(12)

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.

(13)

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.

(14)

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.

(15)

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.

(16)

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),

(17)

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;

(18)

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.

(19)

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

(20)

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

(21)

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.

(22)

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

(23)

‘+’. 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.

(24)

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 ); }

(25)

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:

(26)

# 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.

(27)

# 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 "

(28)

" 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.

(29)

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 */

(30)

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

(31)

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

(32)

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:

(33)

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

(34)

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).

(35)

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:

(36)

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) {

(37)

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 ] ;

(38)

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:

(39)

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) {

(40)

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.

(41)

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;

(42)

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

(43)

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.

(44)

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

(45)

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

(46)

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.

(47)

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

Referências

Documentos relacionados

Note on the occurrence of the crebeater seal, Lobodon carcinophagus (Hombron &amp; Jacquinot, 1842) (Mammalia: Pinnipedia), in Rio de Janeiro State, Brazil.. On May 12, 2003,

Após retirar, da maré observada, o valor da maré teórica, aplicou-se uma linha de tendência aos dados resultantes, a qual representa a variação do nível médio das águas no

 A velocidade de regime do eixo nunca será síncrona com o campo girante do estator, pois assim, o enrolamento do rotor estaria sujeito a fluxo magnético constante, e não

Precisamos de uma ferramenta que fa¸ ca a convers˜ ao de uma linguagem que est´ a em um n´ıvel de abstra¸ c˜ ao mais alto (pr´ oximo ` a linguagem natural) a um n´ıvel de

Também foi realizada uma pesquisa de campo na Escola Municipal de Ensino Fundamental Luiz Vaz de Camões, no município de João Pessoa, no Bairro de Mangabeira, utilizando

Com relação à germinação das sementes armazenadas em câmara fria, aos três meses de armazenamento (Tabela 10), observou-se em sementes tratadas ou não com fungicidas e

X e que n˜ ao seja poss´ıvel inverter a sua fun¸ c˜ ao de distribui¸ c˜ ao ou n˜ ao dispomos de um m´ etodo para gerar dessa vari´ avel aleat´ oria. Entretanto, sabemos

e serviços da empresa; definir um modelo de avaliação global da qualidade, que envolve os objetivos gerais da organização e a contribuição de cada setor, ou área do