• Nenhum resultado encontrado

Lu´ıs Fernando de Oliveira 26 de maio de 2011

N/A
N/A
Protected

Academic year: 2019

Share "Lu´ıs Fernando de Oliveira 26 de maio de 2011"

Copied!
107
0
0

Texto

(1)

Linguagens de Programa¸c˜

ao C e C++: Uma

Introdu¸c˜

ao

Lu´ıs Fernando de Oliveira

(2)
(3)

Sum´

ario

1 Programa¸c˜ao em C e C++ 7

1.1 Aspectos B´asicos do C´odigo-Fonte em C . . . 7

1.2 Compiladores C e C++ . . . 9

1.3 Compila¸c˜ao dos C´odigos-fonte . . . 10

2 Tipos de Dados 13 2.1 Representa¸c˜ao de Dados na Mem´oria . . . 14

2.1.1 Representa¸c˜ao de N´umeros Inteiros . . . 15

2.1.2 Representa¸c˜ao de N´umeros Reais . . . 16

2.1.3 Tipos de Dados Representados . . . 17

2.1.4 Organiza¸c˜ao dos Dados na Mem´oria . . . 18

2.2 Tipos Definidos de Dado . . . 19

2.2.1 Tipo Literal: char . . . 19

2.2.2 Tipo Inteiro: char eint . . . 21

2.2.3 Tipo Real: float e double . . . 22

2.2.4 Tipo Indefinido: void . . . 23

2.2.5 Tipo L´ogico . . . 23

2.2.6 Modificadores de Tipo signed e unsigned . . . 24

2.2.7 Modificadores de Tipo short elong . . . 25

2.2.8 Type Casting . . . 26

2.2.9 Resumo dos Tipos intr´ınsecos . . . 27

2.3 Vetores, Matrizes eStrings . . . 27

2.3.1 Declara¸c˜ao de Vetores e Matrizes . . . 27

2.3.2 Cadeia de Caracteres (Strings) . . . 28

2.4 Tipos Abstratos de Dado . . . 29

2.4.1 Estruturas de Dados: struct . . . 29

2.4.2 Enumera¸c˜oes: enum . . . 32

2.4.3 Uni˜oes: union . . . 33

(4)

2.4.4 Campo de Bits . . . 34

2.4.5 Declara¸c˜aotypedef . . . 36

2.5 Ponteiros . . . 36

2.5.1 Ponteiros e Endere¸co de Mem´oria . . . 36

2.5.2 Declara¸c˜ao de Ponteiros . . . 38

2.5.3 Operador de Endere¸camento 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 Rela¸c˜ao entre Ponteiros, Vetores e Matrizes . . . 42

2.5.9 Aloca¸c˜ao de Mem´oria para Ponteiros . . . 45

3 Operadores Matem´aticos, L´ogicos e Bin´arios 47 3.1 Convers˜ao de Tipo e Operador de Atribui¸c˜ao . . . 47

3.2 Operadores Aritm´eticos . . . 50

3.3 Operadores Relacionais . . . 52

3.3.1 Operadores de Igualdade (==) e de Diferen¸ca (!=) . . 52

3.3.2 Operadores Maior (>) e Maior que (>=) . . . 52

3.3.3 Operadores Menor (<) e Menor que (<=) . . . 52

3.4 Operadores L´ogicos . . . 52

3.4.1 Operador de Conjun¸c˜ao: E L´ogico (&&) . . . 52

3.4.2 Operador de Disjun¸c˜ao: OU L´ogico (||) . . . 52

3.4.3 Operador de Nega¸c˜ao (!) . . . 52

3.5 Operadores Bin´arios . . . 52

3.5.1 Operador E Bin´ario (&) . . . 52

3.5.2 Operador OU Bin´ario (|) . . . 53

3.5.3 Operador OU EXCLUSIVO Bin´ario (ˆ) . . . 53

3.5.4 Operador de Nega¸c˜ao Bin´ario (∼) . . . 53

3.5.5 Operadores de Deslocamento Bin´ario (<< e>>) . . . 53

3.6 Operadores de Atribui¸c˜ao Concatenados . . . 53

3.6.1 Operador de Adi¸c˜ao (+=) . . . 53

3.6.2 Operador de Subtra¸c˜ao (-=) . . . 53

3.6.3 Operador de Multiplica¸c˜ao (*=) . . . 53

3.6.4 Operador de Divis˜ao (/=) . . . 53

3.6.5 Operador L´ogico E Bin´ario (&=) . . . 53

3.6.6 Operador L´ogico OU Bin´ario (|=) . . . 53

(5)

5

4 Estruturas de Controle de Execu¸c˜ao 55

4.1 Estruturas de Condi¸c˜ao . . . 55

4.1.1 Estrutura do Se . . . 55

4.1.2 Estrutura do Se Tern´ario . . . 57

4.1.3 Estrutura de Sele¸c˜ao de Caso . . . 58

4.2 Estruturas de Repeti¸c˜ao . . . 58

4.2.1 Estrutura de La¸co Definido . . . 58

4.2.2 Estrutura de La¸co Condicional . . . 60

5 Fun¸c˜oes 63 5.1 Argumentos por Valor e por Referˆencia . . . 64

5.2 Prot´otipo de uma Fun¸c˜ao . . . 66

5.3 Rela¸c˜ao das Fun¸c˜oes Intr´ınsecas . . . 67

5.4 Fun¸c˜oes de Entrada e Sa´ıda Padr˜oes . . . 74

6 Diretivas de Compila¸c˜ao 75 6.1 Diretiva #include . . . 75

6.2 Diretivas #define|#undef . . . 77

6.3 Diretivas #if–#elif–#else–#endif . . . 79

6.4 Diretivas #ifdef|#ifndef–#endif . . . 80

7 Organiza¸c˜ao da Programa¸c˜ao em C 85 8 Classes e Objetos 87 8.1 Encapsulamento de Atributos e M´etodos . . . 88

8.2 Visibilidade de Implementa¸c˜ao . . . 89

8.3 Hierarquia de Classes e Hereditariedade . . . 91

8.4 Polimorfismo de M´etodos . . . 93

8.5 Objetos das Classes . . . 94

8.6 M´etodos Construtores e Destruidores . . . 95

8.7 Sobrecarga de Operadores . . . 98

(6)
(7)

1

Programa¸

ao em C e C++

1.1

Aspectos B´

asicos do C´

odigo-Fonte em C

Para come¸car, vejamos um exemplo de c´odigo-fonte simples em C para, depois, apresentarmos cada elemento componente dele.

C´odigo 1.1: exemplo1.c # include < stdlib .h >

# include < stdio .h > # include < string .h > # include < math .h >

int main (void) {

int a ;

float b , c ;

printf (" digite um valor inteiro : \ n ");

scanf (" % d ",& a );

b = 0.5; c = a + b ;

printf (" a soma deste valor com 0.5 eh : % f \ n ",c );

return 0; }

Um c´odigo-fonte em C ´e composto por instru¸c˜oes de compila¸c˜ao e instru¸c˜oes de programa¸c˜ao.

As instru¸c˜oes de compila¸c˜ao (ou diretivas de compila¸c˜ao) iniciam com o s´ımbolo “#” e s˜ao direcionadas ao compilador. Elas n˜ao geram c´odigo

