Vous êtes sur la page 1sur 51

1.

1 O Primeiro Programa, Escrita no Standard Output

Um programa em C++ é um conjunto de funções. O conceito de função será


explicado mais à frente, por agora só é preciso saber que todo o programa em C+
+ tem obrigratoriamente de conter uma função main. Aqui está o programa mais
básico do C++, um programa que não faz nada:

// programa que não faz nada!


void main()
{
/* o código que faria o programa funcionar deveria estar aqui */
}

Aquele texto a verde, é um comentário, os comentários são ignorados pelo


compilador e devem ser usados abundantemente, para fazer com que o código
seja mais fácil de compreender. Existem duas maneiras de realizar comentários,
usando "//", o que fará com que toda a linha, apartir daí, seja comentada, e "/* ...
*/", em que o texto que fica entre "/*" e "*/" é um comentário.

Antes de main() está a azul a palavra void, que é uma keyword do C++. Por agora
não é necessário compreender estes conceitos, necessário é compreender que
aquilo acima é a base de qualquer programa em C++.

O C++ é Case Sensitive,ou seja é diferente escrever mAin(), Main(), MAIN(). Se


não houver uma função com o nome exactamente igual a main(), o programa não
funcinará!

Os ficheiros fonte da linguagem C++ têm a extensão ".cpp". Sempre que criar um
ficheiro com código em C++, ponha-lhe um nome do género:
"nome_ficheiro.cpp".

Escrever no Standard Output

Antes de mais, o que é o Standard output? Sempre que me referir ao standard


output falo do sistema de output por defeito no C++. Neste caso falo de um
output para DOS. De seguida encontra-se o programa da praxe para quem
começa a aprender uma linguagem de programação:

// este programa escreve no standard output a mensagem "Olá Mundo!"


#include <iostream> // inclui o necessário para poder usar cout<<"...";
using namespace std;

void main()
{
cout<<"Olá Mundo!"<<endl;
}
Comecemos pelas duas primeiras linhas, o C++ é uma linguagem poderosa
devido às suas bibliotecas, as duas primeiras linhas permitem ao programa poder
escrever no standard output. Por agora o leitor terá de se contentar com esta
explicação, e usar sempre aquelas duas linhas. Só mais adiante elas poderão ser
explicadas com clareza. Explicado assim por alto, o que #include <iostream> faz
é incluir no programa o conteúdo do ficheiro iostream.

Já dentro da função main() encontra-se a instrução cout<<"Olá Mundo!";, que fará


aparecer no ecrã "Olá Mundo!". A palavra endl diz ao programa para mudar de
linha e todas as instruções dentro do main() têm de ser concluídas com ponto e
vírgula ';'. Isto não é bem assim, mas por agora terá de servir.

Não há nada melhor para apreender estes conceitos que experimentar! Faça um
programa parecido, mude as palavras...

E já agora um pouco de teoria extra, existem três momentos na realização de um


programa: design time (tempo de criação do código), compile time(quando o
compilador está a criar o executável) e run time(durante a execução do
programa).

1.2 Variáveis e Seus Tipos, Ler Valores do Standard Output

Até aqui já sabemos escrever no standard output, mas é fundamental que


escrevamos aquilo que queremos e quando queremos. Ao escrever cout<<"Olá!;
sempre que corrermos o programa aparecerá "Olá!", e se quisermos escrever
outras coisas?

O conceito de variável nasce daqui, variável é tipo uma gaveta onde temos um
valor que pode mudar quando quisermos e há vários tipos de variáveis, sendo os
mais simples os seguintes:

// declaração de variáveis
void main()
{
int a; // cria uma variável numérica de tipo inteiro com o nome a
double b; // cria uma variável real com o nome b
char c; // cria uma variável que contém um único caractér
bool d; // cria uma variável booleana (true ou false)
}

Isto assim não dá muito jeito, é preciso poder alterar o conteúdo das variáveis.
Para isso existe o operador '='. O exemplo seguinte mostra como declarar,
inicializar e mostrar no standard output variáveis.
// declaração, inicialização e escrita de variáveis no standard output
#include <iostream>
using namespace std;

void main()
{
int a; // declarar a variável a como inteiro
a = 2; // afectar a variável a com o número 2
cout<<"a = "<<a<<endl; // escrever no standard output "a = 2"

double b = 1.2; // declarar e inicializar a variável b com 1,2


/* atenção que no C++ a vírgula ',' é representada por um ponto '.' */
cout<<"b = "<<b<<endl; // escrever no standard output "b = 1.2"

char c = 'C'; // declarar e inicializar a variável c com o caractér 'C'


cout<<"Linguagem "<<c<<"++"<<endl;
/* escreve no standard output "Linguagem C++" */
}

As variáveis podem ter qualquer nome e é diferente escrever int verde; e int
Verde; Serão criadas duas variáveis distintas.

Não sei se reparou mas por vezes uso aspas ' " ', e outras vezes a plica ' ' ' para
representar texto. Isto funciona assim, texto entre aspas é considerado uma
string (conjunto de caracteres) e entre plicas um único caractér.

O tipo bool apenas suporta os valores 1(true) ou 0(false). A sua utilidade será
verificada mais à frente.

Ler do Standard Output

Em programação é muito comum precisarmos de pedir valores ao utilizador do


programa em run time. Para isso usa-se um método semelhante ao cout<<"";

// declaração, inicialização e escrita de variáveis no standard output


#include <iostream> // a variável de leitura também se encontra neste ficheiro
using namespace std;

void main()
{
int a; // declaração da variável a
cout<<"introduza um número inteiro:"<<endl;
cin>>a; // lê do standard input um valor inteiro e afecta a variável a com ele

cout<<endl<<"Acabou de introduir o número: "<<a<<endl;

cout<<"Pressione ENTER para terminar o programa.";


cin.get(); // espera que o utilizador pressione ENTER
}
A melhor maneira de apreender o conceito de cin, é testar o programa.
Resumidamente o que ele faz é parar o programa à espera que o utilizador
introduza um número seguido de ENTER.

Outra novidade é a instrução cin.get(); Se alguém esperimentou testar algum dos


programas anteriores clicando no seu executável, de certeza que não viu nada!
Isto porque não havia nada que indicasse ao programa para parar, para que o
utilizador podesse ver o que estava a acontecer! O cin.get(); faz com que o
programa pare, à espera da actuação da tecla ENTER por parte do utilizador.

Variáveis Constantes

Pode haver a necessidade de se ter uma variável constante, que não mude
durante a execução do programa, para isso basta fazer const in a =10; Desta
forma o a permanecerá igual a 10 durante todo o programa. A utilidade das
variáveis constantes será verificada novamente quando falarmos de arrays.

Nos próximos capítulos vamos-nos debruçar sobre os tipos de variáveis


numéricas, só depois falando do char.

1.3 Operações Aritméticas com Variáveis Numéricas

Este capítulo mostra como efectuar cálculos na Linguagem C++ com base num
exemplo prático. A tabela seguinte mostra os operadores aritméticos do C++:

operador exemplo descrição


= a = b = c; afectação, o exemplo teria o seguinte resultado a = ( b = c )
+ a = b + c; soma, afecta a variável a com a soma de b e c
- a = b - c; subtração, afecta a variável a com a diferença entre b e c
* a = b * c; multiplicação, afecta a com o produto de b e c
/ a = b / c; divisão, afecta a com o resultado da divisão de b com c

Desta maneira representam-se as operações aritméticas básicas. Contudo o C++


disponibiliza outros operadores(de conviniência):

operador o mesmo que:


a += b; a = a + b;
a -= b; a = a -b;
a /= b; a = a / b;
a *= b; a = a *b;
a %= b; a = a % b;
++a; a = a + 1;
--a; a = a - 1;

Para aquele que ainda está a aprender, penso que a instrução a=a+b; poderá ser
um pouco confusa. O que se vai passar é o seguinte, o a será afectado com a
soma do seu valor com b. Se a=2 e b=3, (a=a+b) é o mesmo que (a=2+3).
Para solidicar esta teoria, fica aqui um exemplo prático. O objectido deste
programa é, pedir ao utilizador 3 números e apresentar a sua média.

// média de 3 valores
#include <iostream>
using namespace std;

void main()
{
// declarações de variáveis
int a, b, c; // o mesmo que int a; int b; int c;
double media; /* variável para armazenar o valor da média, double porque o resultado
pode não ser inteiro */

cout<<"Introduza 3 números inteiros: ";


cin>>a>>b>>c; // o mesmo que cin>>a; cin>>b; cin>>c;

// calcular a média
media = (a+b+c) / 3;

// apresentar o resultado
cout<<endl<<"A média é: "<<media<<endl;
cout<<"Enter para terminar";
cin.get():
}

Operadores de Incremento e Decremento

Existem dois tipos de operadores de incremento e decremento, exemplo: ++i; i+


+; No primeiro caso a variável é incrementada e retorna o seu valor. No segundo,
primeiro é retornado o valor ca variável e só depois este é incrementado. Exemplo
prático:
#include <iostream>
using namespace std;

void main()
{
int i = 2, j = 3; // declaração e inicialização das variáveis j e i
int a = 0; // para testes

a = ++i; // a=3, i=3


a = j++; // b=3, j=4

--i; // i = 2
j--; // j=3

/* para ver se já percebeu isto, mande-me o resultado da seguinte expressão para o


mail, para eu a corrigir */
a += --(++j + i++);
}

Qualificadores

Aos tipos base podem-se acrescentar certos qualificadores. A seguinte tabela


mostra essas variantes:

qualificador aplica-se a representa


short int menor dimensão
long int ; double maior dimensão
signed char ; int com sinal
unsigned char ; int sem sinal

Equivalências:

unsigned int unsigned


short int short
long int long

No ficheiro exemplo_1.zip estão os programas deste primeiro capítulo.

