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++.
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".
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.
Não há nada melhor para apreender estes conceitos que experimentar! Faça um
programa parecido, mude as palavras...
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"
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.
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
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.
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++:
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 */
// 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():
}
void main()
{
int i = 2, j = 3; // declaração e inicialização das variáveis j e i
int a = 0; // para testes
--i; // i = 2
j--; // j=3
Qualificadores
Equivalências:
Operadores Lógicos
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
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.
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;
if( a == b )
cout<<"Os valores são iguais!";
else
cout<<"Os valores sao diferentes!";
cin.get();
}
void main()
{
int 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();
}
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;
Penso que o conceito do if está explicado. Se tiver alguma dúvida pode sempre
contactar-me através do mail.
Operador Ternário
#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 */
#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;
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();
}
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.
#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;
cin.get();
}
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.
Pode-se fazer:
cout<<"Atirei o pau ao gato"<<endl
<<"Mas o gato assustou-se..."<<endl;
#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!
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++.
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<<endl;
i = 2; num += 2; // não são obrigatórios os campos do for
for( ; i < num; ++i )
cout<<c;
cin.get();
}
Não sei que mais dizer sobre o for, penso que com o que foi escrito se pode
apreender o conceito do for.
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;
3. Arrays
#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!
#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 */
#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<<"O maior número foi o "<< e[ max ] <<" e foi o "<< max
<<"º Número a ser introduzido.";
cin.get();
}
#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];
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.
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
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 main()
{
ola_mundo();
f1();
cin.get();
}
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
void main()
{
int v1, v2;
cout<<"introduza dois inteiros: "<<endl;
cin>> v1>> v2;
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.
#include <iostream>
using namespace std;
//variável global
int v1;
void main()
{
int a;
v1 = 2; // correcto, v1 está acessível pois é global
}
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!
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;
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
#include <iostream>
using namespace std;
struct Empregado {
int num];
int idade;
Especialidade esp;
};
void main()
{
// diferentes maneiras de inicalizar estruturas
Empregado emp1 = { 1, 35, VENDAS };
Empregado emp2;
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
#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
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;
int main()
{
int num1 = 1;
int num2 = 2;
cin.get();
return 0;
}
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;
cin.get();
return 0;
}
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;
}
// 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 ]<<" ";
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.
7. O Objecto string
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.
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.
// 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;
}
cin.get();
return 0;
}
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:
// 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();
cout<<endl;
cin.get();
return 0;
}
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_
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; }
#endif // _TUT_CADEIRA_H_
Por norma, uma classe fica sempre alojada num ficheiro de cabeçalho(.h), esta
classe estaria no ficheiro Cadeira.h
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.
#include <iostream>
using namespace std;
// demonstração do uso da class Cadeira
int main()
{
// criado um objecto cadeira
Cadeira cadeira ( 10, 20, BRANCO );
cadeira.mudarPeso( 30 );
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.
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_
class Ponto {
// 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 */ }
#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.
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.
10. Templates
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...
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.
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.
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; }
// operador desreferência
int operator*() { return value;}
};
Contentores
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...
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
// 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();
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
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.
vector
int main()
{
int i;
cout<<endl;
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.
int main()
{
// criar uma lista de inteiros
list <int> intList;
// 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>
int main()
{
map<string,int> listaTelefonica;
// 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.
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();
};
BasicObject b;
NamedObject n;
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");
// 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
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.
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;
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).
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;
Com estas simples funções já poderiamos criar uma janela e apresentar uma
imagem, exemplo:
#include "TUT_SDL.h"
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.
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) { }
}:
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
#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
(...)
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.
macros
(...)
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 '\':
operador #
operador ##
O operador ##, operador concatenador, permite juntar duas strings, mas sem
ligar a aspas:
(...)
int n1, n2;
cout<< VAR(1)<<VAR(2);
Compilação Condicional
São várias directivas e de fácil compreensão, por isso deixo-as aqui num exemplo:
#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...
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:
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?
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.
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.