(8)

execut´avel e somente tˆem efeito durante o processo de compila¸c˜ao. Indicam a¸c˜oes que o compilador deve executar ou modificam um comportamento es-pec´ıfico do compilador.

Asinstru¸c˜oes de programa¸c˜ao(tudo que n˜ao come¸ca com “#”) podem ser classificadas comocomandos de declara¸c˜aoecomandos de execu¸c˜ao. Os comandos de declara¸c˜ao s˜ao usados para definir vari´aveis, tipos de vari´aveis, estruturas de dados e fun¸c˜oes. Os comandos de execu¸c˜ao s˜ao as instru¸c˜oes que ser˜ao efetivamente executadas pelo processador. Durante o processo de com-pila¸c˜ao, estes comandos s˜ao traduzidos em uma linguagem intermedi´aria, cha-mada de linguagem objeto (que n˜ao tem nada a ver com orienta¸c˜ao a objeto) para depois, durante o processo de link-edi¸c˜ao, ser convertida em linguagem de m´aquina.

Neste exemplo inicial, vocˆe pode ver todos estes tipos de instru¸c˜ao. As quatro primeiras linhas de c´odigo (quatro diretivas #include) informam ao compilador que ele deve incluir, no processo de compila¸c˜ao, quatro arquivos com extens˜ao “.h” (chamados de arquivos de cabe¸calho, do inglˆesheader files). Estes arquivos contˆem os prot´otipos de fun¸c˜oes intr´ınsecas da linguagem C e s˜ao usadas basicamente, durante o processo de compila¸c˜ao, para verificar se as fun¸c˜oes intr´ınsecas usadas no programa est˜ao declaradas corretamente (sin-taxe da linguagem). Os arquivos de cabe¸calho n˜ao cont´em o c´odigo-fonte das fun¸c˜oes, mas apenas as declara¸c˜oes (prot´otipos). As fun¸c˜oes em si est˜ao dis-pon´ıveis na forma de bibliotecas pr´e-compiladas que acompanham a instala¸c˜ao do compilador. Os arquivos de cabe¸calho, por assim dizer, funcionam como uma lista condensada com o nome das fun¸c˜oes e seus respectivos argumentos. • O arquivostdlib.hcont´em os prot´otipos das fun¸c˜oes intr´ınsecas b´asicas. • O arquivostdio.h cont´em os prot´otipos das fun¸c˜oes b´asicas de entrada

e sa´ıda de dados.

• O arquivostring.h cont´em os prot´otipos das fun¸c˜oes b´asicas de mani-pula¸c˜ao de strings (cadeia de caracteres).

• O arquivo math.h cont´em os prot´otipos das fun¸c˜oes b´asicas de ma-tem´atica.

Depois das diretivas de compila¸c˜ao, encontramos as instru¸c˜oes de pro-grama¸c˜ao. A primeira instru¸c˜ao ´e uma declara¸c˜ao de fun¸c˜ao: a fun¸c˜ao int main(void). Esta ´e a fun¸c˜ao principal das linguagens C e C++. Ela desem-penha 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 fun¸c˜aomain().

(9)

1.2. COMPILADORES C E C++ 9

Por que? Porque, sendo a linguagem C uma linguagem de programa¸c˜ao es-truturada, as vari´aveis devem ser declaradas antes de serem usadas. Durante o processo de compila¸c˜ao, as declara¸c˜oes de vari´aveis s˜ao usadas 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 pro-cesso de compila¸c˜ao tentar´a criar dois identificadores com os mesmos nomes. Se isso fosse feito depois de processar os comandos de execu¸c˜ao, o compilador perderia o controle na identifica¸c˜ao de que vari´avel est´a sendo referenciada numa instru¸c˜ao espec´ıfica. Ent˜ao, declara¸c˜ao de vari´avel vem sempre antes dos comandos de execu¸c˜ao, iniciando os blocos de instru¸c˜oes.

Depois das declara¸c˜oes de vari´aveis, vemos comandos de atribui¸c˜ao, opera-¸c˜ao aritm´etica, chamadas de fun¸c˜oes e de retorno de dado.

Uma informa¸c˜ao muito importante que deve ser levantada aqui, no in´ıcio do material sobre as linguagens C e C++, ´e que, em C e C++, n˜ao existe a declara¸c˜ao formal de sub-rotina ou procedimento. Tudo em C e C++ ´e fun¸c˜ao. O que ir´a definir se a fun¸c˜ao se comportar´a como uma fun¸c˜ao tradicional (que retorna um dado) ou como uma sub-rotina (que n˜ao possui retorno de informa¸c˜ao atrav´es de opera¸c˜ao de atribui¸c˜ao) ´e o tipo da fun¸c˜ao. Como ser´a visto no cap´ıtulo 2 sobre tipos de dados, existe um tipo chamado 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 fun¸c˜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 declara¸c˜ao de fun¸c˜oes que n˜ao precisam de argumentos (no nosso exemplo, a fun¸c˜aomain() ´e declarada com a palavra reservadavoid no lugar dos argumento. Isto significa que a fun¸c˜ao main n˜ao recebe qualquer informa¸c˜ao de fora do programa. Poderia ser diferente? Sim, poderia. Se o programador precisar passar dados para dentro da fun¸c˜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 compila¸c˜ao.

1.2

Compiladores C e C++

(10)

Estes compiladores n˜ao possuem interfaces de desenvolvimento (as famosas IDE’s, do inglˆesIntegrated Development Environment), mas aceitam que sejam instaladas a parte. As IDE’s para Windows mais famosas s˜ao o VisualC da Microsoft e oC++Buiderda 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::Blocksque tem vers˜oes para Windows e Linux. Outras duas IDE’s s˜ao: EclipseeNetBeans, 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 inten¸c˜ao de for¸car nenhuma das IDE’s mencionadas, apenas determinar que os exemplos apresentados ser˜ao todos testados nos compiladoresgcc eg++. Fica ao cargo de cada um decidir se instala ou n˜ao uma IDE.

1.3

Compila¸c˜

ao dos C´

odigos-fonte

As linhas de comando dos compiladores s˜ao exatamente iguais `as usadas no Fortran (com of95 oug95 ougfortran):

• 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>

(11)

1.3. COMPILAC¸ ˜AO DOS C ´ODIGOS-FONTE 11

Existe a forma reduzida de comando onde a compila¸c˜ao e a link-edi¸c˜ao s˜ao executadas sequencialmente. O resultado final ´e o mesmo em qualquer uma das duas op¸c˜oes. A vantagem de se compilar por etapas se destaca quando o projeto que est´a sendo desenvolvido cont´em muitos arquivos de c´odigo. ´E sem-pre interessante compilar os arquivos 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 manuten¸c˜ao e corre¸c˜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. Por isso, o material ficar´a organizado da seguinte forma:

• Cap´ıtulo 2: comandos de declara¸c˜ao de vari´aveis, os tipos pr´e-definidos das linguagens, ponteiros, vetores e matrizes, tipos abstratos de dados. • Cap´ıtulo 3: comandos de execu¸c˜ao referentes aos operadores alg´ebricos,

relacionais, l´ogicos e bin´arios.

• Cap´ıtulo 4: comandos de execu¸c˜ao condicionais se-sen˜ao, se tern´ario e sele¸c˜ao de caso e de comandos de repeti¸c˜ao para-de-at´e, repita-at´e e enquanto.

• Cap´ıtulo 5: comandos de declara¸c˜ao de fun¸c˜oes, passagem de argumentos e prot´otipos de fun¸c˜ao.

• Cap´ıtulo 6: diretivas de compila¸c˜ao.

(12)
(13)

2

Tipos de Dados

Como em toda linguagem formal, a linguagem C possui tipos pr´e-definidos de dados (tipos intr´ınsecos) que podem ser classificados 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) e • tipo l´ogico (para o “falso” e “verdadeiro”).

E para que o programador tenha uma liberdade de cria¸c˜ao, a linguagem C permite tamb´em a defini¸c˜ao de novos tipos de dados – s˜ao os chamados tipos abstratos de dados. Dentre eles, encontram-se:

• os vetores e matrizes (tanto num´ericas como literais, as strings), • as estruturas de dados (na forma de registros e campos),

• as enumera¸c˜oes (como listas ordenadas de constantes) e • as uni˜oes (que permitem a superposi¸c˜ao de estruturas).

Um t´opico particular da linguagem C versa sobre os ponteiros, 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 computador e descer a um n´ıvel mais baixo de programa¸c˜ao (o que nem sempre ´e necess´ario). Para tanto, se faz necess´ario relembrar algumas caracter´ısticas de representa¸c˜ao bin´aria e organiza¸c˜ao de dados na mem´oria do computador. Isso facilitar´a a compreens˜ao de recursos de programa¸c˜ao em C tais como passagem de parˆametros por referˆencia, declara¸c˜ao de vetores e matrizes e aloca¸c˜ao dinˆamica de mem´oria.

(14)

2.1

Representa¸c˜

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 computadores n˜ao permite que os dados sejam armazenados usando a mesma representa¸c˜ao gr´afica que n´os, humanos, usamos. Como todos devem recordar, os computadores s´o reconhecem dois tipos de informa¸c˜ao: “ligado” e “desligado”. Para maior conforto nosso, as informa¸c˜oes “ligado” e “desligado” podem ser representados como “falso” e “verdadeiro” ou 1 e 0, respectivamente. Ainda assim, representar uma informa¸c˜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 programa¸c˜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 informa¸c˜ao representada no computador. Pode as-sumir dois valores distintos: 0 e 1. Eletronicamente corresponde `as si-tua¸c˜oes de: tem corrente, n˜ao tem corrente, ou tens˜ao diferente de zero, tens˜ao igual a zero. Da´ı a no¸c˜ao de “ligado” e “desligado”.

byte (B): O byte ´e o agrupamento de 8 bits. Forma a menor “palavra” em computadores. Sua decodifica¸c˜ao, ou seja, sua interpreta¸c˜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 nota¸c˜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 10243 bytes.

terabyte (TB): um terabyte ´e o agrupamento de 10244 bytes.

petabyte (PB): um pentabyte ´e o agrupamento de 10245 bytes.

(15)

2.1. REPRESENTAC¸ ˜AO DE DADOS NA MEM ´ORIA 15

2.1.1

Representa¸c˜

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 (internacional e aceito pela maioria das ind´ustrias de componen-tes eletrˆonicos) que definisse a representa¸c˜ao de n´umeros positivos e negati-vos em bin´ario. Uma da agˆencias internacionais que recomendam padr˜oes ´e a IEEE (do inglˆes, Institute of Electrical and Electronic Engineering). Nas recomenda¸c˜oes de representa¸c˜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 obit de sinal, seguindo a seguinte codifica¸c˜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 1byte, 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, 27vale 128. Se pusermos obit 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 desperdi¸cando capacidade computacional. Ent˜ao, para superar esta dificul-dade, propˆos-se um mecanismo de c´alculo de n´umeros negativos chamado de c´alculo por complemento a 2. Este mecanismo funciona assim: pegue a representa¸c˜ao bin´ario do n´umero positivo; retire o bit de sinal; inverta cada bit da representa¸c˜ao, isto ´e, troque os 0’s por 1’s e vice-versa (isto se chama c´alculo de complemento a 1); adicione 1 ao resultado das invers˜oes; acres-cente o digito 1 comomsb, obit de sinal; este novo resultado ´e a representa¸c˜ao negativa do n´umero positivo inicial.

(16)

separar obit de sinal – isso ajuda na visualiza¸c˜ao da representa¸c˜ao bin´aria. A representa¸c˜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 qualquer agrupamento debytes, sempre lembrando que obit de sinal ´e omsb, obit 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

Representa¸c˜

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 ou-tra forma de padroniza¸c˜ao de representa¸c˜ao para n´umeros reais. Da´ı que, novamente, a IEEE gerou uma outra recomenda¸c˜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 representa¸c˜ao m´ınima para n´umeros reais com 4bytes 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 cadabit 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,50,25 =−0,75 reais. O n´umero

0|0000001 0|.1100000 00000000 00000000

vale 21×(1×21

+ 1×2−2

) = 2×0,75 = 1,5, ou

0|0000000 0|1.100000 00000000 00000000

(17)

2.1. REPRESENTAC¸ ˜AO DE DADOS NA MEM ´ORIA 17

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 12bytes 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 maisbytes. 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 representa¸c˜ao do n´umero inteiro utilizar 2 bytes, as faixas de valores aumentam: entre 0 e 2161 (= 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 representa¸c˜ao de n´ ume-ros inteiume-ros, cada linguagem de programa¸c˜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 inteiro). Al´em disso, existe a possibilidade de se “tipar” explicitamente, representa¸c˜oes de n´umeros com e sem sinal. A linguagem C tamb´em permite essa situa¸c˜ao.

Quanto aos n´umeros reais, as linguagem “tipificam” a precis˜ao segundo a quantidade 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 programa¸c˜ao, os dados l´ogicos e os literais. Algumas linguagens implementam os valores l´ogicosfalsoeverdadeiro. 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.

(18)

2.1.4

Organiza¸c˜

ao dos Dados na Mem´

oria

Por fim, e n˜ao menos importante, quando um agrupamento debytes repre-sentando 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 dispo-sitivo (fosse ele um computador, um videogame, uma calculadora, um rel´ogio digital ou qualquer outra coisa que usasse dados digitais). Com a populariza¸c˜ao dos computadores, sua miniaturiza¸c˜ao, seu barateamento e o aumento da com-plexidade das redes de computadores, fez-se necess´ario uma padroniza¸c˜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 endere¸co de mem´oria e o endere¸co dobyte menos significa-tivo ´e o que se chama de endere¸co base. A partir do endere¸co 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 espa¸co de mem´oria com um determinado comprimento em bytes (suficiente para arma-zenar o dado) e que associe o nome da vari´avel ao endere¸co 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 endere¸co referente `a vari´avel declarada. O computador pega o dado, vˆe o nome da vari´avel (que ´e um identificador), recupera o endere¸co associado `a vari´avel e transfere o dado para este endere¸co.

Na linguagem C, existe um “tipo” de dado chamado ponteiro que o diferen-cia de praticamente todas as outras linguagens (pelo menos as mais antigas). O ponteiro nada mais ´e do que o endere¸co de mem´oria onde o dado est´a ou o endere¸co de mem´oria referente a uma vari´avel. Como endere¸co ´e um n´umero inteiro e precisa ficar armazenado em algum lugar, a vari´avel que armazena endere¸cos ´e dita ser do tipo “ponteiro”. Por que? Porque eleapontapara um endere¸co espec´ıfico da mem´oria. Simples assim.

(19)

2.2. TIPOS DEFINIDOS DE DADO 19

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´aveischar s˜ao os caracteres da tabela ASCII.

O fragmento de c´odigo abaixo mostra a declara¸c˜ao de duas vari´aveis char chamadas ch e letra. Estas vari´aveis ser˜ao preenchidas com as constantes literais ‘a’e ‘+’.

C´odigo 2.1: exemplo2.c

void main (void) {

char ch ;

char letra ;

ch = ’a ’;

letra = ’+ ’;

}

Como no Fortran, as vari´aveis podem ser inicializadas diretamente na de-clara¸c˜ao de vari´avel como a seguir:

C´odigo 2.2: exemplo3.c

void main (void) {

char ch = ’a ’;

char letra = ’+ ’;

}

Reparar que a constante literal ´e formada por um ´unico car´acter e que este ´e digitado entre ap´ostrofos.

(20)

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 tabula¸c˜ao horizontal

\v 0x0b VT tabula¸c˜ao vertical

\\ 0x5c \ backslash

\’ 0x27 ’ ap´ostrofo

\” 0x22 ” aspas

\? 0x3f ? interroga¸c˜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 formata¸c˜ao “%c” indica que um car´acter dever´a ser impresso. E para que a fun¸c˜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 # include < stdio .h >

void main (void) {

char ch = ’a ’;

char letra = ’+ ’;

printf (" % c ", ch ); printf (" % c ", letra ); }

Para “quebrar” a linha ap´os a impress˜ao do conte´udo, pode-se incluir a constante literal “\n” na string de formata¸c˜ao:

C´odigo 2.4: exemplo4b.c # include < stdio .h >

void main (void) {

(21)

2.2. TIPOS DEFINIDOS DE DADO 21

char letra = ’+ ’;

printf (" % c % c \ n ",ch , letra ); }

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 tipochar pode assumir valores entre -128 e 127, que s˜ao os valores poss´ıveis para um n´umero inteiro de 8bits, sendo um deles obit de sinal.

C´odigo 2.5: exemplo5.c # include < stdio .h >

void main (void) {

char x = -100;

char i = 54;

printf (" % d % d \ n ",x , i ); }

O c´odigo de controle de formata¸c˜ao para impress˜ao de um n´umero inteiro ´e “%d”. Repare que mesmo a vari´avel sendo do tipochar, 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:

C´odigo 2.6: exemplo5a.c # include < stdio .h >

void main (void) {

char x = ’a ’; char i = ’1 ’;

printf (" % d % d \ n ",x , i ); }

Neste exemplo, o compilador substitui as constantes literais pelos seus respec-tivos ´ındices na tabela ASCII: o ´ındice do car´acter ‘a’ ´e 97 e do car´acter ‘1’ ´e 49.

(22)

C´odigo 2.7: exemplo6.c # include < stdio .h >

void main (void) {

int k = 10000 , a , h ;

a = -1234567; h = 8 5 7 4 8 4 0 3 ;

printf (" % d \ t % d \ t % d \ n ",k ,a , h ); }

A constante literal “\t” ´e respons´avel pela tabula¸c˜ao da impress˜ao. O padr˜ao de tabula¸c˜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 ofloat, 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.

C´odigo 2.8: exemplo7.c # include < stdio .h >

void main (void) {

float f = 0 . 0 3 4 5 3 ; float x = 1.2 e -10; float y = -0.4356 e23 ; float z = 10;

printf (" %f , %f , %f , % f \ n ",f ,x ,y , z ); }

O c´odigo de controle de formata¸c˜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 formata¸c˜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 nota¸c˜ao cient´ıfica. O c´odigo de formata¸c˜ao ´e “%e”. Experimente trocar a formata¸c˜ao no exemplo anterior para ver o efeito.

Al´em do tipo float, a linguagem C possui outro tipo de vari´avel para ar-mazenar 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

(23)

2.2. TIPOS DEFINIDOS DE DADO 23

C´odigo 2.9: exemplo8.c # include < stdio .h >

void main (void) {

double G , k , g , Na ;

G = 6.672 e -11; /* c o n s t a n t e g r a v i t a c i o n a l */

k = 1.3807 e -23; /* 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 padrao */

Na = 6.0220 e23 ; /* numero de A v o g a d r o */

printf (" numero 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 padrao = % lf \ n ",Na ,k ,G , g ); }

O c´odigo de controle de formata¸c˜ao para o tipodouble ´e “%lf” (long float). Novamente, a formata¸c˜ao pode n˜ao ser apropriada para a impress˜ao do n´umero em fun¸c˜ao de sua magnitude. Experimente trocar “%lf” para “%g” que ajusta a formata¸c˜ao automaticamente.

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 ´ecase sensitive, ou seja, “sens´ıvel `a caixa”. Portanto, as vari´aveis G e g s˜ao distintas.

2.2.4

Tipo Indefinido:

void

O tipovoid ´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 endere¸cos de mem´oria se associado a um ponteiro (que ser´a apresentado mais adiante). Sua aplica¸c˜ao mais intensa se refere `a defini¸c˜ao de sub-rotinas que, em C, s˜ao fun¸c˜oes que n˜ao retornam valores.

2.2.5

Tipo L´

ogico

Um dado do tipo l´ogico deve, por defini¸c˜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 lin-guagem C usa a seguinte regra: qualquer dado igual a zero ´e interpretado como o valor “falso” e, por oposi¸c˜ao, qualquer coisa diferente de zero ´e considerado “verdadeiro”.

(24)

que est´a sendo executado pode ser desviado para uma posi¸c˜ao espec´ıfica dentro do c´odigo. O teste l´ogico tamb´em est´a presente nas instru¸c˜oes de repeti¸c˜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 itera¸c˜ao prossegue ou para.

Tome os seguintes exemplos:

C´odigo 2.10: exemplo9.c

void main (void) {

char a = 0 , b = 1;

char c = ’ \0 ’, d = ’z ’;

int g = 0 , h = -100;

float f = 0 , r = 0.1 e -10; double x = 0 , y = 1e -30; }

Se os testes l´ogicos fossem realizados com as vari´aveis declaradas acima, os resultados 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˜aosigned eunsigned. Toda vari´avel inteira ´e, a princ´ıpio, uma vari´avel inteira com sinal (usando-se ou n˜ao a palavrasigned). Se h´a a necessidade de se declarar uma vari´avel inteira sem sinal, deve-se usar a palavra reservada unsigned antes da declara¸c˜ao do tipo (somente os tiposchar eint aceitam o prefixosigned eunsigned). 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 comounsigned (sem a declara¸c˜aoint), o compilador entende que a vari´avel declarada ´e do tipo unsigned int. Exemplo:

C´odigo 2.11: exemplo10.c # include < stdio .h >

void main (void) {

char r ; /* 1 byte com sinal */

u n s i g n e d char t ; /* 1 byte sem sinal */

int i ; /* 4 bytes com sinal */

u n s i g n e d j ; /* 4 bytes sem sinal */

(25)

2.2. TIPOS DEFINIDOS DE DADO 25

" sizeof ( int )=% d \ nsizeof ( u n s i g n e d )=% d \ n ", sizeof(char) ,sizeof(u n s i g n e d char) ,

sizeof(int) ,sizeof(u n s i g n e d)); }

A fun¸c˜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 # include < stdio .h >

void main (void) {

char ch = 129; /* ? */

printf (" % 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 significant bit), que ´e o bit mais a esquerda, ´e o bit de sinal. Por-tanto, 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 entender´a internamente que “char ch = 129;” ´e, na verdade, “char ch = -127;”.

2.2.7

Modificadores de Tipo

short

e

long

(26)

de bytes de comprimento que o tipo int, isto ´e, 4 bytes. Portanto, pode ar-mazenar valores entre -2.147.483.648 e 2.147.483.647. A linguagem C aceita a declara¸c˜ao de vari´aveis usando-se somente as palavras reservadasshort elong. Ela entende que as vari´aveis ser˜ao do tiposhort int elong int respectivamente, mas esta n˜ao ´e uma boa regra de programa¸c˜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 comprimento de uma vari´avel inteira, mas este mecanismo n˜ao ´e padronizado. Alguns compiladores aceitam a declara¸c˜ao integer*8 como um inteiro de 8 bytes, outros declaram integer 8, e h´a aqueles que defineminteger(8). Substi-tuindo o n´umero 8 por 4 ou 2, seria poss´ıvel declarar-se vari´aveis inteiras com 4 ou 2bytes 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, un-signed e assim por diante) entre parˆenteses na frente da vari´avel a ter o tipo convertido. Veja que esta opera¸c˜ao n˜ao muda o tipo da vari´avel, mas somente o seu conte´udo no momento de uma associa¸c˜ao. Veja o seguinte exemplo:

C´odigo 2.13: exemplo12.c # include < stdio .h >

void main (void) {

char c ; int i ;

float f =200.0;

i = (int) f ; /* C o n v e r t e r 200.0 para inteiro

s i g n i f i c a truncar a parte decimal . */

c = (char) f ; /* Aqui , alem do numero 200.0 ser

truncado , pois o tipo char so aceita n´umeros inteiros , o numero 200

u l t r a p a s s a o limite de r e p r e s e n t a c a o do char . Logo , o n´umero 200 sera i n t e r p r e t a d o como -56. */

(27)

2.3. VETORES, MATRIZES E STRINGS 27

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

Declara¸c˜

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 dimens˜ao:

C´odigo 2.14: exemplo13.c

void main (void) {

u n s i g n e d v_int [100]; /* Tipo : u n s i g n e d int Nome : v_int

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 dimens˜oes. Mas cada dimens˜ao ´e apresentada individualmente entre colchetes:

C´odigo 2.15: exemplo14.c

void main (void) {

double dmat [ 1 0 ] [ 1 0 ] ; /* Tipo : double Nome : dmat

D i m e n s ~a o : 10 x10 . */

}

(28)

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 signi-fica 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 dis-pon´ıvel. Para se calcular a quantidade de mem´oria ocupada por um vetor ou matriz, basta multiplicar as dimens˜oes da estrutura (vetor ou matriz) pelo total de bytes correspondente ao tipo de dado que define a estrutura. Ent˜ao, o vetorv int com 100 elementos ocupa 100×4 bytes ou 400 bytes. A matriz dmat ocupa 10×10×8bytes 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 tipocharacter e a palavra reservada len. O len define o comprimento total de caracteres que a cadeia pode assumir. Em C, o tipochar ´e usado para declarar a cadeira de caracteres, que n˜ao ´e mais que um vetor de caracteres. E como vetor, sua declara¸c˜ao em C ´e igual `a usada em qualquer outra situa¸c˜ao:

C´odigo 2.16: exemplo15.c

void main (void) {

/* duas strings : frase e palavra . */

char frase [100] , palavra [30];

}

Astringfrase pode conter at´e 100 caracteres e palavra, 30; O que diferencia o C do Fortran ´e a utiliza¸c˜ao do car´acter NULL como terminador do vetor. Em Fortran, se umastring chamada “palavra” for declarada com comprimento 30 e contiver a palavra “paralelepipedo” (14 caracteres), os 16 caracteres res-tantes 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 fun¸c˜ao “trim(palavra)”.

Em C, o car´acter que indica o fim dastring ´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.

(29)

2.4. TIPOS ABSTRATOS DE DADO 29

Veja que o nome da string (que ´e um vetor) ´e um ponteiro. Portanto, a fun¸c˜ao “printf( )” recebe um ponteiro contendo o in´ıcio da string e come¸ca imprimindo na tela a sequˆencia de caracteres at´e encontrar oNULL. Enquanto a fun¸c˜ao n˜ao encontrar oNULL, ela continuar´a imprimindo. Mas o importante aqui n˜ao ´e a fun¸c˜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).

Se o programador quiser concatenar duas palavras, a string que receber´a a uni˜ao 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´acterNULL.

Duas fun¸c˜oes em C que s˜ao muito ´uteis na manipula¸c˜ao de strings s˜ao: “strcpy( )” e “strlen()”. A fun¸c˜ao “strcpy( )” usa dois argumentos: um pon-teiro que aponta para a ´area de mem´oria que cont´em a string e outro que aponta para o endere¸co de destino. A fun¸c˜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 “parale-lepipedo” 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 constru¸c˜ao de tipos mais complexos que os intr´ınsecos atrav´es de agrupamentos (campos) em estruturas de dados. Uma situa¸c˜ao t´ıpica de aplica¸c˜ao de estrutura de dados ´e a cria¸c˜ao de bancos de dados. Normalmente, deseja-se cadastrar pessoas agrupando, de alguma forma, seus dados de identifica¸c˜ao, tais como: nome completo, identidade, endere¸co, profiss˜ao, etc.

A palavra reservada em C que define uma estrutura ´estruct. Por exemplo, se o programador deseja criar uma estrutura chamada “tDadosPessoais” e que contenha os campos nome, identidade e endere¸co, ele deveria escrever o seguinte fragmento de c´odigo:

(30)

Repare que a declara¸c˜ao struct termina com o ponto e v´ırgula. Para se de-finir uma vari´avel deste novo tipo de dado, o procedimento ´e similar a defini¸c˜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 ne-cess´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

void main (void) {

struct t D a d o s P e s s o a i s {

char nome [256];

char e n d e r e c o [256];

int i d e n t i d a d e ; };

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

/* tipo : struct 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 : campo 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 di-mens˜ao do vetor ap´os o nome da vari´avel estrutura:

C´odigo 2.18: exemplo17.c

void main (void) {

/* d e c l a r a c a o da e s t r u t u r a */

struct t D a d o s P e s s o a i s {

char nome [256];

char e n d e r e c o [256];

(31)

2.4. TIPOS ABSTRATOS DE DADO 31

/* d e c l a r a c a o do vetor de e s t r u t u r a Usuario com 1000 e n t r a d a s */

struct t D a d o s P e s s o a i s Usuario [1000];

/* usuario de ´ı n d i c e 0 */

strcpy ( Usuario [0]. nome ," Joao das Neves ");

strcpy ( Usuario [0]. endereco ," Av . Atlantida , 100/101 ");

Usuario [0]. i d e n t i d a d e = 1234;

/* usuario de indice 100 */

strcpy ( Usuario [100]. nome ," P a t r i c i a Araujo ");

strcpy ( Usuario [100]. endereco ," R . Xavier , 312/708 ");

Usuario [100]. i d e n t i d a d e = 2345;

/* usuario de indice 12 */

strcpy ( Usuario [12]. nome ," Carlos P a r r e i r a ");

strcpy ( Usuario [12]. endereco ," R . Da Cruz , casa 100 ");

Usuario [12]. i d e n t i d a d e = 6343;

/* usuario de indice 999 */

/* ultima entrada de um vetor de 1000 p o s i c o e s */

strcpy ( Usuario [999]. nome ," Raquel de Queiroz ");

strcpy ( Usuario [999]. endereco ," R . Paiva , 3 9 8 / 1 1 0 2 "); Usuario [999]. i d e n t i d a d e = 4444;

}

A linguagem C permite algumas simplifica¸c˜oes muito ´uteis para o progra-mador no que se refere a declara¸c˜ao de vari´aveis de estrutura. A principal ´e a declara¸c˜ao da estrutura propriamente dita combinada `a declara¸c˜ao das vari´aveis. Por exemplo:

C´odigo 2.19: exemplo18.c

void main (void) {

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

struct t D a d o s P e s s o a i s {

char nome [256];

char e n d e r e c o [256];

int i d e n t i d a d e ;

} Usuario [1000] , F u n c i o n a r i o ;

strcpy ( Usuario [0]. nome ," Joao das Neves ");

strcpy ( Usuario [0]. endereco ," Av . Atlantida , 100/101 ");

(32)

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 ;

strcpy ( F u n c i o n a r i o . endereco ," R . S . F r a n c i s c o " " Xavier , 524 "); strcpy ( F u n c i o n a r i o . nome ," Piquet C a r n e i r o Jr . "); }

A declara¸c˜ao das vari´aveis segue a declara¸c˜ao dos campos da estrutura.

2.4.2

Enumera¸c˜

oes:

enum

As enumera¸c˜oes s˜ao agrupamentos de “constantes” associadas `a n´umeros inteiros. Por exemplo:

enum Posicao { PARA_CIMA, PARA_BAIXO,

PARA_ESQUERDA, PARA_DIREITA };

Nesta declara¸c˜ao de enumera¸c˜ao, a constante PARA CIMA ´e vista pelo com-pilador 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 enumera¸c˜oes ´e facilitar a rotula¸c˜ao de determinados n´umeros que tenham um significado especial. E a enumera¸c˜ao impede que uma vari´avel do tipo enumera¸c˜ao assuma outros valores que n˜ao tenham sido declarados na enumera¸c˜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 oenum Posicao. Veja o trecho de c´odigo a seguir:

C´odigo 2.20: exemplo19.c

void main (void) {

/* d e c l a r a c a o da e n u m e r a c a o */

enum Posicao { PARA_CIMA , PARA_BAIXO ,

PARA_ESQUERDA , 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 tipo e n u m e r a c a o */

enum Posicao posicao , situacao , comando ;

(33)

2.4. TIPOS ABSTRATOS DE DADO 33

2.4.3

Uni˜

oes:

union

As uni˜oes (unions) s˜ao estruturas onde os campos compartilham o mesmo espa¸co da mem´oria, isto ´e, os campos que comp˜oem a uni˜ao est˜ao “superpos-tos”. 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 declara¸c˜ao de vari´aveis do tipo uni˜ao segue o mesmo modelo das de-clara¸c˜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 # include < stdio .h >

void main (void) {

/* d e c l a r a c a o da uniao */

union c h a r 4 i n t {

u n s i g n e d char c [4]; int i ;

};

/* d e c l a r a c a o da v a r i a v e l */

union c h a r 4 i n t Byte4 ;

/* a c e s s a n d o o campo ’i ’ da uniao */

Byte4 . 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 vetor ’c ’ como numeros h e x a d e c i m a i s */

printf (" % x \ t % x \ t % x \ t % x \ n ",

Byte4 . c [0] , Byte4 . c [1] , Byte4 . c [2] , Byte4 . c [3]); }

O c´odigo de formata¸c˜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.

(34)

Endere¸co Mem´oria Vari´avel

0x1a3c20 0x03 c[0]

0x08 c[1] 0x20 c[2] 0x01 c[3]            i 0x1a3c24

Na mem´oria, obyte 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 ec[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 elementobit 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 debits 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

void main (void) {

struct C a m p o B i t s {

u n s i g n e d b0 :1; /* 0 ,1 */

(35)

2.4. TIPOS ABSTRATOS DE DADO 35

u n s i g n e d b2_3 :2; /* 0..3 */

u n s i g n e d b4_7 :4; /* 0..15 */

};

struct C a m p o B i t s bits ;

bits . b0 = 0; bits . b1 = 1;

bits . b2_3 = 2; /* 2 decimal em binario ´e 10. */

bits . b4_7 = 6; /* 6 decimal em binario ´e 0110. */

Se o campo debits 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 gera¸c˜ao de “m´ascaras” (muito utilizado quando se precisa acessar o hardware e tes-tar/acionar bits individualmente).

C´odigo 2.23: exemplo22.c

void main (void) {

struct 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 b2_3 :2; u n s i g n e d b4_7 :4; };

union S t a t u s M o u s e {

/* o campo de bits e a v a r i a v e l u n s i g n e d char */ /* c o m p a r t i l h a m a mesma area da memoria . */

struct C a m p o B i t s bits ; u n s i g n e d char ch ;

};

union S t a t u s M o u s e sm ;

sm . ch = 12;

/* 12 em binario ´e 0 0 0 0 1 1 0 0 . O bit b0 eh o */ /* mais a direita e os bits de b4_7 , os mais */ /* a e s q u e r d a . Portanto , b0 ´e 0 , b1 eh 0 , */ /* b2_3 eh 3 (11 em binario eh 3 decimal ) , e */ /* b4_7 eh 0 (0000 binario ). */

}

(36)

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 fa¸ca isso).

2.4.5

Declara¸c˜

ao

typedef

typedef ´e uma palavra reservada da linguagem C que simplifica a declara¸c˜ao de estruturas, uni˜oes, enumera¸c˜oes e campos de bits. Atrav´es do typedef, declara-se formalmente o nome de novos tipos. A sintaxe dotypedef ´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 . */

struct M e u C a d a s t r o {

char nome [256];

char e n d e r e c o [256];

u n s i g n e d t e l e f o n e ; };

/* defini - se o novo tipo para struct M e u C a d a s t r o como sendo s i m p l e s m e n t e C a d a s t r o . */

typedef struct M e u C a d a s t r o C a d a s t r o ;

void main (void) {

/* vetor de 100 e l e m e n t o s do tipo C a d a s t r o ( que ´e , na verdade , struct M e u C a d a s t r o ). */

C a d a s t r o cad [100]; }

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 Cadastro ´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 lin-guagens, ser´a apresentado de forma cuidadosa nesta se¸c˜ao. Antecipando uma informa¸c˜ao crucial, os ponteiros est˜ao intimamente relacionados aos vetores e matrizes.

2.5.1

Ponteiros e Endere¸co de Mem´

oria

(37)

2.5. PONTEIROS 37

ele sabe que a sua vari´avel ser´a alocada em algum endere¸co na mem´oria do computador. O programador n˜ao precisa, a princ´ıpio, saber o endere¸co 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 endere¸co no qual ela foi alocada, e “copiar para” ou “ler de” l´a os dados.

Para facilitar a visualiza¸c˜ao do mecanismo de funcionamento dos ponteiros, imagine a mem´oria do computador como uma grande pilha de caixas onde cada uma possui um endere¸co espec´ıfico e um byte de comprimento. Quando o pro-gramador declara uma vari´avel e executa o programa (depois da compila¸c˜ao), o computador associa uma dessas caixas com o como da vari´avel; ´e como se o nome da vari´avel e o endere¸co na mem´oria fossem sinˆonimos. Quando o pro-gramador acessa uma vari´avel, ´e o endere¸co 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;

Endere¸co Mem´oria Vari´avel

0x1100 10 ch1

0x1101 20 ch2

Pela figura, a vari´avel ch1foi alocada na mem´oria no endere¸co 0x1100 e a vari´avelch2no endere¸co 0x1101. Quando a linha de instru¸c˜ao “ch1 = 10;” ´e executada, o computador copia o n´umero 10 no endere¸co da vari´avel ch1, ou seja, no endere¸co 0x1100.

(38)

int i1, i2;

Endere¸co 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 endere¸co de mem´oria ao nome da vari´avel. O endere¸co associado ´e o endere¸co do primeiro byte dos quatro que formam o n´umero inteiro, o endere¸co base (veja a figura anterior).

Se o programador declara duas vari´aveisint,i1ei2, o computador reserva 4 bytes para cada uma delas. A vari´avel i1 ´e alocada no endere¸co 0x1100. A vari´aveli2s´o poder´a ser alocada 4bytesdepois. Isto significa que seu endere¸co 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 endere¸co 0x1108, uma vez que o byte do endere¸co 0x1107 ainda faz parte da vari´avel i2.

A rela¸c˜ao entre uma vari´avel e seu endere¸co ´e biun´ıvoca, de um para um: toda vari´avel possui um endere¸co espec´ıfico, assim como todo endere¸co corres-ponde a uma vari´avel.

2.5.2

Declara¸c˜

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 defini¸c˜ao de ponteiro a declara¸c˜ao de uma vari´avel de qualquer um destes tipos v´alidos precedido 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 arbitrariamente).

char *ch;

unsigned char *uch;

(39)

2.5. PONTEIROS 39

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 4bytesde mem´oria, independente do tipo de dado apontado. O conte´udo da vari´avel ponteiro ´e um endere¸co de mem´oria. Qualquer endere¸co 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 se¸c˜ao, ser´a assumido um com-putador de 32 bits.

2.5.3

Operador de Endere¸camento de Dado(&)

Um ponteiro pode receber um endere¸co de mem´oria explicitamente (digi-tado pelo programador ou declarado como constante num´erica) ou receber o endere¸co de uma vari´avel atrav´es do operador ‘&’. Este operador ´e usado na frente da vari´avel que se deseja extrair o endere¸co. 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 endere¸co. Ponteiros do tipo int recebem endere¸cos de vari´aveis do tipoint; ponteiros do tipo double recebem endere¸cos de vari´aveis do tipo double e assim por diante.

2.5.4

Operador de Referenciamento de Dado (*)

(40)

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 visualiza¸c˜ao da mem´oria:

char i1, *iptr, i2;

i1 = 20; iptr = &i1; i2 = *iptr;

Endere¸co 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 de-clara¸c˜ao de trˆes vari´aveis: i1, iptr e i2. Ele aloca as vari´aveis, na ordem de declara¸c˜ao, em espa¸cos da mem´oria. Suponha que a vari´avel i1 seja alocada no endere¸co 0x1a3c22, a vari´avel ponteiro iptr fique no endere¸co 0x1a3c23 e a vari´aveli2 fique em 0x1a3c27. As vari´aveisi1 ei2 ocupam 1 byte cada. J´a a vari´aveliptr, por ser um ponteiro, ocupa 4 bytes da mem´oria.

Uma vez criadas as vari´aveis, a primeira instru¸c˜ao executada ´e copiar o n´umero 20 na vari´avel i1. Depois, copiar o endere¸co de i1 em iptr. iptr recebe ent˜ao o endere¸co 0x1a3c22. Por fim, copiar o dado apontado pelo endere¸co contido emiptr para a vari´avel i2, ou seja, o n´umero 20.

2.5.5

Operador de Referenciamento de Campo de

Es-trutura (-

>

)

Quando uma estrutura ´e associada a um ponteiro, seus campos s˜ao aces-sados substituindo os operadores ‘.’ pelos operadores ‘->’. Por exemplo:

struct DadosPessoais { char nome[100]; char matricula; };

struct DadosPessoais Cadastro[1000], *ficha;

/* endere¸co do cent´esimo elemento. */ ficha = &(Cadastro[99]);

/* equivalente a: Cadastro[99].matricula = 5234542; */ ficha->matricula = 5234542;

/* idem: Cadastro[99].nome, ... */

(41)

2.5. PONTEIROS 41

A aplica¸c˜ao de ponteiros com estruturas ´e mais intensa quando o programa a ser implementado envolve banco de dados e as estruturas apontam para outras estruturas. ´E normal que o espa¸co ocupado por uma estrutura seja grande comparado com o espa¸co de um ponteiro. Nestas situa¸c˜oes, ´e mais econˆomico e mais eficiente fazer-se referˆencia ao endere¸co a estrutura que uma c´opia desta.

2.5.6

Aritm´

etica de Ponteiros

Os endere¸cos de mem´oria s˜ao n´umeros inteiros sem sinal. Uma vez ar-mazenados em ponteiros, estes endere¸cos podem ser incrementados ou decre-mentados. Mas o incremento de uma unidade no endere¸co de mem´oria pode n˜ao corresponder ao endere¸co do pr´oximo byte. O n´umero de bytes corres-pondente ao incremento (ou decremento) de uma unidade depende do tipo do ponteiro. Assim, se o ponteiro ´e do tipo char ou unsigned char, somar ou subtrair uma unidade de um endere¸co significa somar ou subtrair 1 byte. Se o ponteiro ´e um short int ou um unsigned short int, a unidade corresponde a 2 bytes. Se o ponteiro ´e do tipo int, unsigned int ou long int e o endere¸co de mem´oria ´e incrementado (ou decrementado) de uma unidade, esta unidade corresponder´a a 4bytes. Portanto, a unidade somada ou subtra´ıda de um en-dere¸co de mem´oria corresponde ao comprimento do tipo, em bytes, associado ao ponteiro. A tabela abaixo resume a correspondˆencia entre o tipo associado ao ponteiro e o n´umero de bytes acrescido ao endere¸co de mem´oria quando se soma uma unidade a este.

tipo apontado bytes unsigned char 1

char 1

short int 2

unsigned int 4

int 4

unsigned long 4

long 4

float 4

double 8

long double 10

2.5.7

Um Cuidado Mais Especial

´

(42)

dos ponteiros e outro que n˜ao, o melhor ´e optar por perder um pouco mais de tempo de desenvolvimento e controlar de forma mais r´ıgida os ponteiros.

Em termos pr´aticos, cuidar da inicializa¸c˜ao dos ponteiros significa inici´a-los com algum endere¸co “inofensivo” ou pr´e-estabelecido. Dentre os milh˜oes de endere¸cos v´alidos, o endere¸co 0x0 (em hexadecimal) ´e o melhor, pois a maioria dos sistemas operacionais “sabe” que escrever no endere¸co 0 ´e errado. Con-sequentemente, o sistema operacional gera uma mensagem de erro abortando o programa. Em C, este endere¸co 0 ´e representado pelo car´acter NULL, que j´a foi mencionado no t´opico sobre vari´aveis do tipo char. A inicializa¸c˜ao do ponteiro com NULL ´e simples e direto:

char *cptr = NULL; double *dptr = NULL; unsigned *uiptr = NULL;

Se o programador compilar seu programa e depois execut´a-lo, e, em algum instante, receber uma mensagem de erro por referˆencia indevida a um endere¸co de mem´oria, ele deve desconfiar que algum ponteiro em seu programa ainda esteja com o endere¸co NULL.

O perigo real ´e que as a¸c˜oes do sistema operacional (ejetar CD, desligar o computador, formatar o HD, etc) s˜ao fun¸c˜oes que est˜ao na mem´oria e tem um endere¸co de in´ıcio. Se, por um azar do destino, um ponteiro n˜ao inicializado contiver o endere¸co de entrada da fun¸c˜ao que formata o HD e este ponteiro for usado, o sistema operacional pode “entender” que o programador esteja querendo formatar seu HD. E a´ı ... No melhor das hip´oteses, o computador ir´a travar ou reiniciar. Mas se o programa estiver rodando em um supercom-putador junto de dezenas de outros programas, reiniciar o comsupercom-putador pode n˜ao ser uma boa.

2.5.8

Rela¸c˜

ao entre Ponteiros, Vetores e Matrizes

Existe uma rela¸c˜ao muito estreita entre os vetores, matrizes e ponteiros. O nome de um vetor e de uma matriz (sem o ´ındice de um elemento) ´e um ponteiro. Nos exemplos apresentados acima,v intedmats˜ao ponteiros do tipo unsigned edouble, respectivamente, e armazenam os endere¸cos onde come¸cam o vetor e a matriz. Logo, para recuperar estes endere¸cos, n˜ao ´e necess´ario usar o operador ‘&’. S´o se usa este operador com vetores e matrizes se o programador deseja descobrir o endere¸co de um elemento individualmente (referenciado por seus ´ındices). Por exemplo:

char cvet[3], *cptr1, *cptr2;

(43)

2.5. PONTEIROS 43

copiado para cptr1. */ cptr2 = &(cvet[2]); /* o endere¸co do terceiro

elemento do vetor cvet ´

e copiado para cptr2. */

Os endere¸cos iniciais do vetor e da matriz correspondem aos endere¸cos dos primeiros elementos do vetor e da matriz, respectivamente. No exemplo apresentado, o endere¸co contido em cvet ´e o mesmo obtido com o comando “&(cvet[0])”. Na figura da mem´oria, assumindo o vetor cvet iniciando no endere¸co 0x1a3c26, seus elementos e demais vari´aveis ficam dispostos da se-guinte forma:

Endere¸co Mem´oria Vari´avel

0x1a3c22 0x26 cvet 0x3c

0x1a 0x0

0x1a3c26 -1 cvet[0]

0x1a3c27 2 cvet[1]

0x1a3c28 100 cvet[2] 0x1a3c29 0x26 cptr1

0x3c 0x1a 0x1a3c2c 0x0

0x1a3c2d cptr2

Endere¸co Mem´oria Vari´avel

0x1a3c2d 0x28 cptr2 0x3c

0x1a 0x0

Para acessar um elemento do vetor ou da matriz, basta referenci´a-lo pelo ´ındice:

cvet[0] = 5; /* copiando o n´umero 5 para o primeiro elemento de cvet. */

(44)

char c[4], ch;

/* acesso direto ao terceiro elemento do vetor c. */

ch = c[2];

/* acesso indireto ao terceiro elemento do vetor c atrav´es de aritm´etica de ponteiros. */

ch = *(c+2);

Endere¸co Mem´oria Vari´avel

0x1a3c22 0x26 c

0x3c 0x1a 0x0

0x1a3c26 6 c[0]

0x1a3c27 20 c[1]

0x1a3c28 -47 c[2] 0x1a3c29 -55 c[3]

0x1a3c2a -47 ch

Assumindo que os elementos do vetor c come¸cam no endere¸co 0x1a3c26, o resultado da soma do ´ındice 2 com este endere¸co gera o endere¸co 0x1a3c28 que ´e o endere¸co do elemento c[2]. Por isso, o dado apontado por c+2, ou seja, *(c+2), e o elemento c[2]s˜ao os mesmos OBRIGATORIAMENTE! Veja que isto funciona para todos os elementos do vetor!

c[0] <-> *(c+0) = *c c[1] <-> *(c+1) c[2] <-> *(c+2) c[3] <-> *(c+3)

A organiza¸c˜ao de uma matriz ´e mais sofisticada e exige mais tempo para ser compreendida.

A matriz ´e um vetor de ponteiros que contˆem os endere¸cos dos dados. Acompanhe o seguinte exemplo: uma matriz m do tipo char com dimens˜ao 2× 2. A declara¸c˜ao da matriz m pode ser vista no fragmento de c´odigo a seguir:

char m[2][2];

m[0][0] = 6; m[1][1] = -55; m[1][0] = -47; m[0][1] = 20;

Referências

Documentos relacionados

[r]

Os candidatos reclassificados deverão cumprir os mesmos procedimentos estabelecidos nos subitens 5.1.1, 5.1.1.1, e 5.1.2 deste Edital, no período de 15 e 16 de junho de 2021,

Neste presente estudo foi aplicado um questionário sobre a aceitação de um dos seus detergentes comparado a dois concorrentes utilizando a escala ideal , além

c.4) Não ocorrerá o cancelamento do contrato de seguro cujo prêmio tenha sido pago a vista, mediante financiamento obtido junto a instituições financeiras, no

Os autores relatam a primeira ocorrência de Lymnaea columella (Say, 1817) no Estado de Goiás, ressaltando a importância da espécie como hospedeiro intermediário de vários parasitos

No presente documento descrevemos o método desenvolvido pelo Laboratório de Ecologia Espacial e Conservação – LEEC (UNESP-Rio Claro) para identifi car áreas com alto potencial

Dois modelos foram utilizados para inferir o potencial de regeneração. Em cada QSHIPSEWIWXVEXɯKMEWEHSXEHEWJSVEQ.. Além de considerar o tamanho e a conectividade entre os fragmentos

§ 2 º As instituições que não atendam ao disposto no caput deste artigo podem submeter seus projetos de criação de curso à Câmara de Educação Superior do Conselho Nacional de