2. Instruções de Controlo de Execução, Operadores Lógicos

Já aprendemos a usar variáveis, a fazer cálculos e apresentar resultados. Agora


chega a a parte de aprender a fazer com que o nosso programa tome decisões,
decida entre fazer isto e aquilo, fazer a mesma coisa um número pré-definido de
vezes, ou simplesmente enquanto nós quisermos!
Nos próximos capítulos falaremos do que são as instruções de Controlo e
Execução, porém, para as podermos utilizar convenientemente e com sabedoria,
é necessário primeiro conhecer os operadores lógicos.

Operadores Lógicos

As seguintes tabelas definem todos os operadores lógicos do C++. Sejam a e b


duas variáveis booleanas (do tipo bool):

Operação AND (&&) Operação OR ( || ) Operaçâo NOT (!) Operação IGUAL ( ==)
a b a && b a b a || b a !a a b a == b
0 0 0 0 0 0 0 1 0 0 1
0 1 0 0 1 1 1 0 0 1 0
1 0 0 1 0 1 1 0 0
1 1 1 1 1 1 1 1 1

Recorde-se que um bool apenas permite os dois valores lógicos true e false, tente
fazer o seguinte exercício:

void main()
{
bool b = true, c = false;
bool a = 0;
/* embora se possa inicializar ou afectar um bool com 0 ou 1
recomenda-se sempre que se use true ou false */

// exemplo
b = !a; // b passou a false
c = a || b; // c continua a false

// tente encontrar o novo valor de a


a = (!b) || (a && c);
}

Se quiser verificar se encontrou o a correcto, mande-me um mail com a resposta.

Operadores de Relacção

operador descrição
a>b retorna true se a maior que b
a<b retorna true se a menor que b
a <= b retorna true se a menor ou igual a b
a >= b retorna true se a maior ou igual a b
a == b retorna true se a igual a b
a != b retorna true se a diferente deb
No próximo capítulo veremos uma aplicação mais útil do tipo bool e dos
operadores lógicos e de relacção.

2.1 Instrução if-else

A instrução if-else tem a seguinte sintaxe:

if( <teste> )
<instrução 1>
else
<instrução 2>

ou simplesmente

if( <teste> )
<instrução 1>

No primeiro caso, se <teste> for true, então realizar-se-á a <instrução 1>, se for
false passa para a <instrução 2>.

No segundo caso apenas se <teste> for true se poderá efectuar a <instrução 1>.
Vejamos o seguinte exemplo prático:

#include <iostream>
using namespace std;

void main()
{
int a, b;

// pedir dois valores inteiros


cout<<"Introduza 2 valores inteiros: "<<endl;
cin>>a>>b;

if( a == b )
cout<<"Os valores são iguais!";
else
cout<<"Os valores sao diferentes!";

cin.get();
}

No parãmetro de teste do if pode-se ter qualquer espressão, mas só será


efectuada a instrução correspondente se o resultado dessa expressão for true.

Quem está a acompanhar este Tutorial, talvez se pergunte: Então e se eu quiser


por mais que uma instrução? Aqui entra o conceito de scoop. Um scoop é sempre
limitado por chavetas, por exemplo, existe o scoop da função main. Penso que
com o seguinte exemplo as coisas se tornarão mais claras:
#include <iostream>
using namespace std;

void main()
{
int a, b;

// pedir dois valores inteiros


cout<<"Introduza 2 valores inteiros: "<<endl;
cin>>a>>b;

if( a == b ) {
cout<<"Os valores são iguais!";
cout<<"Vêm, foi usada outra instrução dentro deste if!";
}
else{
cout<<"Os valores são diferentes!";
}
cin.get();
}

Se a condição do if for true, ele realiza a instrução seguinte, e um scoop é um


conjunto de instruções (também pode ter só uma como se vê no else).

Atenção que uma variável declarada dentro de um scoop deixa de existir fora
dele! Exemplo:

#include <iostream>
using namespace std;

void main()
{
bool b = true;

if(b){ // entrará sempre pois b = true


int a = 4;
cout<<"a= "<<4;
}
a = 3; // ERRO!!!
/* a variavel só existe dentro do scoop em que foi declarada! */
}

Penso que o conceito do if está explicado. Se tiver alguma dúvida pode sempre
contactar-me através do mail.

Operador Ternário

O operador ternário é um caso especial de if e tem a seguinte sintaxe:

( <teste> ) ? <instrução 1> : <instrução 2>;


Programa exempo em que o operador ternário afecta a variável a com o módulo
da variável b. Para os mais esquecidos chama-se módulo de um número à sua
versão positiva, exemplo: módulo de -1 é 1, módulo de 2 é 2, módulo de 0 é 0;

#include <iostream>
using namespace std;

void main()
{
int a, b;
cout<<"Introduza um número inteiro diferente de zero:";
cin>>b;

a = ( b>0) ? b : -b;
/* lê-se: o b é maior que 0? se sim retorna b : se não retorna -b */

cout<<"O modulo de "<<b<<" é "<<a;


cin.get();
}

2.2 Instrução switch

Penso que o switch é fácil de compreender e penso também que a melhor


maneira de o explicar é através de um exemplo prático:

#include <iostream>
using namespace std;

void main()
{
char c;
cout<<"Introduza uma vogal minúscula:"<<endl;
cin>>c;

switch(c) {

case 'a':
cout<<"Introduziu um a!"<<endl;
break;

case 'e':
cout<<"Introduziu um e!"<<endl;
break;

case 'i':
cout<<"Introduziu um i!"<<endl;
break;

case 'o':
cout<<"Introduziu um o!"<<endl;
break;

case 'u':
cout<<"Introduziu um u!"<<endl;
break;

default: // se a variável do switch não for nenhuma das anteriores


cout<<"Não introduziu uma vogal!"<<endl;
break;
}
cin.get();
}

O switch vai ver qual a variável, caso seja uma das opções de que dispõe, faz as
instruções necessárias. O break faz com que se saia imediatamente do switch e
não é obrigatório em cada case.

#include <iostream>
using namespace std;
// programa que diz se um número introduzido é maior, igual ou menor que 3
void main()
{
int a;
cout<<"Introduza um número entre 0 e 5:"<<endl;
cin>>a;

switch(a) {

case 0:
case 1:
case 2:
cout<<"O número "<<a<<" é menor que 3!";
break;

case 3:
cout<<"O número "<<a<<" é igual a 3!";
break;

case 4:
case 5:
cout<<"O número "<<a<<" é maior que 3!";
break;

default:
cout<<"Não introduziu um número entre 0 e 5...";
break;
}
cin.get();
}

2.3 Instrução while


A sintaxe do while é a seguinte:

while( <teste> )
<instrução>

Para este programa de exemplo, devo fazer uma introdução. O cin.get() que
usávamos para esperar por un ENTER, na verdade lê qualquer caracter, e
podemos inclusivé fazer char a=cin.get();

Mais, há vários caractéres especiais (dos quais falaremos mais à frente). O que
vamos usar no próximo programa é o '\n', que tem a função de mudar de linha.

Fazer cout<<"Olá"<<endl; é o mesmo que fazer cout<<"Olá \n"; Quando o


utilizador carrega no ENTER o cin.get() retorna o caractér '\n'.

#include <iostream>
using namespace std;
// programa que pede que o utilizador escreva uma linha de texto e conta os caractéres
void main()
{
unsigned charCount = 0; // inteiro sem sinal, >= 0
cout<<"Introduza uma linha de texto:"<<endl;

while( cin.get() != '\n' ) // enquanto o utilizador não fizer ENTER


++charCount; // conta o caractér

cout<<"\nDigitou "<< charCount <<" caracter"


<< ( charCount != 1 ? "es." : "." ) <<endl;

cin.get();
}

Pois, este programa já é mais complicado, mas temos de começar a evoluir...

Ora bem, enquanto a condição de teste dentro do while for true, ou seja,
enquanto o utilizador não digitar ENTER, realizar-se-á a instrução ++charCount.

Exemplo:
Tutorial de c++ <ENTER>
O programa irá ler todos os caractéres até encontrar <ENTER> e por cada caractér que
ler incrementará o charCount.

Compreendo que seja complicado compreender isto, tem de testar o programa e


ver como ele funciona, só assim compreenderá.

Depois de contar o número de caractéres aparece um cout um pouco esquesito...


Penso que ninguém tem dúvidas que o operador ternário está lá para completar a
frase com "." ou com "ers.", consoante o número de caractéres.
No cout, em vez de se estar a fazer
cout<<"Atirei o pau ao gato"<<endl;
cout<<"Mas o gato assustou-se..."<<endl;

Pode-se fazer:
cout<<"Atirei o pau ao gato"<<endl
<<"Mas o gato assustou-se..."<<endl;

Que é o mesmo que


cout<<"Atirei o pau ao gato"<<endl<<"Mas o gato assustou-se..."<<endl;

2.3 Instrução do-while

Na instrução while, primeiro testa-se a condição e somente depois se prosegue


com as instruções.

No entanto poderá haver casos em que se queira fazer primeiro a instrução e só


depois testar a condição.

#include <iostream>
using namespace std;
// programa que soma números até o utilizador escolher -1
void main()
{
int soma = 0, num=0;
cout<<"Introduza vários números. -1 para acabar"<<endl;

do{
soma += num;
cin>>num;
} while( num != -1 );

cout<<"Soma: "<<soma;
cin.get();
}

Penso que é com exemplos que se aprende, parece-me que é um bom método
tentar descobrir o que cada programa faz sozinho! Lembre-se que estar a
acompanhar um programa que está explicado e percebê-lo, não tem nada a ver
com saber programar! uma coisa é perceber quando explicam, outra é criar as
coisas sozinho!

2.4 Instrução for

A sintaxe do for é a seguinte:

for( <inicialização> ; <teste> ; <incremento> )


