Linguagens de Programa¸c˜
ao C e C++: Uma
Introdu¸c˜
ao
Lu´ıs Fernando de Oliveira
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
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
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
1
Programa¸
c˜
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
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().
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++
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>
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.
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.
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.
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.
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,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
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 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 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.
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.
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.
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) {
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.
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
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”.
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 */
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
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. */
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 . */
}
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.
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:
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];
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 ");
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 ;
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.
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 */
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 ). */
}
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
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.
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;
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 (*)
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, ... */
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
´
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;
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. */
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;