Escolar Documentos
Profissional Documentos
Cultura Documentos
Introdução
26 de maio de 2011
2
Sumário
1 Programação em C e C++ 7
1.1 Aspectos Básicos do Código-Fonte em C . . . . . . . . . . . . 7
1.2 Compiladores C e C++ . . . . . . . . . . . . . . . . . . . . . 9
1.3 Compilação dos Códigos-fonte . . . . . . . . . . . . . . . . . . 10
2 Tipos de Dados 13
2.1 Representação de Dados na Memória . . . . . . . . . . . . . . 14
2.1.1 Representação de Números Inteiros . . . . . . . . . . . 15
2.1.2 Representação de Números Reais . . . . . . . . . . . . 16
2.1.3 Tipos de Dados Representados . . . . . . . . . . . . . . 17
2.1.4 Organização dos Dados na Memória . . . . . . . . . . . 18
2.2 Tipos Definidos de Dado . . . . . . . . . . . . . . . . . . . . . 19
2.2.1 Tipo Literal: char . . . . . . . . . . . . . . . . . . . . 19
2.2.2 Tipo Inteiro: char e int . . . . . . . . . . . . . . . . . 21
2.2.3 Tipo Real: float e double . . . . . . . . . . . . . . . . . 22
2.2.4 Tipo Indefinido: void . . . . . . . . . . . . . . . . . . . 23
2.2.5 Tipo Lógico . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2.6 Modificadores de Tipo signed e unsigned . . . . . . . . 24
2.2.7 Modificadores de Tipo short e long . . . . . . . . . . . 25
2.2.8 Type Casting . . . . . . . . . . . . . . . . . . . . . . . 26
2.2.9 Resumo dos Tipos intrı́nsecos . . . . . . . . . . . . . . 27
2.3 Vetores, Matrizes e Strings . . . . . . . . . . . . . . . . . . . . 27
2.3.1 Declaração 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ções: enum . . . . . . . . . . . . . . . . . . . 32
2.4.3 Uniões: union . . . . . . . . . . . . . . . . . . . . . . . 33
3
4
5 Funções 63
5.1 Argumentos por Valor e por Referência . . . . . . . . . . . . . 64
5.2 Protótipo de uma Função . . . . . . . . . . . . . . . . . . . . 66
5.3 Relação das Funções Intrı́nsecas . . . . . . . . . . . . . . . . . 67
5.4 Funções de Entrada e Saı́da Padrões . . . . . . . . . . . . . . 74
6 Diretivas de Compilação 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ção da Programação em C 85
8 Classes e Objetos 87
8.1 Encapsulamento de Atributos e Métodos . . . . . . . . . . . . 88
8.2 Visibilidade de Implementação . . . . . . . . . . . . . . . . . . 89
8.3 Hierarquia de Classes e Hereditariedade . . . . . . . . . . . . 91
8.4 Polimorfismo de Métodos . . . . . . . . . . . . . . . . . . . . . 93
8.5 Objetos das Classes . . . . . . . . . . . . . . . . . . . . . . . . 94
8.6 Métodos Construtores e Destruidores . . . . . . . . . . . . . . 95
8.7 Sobrecarga de Operadores . . . . . . . . . . . . . . . . . . . . 98
8.8 Virtualização de Métodos . . . . . . . . . . . . . . . . . . . . 104
6
Programação em C e C++
1
1.1 Aspectos Básicos do Código-Fonte em C
Para começar, vejamos um exemplo de código-fonte simples em C para,
depois, apresentarmos cada elemento componente dele.
b = 0.5;
c = a+b;
return 0;
}
7
8 CAPÍTULO 1. PROGRAMAÇÃO EM C E C++
• gcc:
– por etapas:
gcc -c <lista códigos fontes>
gcc -o <programa executável><lista códigos objeto>
– forma resumida:
gcc -o <programa executável><lista códigos fontes>
• g++:
– por etapas:
g++ -c <lista códigos fontes>
g++ -o <programa executável><lista códigos objeto>
– forma resumida:
g++ -o <programa executável><lista códigos fontes>
13
14 CAPÍTULO 2. TIPOS DE DADOS
b7 b6 b5 b4 b3 b2 b1 b 0
msb
ch = ’a ’;
letra = ’+ ’;
}
Reparar que a constante literal é formada por um único carácter e que este
é digitado entre apóstrofos.
A linguagem C reserva algumas constantes literais especiais para controle
de edição e exibição de caracteres tais como os exibidos na tabela a seguir.
20 CAPÍTULO 2. TIPOS DE DADOS
constante ASCII
literal hexadecimal carácter 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ão
\t 0x09 HT tabulação horizontal
\v 0x0b VT tabulação vertical
\\ 0x5c \ backslash
\’ 0x27 ’ apóstrofo
\” 0x22 ” aspas
\? 0x3f ? interrogação
Fora desta tabela, existe um outro carácter especial chamado NULL que
é ‘\0’. Como seu nome diz, ele é um carácter nulo que corresponde ao valor
zero. Ele será apresentado no tópico sobre ponteiros.
Para imprimir na tela o conteúdo de variáveis do tipo char quando o
conteúdo é uma constante literal, usamos o seguinte comando:
printf("%c",/*variavel*/);
onde o código de formatação “%c” indica que um carácter deverá ser impresso.
E para que a função printf() funcione corretamente, é necessário incluir a
diretiva “#include <stdio.h>” no inı́cio do código. Repetindo o exemplo2:
Código 2.3: exemplo4a.c
# include < stdio .h >
char letra = ’+ ’;
a = -1234567;
h = 85748403;
que está sendo executado pode ser desviado para uma posição especı́fica dentro
do código. O teste lógico também está presente nas instruções de repetição. O
teste de parada pode usar uma variável lógica ou o resultado de uma expressão
lógica para determinar se a iteração prossegue ou para.
Tome os seguintes exemplos:
Código 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;
}
printf ( " sizeof ( char )=% d \ nsizeof ( unsigned char )=% d \ n "
2.2. TIPOS DEFINIDOS DE DADO 25
de bytes de comprimento que o tipo int, isto é, 4 bytes. Portanto, pode ar-
mazenar valores entre -2.147.483.648 e 2.147.483.647. A linguagem C aceita a
declaração de variáveis usando-se somente as palavras reservadas short e long.
Ela entende que as variáveis serão do tipo short int e long int respectivamente,
mas esta não é uma boa regra de programação.
O tipo double também admite o modificador long. Uma variável do tipo
long double pode assumir valores com precisão ou amplitudes muito grandes
(±3, 37 × 10±4932 ). Ela possui um comprimento de 80 bits (ou 10 bytes) com
18 dı́gitos de precisão.
Os compiladores Fortran possuem um mecanismo que permite definir o
comprimento de uma variável inteira, mas este mecanismo não é padronizado.
Alguns compiladores aceitam a declaração integer *8 como um inteiro de 8
bytes, outros declaram integer 8, e há aqueles que definem integer (8). Substi-
tuindo o número 8 por 4 ou 2, seria possı́vel declarar-se variáveis inteiras com
4 ou 2 bytes respectivamente.
A string frase pode conter até 100 caracteres e palavra, 30; O que diferencia
o C do Fortran é a utilização do carácter NULL como terminador do vetor.
Em Fortran, se uma string 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és do comando “write(unit=*,fmt=“(a)”) palavra”, os 30
caracteres serão impressos. Para eliminar os 6 caracteres restantes, é preciso
usar a função “trim(palavra)”.
Em C, o carácter que indica o fim da string é o NULL. No exemplo acima,
se o vetor palavra[30] contiver a palavra “paralelepipedo”, o décimo quinto
carácter, ou seja, palavra[14], será o carácter NULL.
Quando o programador usar a função “printf(“%s”,palavra);” que imprime
na tela (idêntico ao comando “write” do Fortran), serão impressos somente os
14 caracteres da palavra “paralelepipedo”. Se não houver o terminador NULL,
será impresso a palavra “paralelepipedo” e um monte de outros caracteres. A
sequência de “lixos” só irá parar quando o computador encontrar um carácter
NULL perdido na memória.
2.4. TIPOS ABSTRATOS DE DADO 29
strcpy(palavra,‘‘paralelepipedo’’);
struct tDadosPessoais {
char nome[256];
char endereco[256];
int identidade;
};
30 CAPÍTULO 2. TIPOS DE DADOS
Repare que a declaração struct termina com o ponto e vı́rgula. Para se de-
finir uma variável deste novo tipo de dado, o procedimento é similar a definição
de variáveis de qualquer outro tipo:
/* 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]. identidade = 1234;
/* usuario de indice 12 */
strcpy ( Usuario [12]. nome , " Carlos Parreira " );
strcpy ( Usuario [12]. endereco , " R . Da Cruz , casa 100 " );
Usuario [12]. identidade = 6343;
posicao = PARA_CIMA ;
situacao = PARA_DIREITA ;
comando = PARA_BAIXO ;
}
2.4. TIPOS ABSTRATOS DE DADO 33
union char4int {
unsigned char c[4];
int i;
};
/* declaracao da variavel */
union char4int Byte4 ;
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;
};
bits . b0 = 0;
bits . b1 = 1;
bits . b2_3 = 2; /* 2 decimal em binario é 10. */
bits . b4_7 = 6; /* 6 decimal em binario é 0110. */
union StatusMouse {
/* o campo de bits e a variavel unsigned char */
/* compartilham a mesma area da memoria . */
struct CampoBits bits ;
unsigned char ch ;
};
union StatusMouse sm ;
sm . ch = 12;
/* 12 em binario é 00001100. O bit b0 eh o */
/* mais a direita e os bits de b4_7 , os mais */
/* a esquerda . Portanto , b0 é 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ão está
mostrado; assuma que exista um mecanismo que faça isso).
2.5 Ponteiros
Dada a importância e a frequência com que os ponteiros são utilizados em
C, este “tipo” único de dado, que é tı́pico do C e de umas poucas outras lin-
guagens, será apresentado de forma cuidadosa nesta seção. Antecipando uma
informação crucial, os ponteiros estão intimamente relacionados aos vetores e
matrizes.
ele sabe que a sua variável será alocada em algum endereço na memória do
computador. O programador não precisa, a princı́pio, saber o endereço da
variável para fazer sua lógica funcionar ou armazenar um dado; o computador
é que faz o papel de relacionar o nome da variável com o endereço no qual ela
foi alocada, e “copiar para” ou “ler de” lá os dados.
0x1100 i1
0x1107
0x1108
Vejamos um outro exemplo agora usando variáveis do tipo int que tem
4 bytes de comprimento. Quando o programador declara uma variável do
tipo int, ele está solicitando ao computador que reserve 4 bytes contı́guos
na memória para serem usados no armazenamento de números inteiros. O
computador irá, novamente, associar um endereço de memória ao nome da
variável. O endereço associado é o endereço do primeiro byte dos quatro que
formam o número inteiro, o endereço base (veja a figura anterior).
Se o programador declara duas variáveis int, i1 e i2, o computador reserva
4 bytes para cada uma delas. A variável i1 é alocada no endereço 0x1100. A
variável i2 só poderá ser alocada 4 bytes depois. Isto significa que seu endereço
de memória será 0x1104. Além disso, qualquer outra variável que tiver de ser
alocada na memória, só poderá estar a partir do endereço 0x1108, uma vez
que o byte do endereço 0x1107 ainda faz parte da variável i2.
A relação entre uma variável e seu endereço é biunı́voca, de um para um:
toda variável possui um endereço especı́fico, assim como todo endereço corres-
ponde a uma variável.
char *ch;
unsigned char *uch;
short *si; /* mesmo que ‘short int’ */
2.5. PONTEIROS 39
struct DadosPessoais {
char nome[100];
char matricula;
};
dos ponteiros e outro que não, o melhor é optar por perder um pouco mais de
tempo de desenvolvimento e controlar de forma mais rı́gida os ponteiros.
Em termos práticos, cuidar da inicialização dos ponteiros significa iniciá-los
com algum endereço “inofensivo” ou pré-estabelecido. Dentre os milhões de
endereços válidos, o endereço 0x0 (em hexadecimal) é o melhor, pois a maioria
dos sistemas operacionais “sabe” que escrever no endereço 0 é errado. Con-
sequentemente, o sistema operacional gera uma mensagem de erro abortando
o programa. Em C, este endereço 0 é representado pelo carácter NULL, que
já foi mencionado no tópico sobre variáveis do tipo char . A inicialização do
ponteiro com NULL é simples e direto:
char m[2][2];
m[0][0] = 6;
m[1][1] = -55;
m[1][0] = -47;
m[0][1] = 20;
char m[2]
| {z }[2]
vetor
47
48CAPÍTULO 3. OPERADORES MATEMÁTICOS, LÓGICOS E BINÁRIOS
não haverá conversão de tipo. Qualquer outro tipo de variável que receba este
mesmo dado exigirá adequação, ou seja, conversão de tipo.
Veja este caso:
char i = 200;
char j = 10.0;
Mas a variável i é representada com 1 byte com sinal. Não “casa” nenhuma
das opções de representação binária da constante numérica 200. Logo, alguma
conversão de tipo deverá acontecer. A regra do compilador é tentar usar sempre
a menor representação binária em termos de comprimento do formato. Desta
forma, o compilador converterá a representação binária 11001000(b) (constante
inteira de 1 byte sem sinal) em 1|1001000(b) (constante inteira de 1 byte com
sinal). A constante 1|1001000(b) é o número −56. Se o conteúdo da variável
i fosse impressa na tela, observar-se-ı́a a resposta “−56”.
Na segunda declaração, a variável j está recebendo a constante real 10.0.
É óbvio que uma variável do tipo inteiro não pode armazenar um dado real.
Logo, haverá uma conversão de tipo com truncamento da parte decimal. A
constante real 10.0 é um valor com sinal. Quando a parte decimal é truncada,
a constante assume a representação numérica de uma constante inteira com
sinal (valor 10). A constante 10 pode ser atribuı́da a uma variável do tipo
char sem problemas. Então, o efeito final da conversão foi apenas o trunca-
mento. Se a constante real fosse 200.0, provavelmente o processo de conversão
aplicaria um truncamento da parte decimal e depois uma conversão do valor
200 em −56. Bem, o resultado em si não importa aqui. O importante é que
o resultado final desta atribuição não será o que o programador estará espe-
rando: o programador tentou inicializar a variável j com o valor 200.0 e, no
final, ele terá qualquer outro resultado que não o 200.0. Esse é um erro de
lógica comum de acontecer com os principiantes em programação. Portanto,
CUIDADO!
unsigned k = -10;
short l = 600000;
float m = 1;
double n = ‘x’;
Com os tipos reais não é diferente. A variável m foi declarada como float
e está recebendo uma constante inteira. O tipo da constante será trocada de
inteira para real e uma parte decimal será acrescentada à constante. Como
efeito final, a variável m armazenará o valor 1.0. Esta conversão não oferece
50CAPÍTULO 3. OPERADORES MATEMÁTICOS, LÓGICOS E BINÁRIOS
char i = 20;
unsigned k = 10;
float m = 1e-3;
i++;
k--;
char i = 20, j, k;
j = i++;
k = ++j;
unsigned l = 10, m, n;
m = --l;
n = m--;
if (condiç~
ao for verdadeira) {
/*
bloco de comandos para serem
executados caso a condiç~
ao seja
verdadeira
*/
}
if (condiç~
ao for verdadeira) {
/*
bloco de comandos para serem
executados caso a condiç~
ao seja
verdadeira
*/
} else {
/*
bloco de comandos para serem
executados caso a condiç~
ao seja
falsa
*/
}
55
56 CAPÍTULO 4. ESTRUTURAS DE CONTROLE DE EXECUÇÃO
Primeira observação: repare que a limitação dos blocos que serão executados
nos casos de testes verdadeiros ou falsos é definida pelos caracteres “{” e “}”.
Na linguagem C, estes caracteres são chamados de “limitadores de escopo”.
Como situação particular, caso o bloco de instruções do if ou do else for com-
posto por uma única instrução, os limitadores de escopo podem ser omitidos.
Mas isso gera uma péssima prática de programação, pois, se o programador,
durante uma manutenção de seu código, achar que são necessárias novas ins-
truções dentro do bloco do if (ou else) e ele não colocar os limitadores de
escopo, o código não fara nada do que ele acredita estar fazendo. Os resulta-
dos serão imprevisı́veis, além de incontroláveis. Portanto, como sugestão, use
sempre os limitadores de escopo, mesmo que o if e/ou o else encerrem uma
única instrução em seus blocos.
verdadeira
*/
} else {
/*
bloco de comandos para serem
executados caso a condiç~
ao3 seja
falsa
*/
}
condiç~
ao ? resultado1 : resultado2 ;
limite = 60.0;
temp = sensor_de_temperatura();
on_off = (temp > limite) ? 1 : 0;
ligar_alarme_de_incendio(on_off);
switch(valor) {
case n1:
/* bloco corresponde ao caso n1 */
break;
case n2:
/* bloco corresponde ao caso n2 */
break;
case n3:
/* bloco corresponde ao caso n3 */
break;
/* outros ‘case’s testando outros
conteudos de ‘‘valor’’ */
default:
/* bloco corresponde ao caso default */
}
Cada case é encerrado por um comando break que tem o papel de inter-
romper a execução do bloco de instruções. Assim, se o conteúdo da variável
valor for igual a número representado por n1, a execução do código é desviado
para o bloco do “case n1” e terminará no primeiro comando break que for
encontrado. Se “case n1” não tiver o comando break, a execução do código
invadirá o bloco de “case n2” e parará no comando break. Este comporta-
mento da estrutura do switch é útil quando vários case’s são tratados por um
mesmo bloco de instruções. Veja o seguinte exemplo:
falta o exemplo
soma = 0;
for ( cont=-10; cont<10; cont++ ) {
/* somatório de uma série aritmética */
soma = soma + 1;
}
1. o contador é inicializado;
soma = 0;
cont = -10;
while ( cont<10 ) {
/* somatório de uma série aritmética */
soma = soma + 1;
cont++;
}
soma = 0;
cont = -10;
do {
/* somatório de uma série aritmética */
soma = soma + 1;
cont++;
} while ( cont<10 );
double quadrado(double x) {
double y;
y = x*x;
return y;
}
O exemplo acima define uma função chamada quadrado que recebe como
argumento uma variável do tipo double chamada x. A função quadrado retorna
um valor do tipo double (variável y). O corpo da função é composto pela
declaração da variável y, pela linha que diz que y é igual a x vezes x e pelo
“return y;”. A palavra reservada return indica o que é que deve ser retornado
pela função.
Uma função pode ter mais de um return, dependendo da lógica da função.
Por exemplo:
63
64 CAPÍTULO 5. FUNÇÕES
if (a>b)
/* retorna "verdadeiro" e a funcao termina; */
return 1;
/* senao */
else
/* retorna "falso" e a funcao termina. */
return 0;
}
void ImprimeValor(int a) {
printf("O valor da variavel eh: %d\n",a);
}
void MensagemPadrao(void) {
printf("Estou vivo.\n");
}
double vezes2(double a) {
a = a*2;
return a;
}
x = 29.3;
y = vezes2 ( x );
Da forma como a função vezes2 foi declarada, o valor de ‘x’ (no programa
principal) será COPIADO para dentro da função. Quem “captura” a cópia do
valor de ‘x’ dentro da função é a variável ‘a’. Modificar o conteúdo de ‘a’ não
afetará o valor de ‘x’ (que está fora da função), porque ‘a’ recebeu uma cópia
do valor de ‘x’. Pensando em termo de endereço de memória, as variáveis ‘x’
e ‘a’ estão localizadas em lugares DISTINTOS na memória do computador.
Logo, modificar ‘a’ não perturbará ‘x’. Isto é passagem de argumento por
valor. O mais correto seria dizer passagem de argumento por cópia de
valor.
Para que o dado seja passado para dentro de uma função e as alterações
que forem feitas sejam percebidas fora da função, é necessário que o dado
não seja simplesmente copiado. É necessário fazer-se uma referência ao lo-
cal na memória onde o dado original se encontra. E endereço de memória é
PONTEIRO!!! Portanto, para que um dado seja passado para dentro de uma
função e que a manipulação desse dado seja refletida fora da função, deve-se
passar como argumento o endereço do dado na memória. Isto é passagem de
argumento por referência. Exemplo:
66 CAPÍTULO 5. FUNÇÕES
x = 29.3;
dobro ( x );
tipo de código de
variável formatação
unsigned char %ud
char %c, %d
short int %d
int %d
long int %ld
unsigned short int %ud
unsigned int %ud
unsigned long int %uld
float %f
double %lf
long double %lf
Repare que o tipo char tem dois códigos de formatação: ‘%c’ e ‘%d’. ‘%c’ é
usado quando a variável char possui uma constante literal e ‘%d’, quando o
conteúdo for um número inteiro de 8 bits.
Diretivas de Compilação
6
As diretivas de compilação, como já foi dito, são comandos para o compi-
lador e não compõem o conjunto de instruções de comando de declaração e de
execução. Mas são muito úteis quando o código-fonte precisa se adaptar às
condições do sistema operacional ou do próprio computador. De uma forma
geral, as diretivas auxiliam na portabilidade dos códigos-fonte.
75
76 CAPÍTULO 6. DIRETIVAS DE COMPILAÇÃO
isto é, está pré-instalado e não foi confeccionado pelo programador, a diretiva
#include usa os sı́mbolos ‘<’ e ‘>’ envolvendo o nome do arquivo de cabeçalho.
Você já deve ter visto isso em vários exemplos:
exemplo3b.c
# include < stdio .h >
#include "minhas_funcoes.h"
#include "/home/usuario/programador/minhas_funcoes.h"
No caso dos arquivos de sistema, o próprio sistema sabe onde eles estão ar-
mazenados. Então o compilador pode usar esta informação e não exige do
programador a inclusão do caminho dos arquivos de sistema. Mas os arquivos
do programador não estão em locais pré-definidos e nem faz sentido que es-
tejam. Então, o compilador usa o caminho associado ao nome do arquivo de
6.2. DIRETIVAS #DEFINE|#UNDEF 77
double quadrado(double a) {
return a*a;
}
y = quadrado ( x );
y = quadrado ( x +10);
y = quadrado ( x +10);
Neste exemplo, a diretiva #if está sendo usada para definir que bloco de
código será compilado. Como qualquer coisa diferente de 0 é considerado ver-
dadeiro, a declaração “#if 1” retorna verdadeiro para o compilador e ele
usará o trecho de código entre o #if e o #else no processo de compilação
que é o comando “#define quadrado(a) a*a”, ou seja, o compilador usará a
definição da macro quadrado(a). Se o programador trocar a constante 1 para
0, a declaração “#if 0” retornará falso e o trecho de código compilado será a
da função “double quadrado(double a) { ... }”. Em termos de legibili-
dade, o código-fonte não foi afetado pelo teste da diretiva #if . Mas em termos
de performance, usar macros gera programas mas rápidos porque a chamada
de uma função envolve várias instruções em código de máquina. É claro que
neste exemplo não é possı́vel avaliar-se esta diferença de desempenho, pois o
código é muito simples. Mas no caso de um laço com muitas iterações, o uso
de funções ou macros pode fazer diferença na avaliação final de desempenho.
Mas toda vantagem cobra um preço. Neste caso, o aumento de desempenho é
acompanhado do aumento do tamanho do código executável. O uso de funções
gera programas mais enxutos. O uso de macros gera programas mais rápidos.
Use isso com sabedoria!
O programador pode aninhar e encadear diretivas condicionais tal qual as
instruções if ( ) { } else { }. A diretiva #elif é a combinação do #else com o
#if .
A diretiva #endif encerra a atuação das diretivas condicionais.
typedef struct {
double x , y ;
} upla ;
upla c ;
c . x = 1;
c . y = 1;
return 0;
}
typedef struct {
double x , y ;
} upla ;
# endif
# endif
# endif
Algum texto
85
86 CAPÍTULO 7. ORGANIZAÇÃO DA PROGRAMAÇÃO EM C
Classes e Objetos
8
A linguagem C segue o modelo (paradigma) da programação estruturada.
Ela se caracteriza principalmente na organização do código fonte na forma
de funções. Por isso ela é classificada como uma linguagem procedural (que
usam funções e procedimentos). A organização baseada em funções e proce-
dimentos permite que o usuário reutilize seus recursos já programados sempre
que precisar, bastando, para isso, chamar a função ou sub-rotina necessária.
Na programação estruturada, a solução dos problemas é obtida através da
execução da sequência de instruções implementadas no código. Portanto, as
linguagens estruturadas são ditas serem orientadas a fluxo de execução.
Mas nem todos os problemas são modelados facilmente através do mo-
delo de programação estruturada. A natureza de alguns problemas exige uma
abordagem diferente. Um exemplo simples está na aplicação da computação
para reprodução de sistemas, ou seja, simulação. Neste tipo de problema,
várias partes do sistema real estão operando simultaneamente e cada parte do
sistema possui caracterı́sticas próprias. A colaboração entre as partes é que
“materializam” a solução do problema.
Imagine uma célula. Cada estrutura da célula faz uma coisa diferente, mas
tire uma delas para você ver o que acontece... Então, cada parte, cada elemento
é essencial para o funcionamento do todo, mas a parte, o elemento, não é o
todo. O importante neste paradigma é a colaboração entre as partes, cada um
fazendo o que sabe e colaborando com as demais. E a colaboração se dá através
de trocas de mensagens. No caso da célula, as mensagens são as substâncias
quı́micas liberadas por cada estrutura. Essas substâncias provocam alterações
no funcionamento das outras estruturas produzindo ações diferentes.
Se fôssemos simular uma célula, partirı́amos do projeto dos elementos cons-
tituintes dela, pois cada uma possui caracterı́sticas e habilidades particulares.
Podemos olhar para cada elemento projetado como um objeto, algo que possui
propriedades e capacidades. Depois, colocarı́amos estes objetos juntos numa
caixa e a “ligarı́amos”. Imaginando que cada objeto começasse a interagir com
87
88 CAPÍTULO 8. CLASSES E OBJETOS
A outra caracterı́stica das classes são os métodos. Como foi dito, método
é toda habilidade que a classe possui. É sua capacidade de fazer coisas. A
implementação dos métodos de uma classe é idêntica à implementação de uma
função. Um método pode receber dados e devolver dados. Estes dados que
“vão e vem” através dos métodos são as mensagens. É através destas trocas
de mensagens que um programa orientado a objeto funciona.
Em C++, uma classe é implementada da mesma forma que uma estrutura
do tipo struct. Ela possui campos (os atributos da classe) e incorpora funções
(os métodos da classe), tudo na mesma estrutura. Esta propriedade se chama
encapsulamento. A declaração de uma classe usa a palavra reservada class:
class Nome_da_Classe {
/* declaraç~
ao dos atributos */
char dado1;
unsigned dado2;
struct estrutura1 {
} dado3;
/* declaraç~
ao dos métodos */
void medodo1(void);
unsigned metodo2(void);
void metodo3(float arg1);
};
Os atributos são dado1, dado2 e dado3 dos tipos char , unsigned e struct es-
trutura1, respectivamente, e os métodos são metodo1(void), metodo2(void)
e metodo3(float arg1).
Os tipos intrı́nsecos são classes.
class Nome_da_Classe {
private:
/* declaraç~
ao dos atributos privados */
/* declaraç~
ao dos métodos privados */
90 CAPÍTULO 8. CLASSES E OBJETOS
public:
/* declaraç~
ao dos atributos públicos */
/* declaraç~
ao dos métodos públicos */
};
class ContaBancaria {
private:
unsigned senha;
char autenticado;
public:
void CadastrarSenha(unsigned minha_senha);
unsigned SolicitarSenha(void);
char AcessarConta(long conta);
void VerificarSaldo(void);
};
Este é um exemplo bastante simplificado, mas serve para se ter uma noção
de como usar as propriedades de atributos e métodos privados e públicos. Dá
para fazer muito melhor que isso, mas não é o caso neste momento. Não faz
sentido, por exemplo, o usuário ter acesso a senha cadastrada. Este papel é
do sistema de acesso. Portanto, a senha deve ser privada. O método de au-
tenticação também não deve ser público. Quem deve acessá-lo é o método de
acesso a conta. Daria para imaginar, inclusive, um método para criptografia
dos dados, dificultando ainda mais o acesso aos dados. Este método de cripto-
grafia deveria ser privado para que a chave criptográfica não seja descoberta.
A execução deste procedimento é realizado por um objeto da classe Con-
taBancaria e não pela classe em si. Lembrando, a classe é um modelo. O
objeto da classe é que executa efetivamente as tarefas. Mas, imaginando o que
o objeto deveria fazer, é possı́vel se projetar a classe. Normalmente, esse é o
processo de design de uma programação orientada a objeto: faz-se um estudo
de caso, registra-se os requisitos, anota-se os procedimentos e projeta-se as
classes. Este processo deve ser realimentado, permitindo uma otimização da
programação.
protected:
float dado2;
int metodo2(char a) {
metodo1();
dado2 += a;
return dado2;
}
};
ClasseBase::metodo1(void) {
dado1 = (unsigned)dado2;
}
class ClasseUm {
protected:
char var1;
public:
void met1(void);
char met1(char a);
94 CAPÍTULO 8. CLASSES E OBJETOS
class Ponto {
protected:
double x, y, z;
public:
void X(double ix) { x = ix; }
void Y(double iy) { y = iy; }
void Z(double iz) { z = iz; }
Seus atributos são “double x, y, z;” e os métodos são tais que cada or-
denada pode ser manipulada individualmente, tanto para leitura como para
preenchimento.
8.6. MÉTODOS CONSTRUTORES E DESTRUIDORES 95
class Ponto {
protected:
double x, y, z;
public:
/* construtor da classe Ponto */
Ponto(void) { x = y = z = 0; }
Você deve perceber que o construtor tem o mesmo nome da classe, é declarado
como um método – logo, pode ou não ter argumentos – e não tem um tipo
definido – significa que o construtor não retorna nada. No exemplo, classe
Ponto implementa um construtor sem argumentos cujo papel é simplesmente
inicializar os atributos com o valor zero. Poderı́amos criar um outro construtor
que permitisse a inicialização dos atributos com outros valores que não zero:
96 CAPÍTULO 8. CLASSES E OBJETOS
class Ponto {
protected:
double x, y, z;
public:
/* construtores da classe Ponto */
Ponto(void) { x = y = z = 0; }
Ponto(double ix, double iy, double iz) {
x = ix; y = iy; z = iz;
}
class Ponto {
protected :
double x , y , z ;
public :
/* construtores da classe Ponto */
Ponto ( void ) { x = y = z = 0; }
Ponto ( double ix , double iy , double iz ) {
x = ix ; y = iy ; z = iz ;
}
void X ( double ix ) { x = ix ; }
void Y ( double iy ) { y = iy ; }
void Z ( double iz ) { z = iz ; }
return 0;
}
...
q = Ponto(1,-2,2);
...
int main(void) {
/* inst^
ancias */
Ponto p; /* construtor Ponto(void) */
Ponto q; /* construtor Ponto(void) */
/* invocando construtor */
/* Ponto(double,double,double) */
q = Ponto(1,-2,2);
printf("p = (%lf,%lf,%lf)\n",p.X(),p.Y(),p.Z());
98 CAPÍTULO 8. CLASSES E OBJETOS
printf("q = (%lf,%lf,%lf)\n",q.X(),q.Y(),q.Z());
return 0;
}
class vetor {
protected:
double x, y, z;
8.7. SOBRECARGA DE OPERADORES 99
public:
// construtor da classe vetor
vetor(void) { x = y = z = 0; }
// sobrecarga do operador +
// adiç~
ao de vetores
vetor operator +(vetor v) {
return vetor(x + v.x,y + v.y,z + v.z);
}
// sobrecarga do operador -
// subtraç~
ao de vetores
vetor operator -(vetor v) {
return vetor(x - v.x,y - v.y,z - v.z);
}
// sobrecarga do operador *
// produto escalar de vetores
double operator *(vetor v) {
return x*v.x + y*v.y + z*v.z;
}
// sobrecarga do operador *
// escala de vetores
vetor operator *(double f) {
return vetor(f*x,f*y,f*z);
}
// sobrecarga do operador *
// produto vetorial de vetores
vetor operator |(vetor v) {
return vetor(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x);
}
};
class vetor {
protected :
double x , y , z ;
public :
vetor ( void ) { x = y = z = 0; }
vetor ( double ix , double iy , double iz ) {
x = ix ; y = iy ; z = iz ;
}
a = vetor (1 ,0 ,0);
b = vetor (0 ,1 ,0);
printf ( " a = (% lf ,% lf ,% lf )\ n " ,a . X () , a . Y () , a . Z ());
printf ( " b = (% lf ,% lf ,% lf )\ n " ,b . X () , b . Y () , b . Z ());
c = a+b;
printf ( " a + b = (% lf ,% lf ,% lf )\ n " ,c . X () , c . Y () , c . Z ());
c = a|b;
8.7. SOBRECARGA DE OPERADORES 101
return 0;
}
c = a.+(b);
Isto significa dizer que não podemos escrever (por enquanto) a seguinte ins-
trução:
pois o compilador tentará usar uma definição do operador “∗” que deveria
estar declarada na classe float.1 Mas como a classe float não declara um
operador “∗” que receba a classe vetor como argumento, esta instrução “c =
0.5*a;” não é valida.
1
Sim, é isso mesmo. Os tipos intrı́nsecos da linguagem C são “encarados” como classes
no C++.
102 CAPÍTULO 8. CLASSES E OBJETOS
class vetor {
protected :
double x , y , z ;
public :
// construtores da classe vetor
vetor ( void ) { x = y = z = 0; }
vetor ( double ix , double iy , double iz ) {
x = ix ; y = iy ; z = iz ;
}
// sobrecarga do operador +
// adiç~
a o de vetores
vetor operator +( vetor v ) {
return vetor ( x + v .x , y + v .y , z + v . z );
}
8.7. SOBRECARGA DE OPERADORES 103
// sobrecarga do operador -
// subtraç~
a o de vetores
vetor operator -( vetor v ) {
return vetor ( x - v .x , y - v .y , z - v . z );
}
// sobrecarga do operador *
// produto escalar de vetores
double operator *( vetor v ) {
return x * v . x + y * v . y + z * v . z ;
}
// sobrecarga do operador *
// escala de vetores
vetor operator *( double f ) {
return vetor ( f *x , f *y , f * z );
}
// sobrecarga do operador *
// escala de vetores
friend vetor operator *( double f , vetor v ) {
return vetor ( f * v .x , f * v .y , f * v . z );
}
// sobrecarga do operador |
// produto vetorial de vetores
vetor operator |( vetor v ) {
return vetor ( y * v . z - z * v .y ,
z * v . x - x * v .z ,
x * v . y - y * v . x );
}
};
a = vetor (1 ,0 ,0);
b = vetor (0 ,1 ,0);
printf ( " a = (% lf ,% lf ,% lf )\ n " ,a . X () , a . Y () , a . Z ());
printf ( " b = (% lf ,% lf ,% lf )\ n " ,b . X () , b . Y () , b . Z ());
c = a+b;
printf ( " a + b = (% lf ,% lf ,% lf )\ n " ,c . X () , c . Y () , c . Z ());
c = a|b;
104 CAPÍTULO 8. CLASSES E OBJETOS
return 0;
}
class figura {
protected :
char nome [30];
double dim1 , dim2 ;
public :
// metodo Nome da classe figura
char * Nome ( void ) { return nome ; }
// metodo virtual Area da classe figura
virtual double Area ( void ) { }
};
imprimir_area ( q );
imprimir_area ( t );
return 0;
}
106 CAPÍTULO 8. CLASSES E OBJETOS
Referências Bibliográficas
107