<instrução>

Programa de exemplo:
#include <iostream>
using namespace std;
// programa que escreve o alfabeto
void main()
{
for( char c = 'A'; c <= 'Z' ; ++c ) {
cout<<' '<< c;
if( (c - 'A' + 1) % 4 == 0 ) cout<<endl; /* esta parte serve para mudar de linha
de 4 em 4 caractéres */
}
}

É claro que em vez de um char podia-se ter um utilizado um int, ou qualquer outra
coisa. Usei o char neste exemplo para mostar esta potencialidade do C++.

O for funciona assim:

1. Primeiro, faz o que se pede na iniciação:


char c = 'A';

2. faz o teste, se for true, realiza as instruções, se for false sai do for:
cout<<' '<< c;
if( (c - 'A' + 1) % 4 == 0 ) cout<<endl;

3. Realiza o incremento:
++c;

4. Volta ao ponto 2

Sublinho que só se sai do for quando o teste for false! Ou seja, quando a variável
c for 'Z'. De seguida estão vários exemplos do for.

#include <iostream>
using namespace std;
// programa que pede um caracter e um numero(n) e escreve esse caracter n vezes
void main()
{
int num; char c;

cout<<"introduza um numero:"; cin>>num;


cout<<"introduza um caracter:"; cin>>c;
cout<<endl;

int i; // não é obrigatório daclarar a variável dentro do for


for( i = 0; i < num; ++i )
cout<<c;

cout<<endl;
i = 2; num += 2; // não são obrigatórios os campos do for
for( ; i < num; ++i )
cout<<c;

cin.get();
}

Por vezes utiliza-se mesmo um for forever:


// não experimentar este programa
void main()
{
for( ; ; ) // forever!
;
/* pode-se dar o caso de se ter um for sem nenhuma instrução específica
nesse caso utiliza-se o ';' como se mostra no exemplo. Põe-se na linha seguinte
para mostrar que é mesmo essa a intenção do for */
}

Não sei que mais dizer sobre o for, penso que com o que foi escrito se pode
apreender o conceito do for.

Aproveito para deixar um pequeno reparo:

// programa de exemplo sobre o scoop do for


void main()
{
for( int a = 0 ; ... ; ... ) { // os pontos significam qualquer coisa
....
}
a = 2; // já está fora do scoop do for, dará erro?
}

Pois, isso irá depender do compilador. O Visual C++, por exemplo, faz com que
aquela variável a esteja acessível no resto do scoop da função main, não dando
erro a instrução a=2;

Mas em compiladores como o g++ a coisa já não é assim... Nesses, o scoop da


variável é o do for! O que por acaso até é standard C++...

No ficheiro exemplo_2.zip tem todos os exemplos deste segundo capítulo.

3. Arrays

Para introduzir o conceito de array, vou começar por apresentar um programa


simples, que não contém nada de novo e depois, mostro a utilidade dos arrays. O
seguinte exemplo pede 3 números ao utilizador e de seguida os mostra pela
ordem inversa de insersão:

#include <iostream>
using namespace std;
// programa que lê 3 números e os escreve de ordem inversa
void main()
{
// declarar as variáveis
int a, b, c;

// pedir as variáveis
cout<<"Introduza 3 inteiros:"<<endl;
cin >> a >> b >> c;

// escrever
cout<<"Os numeros de ordem inversa:"<<endl
<<c<<b<<a;

cin.get();
}

Este programa cumpre a sua função sem qualquer problema. Mas imaginemos
que queriamos fazer o mesmo com... 100 números! Teriamos de declarar 100
variáveis distintas e para lê-las... imaginem o código!

Um array é um género de uma tabela, e podem ser de qualquer tipo. A sintaxe de


um array é a seguinte: tipo nome_variavel[n_elementos]; e cria uma tabela de 0 a
n_elementos-1. Vejamos agora o programa utilizando arrays.

#include <iostream>
using namespace std;
// programa que lê n números e os escreve de ordem inversa
void main()
{
// declarar as variáveis
const int n_elementos = 3;
int i;
int a[n_elementos]; /* a variável a fica com 3 "casas". a[0], a[1] e a[2] e podemos
trabalhar com cada uma delas como sendo variáveis distintas */

// pedir as variáveis
for( i=0; i<n_elementos; ++i ) {
cout<<"introduza o "<< i <<" Numero: ";
cin>>a[ i ];
cout<<endl;
}

// escrever
for( i=n_elementos-1; i>=0; --i )
cout<<a[ i ]<<endl;

cin.get();
}
/* na inicialização de um array também se pode fazer o seguinte: int a[2] = { 1,2 }; o que
faria com que a[0]=1 e a[1]=2 */

Em C++ não se pode inicializar arrays com variáveis comuns, só o é possível


fazer com constantes. Mais à frente quando falar em apontadores veremos como
ultrapassar esta "característica".

Chamo à atenção que a0 é diferente de a[0]. São os parêntesis rectos que


indicam que se trata de um array. a0 é uma variável como qualquer outra.

Mais uma vez, a melhor maneira de apreender o conceito de array, é realizar o


programa e testá-lo. Ver como funciona!
Deixo aqui mais um programa, este encontra o maior de 10 números lidos.

#include <iostream>
using namespace std;
// programa que encontra o maior de 10 números lidos
void main()
{
const int n_elementos = 10;
int i, e[n_elementos];

cout<<"Introduza 10 números: "<<endl;

for( i = 0 ; i < n_elementos ; ++i )


cin>> e[ i ];

int max = 0; // variável que irá conter o índice do valor máximo

for( i = 0; i < n_elementos; ++i )


if( e[ i ] > e[ max ] )
max = i;

cout<<"O maior número foi o "<< e[ max ] <<" e foi o "<< max
<<"º Número a ser introduzido.";

cin.get();
}

3.1 Arrays de Caractéres

Até agora praticamente só tratámos números, com arrays de caractéres temos a


possibilidade de tratar também strings. Um array de caractéres é um array
normal, só que tem letras em cada índice.

#include <iostream>
using namespace std;

void main()
{
// maneiras de inicializar arrays de caracteres
char s1[ 5 ] = "Olá\0";
char s2[ 6 ] = { 'A', 'd', 'e', 'u', 's', '\0' };
char nome[10];

cout<<"Introduza o seu nome: (max 10 letras)"<<endl;


cin >> nome;

cout<< s1 << nome << endl;


cout<< s2;

cin.get();
}
Este exemplo mostra como se pode pedir uma string ao utilizador e depois
escrevê-la no standard output novamente. De notar que todas as strings que
foram inicializadas têm '\0' como último caractér. Todas as cadeias de caractéres
em C++ têm de terminar com '\0', o caractér terminador.

Não se podem afectar arrays, não se poderia fazer nome = "Pedro"; O que se teria
de fazer era afectar cada casa do array com a letra correspondente. Em princípio
eu devia neste Tutorial mostrar uma série de algoritmos para tratamento de
strings, como concatenar, substituir letras... etc. Não o faço porque o C++ fornece
ao utilizador um objecto string que já faz essas coisas todas e onde se pode fazer
string p,r; p="Pedro"; p += r;

Este objecto será tratado mais à frente. Seja como for aconselho aos interessados
que procurem noutro lado toda essa matéria relacionada com arrays de
caractéres.

Tabela de Caracteres Especiais

Caractér Descrição
\a bell, emite um som
\b retrocesso ( backspace )
\0 caractér terminador
\f início de página
\n mudança de linha
\r início de linha
\t tab horizontal
\v tab vertical
\\ backslash
\' plica
\" aspas
\ooo caractér cujo código em octal é ooo
\xhh caractér cujo código em hexadecimal é hh

No ficheiro exemplo_3.zip estão os programas deste terceiro capítulo.

4. Funções

Já tinha dito que a função main é a primeira a ser chamada, é como se fosse o
corpo do programa. Mas o C++ não vive só da função main e podemos criar
quantas funções quisermos como quisermos a fazer aquilo que quisermos!
#include <iostream>
using namespace std;
// programa que introduz as funções

void ola_mundo() // declaração + definição


{
cout<<"OlA Mundo!"<<endl;
}

void f1() // declaração + definição


{
cout<<"Estou na funcao f1!"<<endl;
}

void main()
{
ola_mundo();
f1();
cin.get();
}

O programa entra em main(), quando chega à instrução ola_mundo(); vai à


procura dessa função e faz tudo o que ela mandar antes de proseguir.

Sempre usámos a palavra chave void antes da função main. O que ela indicava é
que a função não retornava nada. Estas funções são denominadas de
procedimentos.

Se quisermos que uma função retorne qualquer coisa, mudamos o tipo de retorno.
O exemplo seguinte mostra uma função que retorna o maior de 2 valores.

#include <iostream>
using namespace std;
// programa que introduz as funções

int max( int a, int b ); // declaração

void main()
{
int v1, v2;
cout<<"introduza dois inteiros: "<<endl;
cin>> v1>> v2;

cout<<endl<<"O maior é "<< max(v1,v2) << endl;


cin.get();
}

int max( int a, int b ) // definição


{
return a>=b ? a : b;
}
A função max recebe dois valores como parâmetro, o que se vai fazer é que o
compilador irá criar duas variáveis a e b e irá afectá-las com os valores de v1 e v2
respectivamente. É como se fossem criadas cópias dos valores originais.

Todas as funções que não retornem void têm obrigatoriamente de ter a palavra
return a retornar qualquer coisa. No caso da função max o operador ternário irá
ver qual o maior e retorna esse valor.

Sempre que o return é chamado a função acaba, e pode ser chamado a qualquer
altura.

Aproveito para lançar também o conceito de variável global. Se houvesse uma


variável declarada fora da função main ela estaria acessível em todas as funções
do ficheiro. Exemplo:

#include <iostream>
using namespace std;

//variável global
int v1;

int max( int a, int b ); // declaração

void main()
{
int a;
v1 = 2; // correcto, v1 está acessível pois é global
}

void f1() // definição


{
v1 = 5; // correcto, v1 está acessível pois é global
a = 2; // ERRO! variavel declarada na função main e portanto só está acessivel em
// main! teria de haver também uma declaração int a; nesta função para que
// não desse erro
}

Mais uma coisa, é standard C++ que o tipo de retorno da função main seja
inteiro. Há mesmo compiladores que não aceitam void main(). Por norma usa-se
int main() { /*.. */ return 0; } A partir de agora não usarei mais void main().

Para solidificar o conceito de função e fazer uma revisão em tudo o que já foi
falado, vou deixar aqui um jogo simples. O objectivo será o utilizador advinhar
uma palavra que só o programa saberá. Não passo aqui o código pois iria ocupar
muito espaço. Todas as funções e variáveis estão comentadas e explicadas, mas
se tiver alguma dúvida pode sempre contactar-me por mail. Tente fazer uma
evolução do jogo, incluir alguns temas, mais palavras, por mais opções!

Tanto o ficheiro do jogo como os programas desta secção se encontram no


ficheiro exemplo_4.zip.

5. Enumerações
As enumerações são uma alternativa a definir constantes e, visto que em C++
são mesmo um tipo, são mesmo muito úteis. Exemplo: enum { GREEN, YELLOW,
RED }; era o mesmo que criar três constantes com os valores 0, 1 e 3. Por defeito
o primeiro valor de uma enum é zero. Exemplos:

enum Mes { JAN = 1, FEV, MAR, ABR, MAI, JUN, JUL, AGO, SET, NOV, DEZ };
// o valor de FEV = 2, MAR = 3... por ai adiante

Mes mes;
// ...

switch( mes )
{
case JAN: //...
break;

case FEV: //...


break;
}

As enums ajudam muito em termos de raciocínio pois podemos usar palavras para
descrever o que quisermos. Por norma as constantes são escritas com letras
maiúsculas.

Estruturas

Uma estrutura em C servia para agrupar diferentes variáveis. Em C++ é muito


mais poderosa dando para incluir construtores e métodos, mas destes assuntos só
falarei no capítulo das classes. Aqui veremos as estruturas como agrupamentos
de variáveis.

#include <iostream>
using namespace std;

enum Especialidade { VENDAS, PUBLICIDADE };

struct Empregado {
int num];
int idade;
Especialidade esp;
};

void main()
{
// diferentes maneiras de inicalizar estruturas
Empregado emp1 = { 1, 35, VENDAS };
Empregado emp2;

// utiliza-se o ponto '.' para se aceder a cada atributo da variavel emp2


emp2.num = 2;
emp2.idade = 19;
emp2.esp = PUBLICIDADE;

cout<<"Empregados:"
<<endl<< emp1.num <<", "<< emp1.idade <<" anos. Especialidade: "<< emp1.esp
<<endl<< emp2.num <<", "<< emp2.idade <<" anos. Especialidade: "<< emp2.esp;
cin.get();
}

Quando se faz cout<<emp1.esp; não aparecerá escrito VENDAS, mas sim 0, que
é o valor da constante lógica.

Penso que com este exemplo se pode ter uma ideia da utilidade das enums e das
estruturas. Este exemplo encontra-se no ficheiro exemplo_5.zip.

6. Referências

Uma referência é a "morada" de uma certa variável na memória. Quando fazemos


int i; estamos a criar uma variável que ficará alojada algures. A sua referência é o
seu endereço na memória e define-se com o caractér '&' antes do seu nome.

#include <iostream>
using namespace std;
// demonstração do uso de referências
int main()
{
int i;
int &i_ref = i; // i_ref irá conter o endereço na memória da variável i
// a partir de agora pode-se afectar o conteúdo de i com i_ref

cout<< i <<" = "<< i_ref <<endl;


cout<<"Incrementar... o i"<<endl;
++i;

cout<< i <<" = "<< i_ref <<endl;


cout<<"Incrementar... o i_ref"<<endl;
++i_ref;

cout<< i <<" = "<< i_ref <<endl;


cin.get();
return 0;
}

Pelo que se vê, uma referência é uma variável com outro nome para o mesmo
endereço. É como se i e i_ref fossem a mesma coisa, e na prática até são. Então
qual a utilidade das referências? As referências tomam especial importância no
tratamento de funções, pois se quando passávamos um argumento por valor,
havia uma cópia deste, agora podemos passar a sua referência.

#include <iostream>
using namespace std;

// troca o conteúdo de duas variáveis inteiras


void swap( int &a, int &b )
{
int temp = a; // para não se perder o valor de a
a = b;
b = temp;
}

int main()
{
int num1 = 1;
int num2 = 2;

cout<<"Numero 1 = "<< num1 <<" - Numero 2 = "<< num2 << endl;


cout<<"Swap..."<<endl;
swap(num1,num2);
cout<<"Numero 1 = "<< num1 <<" - Numero 2 = "<< num2 << endl;

cin.get();
return 0;
}

Ao passar os parâmetros, é passava a sua referência, pelo que é como se


estivéssemos a trabalhar com as próprias variáveis num1 e num2;

Apontadores

Parece que os apontadores são uma dor de cabeça para todo aquele que começa
a aprender C/C++ e alguns autores dizem que é por causa dos apontadores que
existe a maior parte de erros nos programas.

Seja como for o apontador é uma parte do C/C++ e deve-se aprender a trabalhar
com ela, tal como nas outras, até porque dá muito jeito.
Se à bocado falávamos do endereço das variáveis, agora falamos dos apontadores
para elas. Um apontador é uma variável que aponta para uma referência e
declara-se com um '*' antes do nome.

#include <iostream>
using namespace std;

int main()
{
int i = 10;
int *p = &i;

// para aceder ao conteúdo apontado por um apontador


// usa-se o operador desreferência *
cout<< *p ;

cin.get();
return 0;
}

A seguir falaremos de memória dinamica e mostrarei um bom uso para os


apontadores.

6.1 Memória Dinamica

Quando falei dos arrays disse que só poderiam ser inicializados (em termos de
tamanho) de uma forma constante, que não podesse ser alterada ao longo do
programa. A isto chama-se reservar memória estáctica. Pode-se reservar memória
em run time, basta para isso usarmos o operador new. O seguinte exemplo
mostra como criar um inteiro dinamicamente.

#include <iostream>
using namespace std;
// demonstração do uso de memória dinamica
int main()
{
int *i = new int; // alojar
*i = 10; // afectar
cout<< *i << endl;
delete i; // desalojar

cin.get();
return 0;
}

Sempre que um programador aloja memória dinamicamente num programa fica


com a responsabilidade de a libertar quando já não precisar mais dela. Para isso
usa o operador delete.

O seguinte exemplo mostra como criar arrays dinamicamente:


#include <iostream>
using namespace std;
// programa troca a ordem de insersão de quantos números o utilizador quiser
int main()
{
int tam, i;

cout<<"Pretende introduzir quantos numeros?"<<endl;


cin >> tam;

// reservar espaço
int *a = new int[tam];

cout<<"Introduza os numeros..."<<endl;
// ler
for(i=0; i<tam; ++i)
cin>>a[ i ];

// escrever ao contrario
for(i=tam-1; i>=0; --i)
cout<<a[ i ]<<" ";

// libertar a memória alojada dinamicamente


delete [ ] a;

cin.get();
return 0;
}

Quando se faz int *a = new int[ n ]; o que o programa faz é criar um array de n
posições e afectar o apontador a para a casa do primeiro índice. Como se trata de
um array, teriamos de fazer delete a todos os seus índices, mas se utilizarmos
delete [] a; isso é feito automaticamente.

Os exemplos deste capítulo estão no ficheiro exemplo_6.zip.

7. O Objecto string

Como já tinha referido, o tratamento de cadeias de caractéres exigia certos


algoritmos e conhecimento de variadas funções. Em C++ foi criado um objecto
string que facilita muito a tarefa do programador. Este já não precisa de se
preocupar com os arrays dinamicos de caractéres e ganha inclusivé em eficiência.

#include <string> // necessário para se ter acesso ao objecto string


#include <iostream>
using namespace std;
// demonstração do uso de strings
int main()
{
string nome, sobrenome;

cout<<"Introduza o seu nome:"<<endl;


cin >> nome;

cout<<"Então " + nome + ", que tal este Tutorial? :) "<<endl;


cout<<"Qual o seu último nome?"<<endl;
cin >> sobrenome;

sobrenome = " " + sobrenome; // para ficar " Sobrenome"


nome += sobrenome; // para ficar "Nome Sobrenome"

cout<<"O seu nome completo é: "<< nome;

cin.get();
return 0;
}

Penso que este exemplo demonstra algumas das facilidades dadas pelo objecto
string. De facto, tratar strings como um objecto em vez de como um array de
caractéres é mesmo muito prático.

Introdução aos Iteradores

Os iteradores serão dados mais à frente, mas fica aqui já o seu conceito e toda a
sua utilidade. Um iterador é uma generalização de um apontador, de facto, é
construído apartir de um apontador. Ainda é difícil demonstrar a utilidade de um
iterador, por isso deixo somente uma demonstração da sua utilização.

#include <string> // necessário para se ter acesso ao objecto string


#include <iostream>
using namespace std;

// demonstração do uso de iteradores de strings


// este programa conta o número de vogais de uma frase introduzida
int main()
{
string::iterator it; // declarar um iterador de strings
string p;
int a,e,i,o,u, consoantes;
a = e = i = o = u = consoantes = 0;

cout<<"Introduza uma palavra (letras minusculas): ";


cin>>frase;

// percorrer a string
for( it = p.begin() ; it < p.end() ; ++it )
switch( *it )
{
case 'a': ++a; break;
case 'e': ++e; break;
case 'i': ++i; break;
case 'o': ++o; break;
case 'u': ++u; break;
default: if( *it != " " ) ++consoantes; break;
}

cout<<"A palavra: \""<< p <<"\" tem"<<endl


<< a+e+i+o+u <<" vogais, "<<a<<", "<<e<<", "<<i<<", "<<o<<", "<<u
<<" respectivamente, num total de "<< a+e+i+o+u + consoantes<< endl
<<" letras."<<endl;

cin.get();
return 0;
}

O método p.begin() devolve um iterador para o primeiro caractér da palavra p e o


método p.end() devolve um iterador para o caractér terminador '\0'.

Usando o operador desreferência(*) obtemos o conteúdo iterado pelo iterator it.

Os exemplos deste capítulo estão no ficheiro exemplo_7.zip.

8. Acesso a Ficheiros (Modo Texto)

A sintaxe de escrita e leitura num ficheiro de texto é igual àquela usada no cout e
no cin, só que desta vez somos nós que declaramos as variáveis, com o ficheiro
pretendido. Para se ler de um ficheiro usa-se um objecto ifstream( de input file
stream ) e para escrita usa-se o ofstream ( de output file stream ). Vejamos o
seguinte exemplo:

#include <fstream> // necessário para se ter acesso a ficheiros


#include <iostream>
using namespace std;
// demonstração de escrita e leitura em ficheiros
int main()
{
char aux[10];

// criar o ficheiro
ofstream out("output_file.txt");
// agora irá escrever no ficheiro
out << "Ficheiro criado recorrendo à biblioteca fstream do C++"<<endl;
// fechar o ficheiro
out.close();

// abrir novamente o ficheiro,mas agora para leitura


ifstream in("output_file.txt");
// ler e escrever no standard input
while( in >> aux ) // enquanto houver alguma coisa para ler
cout<<aux<<" ";

cout<<endl;
cin.get();
return 0;
}

Não é necessário fechar o ficheiro in porque ele é fechado automaticamente


quando a função em que está acaba. Falando de acesso a ficheiros em modo
texto, não há muito mais para dizer, mas deixo só mais um exemplo, que por ser
um pouco grande, não passo aqui. Esse programa irá pedir um caractér ao
utilizador e irá mostrar o seu número de ocorrências num determinado ficheiro.

Os programas deste capítulo encontram-se no ficheiro exemplo_8.zip.


9. Classes, Introdução

Até agora tudo o que vimos, também estava incluido no C (com outros métodos),
as classes são o que distingue verdadeiramente o C do C++. São as classes que
fazem do C++ uma linguagem orientada a objectos.

Um outro nome para classe, é objecto. E é para isso que as classes servem, para
simular objectos. Imaginemos uma cadeira, em C++ seria uma classe, com
diversos atributos, como a cor, peso, material... E como objecto totalmente
manipulado pelo programador, a qualquer hora poderiamos mudar a cor da
cadeira, ou qualquer outro atributo.

Para introduzir as classes, vamos começar por simular a tal cadeira de que tenho
falado, eu sei que é um exemplo demasiado simples, mas é sobretudo para
mostrar as bases das classes que o faço.

#ifndef _TUT_CADEIRA_H_
#define _TUT_CADEIRA_H_

// para a cor das cadeiras


enum Cor { BRANCO, PRETO };

class Cadeira {

private:
// atributos privados
unsigned altura, peso;
Cor cor;

public:
// metodos publicos
Cadeira( unsigned a, unsigned p, Cor c )
{ altura=a; peso=p; cor=c; }

// metodos para alterar os atributos do objecto


void mudarPeso( unsigned p ) { peso=p; }
void mudarAltura( unsigned a ) { altura=a; }
void mudarCor( Cor c ) { cor=c; }

// metodos para saber os atributos do objecto


unsigned retornarAltura() { return altura; }
unsigned retornarPeso() { return peso; }
Cor retornarCor() { return cor; }

}; // nunca esquecer deste ;

#endif // _TUT_CADEIRA_H_

Por norma, uma classe fica sempre alojada num ficheiro de cabeçalho(.h), esta
classe estaria no ficheiro Cadeira.h

Comecemos pelas duas primeiras linhas, imaginemos que tinhamos chamado o


ficheiro Cadeira.h em dois sítios, o que aconteceria é que a classe cadeira seria
definida duas vezes, dando erro ao compilar. Em todos os ficheiros de cabeçalho
se têm de por aquelas duas linhas e no fim acabar com o #endif.

Dentro da classe cadeira econtramos os atributos depois da palavra private.


Todos os atributos ou métodos que sejam privados só podem ser acedidos pela
classe em questão, ficando inacessíveis ao exterior. Por outro lado, os métodos ou
atributos públicos (public), podem ser acedidos pelo exterior.

Uma classe tem de ter sempre um construtor, um método com o mesmo nome da
classe, onde o programador inicializa o que achar necessário. Cada classe
também possui um destrutor, que é chamado quando o objecto é eliminado.
Nesta classe cadeira o destrutor não é necessário, mas se usássemos variáveis
dinamicas, seria no destrutor que teriamos oportunidade de libertar essa
memória.

Todos os métodos da classe Cadeira estão definidos inline, mas poderiam estar
num outro ficheiro. No próximo capítulo veremos esses aspectos.

O seguinte exemplo mostra como se poderia usar a classe cadeira:

#include <iostream>
using namespace std;
// demonstração do uso da class Cadeira

// incluir o ficheiro onde se encontra a classe cadeira


#include "Cadeira.h"

int main()
{
// criado um objecto cadeira
Cadeira cadeira ( 10, 20, BRANCO );

// a seguinte linha daria erro


// cout<< cadeira.peso
// isto porque não se pode aceder aos métodos privados

cout<< cadeira.retornarPeso() <<endl;

cadeira.mudarPeso( 30 );

cout<< cadeira.retornarPeso() <<endl;

cout<<endl;
cin.get();
return 0;
}

O ficheiro "Cadeira.h" está entre aspas e não entre <> porque só os ficheiros
standard do C++ é que devem/podem estar entre <>. Todos os fcheiros criados
pelo programador estão entre aspas.

A matéria que envolve as classes é muito extensa, como referi na introdução,


apenas irei expor simples tópicos, porque não tenho assim tanto tempo para a
elaboração deste Tutorial.. (nem paciência :). Para quem queira aprofundar mais o
conhecimento, vá à secção bibliografia e veja os livros que aconselho, ou os links
que lá estão, que contêm muita informação e tuturiais sobre C/C++. É claro que
também me poderão contactar com as vossas dúvidas.

9.1 Classes, Aspectos Básicos

Nesta secção falarei dos aspectos mais básicos de uma classe, aqueles que todos
devem saber para fazer um melhor uso das classes. Para variar, não há nada
melhor do que um exemplo. Irei desenvolver uma classe Ponto que, por acaso,
até costuma ser muito usada pelos programadores gráficos (penso eu :).

#ifndef _TUT_PONTO_H_
#define _TUT_PONTO_H_

// para leitura e escrita


#include <iostream>
using namespace std;

class Ponto {

/* por defeito os atributos/métodos de uma classe são sempre privados


por isso não é necessário por aqui private: */

public: // mEtodos publicos

// atributos do ponto, tambEm se poderia adicionar a cor


// mas como nAo irei mexer em grafismo, não vale a pena
int x, y;

// construtor
Ponto( const int &_x = 0, const int &_y = 0 ): x(_x), y(_y) { }
// construtor por copia
Ponto& Ponto( const Ponto & p );
// destrutor
~Ponto() { /* não há nada para ser feito */ }

// afetar um Ponto com outro


Ponto& operator= ( const Ponto &p );
// somar dois pontos
Ponto operator+ ( const Ponto &p );
Ponto operator+= ( const Ponto &p );
// subtrair...
Ponto operator- ( const Ponto &p );
Ponto operator-= ( const Ponto &p );

// operadores de incremento e decremento


Ponto& operator++ (); // ex: Ponto a; ++a;
Ponto& operator++ (int); // ex: Ponto a; a++;
Ponto& operator-- (); // ex: Ponto a; --a;
Ponto& operator-- (int); // ex: Ponto a; a--;

// operadores relacionais lógicos


bool operator== ( const Ponto &p );
bool operator!= ( const Ponto &p );
bool operator>= ( const Ponto &p );
bool operator<= ( const Ponto &p );
bool operator> ( const Ponto &p );
bool operator< ( const Ponto &p );

}; // nunca esquecer deste ;

// funções globais de escrita e leitura


ostream& operator << ( ostream& os, const Ponto &p ); // para fazer Ponto _k; cout<<_k;
istream& operator >> ( istream& is, const Ponto &p ); // para fazer Ponto a; cin>>a;

#endif // _TUT_PONTO_H_

Ok, acabei de deixar aqui muita matéria, eu compreendo. Mas por um lado é bom
para quem está a aprender tentar descobrir como se fazem as coisas sozinho,
experimentar esta classe será muito bom para a sua aprendizagem...

Os, muitos dos métodos não têm código nenhum associado... Todos os que têm
estão definidos inline, ou seja, por cada objecto criado esse código será "anexado"
a essa instância. Este é um bom método para pouco código, mas se fosse uma
função muito grande, não dava nada jeito estar a inclui-la inline, e o executável
ficaria com um tamanho enorme. Outra maneira é definir os métodos num cpp.

Desta maneira essas funções estarão num sitio da memória e cada objecto criado
utilizará as mesmas funções.

Outra novidade é a maneira como inicializei os atributos no construtor, de facto o


que fiz tem o mesmo efeito prático que fazer Ponto( ..... ) { x=_x; y=_y; }

A definição dos métodos levaria muito espaço e eu sinceramente não tenho


paciência para o passar aqui... até porque pelas estatísticas vê-se que a malta
prefere tirar logo os exemplos do que estar a ler aquilo que eu tão de boa vontade
escrevo.

Por isso, no exemplo está tudo, devidamente comentado e explicado, e com um


programa de exemplo.
É claro que não ficou tudo o que havia a dizer sobre classes, mas já ficou um
cheirinho... Com o tempo e com a prática é que se aprende.

Mais um àparte, esta classe poderia ser tratada como uma estrutura, visto que
todos os seus atributos e métodos são publicos. E essa é a principal diferença
entre struct e class. Nas estruturas tudo é público por defeito, nas classes tudo é
privado por defeito.

Os exemplos deste capítulo de classes estão no ficheiro exemplo_9.zip

10. Templates

Os templates aplicam-se a funções e a classes e dão uma grande liberdade ao


utilizador. Vejamos o seguinte caso:

// funções para somar dois valores

int soma( int a, int b )


{
return a+b;
}

double soma( double a, double b )


{
return a+b;
}

float soma( float a, float b )


{
return a+b;
}

O que fiz foi sobrecarga da função soma, para o compilador, apresar de terem
todas o mesmo nome, são as três funções distintas, pois recebem parâmetros de
diferente tipo. Bem, e que tal fazermos agora o mesmo para a multiplicação,
divisão, e fazer o mesmo para parâmetros de tipos diferentes...

Em C haviam as macros, directivas do pré-processador com que se podia dar a


volta a este problema. Em C++ existem os templates. Usando templates bastaria
uma função para simbolizar todas as variantes de uma mesma função soma:

// função template para somar dois valores


template< typename T>
T soma( T a, T b )
{
return a+b;
}

O que estamos a dizer é que a função de template soma recebe dois parâmetros
de um mesmo tipo T, que pode ser qualquer. Experimente...

Templates de Classe
Tal como se pode criar funções de template, também se pode criar templates de
classes com uma sintaxe parecida. Para demonstrar um template de classes
simularei um stack. Um stack é como uma pilha de papeis, vamos acrescentando,
só dá para remover do topo e acrescentar ao topo.

template< typename T, unsigned DIM = 10 >


class Stack {
T data[DIM];
unsigned size; // tamanho do stack
public:
Stack() { size = 0; }
// retorna true se o stack estiver vazio
bool empty() const { return size==0; }
// retorna uma referência para o elemento no topo
T& top() { return data[size-1]; }
// acrescenta ao topo
void push( const T &p ) { data[size++] = p; }
// remover o ultimo
void pop() { --size; }
};

Esta classe é bastante limitada, como se pode ver, mas o objectivo é somente
exemplificar um template de classes. Para declarar objectos desta classe faria-se
Stack<int> Int_Stack; para um stack de inteiros com DIM= 10 por defeito ou
Stack<unsigned,20> Unsigned_Stack; para um stack de unsigned com dimensão
de 20 elementos.

Os métodos de um template de classes têm de ser sempre definido inline, de


preferência no próprio ficheiro (.h), no ficheiro de exemplo mostro como isso se
faz. Quando eu falar da STL (Standard Template Library), verificaremos toda a
utilidade das templates.

11. Iteradores

Já tinha dado uma breve introdução aos iteradores quando falei do objecto string,
agora dá-me jeito aprofundar mais esse conceito antes de introduzir a STL. Quem
não tiver visto a introdução que fiz, convém que o faça agora.

O iterador que aqui vou deixar é muito simples e serve para iterar todos os
números pares. Não encontro nenhum exemplo prático para este iterador, mas
penso que servirá para demonstrar o quão versátil pode ser um iterador.
class ParIter {
// valor corrente do iterador
int value;

public:
// usando este construtor pode-se fazer
// ParIter a(10); em que value ficarA com 10
// ou ParIter a; em que value ficarA com 0
ParIter( int v = 0 ) : value(v) {
// caso algum nabo inicialize value com
// um nUmero impar, muda-se value para o nUmero par anterior
if( value%2 )
--value;
}

/*
o operator ++ farA com que o ParIter avance para o prOximo
valor vAlido, para o prOximo par
*/
ParIter operator++() { return value+=2; }
ParIter operator++(int)
{ ParIter aux=*this; ++(*this); return aux; }

ParIter operator--() { return value-=2; }


ParIter operator--(int)
{ ParIter aux=*this; --(*this); return aux; }

// operador desreferência
int operator*() { return value;}
};

Os iteradores são mais usados para fazer a ligação entre o programador e o


contentor. Desta forma torna-se mais fácil a utilização de contentores diferentes,
pois podem percorrer-se com um for todos da mesma maneira.

Contentores

Em programação enfrentamos muito o problema de armazenar os dados de uma


forma eficiente e útil. Até agora só falei do array, que como tudo, tem vantagens
e desvantagens. Outro contentor muito usado e badalado é a lista. Por último
basta referir as árvores.

O conceito de array já deve estar apreendido, o que vou fazer agora é dar uma
breve introdução aos outros tipos de contentores.

Lista: tem vantagem de se poder inserir e remover qualquer elemento com uma
rapidez enorme, contudo, não pode ser indexada. A lista são um conjunto de nós
ligados entre si: sent->a->b->c->d->sent, em que sent é o mesmo nó. Este mini
exemplo seria uma lista simplesmente ligada. Para inserir ou remover bastaria
efectuar uma troca de apontadores.
Árvore: Uma árvore representa-se como aquelas árvores genelógicas que tão bem
conhecemos. Devem ser usadas principalmente quando se quer realizar acções
de procura, pois pela forma como estão organizadas, é muito mais rápido
encontrar um elemento numa árvore do que num vector. E nas listas ainda é
muito mais lento...

Estes contentores têm muita teoria associada... passei um semestre a estudá-los!


O que deixo aqui são algumas propriedades, pelo que aconselho que procurem na
net informações sobre listas e árvores.

11.1 Lista Simplesmente Ligada

Este é o exemplo mais simples de lista que conheço, uma lista simplesmente
ligada. Não é a mais viável mas serve para apreender as bases de uma lista.

Mas o que é mesmo uma lista?... Num array todos os seus elementos estão
dispostos na memória uns a seguir aos outros, numa lista, estão em posições
completamente aleatórias. De seguida estão dois exemplos que tentam
representar um array e uma lista.

Uma lista é composta por diversos nós, nós esses que são estruturas que contém
a informação pretendida e um pointer para o nó seguinte. Na implementação da
lista que vou aqui fazer, o último nó aponta sempre para NULL. Quando a lista é
inicializada, o head aponta sempre para para NULL, tal como o tail. Esqueci-me de
por o tail no desenho acima :) Quando a lista tem elementos, o head aponta
sempre para o primeiro elemento da lista e o tail para o último. O seguinte
esquema mostra o esqueleto da nossa TUT_IntSList:

Esta é uma lista com apenas 2 elementos, de reparar que o último nó tem o
atributo next a NULL.

TUT_IntSList

De seguida encontra-se a declaração da classe TUT_IntSList:


class TUT_IntSList {

// definição de Node
struct Node {
Node* next;
int data;
Node( Node* _next = NULL, int d = 0 )
: next(_next), data(d) {}
};

/* atributos privados */
Node* head;
Node* tail;
int sz;

/* métodos privados */
Node* newNode( int d = 0 ) const;

public:

TUT_IntSList();
~TUT_IntSList();

int size() const { return sz; }


void insert( int d );
int remove();
void clear();
void printOn( std::ostream &os ) const;
};

Na listagem está tudo comentadinho. Por isso para poupar espaço (e trabalho :)
vou só descrever o método que penso ser mais importante: insert().

insert

void TUT_IntSList::insert( int d ) // insere à cauda


{
Node* novo = newNode(d); // criar um novo nó
if( tail->next != NULL ) // para o caso da lista estar vazia!
tail->next->next = novo; // o último nó passa a apontar para o novo
else head->next = novo; // head aponta para o primeiro

tail->next = novo; // actualizar tail


++sz; // acrescentar 1 ao tamanho da lista
}

Penso que com os comentários se percebe bem, não me lembro de mais nada
para dizer. Não esquecer que quando a lista é inicializada head->next = tail-
>next = NULL.

Na listagem tenho todo o código comentado como este que aqui passei, por isso
penso que não é difícil de perceber. Qualquer dúvida ou sugestão: mail me. O
código está neste ficheiro: exemplo_11_slist.

12. STL (Standard Template Library)


Durante muito tempo os programadores viam-se obrigados a desenvolver novos
contentores cada vez que queriam usar tipos diferentes. Com os templates
consegue-se generalizar os contentores para qualquer tipo e o que a STL traz são
contentores altamente rentáveis e com mais de 30 anos de desenvolvimento! A
STL não são só os contentores, mas é só destes que vou falar, nomeadamente do
vector, da list e do map.

vector

O vector é um "upgrade" ao array, uma generalização. Podemos usar um vector


como se de um array se tratasse mas com mais facilidades. Imaginemos que
tinhamos um array com 10 casas e queriamos acrescentar uma. Teriamos de criar
um novo array dinamicamente, copiar o conteúdo do antigo e adicionar a nova
informação... Num vector isso fica tudo escondido, se for preciso redimensionar o
array base, o vector tratará disso sozinho sem que seja preocupações para o
programador.

#include <vector> // necessário para usar o vector


#include <iostream>
using namespace std;

int main()
{
int i;

// criar um vector de inteiros


vector<int> intVector;
// criar um vector de double's
// jA com 20 posições
vector<double> doubleVector(20);

// adicionar inteiros ao intVector


for( i=0; i< 10; ++i )
intVector.push_back(i); // acrescenta no fim

// adicionar double's ao doubleVector


// usando indexaCAo
for( i=0; i< 20; ++i )
doubleVector[i]=i; // acrescenta no indice i

//mostrar o conteudo de intVector usando iteradores


for( vector<int>::iterator it = intVector.begin(); it < intVector.end();
++it)cout<<*it<<" ";

cout<<endl;

//mostrar o conteudo de doubleVector usando indexaCAo


for( i=0; i> 20; ++i )
cout<<doubleVector[i]<<" ";

cin.get();
return 0;
}

list
Uma lista funciona de maneira análoga ao vector, somente não possui o operador
indexação. Como já referi na introdução aos contentores, a lista é o contentor
mais indicado quando há a necessidade de inserir e remover elementos com
frequência.

#include <list> // necessArio para usar a lista


#include <iostream>

using namespace std;

int main()
{
// criar uma lista de inteiros
list <int> intList;

// acrescentar em último lugar


intList.push_back(3);

// acrescentar em primeiro lugar


intList.push_front(2);

// iterar a lista
for( list<int>::iterator it=intList.begin(); it!= intList.end();
++it)cout<<*it<<" ";

cin.get();
return 0;
}

map

Um map porta-se como uma tabela em que temos uma chave de indexação e um
atributo associado a essa mesma chave. O map tem por base uma RBTree e
portanto é o mais viável quando se pretende fazer pesquisas sobre dados. Tanto a
chave de pesquisa como os dados podem ser de qualquer tipo.
#include <map> // necessArio para usar o map
#include <iostream>
#include <string>

// para o VC++ não se incomodar com os nomes grandes


// originados pelo <map>
#pragma warning(disable:4786)

using namespace std;

int main()
{
map<string,int> listaTelefonica;

// acrescentar um campo: Jose 214919513


listaTelefonica["Jose"] = 214919513;
// acrescentar um campo: Jose 212459513
listaTelefonica["Joaquim"] = 212459513;

// iterar o map
for( map<string,int>::iterator it=listaTelefonica.begin();
it != listaTelefonica.end();
++it )
cout<first<<"\t - \t"<second;

cin.get();
return 0;
}

Pronto, aqui ficam três simples exemplos dos objectos da STL que uso mais. Estes
três programas encontram-se no ficheiro exemplo_12.zip.

13. Herança e Poliformismo

Para introduzir estes conceitos vou fazer uma aplicação que gere uma loja. Esta
aplicação apenas irá calcular o ordenado de cada empregado e só existem duas
categorias de empregado, ou gerente, ou empregado de balcão. O que há de
novo é a maneira como o objecto Empregado será representado. Termos uma
classe geral de nome empregado e duas classes derivadas de empregado:
gerente e empregado de balcão. Como se deriva uma classe?
class BasicObject {
private:
int atributo;
protected:
int ID;
public:
int getID;
};

// classe derivada
class NamedObject : public BasicObject {
string nome;
public:
string getName();
};

O que estamos a dizer na declaração de NamedObject é que NamedObject é


também um Basic Object, ou seja, herda todos os atributos da classe mãe. Na
classe BasicObject existe a palavra chave protected, que está, em termos de
acesso, entre o private e o public, ou seja é público para as classe derivadas, mas
privado para o utilizador directo.

BasicObject b;
NamedObject n;

b.ID = 0; // erro! atributo ID protected


n.getName(); // correcto
n.getID(); // correcto, n herda todos os métodos e atributos public ou protected

Resumindo, um NamedObject tem também um ID e um getID(), que herda da


classe mãe. É claro que poderiamos alterar o conteúdo de getID, bastaria para
isso redeclarar e redefinir esse método.

Passando novamente à aplicação da loja, representei as classes no seguinte


esquema UML (Unified Modeling Language), que é uma linguagem que se deve
usar sempre pela sua facilidade de representação.
Temos uma classe loja tem tem um contentor vector com apontadores para todos
os empregados. A relação de Loja com Empregado é que cada loja tem um (has a)
Empregado. Depois, temos uma classe Empregado, da qual derivam a classe
Gerente e a classe Empregado de Balcão. Realço que um Gestor é um
Empregado, mas que um Empregado pode não ser um Gestor, e por ai adiante.

O código é um pouco grande, por isso não o vou passar aqui, vou simplesmente
comentar aqueles aspectos que são novidade.

Ora bem, no método pagar da classe Loja queremos que seja mostrado todos os
empregados presentes no vector empregados seguidos dos respectivos salários.
Para isso faremos algo do género:
int main()
{
Loja papelaria;
Empregado *g = new Gerente("Antonio");
EmpBalcao *e = new EmpBalcao("Jose");

// ok - um Gerente também é um empregado


papelaria.adicionarEmpregado(g);

// ok - um Empregado de Balcao também é um empregado


papelaria.adicionarEmpregado(e);

// mostrar os ordenados
papelaria.pagar();

delete g,e;

cin.get();
return 0;
}

e na definição de Loja::pagar()

void Loja::pagar() {
// percorrer o vector empregados
for( vector<Empregado*>::iterator it = empregados.begin(); it < empregados.end(); ++it )
cout<< (*it)->getName() << " tem de receber: " << (*it)->calcOrdenado() <<"."<< endl;
}

Como se pode ver, tratamos cada empregado de forma igual, não nos importando
se são Gerentes ou Empregados de Balcão... Mas não é isso que queremos,
dependendo das suas funções cada qual receberá um ordenado correspondente.

métodos virtuais

É aqui que entra a palavra-chave virtual. O método calcOrdenado está declarado


em Empregado como um método virtual, e quando é assim, todas as classes
derivadas também herdam esse método como virtual. O que virtual faz é que,
quando for chamado o método calcOrdenado sobre a classe Empregado, seja
verificado a que categoria pertence mesmo esse empregado e seja chamado o
método correspondente.

14. SDL

O que dizem os autores: "This library is designed to make it easy to write games
that run on Linux, Win32 and BeOS using the various native high-performance
media interfaces, (for video, audio, etc) and presenting a single source-code level
API to your application. This is a fairly low level API, but using this, completely
portable applications can be written with a great deal of flexibility."

Na verdade, esta é uma biblioteca muito poderosa, que cresce de dia para dia! Já
foram feitos alguns jogos comerciais com ela, como o Duke Nukem 3D, Civ 3...
Os tuturiais do capítulo 4 irão introduzir alguns conceitos básicos e exemplos
práticos de como mostrar imagens, tratar eventos... isto tudo com base na SDL. E
será criado um header com algumas funções simples para mostrar imagens,
inicializar a SDL, e outras coisas.

instalação

Para usar a SDL é preciso fazer o seu download, que poderá ser feito em
www.libsdl.org, o que interessa tirar são as run-time libraries e o SDL-devel (devel
de development). Depois descompactar tudo para um directório e pronto! Com a
SDL vem também a sua documentação e também a informação necessária para
usar a SDL no Visual C++. Se seguirem as instruções desse ficheiro não devem
ter problemas em configurar o VC++, seja como for qualquer coisa poderão
sempre contactar-me.

Os exemplos serão feitos no VC++ e também terão as workspaces


correspondentes, só não terão nada relacionado com a SDL! Nem mesmo as DLLs,
isto para poupar espaço no servidor.

14.1 SDL - Conceitos Básicos

A primeira coisa de que falarei é como inicializar a SDL e mostrar imagens. Para
isso convém ter presente pelo menos um conceito base: uma Surface.
Resumidamente uma Surface representa uma imagem.

inicializar a SDL

// inicializar o Video
if( SDL_Init ( SDL_INIT_VIDEO ) == -1 ) {
/* caso a SDL encontre um erro ao inicilaizar */
}
// necessario
atexit(SDL_Quit);

inicializar o "display"

SDL_Surface *s;

s = SDL_SetVideoMode(400, 400, 0, SDL_SWSURFACE | SDL_DOUBLEBUF );


if( s == NULL ){
/*foi encontrado um erro... */
}

A função SDL_SetVideoMode irá criar uma janela de 400 por 400, os outros
parâmetros deixemos como estão, porque não precisaremos de os modificar (pelo
menos nos exemplos deste Tutorial).

Depois de criar a janela retorna a Surface correspondente que será igualada a s.


Por comodidade será criada uma função TUT_initSDL(int x, int y) a receber
somente as dimensões da janela.
Com isto já se criou uma janela, agora convinha mostrar uma imagem. O código
correspondente estará na função TUT_showImage("file.bmp", 10,20); Esta função
suportará apenas o formato BMP. No código de exemplo para download tenho
tenho bem explicadinho e comentado, não o vou passar aqui pelo tamanho, vou
só explicar alguns conceitos basicos.

Neste exemplo, screen seria a própria janela. Para desenharmos a SDL_Surface


*image teriamos de dizer em que zona seria, ouseja dar as coordenadas
pretendidas.

SDL_Rect rectIm, rectScr;

rectIm.x = 0;
rectIm.y = 0;
rectIm.h = image->h;
rectIm.w = image->w;

rectScr.x = x;
rectScr.y = y;
rectScr.h = image->h;
rectScr.w = image->w;

// afectar a SDL_Surface de destino


SDL_BlitSurface ( image , &rectIm , screen , &rectScr );

Em português, no Blit o que se fará será pegar em todo o conteúdo da


SDL_Surface* image e copiá-lo para a zona de screen dada pelo SDL_Rect
pretendido. Isto por si só não mostra a imagem no ecrã! Aida se tem de actulizar
a janela utilizando uma das seguintes funções:

// pinta de novo toda a SDL_Surface* screen;


SDL_FLip(s);
// actualiza somente uma dada área
SDL_UpdateRect(screen, x, y , w ,.h);
Porque é que não se pinta directamente no display? Pintar directamente no
display seria mais lento e faria com que se visse a imagem a piscar. É muito mais
rápido e eficaz afectar na memória e somente depois actualizar a imagem no
ecrã. Esta técnica é conhecida por bouble buffering.

Com estas simples funções já poderiamos criar uma janela e apresentar uma
imagem, exemplo:

#include "TUT_SDL.h"

int main( int argc, char* argv[] )


{
SDL_Surface *screen = TUT_initSDL();

TUT_showImage( screen, "imagem.bmp" );

return 0;
}

É claro que se esperimentarem este exemplo não vão ver nada, isto porque nada
diz ao programa para parar e esperar uma acção do utilizador.

Esse método será visto no próximo capítulo, em eventos.

14.2 SDL - Eventos

Agora falarei dos eventos relacionados com o rato e com o teclado. Para isso é
preciso perceber o que é um evento... Vejamos este exemplo que fica à espera
que o utilizador prima um botão do rato.

TUT_Point whaitForM(){
SDL_Event event;

while (1)
{
while(SDL_PollEvent(&event)) {
// se um botão foi pressionado
if( event.type == SDL_MOUSEBUTTONDOWN )
// retorna o ponto onde houve o clique
return TUT_Point( event.button.x, event.button.y );
}
}
}
struct TUT_Ponto {
int x,y;
TUT_Ponto( int _x, int _y ) : x(_x), y(_y) { }
}:

A estrutura ponto poderia ser algo mais trabalhado (como a realizada na


introdução às classes), mas esta como demonstração serve.

O SDL_PollEvent vai ver qual é o evento a ser processado de momento e afecta a


variável event com ele, o while(1) é só para ficar ali à espera que o utilizador
prima o rato. No TUT_SDL.h estão definidas duas funções TUT_whaitForM() e
TUT_whaitForK() que esperam por uma acção do rato ou do teclado
respectivamente.

O programa de exemplo que vou deixar aqui é muito simples e usa todos os
imensos conceitos falados até aqui :)

Na verdade existe uma imagem quadrangular que se move, e nós, por intermédio
das setas, mudamos a sua direcção e sentido.

Todo o código está comentado e penso ser fácil perceber como tudo funciona. O
objectivo deste exemplo era somente mostrar a facilidade de uso da SDL.

O código deste simples exemplo, tal como o header TUT.SDL, estão no ficheiro
exemplo_14.zip.

15. Pré-Processador

O Pré-Processador não é C++ propriamente dito, mas faz parte da linguagem.


Nesta secção vou deixar aqui os comandos do Pré-Processador que existem,
alguns dos quais já largamente conhecidos como o #include ou o #define...
#include

Esta deve ser a directiva de pré-processamento mais usada em C/C++...

#include <iostream>
#include "MyClasses.h"

O que irá acontecer é que a linha #include <iostream> será substituida pelo
conteúdo do ficheiro iostream, e assim se pode usar todas as definições e código
relacionadas com esse ficheiro. Por convenção, os headers(ficheiros .h que se usa
nos includes) ficam entre <> quando são standard C/C++ e entre "" quando são
criados pelo programador.

#define

Esta directiva de pré-processamento serve para definir constantes simbólicas ou


macros:

#define NOME "Pedro Santos"


#define AGE 20

(...)
cout<< NOME <<" "<< AGE;

O que acontece é que o pré-processador irá substituir todas as ocorrências de, por
exemplo, a palavra AGE, pela sua definição, neste caso, por 20. AGE não está
definida na memória.. e será usada somente em compile time.

Por norma, não é obrigatório, as constantes são escritas em letras maiúsculas.

macros

Podemos usar o #define e fazer algumas "funçõezitas", isso são as macros:

#define MUL(x,y) x*y

(...)
cout<< MUL(2,3);

Como se pode ver, aquela macro funciona como uma função, mas com uma
particularidade, não lhe interessa o tipo das variáveis x e y. De facto, era assim
que em C se tratavam de vários tipos numa só função, com o aparecimento dos
templates as macros deixaram de ter a importãncia que tinham, mas continuam a
ter diversas utilidades.

Ora bem, o que aconteceria é que seria substituido MUL(2,3) por 2*3. Há no
entanto regras precisas no uso de macros, pois suponhamos que punhamos
MUL(2+3,4+5), ela seria transformada em 2+3*4+5... o que é diferente de
(2+3)*(4+5). Teriamos de usar parêntises , aqui ficam as regras, numa macro são
sempre colocados 3 níveis de parêntises:
1. À volta de cada um dos parâmetros existentes na expansão da macro.
2. À volta de cada expressão.
3. À volta de toda a macro
#define MUL(x,y) ( (x)*(y) )

(...)
cout<< MUL(2+3,4+5); // cout<< ( (2+3)*(4+5) );

Só mais uma coisa, as macros têm de ser escritas na mesma linha, se quisermos
usar mais que uma linha, teremos de usar o operador continuidade '\':

#define MIN ( ( (a) < (b) ) ? (a) : (b) )


// pode ficar
#define MIN ( ( (a) < (b) ) ? \
(a) : \
(b) )

operador #

O operador #, normalmente denominado de stringizing faz com que a variável à


sua frente seja "retornada" entre aspas:

#define SHOW(x) cout<<#x


// ao chamar SHOW(Pedro Santos);
cout<<"Pedro Santos";

operador ##

O operador ##, operador concatenador, permite juntar duas strings, mas sem
ligar a aspas:

#define VAR(x) n##x

(...)
int n1, n2;
cout<< VAR(1)<<VAR(2);

// este código será expandido como


cout<<n1<<n2;

Compilação Condicional

São várias directivas e de fácil compreensão, por isso deixo-as aqui num exemplo:

#define TUT_WIN32 // definir o SO como win32


#define DEBUG // estamos a compilar em Debug Mode

#ifndef TUT_WIN32
#error Este programa (faz de conta :) que só trabalha em win32
#endif

#include <iostream>
using namespace std;
main()
{
#ifdef DEBUG
cout<< "In Debug Mode";
#endif
}

Pronto, penso que com este exemplo dá para se ter uma noção básica daquilo
que se pode fazer com o pré-processador. Se experimentarem este programa,
comentem a linha #define TUT_WIN32 para ver o que acontece...

E pronto, penso que acerca do Pré-Processador já estamos conversados, no


ficheiro exemplo_15.zip têm estes exemplos todos num só cpp.

6. Operações Relacionadas com Bits

O C++ é uma linguagem que permite tratar as variáveis como elas são
realmente, como um conjunto de bits. Os quadros seguintes demonstram as
operações elementares:

Operação AND (&) Operação OR ( | ) Operaçâo NOT (~)


a b a&b a b a |b a ~a
0 0 0 0 0 0 0 1
0 1 0 0 1 1 1 0
1 0 0 1 0 1
1 1 1 1 1 1

Nos operadores lógicos, já tinha apresentado uns operadores semelhantes a


estes, naquele caso, eles tinham em conta o sentido booleano das expressões,
nestes operadores é tido em conta todos os bits que formam a variável. Exemplo:

a = 11110000
b = 00001111

a | b = 11111111
a & b = 00000000
~a = 00001111

Para além destes também temos os operadores shift rifgt e shift left, que rodam
para a direcção indicada, a quantidade de bits indicada.

a = 11110000
b = 00001111
b << 1 = 00011110
a >> 2 = 00111100

font

Para realizar uma aplicação prática desta matéria vamos simular uma font em
memória, e depois mostrá-la no ecrã:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1
1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1
1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0

Bem, eu não sei se se nota bem, mas lá em cima está escrito CPP. Como podem
ver cada letra é composta por um mapa de bits e, neste caso, os 1s significam
que que essa célula está preenchida e os 0s o contrário.

Primeiro temos que ter onde armazenar aquela informação, parece-se que um
arrayzito de 8 posições vem mesmo a calhar, e cês perguntam, não devia ser de
8x8?

Cada char tem 8 bits, e serão esses 8 bits que nos indicaram o "código" de cada
linha. Mas, para quem não sabe, um char é, por defeito um signed char, ou seja,
uma variável com sinal. Isso daria alguns problemas e então teremos de ignorar
sinais fazendo unsigned char. Como dá muito trabalho andar sempre a escrever
unsigned char, vamos chamar-lhe Byte, que tal?

typedef unsigned char Byte;

Byte letraC[ ] = { 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff };
Byte letraP[ ] = { 0xff, 0xff, 0xc3, 0xc3, 0xff, 0xff, 0xc0, 0xc0 };

Em casa posição do array está uma coisa esquesita com um 0 e um x e tal... Isso
é codigo hexadecimal, e usa-se muito frequentemente para representar bits,
imaginem a quantidade de uns e zeros que eu teria de por ali... O prefixo 0x serve
somente para indicar que se trata de um hexadecimal.

Decimal Hexadecimal Binário


0 0 0000
1 1 0001
2 2 0010
3 3 0011
4 4 0100
5 5 0101
6 6 0110
7 7 0111
8 8 1000
9 9 1001
10 A 1010
11 B 1011
12 C 1100
13 D 1101
14 E 1110
15 F 1111

Na tabela acima estão as correspondências entre hexadecimal, binário e


hexadecimal para os primeiros 16 dígitos. Por exemplo, uma linha cheia de 1s
( 11111111 ) seria facilmente representada por 0xff. E é assim que o código para
cada letra foi feito. Agora, o algoritmo usado para pintar letras:

void paint( Byte v[] )


{
for( int i=0; i<8; ++i ){
Byte mask = v[ i ];
for( int j=7; j>=0; --j )
if( mask>>j & 0x01 )
cout<<"X";
else
cout<<" ";
cout<<endl;
}
}

Penso que a linha if( mask>>j & 0x01 ) é a que custa mais a perceber. O j é o
índice( no Byte) que estamos a tratar de momento e shiftamos o bit de índice j
para a posição de menor peso (aquela mais à direita). Depois fazemos um and
com 0x01 para apagar todos os bits excepto o de menor peso. Dese modo
ficamos só com o bit a tratar. Depois é só ver se ficou 0 ou 1.

Vous aimerez peut-être aussi