0% acharam este documento útil (0 voto)
4 visualizações32 páginas

Aulas de Programação de Jogos

Guilherme M. Ferreira apresenta um curso de programação de jogos, enfatizando a importância de conhecimentos prévios em programação e depuração de código. O curso abordará a criação de um jogo para DOS, focando na estrutura e parte gráfica, e será continuamente atualizado. Ferreira compartilha sua paixão por jogos e encoraja os alunos a persistirem em seus objetivos de desenvolvimento de jogos.
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato DOC, PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
4 visualizações32 páginas

Aulas de Programação de Jogos

Guilherme M. Ferreira apresenta um curso de programação de jogos, enfatizando a importância de conhecimentos prévios em programação e depuração de código. O curso abordará a criação de um jogo para DOS, focando na estrutura e parte gráfica, e será continuamente atualizado. Ferreira compartilha sua paixão por jogos e encoraja os alunos a persistirem em seus objetivos de desenvolvimento de jogos.
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato DOC, PDF, TXT ou leia on-line no Scribd

GAME Programming™ Aulas

Introdução
por Guilherme M. Ferreira

Bem vindos, meu nome é Guilherme, programo computadores ha mais de 5 anos,


e espero de hoje em diante guiá-los para o fascinante mundo do desenvolvimento de
jogos. Quero que se sintam à vontade para me enviarem qualquer dúvida, sugestão ou
crítica a respeito do andamento das aulas, claro que darei prioridade as mensagens
dos participantes inscritos, mas isso não quer dizer que não lerei os outros e-mails.

Gostaria de pedir desculpas pela demora em iniciar as aulas, isso se deve ao fato
de eu estar muito ocupado ultimamente, e tenho tempo somente de criá-las ao finais
de semana. Farei o possível para publicar mais de uma aula por mês, contudo, pode
ser que próximo ao vestibular, eu consiga publicar uma aula apenas. E esse é o mínimo
que vou disponibilizar! ;- )

Nelas, eu darei enfoque a tópicos relacionados à programação de jogos, macetes


e exercícios ao final de cada aula para fixar seu aprendizado. Em algumas aulas, eu
lançarei pequenos desafios para os participantes, onde eles poderão debater sobre o
assunto e descobrir a solução. Ao longo das aulas, nos iremos montando um jogo para
DOS tipo os do SNES, onde aprenderemos os conceitos básicos por trás da
programação dos jogos, e depois iniciaremos com aulas para DirectX. Mas não se
enganem a respeito dos jogos para DOS, pois a parte realmente importante do jogo
não está na plataforma onde ele é desenvolvido, mas sim na sua estrutura, o que é
praticamente igual tanto no DOS como no Windows. Nas aulas, vou procurar dar
embasamento a toda a arquitetura do jogo, contudo darei maior enfoque a parte
gráfica deles. Sendo que, além de ser o mais interessante, é o meu ponto forte.

O curso não tem um tempo estimado de término, talvez nunca termine, pois eu
sempre estarei o atualizando, adicionando novas aulas sobre novos assuntos, já que a
programação de jogos é algo muito dinâmico.

Se vocês estão lendo isso, é porque concordam comigo num aspecto: que jogos
são realmente fascinantes. E infelizmente, é uma coisa que, de certa forma, você tem
que aprender sozinho. Talvez por isso, muitos pensem que é algo difícil de fazer e
outros nem sequer se interessem em saber como desenvolver. Por isso, meu intuito
aqui é mudar isso.

A partir deste momento, é necessário que você, leitor, possua alguns


conhecimentos indispensáveis para entender o curso. Primeiro de tudo, espero que
você saiba programar, que pelo menos tenha os fundamentos da programação, sendo
que as aulas se destinam a ensinar a programar jogos, e não o básico. Em algumas
ocasiões, se eu achar necessário, explicarei alguns conceitos avançados típicos da
programação de jogos; Segundo, seria interessante você saber depurar seu código, ou
seja, simular o computador para executar algumas tarefas. Isso é essencial para
entender a lógica (algoritmo) por trás das rotinas que serão mostradas no curso.

Em 1997 quando ganhei meu primeiro computador, um 486DX4 100MHz, me


presentearam com um CD contendo 128 jogos clássicos para DOS (tenho ele até hoje).
Jogos como Doom, Abuse, Mortal Kombat, Prince of Persia, Wolfstain3D, e muitos
outros. No entanto, um desses jogos realmente mudou minha vida. Se chamava Ultima
8, era um jogo tipo o Diablo, mas não sei o que ele tinha que eu o adorava, era e é pra
mim o melhor jogo que já fizeram, não pelos gráficos ou pelo som, mas porque ele
possuía algo mais, ele tinha a magia dos jogos, e é isso que me fez daquele momento
em diante voltar todos os meus esforços para a programação de games.

Esse último parágrafo pode ter-lhe parecido sem nexo, mas o que eu quis dizer
foi que eu tenho um objetivo, contudo, já se passaram anos e eu nem sequer terminei
meu primeiro grande jogo, tive que aprender muitas coisas que me custaram muito
tempo de estudo. Talvez, se eu encontrasse um lugar onde eu pudesse ter um
embasamento sobre o assunto, as coisas hoje poderia ser melhores. Então, meu
recado é: nunca desistam e sempre vão em busca de seus objetivos!

Hoje em dia, se você quer ser um desenvolvedor de jogos, tem que saber inglês e
programação C++, sendo que a maioria dos artigos, livros e tutoriais sobre o assunto
são em inglês e destinados a programadores com um bom conhecimento dessa
linguagem. Na verdade, se você quer aprofundar-se em qualquer assunto relacionado à
computação, o aconselho a ler livros de autores americanos, isso não significa que não
existam bons autores aqui no Brasil, pelo contrário, mas o fato é que, a tecnologia é
Americana, e nada melhor que aprender a usar algo com quem a cria.

Você pode não concordar comigo em alguns aspectos, mas eu aqui estou
partindo do ponto de que você queira desenvolver jogos para competir a nível mundial.
Mencionei isso pois é possível desenvolver jogos em outras linguagens e sem saber
uma palavra de inglês, contudo, você ficaria alienado do mundo dos games. Seria
como viver na China falando Espanhol.

Advirto desde agora que nosso caminho será longo, e até nós nos tonarmos bons
desenvolvedores de jogos, pode-se levar anos e anos. Ganhar pouco e ainda conviver
com a pirataria, é algo que você terá que enfrentar. Mas nada no mundo se compara a
satisfação pessoal de você realizar seu objetivo, e creio que o seu objetivo, como o
meu, é qualquer dia ouvir alguém dizer: "- joguei seu jogo, aquela fase foi bem legal,
quando vai sair a segunda versão?". Isso é algo inestimável. Mas não espere que seu
jogo revolucione o mundo, e sim que ele revolucione sua vida.

Como já devem ter notado, sou apaixonado pelo assunto, espero então contagiá-
los com isso e fazê-los gostar desse novo mundo que entraremos, o mundo da GAME
Programming.

GAME Programming™ Aulas


Aula 1 - Game Start!
por Guilherme M. Ferreira

Como é que começou isso mesmo?

A história dos jogos eletrônicos é antiga, tanto quanto a do próprio computador,


não sei dizer bem ao certo quando foi feito o primeiro game, no entanto, ele data de
meados da década de 60.

Desde os primórdios da informática, quando os primeiros computadores estavam


sendo desenvolvidos, existiam pessoas que pensavam em divertir-se usando essas
máquinas. Tanto que, é de conhecimento do leitor que primeiro surgiram as geladeiras
com chip (mainframes) e depois de algum tempo que a indústria resolveu direcionar
seus produtos também para o mercado dos computadores pessoais. Então, foi em uma
máquina rodando uma antiga versão do Unix, que um dos primeiros jogos foi feito.
Desconheço seu nome, entretanto, uma coisa é certa, ninguém deve jogá-lo mais.

Outro fato engraçado é o de que esses jogos dispunham de tecnologia multiplayer


(óbvio que não tão avançada como hoje). E muitos deles surgiram a partir de projetos
utilizados pelo governo americano, como simuladores de vôo e de rota de mísseis.

Em meados da década de 80 que os games começaram a se popularizarem,


mesmo assim, eram poucas as empresas que lucravam com a comercialização desses
produtos, pelo fato de que era pouca a demanda de profissionais nessa área, mesmo
nos EUA.

Poucas pessoas arriscavam-se nesse terreno, pois era exigido além de


criatividade, um enorme conhecimento de programação Assembly, a única linguagem
que permitia desenvolver jogos naquela época, pois permitia flexibilidade sobre o
computador.

Eram diversos os obstáculos aos game programmers daquela época. Para fazer
um jogo, era necessário partir praticamente do zero, desenvolver todas as rotinas de
manipulação de vídeo, som, estrutura de dados, etc., porque ninguém iria lhe dar o
que eles arduamente fizeram.

Mesmo após vencer essas adversidades, havia ainda um fator chamado


incompatibilidade de hardware, sendo que, tudo o que você fazia era executado
diretamente no hardware, o programador era obrigado a disponibilizar inúmeras
versões das bibliotecas do jogo para as diversas placas existentes no mercado.

Apesar de nosso curso dar foco aos micros padrão IBM PC e compatíveis devido a
sua supremacia como plataforma de desenvolvimento, nem sempre foi assim, sendo
que antigamente essa plataforma não oferecia nem o melhor nem o mais fácil suporte
ao desenvolvimento dos jogos. Seu hardware pobre em relação aos consoles e micros
Apple daquela época, fazia com que os desenvolvedores de jogos não optassem pelos
micros padrão IBM e os que optavam, ficavam limitados a jogos pobres em recursos
relacionados aos jogos desenvolvidos em outras plataformas. Esse quadro mudou
quando games como DOOM surgiram. Muitos pesos pesados da indústria começaram a
ver que o mercado de entretenimento eletrônico poderia ser produtivo, e isso fez tanto
os produtores de hardware quanto os de software darem a devida atenção aos jogos,
assim os micros IBM PC começaram a ganhar mais força e Megahertz para competir
lado a lado com outras plataformas existentes no mercado.

Entendendo os termos

Antes de começarmos a explorar as entranhas da programação dos games, é


necessário conhecer o que é o que no mundo dos jogos. Para isso, vou mostra-lhes as
diferenças entre um e outro, e se você pensa que todos eles são a mesma coisa, saiba
que a diferença entre um e outro vão além do tipo de gráfico que utilizam. Coisas sutis
podem representar a diferença entre o preto e o branco. A partir deste momento, você
passará a notar pequenas diferenças nos jogos que joga. Coisas que creio nunca havia
percebido antes. Passará a entender melhor como funcionam.

Se você notar, os primeiros jogos que foram desenvolvidos, como por exemplo o
PackMan (um antigo jogo do tipo "come-come"), verá que seus personagens são na
verdade imagens mapeadas, ou seja, são um conjunto de pontos (pixels) que o jogo
move de um lado para o outro. Vou esclarecer, veja a figura 1.1, esse é o personagem
PackMan, ele é um gráfico, onde cada pixel dele assume uma cor diferente, assim, ele
não passa de uma imagem que vai sendo movida pela tela ao comando do jogador.

Figura 1.1 - Exemplo de imagem mapeada, PackMan.

Cada um dos pixels da figura é representado por uma posição de memória


diferente. Para ser franco, a manipulação de imagens bidimensionais não difere muito
da de strings (cadeia de caracteres). E é muito fácil entender o funcionamento delas.
Contudo, esse não é nosso objetivo no momento, então não se preocupe com isso por
enquanto, eu irei explicar os conceitos de manipulação de imagens em ocasiões
posteriores.

O jogo que faremos difere em muito do nosso amigo Pack. Nosso jogo utiliza uma
técnica chamada Scrolling, que consiste não apenas no movimento dos personagens,
mas sim do cenário inteiro. Se você notar nos jogos do SNES, verá que o fundo se
move tanto no eixo X quanto no Y, ou seja, horizontalmente e verticalmente. Por falar
nisso, de agora em diante eu vou me referir as imagens de fundo como Background, e
as outras como Foreground. Ambos são manipulados de forma diferente pelo nosso
jogo. Mais adiante vou falar mais sobre esse tipo de jogo, já que ele é o foco das aulas.

Um outro tipo de jogo, são os em 3 dimensões, esses jogos utilizam uma


exaustiva matemática em sua parte gráfica, são difíceis de produzir e em muitos casos
não possuem a beleza que um Scroller tem, além de consumirem muito tempo de
processamento. Como o autor americano Andre LaMothe diz: "Jogos são metade lógica
metade matemática, jogos em 3 dimensões são só matemática". E se você pretende
desenvolver jogos assim, prepare-se para passar mais tempo desenvolvendo fórmulas
que escrevendo código.

Agora vou continuar a falar de nosso jogo, primeiro gostaria de explicar mais
detalhadamente os aspectos gráficos dos Scrolled games. Todos os personagens e
animações que você vê nesse tipo de jogo, são chamados de sprites, são as imagens
de primeiro plano (foreground), eles podem interagir tanto com o background como
com outros sprites. Controlado por um dispositivo de entrada como teclado, mouse ou
joystick, os sprites se movem em diversas direções. Para dar a impressão de
movimento sobre o fundo, por exemplo, um Boneco (sprite) andando sobre o chão, é
preciso que você coloque-o exatamente na posição que ele deve estar, ou seja, deve
haver sincronismo entre as ações dos personagens.

Gostaria que você executasse o programa [Link], ele é muito simples, mas
serve para você entender melhor os conceitos relativos a sprites e scrolling. Nele, um
pequeno boneco amarelo move-se pela tela para o lado que você escolher utilizando as
setas. Ressalto que não utilizei nenhum recurso avançado nesse exemplo, ele é
realmente muito simples, e para quem sabe utilizar bem C irá achar o código bem
familiar. Sua simplicidade permite que você entenda melhor os conceitos básicos por
trás dele. Abaixo está o código do programa, eu coloquei comentários e vou explicar
algumas partes dele.

Listagem 1.1, [Link]

1 : #include <stdio.h>
2 : #include <conio.h>
3 : #include "videolib.h" /* espero que a videolib.h esteja no mesmo
diretório do programa */

4 : #define ALTURA 10 /* aqui eu defino as dimensões do bitmap */


5 : #define LARGURA 10

6 : typedef unsigned char byte; /* como no C eu não tenho o tipo byte,


eu o crio */
7 : typedef unsigned int word;

/* essa rotina é a mesma coisa que o ClrScr do Pascal, só que essa é pro
modo vídeo */
8 : void limpa() {
9 : word x;
10 : for (x=0; x<64000; x++)
11 : VGA[x] = 0x00;
12 : }

13 : void main() {
14 : word shift_x=100, shift_y=50;
15 : word x, y;
16 : word i;
17 : char key;
18 : const byte S = (byte) 14;
19 : const byte N = (byte) 0;
20 : byte smileface[101] = {N,N,N,S,S,S,S,N,N,N,
N,N,S,S,S,S,S,S,N,N,
N,S,S,S,S,S,S,S,S,N,
S,S,N,N,S,S,N,N,S,S,
S,S,N,N,S,S,N,N,S,S,
S,S,S,S,S,S,S,S,S,S,
S,S,N,S,S,S,S,N,S,S,
N,S,S,N,N,N,N,S,S,N,
N,N,S,S,S,S,S,S,N,N,
N,N,N,S,S,S,S,N,N,N};
21 : clrscr();
22 : printf("\n Exemplo 1 - Scrolling a yellow face!");
23 : printf("\n Esse Programa demonstrara o funcionamento de um
sprite,");
24 : printf("\n utilize as setas para mover o Smile Face pela tela e");
25 : printf("\n para sair pressione a tecla q.");
26 : printf("\n Precione qualquer tecla para proseguir...");
27 : getch();
28 : videomode(0x13);
29 : do {
30 : limpa();
31 : for(y=shift_y, i=0; y<(shift_y+ALTURA); y++)
32 : for(x=shift_x; x<(shift_x+LARGURA); x++, i++)
33 : putpixel(x,y,smileface[i]);
34 : key = getch();
35 : switch (key) { /* controla o movimento do personagem através de
mudanças */
36 : case 72: --shift_y; break; /* nas variávis responsáveis pela
posição */
37 : case 75: --shift_x; break; /* inicial do boneco */
38 : case 77: ++shift_x; break;
39 : case 80: ++shift_y; break;
40 : }
41 : } while ((key != 'q') && (key != 'Q'));
42 : getch();
43 : videomode(0x03);
44 : printf("\n GAME Programming \n [Link] \n
guilherme-mf@[Link] \n ;-)");
45 : }

Pode parecer meio estranho o código do programa acima, e por essa razão é
importante ter-se um bom conhecimento em C, no entanto, irei explicá-lo.

As linhas 1,2 e 3 são para incluir as bibliotecas de funções que o programa utiliza,
sendo que a terceira é a biblioteca gráfica que nós estamos desenvolvendo. Se ela
estiver em outro diretório sem ser o do programa, você deverá incluir o diretório inteiro
junto ao arquivo, como por exemplo: #include "C:\TC\Libs\videolib.h" .

As linhas 4 e 5 definem o tamanho que o bitmap terá, isso é necessário já que


precisamos dessas medidas para desenhar o bitmap. Em um arquivo bmp, pcx, jpg, gif
e todos os outros tipos, essas medidas são definidas no cabeçalho do arquivo, então é
preciso que se obtenha esses valores antes de qualquer função de desenho. Se tiver
mais curiosidade, dê uma olhada na função "putbmp" da videolib.h.

Como os tipos de dados pré definidos que a linguagem C possui são todos com
sinal (signed), ou seja, possuem valores positivos e negativos, criei o byte, que é um
tipo de dado sem sinal (unsigned) possuindo uma faixa de valores entre 0 e 255, que é
um byte, já o char normal do C vai de -127 a 128, a mesma coisa serve para o Word.
Nas linhas 6 e 7 você pode ver isso.

A rotina void limpa() que está entre as linhas 8 e 12, é responsável por limpar a
memória de vídeo. Na verdade, ela não limpa, o que ela faz é colocar em cada byte de
memória o valor 0, que representa a cor preta. VGA, que está declarado na videolib.h,
é um ponteiro para 0xA000, que é o endereço inicial da memória de vídeo padrão, a
qual no modo de vídeo que usamos (modo 0x13 - linha 28) possui 64KB. Você deve
saber que ponteiros são muito importantes em C! Então, o que essa rotina faz é o
seguinte: começa a colocar o valor 0 (cor preta) desde o começo da memória de vídeo
até o fim dos 64000 bytes. Tente substituir 0x00 por outro valor e você verá que a cor
muda. Outro ponto que menciono é o de que quando a variável x é igual a zero o
ponteiro VGA está na posição 0 da memória de vídeo, e isso representa o canto
superior esquerdo do seu monitor. Já quando x, no loop for (linha 10), é igual a 32000,
o ponteiro VGA está no meio do seu monitor. Não se preocupe com essa função, pois
isso é responsabilidade da biblioteca gráfica que estiver usando, e no geral você não
teria que saber o funcionamento interno dela. :-(

Das variáveis declaradas entre as linhas 14 e 20, a que merece maior explicação
é a matriz de bytes SmileFace[101], ela possui 101 bytes dos quais 100 são
efetivamente utilizados, o último byte eu deixo vago. Se você notar o Smile depois de
executado e olhar a matriz inicializada na linha 20, verá que ele é a matriz! As
constantes N e S (linhas 18 e 19) definem as cores preta e amarela respectivamente, e
como o nosso fundo é preto, você tem a impresão de que os olhos e as bordas do
Smille são invísiveis. Mas o que ocorre na realidade é que elas coincidem com as cores
do fundo, no entanto, imagine se o fundo fosse uma paisagem, se você fizesse isso,
você veria o smile com um grande quadrado preto em sua volta, então como você faria
para não imprimir apenas o smile? A técnica que o fará colocar apenas os pixels
respectivos ao personagem ignorando os pretos seria a seguinte: permitir apenas que
os pixels amarelos fossem colocados, ignorando os pretos. Seria algo mais ou menos
assim:

31 : for(y=shift_y, i=0; y<(shift_y+ALTURA); y++)


32 : for(x=shift_x; x<(shift_x+LARGURA); x++, i++)
33 : if (smilleface[i] != '0x00') putpixel(x,y,smilleface[i]);

Veja que eu ignoro apenas os pontos pretos, então essa é minha cor invisível. E
mesmo se o smile tivesse outro cor além do amarelo, ela seria posta.

Na linha 33 você utiliza a condição: se a cor de smilleface for diferente de preto


eu coloco o ponto, caso contrário a cor é ignorada. É simples! :-)

No condicional switch, linha 35, você possui a estrutura que controla a posição
que o smille será colocado, já que as variáveis shift_x e shift_y são responsáveis pela
posição inicial do smille nos eixos X e Y respectivamente (veja linha 31 e 32). Para
entender melhor esse exemplo, é muito importante que você realize a execusão passo
a passo no papel, assim entenderá melhor o algoritmo por trás da listagem 1.1.

Entrando no mundo Gráfico


Dois aspectos importantes que devem ser levados em consideração quando
desenvolvemos a parte gráfica de jogos são: Resolução e Quantidade de cores. Essas
duas características são pertinentes ao modo de vídeo no qual estamos. Mas o que é
modo de vídeo afinal? Bem, esse é um assunto que vamos ver agora.

Como deve saber, a interface com o usuário nos sistemas operacionais DOS (MS-
DOS, Mac DOS, O/S DOS, etc.), é chamada de console, e nela, a única representação
gráfica que você tem disponível são caracteres. Você não pode por exemplo, desenhar
uma linha ou um triângulo, a não ser que você o faça como na figura 1.2. Esse modo
de vídeo é conhecido como console ou texto. E o menor elemento que podemos
manipular nesse modo de vídeo é um caracter.
Figura 1.2 - Desenhando um triângulo em modo texto.

Não é interessante fazer um jogo em modo texto, então, é necessário utilizarmos


um outro modo de vídeo, o modo gráfico. Nele nós temos acessos a cada pixels
individualmente, o que nos permite fazer desenhos ou exibir bitmaps.

No nosso jogo, utilizaremos o famoso modo 13h, o qual possui uma resolução de
320 colunas por 200 linhas com 256 cores. Você deve estar imaginando que pobreza,
não? E perto dos modos gráficos atuais, ele é realmente muito pobre, mas eu garanto
que dá para fazer muitas coisas nele e o mais importante é que ele é o modo de vídeo
mais fácil de se trabalhar.

O aluno deve saber que determinados jogos exigem diferentes quantidades de


memória, mas agora eu pergunto: O que afinal define a quantidade de memória de
vídeo necessária a um jogo? A resposta é a resolução e a quantidade de cores, e essas
são características relativas ao modo de vídeo que usamos.

Nas linhas 28 e 43 da listagem 1.1, você tem a função responsável por alternar
entre os modos de vídeo. Ao executar o exemplo dessa listagem, você nota que
primeiro está no modo texto, onde é exibido um texto, após isso vai para o modo
gráfico onde é visto o smile face, e então retorna ao modo console. Essa mudança
entre os modos de vídeo é o que nos permite desenvolver aplicações gráficas em DOS.
As interfaces gráficas são um bom exemplo de como é possível trabalhar em modo
gráfico.

Para entender o por quê que a resolução e o número de cores determinam a


quantidade de memória de vídeo necessária, gostaria que vocês dessem uma olhada
na figura 1.3. abaixo.

Figura 1.3 - Monitor em matriz de pixels.

Essa figura representa a relação entre o monitor e a memória de vídeo. Cada


quadrado na figura representa um pixel, o pixel na posição (0,0) do monitor é o
primeiro pixel na memória, já o pixel no monitor em (1,0) representa o segundo na
memória e assim por diante. Obs.: O ângulo admitido aqui é o mesmo aplicado ao
plano cartesiano, ou seja, (x,y) onde x representa as colunas e y as linhas.

Como cada pixel no modo gráfico 13h contém uma gama de 256 cores, podemos
afirmar que cada um deles é representado por 1 byte na memória, pois 2 elevado à
potência 8 é igual a 256, então 2 representa os dois estados binários, 1 ou 0, possíveis
em um bit e 8 é a quantidade de bits para cada pixel.

Figura 1.4 - Relação entre cor e pixel.

Na figura 1.4 você nota que cada pixel é uma cor de 8 bits ou 1 byte, assim
concluímos que basta multiplicar o número de pixels pela quantidade de bytes que ele
ocupa para sabermos a quantidade de memória de vídeo que nosso jogo vai precisar.
Deste modo, cada pixel ocupa 1 byte na memória, onde temos disponíveis 320x200
pixels, o que eqüivale a 64000 bytes ou 64KB de memória necessários, pois temos a
relação de 1:1 pixel/byte.

Não se preocupe caso não tenha entendido algum dos assuntos tratados nessa
aula, pois retomaremos eles mais detalhadamente em uma ocasião posterior.

Macetes de programação
Em muitas aulas existirão seções como esta, onde vou mostrar-lhe diversas dicas
úteis sobre programação, e muitas delas poderão ser usadas não somente em jogos
como nos seus programas diários (bem, creio que você programa todo dia não?).

Uma observação que quero fazer sobre o código da listagem 1.1 é o de que ele
não é totalmente compatível com a linguagem C, pois possui alguns recursos
existentes somente em C++. Se você conseguir um compilador C++ está tudo bem,
mas caso não, adapte algumas partes dele para rodarem em C. Um exemplo disso é o
uso da palavra reservada const, ela é um recurso provido pelo C++ e não existe na
linguagem C padrão. Você poderia substituir as duas constantes das linhas 18 e 19
(que são variáveis somente leitura), por duas constantes macros definidas pela diretiva
#define. Veja abaixo:

const byte S = (byte) 14; poderia virar #define S (byte)14

Claro que a diretiva #define deve ser posta fora do escopo de main(), e na
verdade, S não seria mais uma variável, se tornaria uma espécie de macro.

Ressalto novamente que o foco aqui é ensinar programação de jogos, não


linguagem C/C++. Por isso, se você estiver sentindo alguma dificuldade, o aconselho a
procurar um curso sobre essas linguagens. O curso de linguagem C da UFMG na área
de downloads é um bom começo. De agora em diante, vou utilizar um método
diferente, irei colocar tanto listagens em C++ quanto em Pascal, pois sei que essa é a
linguagem que a maioria das pessoas está habituada a usar. Eu pessoalmente acho o
Pascal uma linguagem pobre em recursos, e não gosto muito de sua sintaxe, mas vou
fazê-lo mesmo assim, talvez isso facilite seu progresso no curso.

Velocidade é tudo em um programa, tem pessoas que ganham dinheiro só pra


otimizar um código, mas organização também é algo fundamental, e como você deve
ter percebido, a listagem 1.1 está mal organizada, e por isso, talvez você encontrou
certa dificuldade em entendê-la. Por isso, antes de aumentar a velocidade de seu
programa ou adicionar novos recursos, é estritamente aconselhável que você organize
seu código, modularizar é um bom começo.

Na listagem 1.2, eu fiz algumas mudanças que deixaram o código mais "limpo",
isso facilita a compreensão, o que por sua vez permite adicionar novos recurso e
melhorar os existentes facilmente. A princípio pode parecer que o código ficou mais
complicado, no entanto, perceba que assim ele se parece mais com um programa!

Listagem 1.2, [Link]

0: /***************************************************\
1: |* *|
2: |* Reestruturacao do exemplo [Link] *|
3: |* *|
4: \***************************************************/
5:
6: #include <stdio.h>
7: #include <conio.h>
8: #include "videolib.h"
9:
10: #define ALTURA 10
11: #define LARGURA 10
12:
13: #define S (byte)14
14: #define N (byte)0
15:
16: typedef unsigned char byte;
17: typedef unsigned int word;
18:
19: word shift_x;
20: word shift_y;
21:
22: byte *ptsmileface;
23:
24: void limpar() {
25: word x;
26: for (x=0; x<64000; x++)
27: VGA[x] = 0x00;
28: }
29:
30: void desenhar() {
31: word x, y;
32: word i;
33: for(y=shift_y, i=0; y<(shift_y+ALTURA); y++)
34: for(x=shift_x; x<(shift_x+LARGURA); x++, i++)
35: putpixel(x,y,ptsmileface[i]);
36: }
37:
38: void movimento(char tecla) {
39: switch (tecla) {
40: case 72: --shift_y; break;
41: case 75: --shift_x; break;
42: case 77: ++shift_x; break;
43: case 80: ++shift_y; break;
44: }
45: }
46:
47: void main() {
48: /* inicializacao de dados */
49: shift_x = 100;
50: shift_y = 50;
51: char key;
52: byte smileface[101] = {N,N,N,S,S,S,S,N,N,N,
53: N,N,S,S,S,S,S,S,N,N,
54: N,S,S,S,S,S,S,S,S,N,
55: S,S,N,N,S,S,N,N,S,S,
56: S,S,N,N,S,S,N,N,S,S,
57: S,S,S,S,S,S,S,S,S,S,
58: S,S,N,S,S,S,S,N,S,S,
59: N,S,S,N,N,N,N,S,S,N,
60: N,N,S,S,S,S,S,S,N,N,
61: N,N,N,S,S,S,S,N,N,N};
62: ptsmileface = smileface;
63: clrscr();
64: printf("\n Exemplo 1 - Scrolling a yellow face!");
65: printf("\n Esse Programa demonstrara o funcionamento de um
sprite,");
66: printf("\n utilize as setas para mover o Smille Face pela tela e");
67: printf("\n para sair pressione a tecla q.");
68: printf("\n Precione qualquer tecla para proseguir...");
69: getch();
70: videomode(0x13);
71: /* loop principal */
72: do {
73: limpar();
74: desenhar();
75: key = getch();
76: movimento(key);
77: } while ((key != 'q') && (key != 'Q'));
78: getch();
79: videomode(0x03);
80: printf("\n GAME Programming \n [Link] \n
guilherme-mf@[Link] \n ;-)");
81: }
Vamos começar analisando as mudanças feitas no código. A primeira modificação
que fiz foi definir como globais algumas das variáveis que na listagem 1.1 eram locais.
Se você está emigrando do Pascal para o C/C++, deve estar acostumado a declarar um
monte de variáveis globais (aquelas que ficam dentro do escopo do begin principal e
estão "vivas" durante toda a execução do programa), contudo, alguém provavelmente
deve lhe ter dito que variáveis locais são melhores pois não saturam tanto a memória
principal. Contudo, você deve saber exatamente quando usar locais e quando usar
globais (não tenha medo em usá-las). Por exemplo, veja que as variáveis shift_x e
shift_y são usadas nas funções movimento() e desenhar(), e notou que essas duas
funções são executadas constantemente, e se você não sabe, passar parâmetros para
funções é uma operação relativamente lenta, e quanto mais argumentos você passar
para ela, mais instruções de pilha terão de ser realizadas.

Apesar do que falei, note que na função movimento() usei argumentos, mas
espera aí, você não disse pra evitar passar parâmetros à funções Guilherme? Calma,
vou explicar. Fiz isso porque é apenas um argumento para passar, e ele não exigiria
muitos ciclos de clock por instruções para ser executado , porém na função desenhar(),
optei por variáveis globais pois eram dois argumentos na lista, o que geraria maior
número de instruções (demora), pois ambos serão postos na pilha, e esse tipo de
instrução é mais lenta que um simples acesso à memória.

A dica aqui é a seguinte: evite longas listas de argumentos em funções que são
executadas continuamente, especialmente em funções que estão em loops. Você pode
até declarar a variável key como global, mas isso não faria uma diferença significativa.
O importante é você saber quando usar uma ou outra. :- )

Na linha 22, declarei um ponteiro global do tipo byte e o inicializei na linha 62, ele
irá ser usado como se fosse o próprio vetor smileface[], isso para evitar passá-lo como
argumento à função desenhar().

Não vou entrar em muitos detalhes aqui sobre a utilização de ponteiros, mas se
você é novato em C/C++, fique sabendo que: se você não sabe utilizar ponteiros, você
não sabe usar C. Sem ponteiros, você está apenas usando o Pascal com sintaxe
diferente.

Creio que o pessoal do Pascal está acostumado com a limitação dos ponteiros
usados nessa linguagem, e o pior, jogo 10 contos contigo que você nunca fez alguma
coisa realmente útil em Pascal utilizando ponteiros, acertei?

var
ponteiro1 : ^integer; {declara um ponteiro para inteiros}
var1 : integer; {declaro minha variável}
begin
ponteiro1 := @var1; { ponteiro recebe o endereço de var1}
ponteiro1^ := 10; {var1 recebe 10 através de ponteiro1}
end;

Eqüivale em C à:

{
int *ponteiro1;
int var1;
ponteiro1 = &var1; /* ponteiro recebe o endereço de var1 */
*ponteiro1 = 0x10; /* var1 recebe 16 através de ponteiro1, esqueceu o
hexadecimal? */
}

Mas se você conhece bem C/C++, tem consciência da potência dos ponteiros
nessa linguagem. Se não, aprenda com urgência! >:- (

O restante das mudanças você vê por si só, basta comparar ambas as listagens e
ver o que foi mudado. Na próxima aula vou dar mais dicas legais sobre programação.

Metendo as caras!
Chegou o momento de deixá-lo praticar. Essa é a hora em que você irá testar
seus conhecimentos tanto do que foi visto nessa aula como os que você já possui.
Esses exercícios são puro raciocínio, e o nível de dificuldade pode variar de pessoa
para pessoa, ou melhor, de programador para programador. Na próxima aula vou
disponibilizar as respostas para os exercícios, exceto para o desafio (aí você quer
demais, não?)

Exercício 1 - Modifique a listagem 1.1 ou 1.2 para que ela exiba outra figura sem ser o
Smile Face. Para isso, você deve modificar tanto a matriz smileface[101] (linha 20,
listagem 1.1), quanto as definições ALTURA e LARGURA nas linhas 4 e 5
respectivamente. Essa tarefa é simples, mas exigirá que você tenha entendido o
código. Dica: Veja que a inicialização da matriz na linha 20 representa o rosto do
boneco, basta você fazer outra figura com as constantes S e N, ou se quiser, declare
outras constantes que representem outras cores. Para isso, veja a tabela de cores
abaixo:

Cor
Valor 0 1 2 3 4 5 6 7 8 9 A B C D E F

Tabela 1.1 - Relação entre cor e pixel.

Exercício 2 - Insira um limite ao movimento do Smile Face, ou seja, não permita que
ele saia da tela como acontece nos exemplos das listagens 1.1 e 1.2. Preste atenção a
parte do código que é responsável pelo incremento e decremento das coordenadas
iniciais onde o boneco é posto. O condicional switch (linhas 35 a 40 da listagem 1.1)
modifica as variáveis shift_x e shift_y que são responsáveis pela posição inicial onde o
Smile será posto. Dica: Basta pôr um condicional if para testar se as variáveis shift_x e
shift_y são menores que a origem dos eixos X e Y (0,0) ou maiores que o limite da tela
(319,199) mais as dimensões do boneco.

Desafio - A partir do código exibido nas duas listagens, faça um programa que utilize
dois ou mais bonecos ao invés de apenas um. Os bonecos devem ser movidos por
teclas distintas.

Turn off this shit man!


Não pense que o que nós vimos nessa aula não tenha nada haver com a
programação jogos, muito pelo contrário, o trabalho que fizemos aqui lhe deu um
melhor entendimento de como o negócio funciona. Veja qualquer outro jogo e imagine
que aquele personagem é o nosso Smile Face, claro que existem muitos outros
recursos pendentes, mas o princípio é basicamente o mesmo.

Essa aula está chegando ao fim meus amigos, espero que tenham gostado e
anseio por sua opinião. Quanto mais mensagens de apoio receber, maior motivação
terei para produzir aulas novas e melhores. O próximo capítulo será mais leve, e vou
comentar mais sobre a estrutura de funcionamento dos jogos. Boa sorte e até nosso
próximo encontro!

-- Guilherme M. Ferreira --

GAME Programming™ Aulas


Aula 2 - Os Fundamentos
por Guilherme M. Ferreira

Doces recordações

Olá pessoal, bem vindos a mais uma aula. Prometo que essa aula será mais
leve, pois creio que muitos de vocês sentiram dificuldades na primeira, mas fiz
propositadamente. Nesta aqui, vou abordar diversos assuntos. Começarei com
alguns conceitos de programação C que lhe ajudarão a entender melhor o processo,
contudo, como havia mencionado anteriormente, não vou ensinar C, apenas dar
uma retomada em conceitos importantes.

Eu sei que tem um monte de erros ortográficos nessa aula, devido a eu não ter
passado o corretor nela. Mas não liguem, o importânte é vocês entenderem.

A resposta aos exercícios da aula anterior estão na listagem 2.1, não se


preocupe, pois vou explicá-la em detalhes oportunamente. Caso não se lembre,
ambos pediam para modificar o exemplo da listagem 1.2, em caso de dúvida dêem
uma olhada na aula1. Baixe o executável da listagem 2.1 ([Link]).

Listagem 2.1, [Link]

0: /****************************************************\
1: |* *|
2: |* [Link] *|
3: |* *|
4: \****************************************************/
5:
6: #include <stdio.h>
7: #include <conio.h>
8: #include "videolib.h"
9:
10: #define ALTURA 12
11: #define LARGURA 13
12:
13: #define S (byte)7
14: #define V (byte)11
15: #define P (byte)4
16: #define G (byte)14
17: #define N (byte)0
18:
19: typedef unsigned char byte;
20: typedef unsigned int word;
21:
22: word shift_x;
23: word shift_y;
24:
25: byte *ptspacenave;
26:
27: void limpar() {
28: word x;
29: for (x=0; x<64000; x++)
30: VGA[x] = 0x00;
31: }
32:
33: void desenhar() {
34: word x, y;
35: word i;
36: for(y=shift_y, i=0; y<(shift_y+ALTURA); y++)
37: for(x=shift_x; x<(shift_x+LARGURA); x++, i++)
38: putpixel(x,y,ptspacenave[i]);
39: }
40:
41: void movimento(char tecla) {
42: switch (tecla) {
43: case 72: if (shift_y > 0 )
44: --shift_y; break; // cima
45: case 75: if (shift_x > 0)
46: --shift_x; break; // esquerda
47: case 77: if (shift_x+LARGURA < 319)
48: ++shift_x; break; // direita
49: case 80: if (shift_y+ALTURA < 199)
50: ++shift_y; break; // baixo
51: }
52: }
53:
54: void main() {
55: /* inicializacao de dados */
56: shift_x = 100;
57: shift_y = 50;
58: char key;
59: byte spacenave[157] = {N,N,N,N,N,N,N,N,N,N,N,N,N,
60: N,N,N,N,N,N,S,N,N,N,N,N,N,
61: N,N,S,N,N,S,S,S,N,N,S,N,N,
62: N,S,S,S,N,S,V,S,N,S,S,S,N,
63: N,S,S,S,S,S,V,S,S,S,S,S,N,
64: S,S,S,S,S,S,V,S,S,S,S,S,S,
65: S,S,S,S,S,S,S,S,S,S,S,S,S,
66: N,S,S,S,S,S,S,S,S,S,S,S,N,
67: N,S,S,S,N,S,S,S,N,S,S,S,N,
68: N,N,S,N,N,P,G,P,N,N,S,N,N,
69: N,N,S,N,N,N,P,N,N,N,S,N,N,
70: N,N,N,N,N,N,N,N,N,N,N,N,N};
71: ptspacenave = spacenave;
72: clrscr();
73: printf("\n Exemplo 1 - Scrolling a yellow face!");
74: printf("\n Esse Programa demonstrara o funcionamento de um
sprite,");
75: printf("\n utilize as setas para mover o Smille Face pela tela
e");
76: printf("\n para sair pressione a tecla q.");
77: printf("\n Precione qualquer tecla para proseguir...");
78: getch();
79: videomode(0x13);
80: /* loop principal */
81: do {
82: limpar();
83: desenhar();
84: key = getch();
85: movimento(key);
86: } while ((key != 'q') && (key != 'Q'));
87: getch();
88: videomode(0x03);
89: printf("\n GAME Programming \n [Link] \n
guilherme-mf@[Link] \n ;-)");
90: }

Princípios da programação em C

O "C" é uma linguagem compilada de alto nível, apesar de muitos autores a


considerarem de médio nível (principalmente pelo poder de seus ponteiros e
operadores de manipulação de bit).

Muitos programadores sentem dificuldade ao trabalhar com C/C++, talvez por


sua sintaxe "exótica" ou talvez porque se sintam meio perdidos em meio a seus
operadores. Não querendo dizer nada, mas C é uma linguagem indispensável a
qualquer profissional que pretenda seguir uma carreira promissora na área de
programação, sendo que esta é a base para diversas outras linguagens existentes
atualmente no mercado, como por exemplo Java, C# e o próprio C++. Sem falar que
a maioria dos S.O.s e programas são escritos nela.

Concordo que não é uma linguagem muito fácil no começo, no entanto, após
algum tempo de trabalho com ela, você acaba a amando. Meu intuito agora é
mostrar-lhe o quão fácil e poderosa é essa linguagem de programação, que se
tornou minha preferida para trabalhar.

O ideal seria que o leitor soubesse programar em Pascal, pois esta é uma das
poucas linguagens que possui aspectos comuns ao C, não é derivada dela como as
citadas acima e todos que aprendem a programar começam por ela. Chega de
conversa, vamos começar!

O Programa mais simples do Universo


A melhor maneira de começar a entender uma linguagem ou programa é vê-
los em ação, certo? Então, vou apresentá-los o programa mais simples em C que
contenha todos os itens típicos desta linguagem.

Listagem 2.2, [Link]

#include <stdio.h>

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


{
char mensagem[21] = "sou um programa em C";
printf("\n Oi, %s",mensagem);
return(0);
}

Analisando o programa acima de uma forma simplificada, na primeira linha nós


temos a diretiva de complilação #include, a qual instrui o compilador a incluir
arquivos externos, que neste caso é o stdio.h onde está declarada a função printf().
Na segunda linha, temos o protótipo da função main, pela qual se inicia o fluxo
dos programas em C. Não se preocupe pois irei explicá-la mais a frente. Na terceira
linha, temos a declaração de um vetor de 21 caracteres, logo após, na quarta, temos
a chamada à função printf que tem por fim, neste caso, imprimir dados na tela. Por
último, na quinta linha, a função return (não é exatamente uma função) que como
deve ter deduzido, serve para retornar um valor a função que a chamou. E como
quem chamou a função main foi o S.O., ela retornará esse valor a ele.
Declarando Dados - Variáveis e Contantes
Para declarar variáveis em C, é seguido o seguinte esquema:
tipo_de_dado identificador;
Onde tipo_de_dado pode ser um dos tipos de dados existentes no C ou
qualquer outro definido pelo programador. Consulte a tabela 2.1 para saber quais os
tipos de dados predefinidos existentes na linguagem C.
No exemplo acima, char mensagem[21] é a declaração de um vetor de 21
caracteres, onde: char é a palavra reservada que define o tipo de dado da variável;
mensagem é o identificador da variável; e [21] é a especificação da quantidade de
elementos que constituem o vetor. A propósito, a indexação em C começa sempre
em 0, ou seja, no vetor mensagem você terá os indice de mensagem[0],
mensagem[1],... até mensagem [20];
Número de Intervalo
Tipo
bits Bytes Início Fim
char 8 1 -128 127
unsigned char 8 1 0 255
signed char 8 1 -128 127
int 16 2 -32.768 32.767
unsigned int 16 2 0 65.535
signed int 16 2 -32.768 32.767
short int 16 2 -32.768 32.767
unsigned short int 16 2 0 65.535
signed short int 16 2 -32.768 32.767
long int 32 4 -[Link] [Link]
signed long int 32 4 -[Link] [Link]
unsigned long int 32 4 0 [Link]
float 32 4 3,4E-38 3.4E+38
double 64 8 1,7E-308 1,7E+308
long double 80 10 3,4E-4932 3,4E+4932
Tabela 2.1 - Dados predefinidos na linguagem C.
É importante ressaltar que a declaração de constantes através da palavra
reservada const é exclusiva da linguagem C++ e não é definida na linguagem ANSI
C (padrão), por isso, verifique sua validade junto a seu compilador, e caso ele não a
suporte, a substitua pela diretiva de pre-compilação #[Link].: ao invés de
"const num = 10;" use "#define num 10", sem as aspas é claro.
Modeladores (casts)
Esse é um recurso que utilizo exaustivamente em meus exemplos, e apesar de
ser de fácil compreensão, não poderia deixar de mencioná-lo aqui. Sua sintaxe é a
seguinte:
variável1 = (modelador) variável2;
Onde modelador pode ser qualquer um dos tipos pre definidos na linguagem C,
veja a tabela 2.1.
A função primordial dos modeladores é modelarem um tipo de dado em outro,
ou seja, forçar uma conversão entre um tipo e outro. Acerca sobre eles, advirto que
não se ganha precisão ao converter de um tipo a outro, pelo contrário, caso haja
uma conversão de um dado com precisão alta para um com baixa, há perda da
mesma. Exemplo:

int nNum = 20;


float fNum = 3.1451;
int Num;

Num = nNum; /* Num = 20 */


nNum = (int) fNum; /* houve perda de precisão, nNum = 3
*/
fNum = (float) Num; /* não houve ganho de pecisão, fNum =
20.00 */

Simples não? Todavia, tenham consciência ao utilizá-los, pois algumas vezes


eles atrapalham mais do que ajudam.
Declaração de Funções
A estrutura básica de uma função em C/C++ é a seguinte:
tipo_de dado nome_da_funcão (lista_de_argumentos)
{
comandos;
}
Onde tipo_de_dado pode ser qualquer um dos existentes na tabela 2.1 e mais
o tipo void, o qual indica que a função não retornará valor algum, é análogo às
procedures em Pascal. E já que estou falando em Pascal, vou fazer a comparação
entre duas funções em ambas as linguagens, então, dê uma olhada na tabela 2.2.
C/C++ Pascal
int soma (int num1, int num2) function soma (num1, num2 : integer);
{ begin
return (num1 + num2); soma := num1 + num2;
} end;

void imprimir
void imprimir () procedure imprimir;
(void)
{ begin
{
cout<<"oi"; write('ola');
printf("oi");
} end;
}
Tabela 2.2 - Comparando funções em C/C++ e Pascal.
Obs.: A declaração de uma função sem argumentos é diferente em C e C++. Pois em C,
uma função sem qualquer coisa entre parênteses (Ex.: funcao()) é uma função com
argumentos infinitos, já em C++, essa mesma declaração é a de uma função que não recebe
parâmetros. Deste modo, se você quiser declarar uma função em C que não possua
argumentos, você deve fazê-la do seguinte modo: funcao(void); E em C++ tanto faz um quanto
o outro. Agora, para chamar uma função sem parâmetros, é estritamente necessário que você
utilize os parênteses. Ex.: getch() --- chama uma função que não possui argumentos.
Todo o programa escrito em C é dividido em módulos (ou funções se preferir), todavia,
uma função é estritamente necessária para todo e qualquer programa desenvolvido nela, pois
ela marca o ponto de partida do mesmo. Essa função chama-se main, e possui o protótipo
esquematizado abaixo:
int main (int argc, char argv[][])
Se fosse traduzia para Pascal, ficaria mais ou menos assim:
function main (argc:integer; argv: array [0..(argc-1)] of strings ) : integer;
A palavra reservada int define que tipo de dado a função retorna, neste caso
um inteiro. Outro ponto que lhe chamo a atenção, é para o fato da linguagem C ser
sensível a maiúsculas e minúsculas (case sensitive), deste modo, a palavra main é
diferente Main, MAIN, mAin, e assim por diante.
Entre os parênteses, estão a lista de parâmetros que serão passados à função.
O primeiro argumento da lista (argc) especifica a quantidade de argumentos
(argument count) passados à função na linha de comando, e por isso é um inteiro.
Já o segundo argumento (argv - argument value) é uma matriz (vetor, array)
bidimensional de caracteres, ou de uma forma mais simples, ele é uma matriz de
vetores de caracteres. E lembre-se: esse é um programa para DOS, e caso não
saiba, no DOS você executa programas da seguinte maneira: nome_do_programa
argumentos. Ex.: dir /p;
Coloquei abaixo, um esquema onde você verá o valor destes dois argumentos
ao chamar o programa DIR do MS-DOS:

Valor
Variável

argc 3

argv[0] "dir"

argv[1] "/p"

argv[2] "/w"

Figura 2.1 - Chamando o programa DIR com dois argumentos.


Estruturas de Controle de Fluxo
Essa é a parte mais fácil do C, alem do mais, outras linguagens possuem
estruturas parecidas, por isso, serei breve.
if ... else : estrutura condicional, se a condição for verdadeira, o comando é
executado, caso contrário, é executado o comando abiaxo do else. Sintaxe:
if (condição)
comandos, se condição verdadeira
else
comandos, se condição falsa
Veja um exemplo abaixo:
if (num > 10) {
printf("\n Num é maior que 10");
}
else {
printf("\n Num não é maior que 10");
}
switch: estrutura condicional, testa um valor dentre diversas alternativas.
switch (valor) {
valor1 : comandos, se "valor" for igual a "valor1"
valor2 : comandos, se "valor" for igual a "valor2"
valor3 : comandos, se "valor" for igual a "valor3"
}
Veja o exemplo abaixo:
switch (num) {
1 : printf(\n Num = 1"); break;
2 : printf(\n Num = 2"); break;
3 : printf(\n Num = 3"); break;
}
Obs.: utilize sempre a palavra reservada break após cada condição, caso
contrário, o comando switch será executado infinitamente.
for: loop, ele executará o comando abaixo até que sua condição seja falsa. Sintaxe:
for (inicialização; condição; incremento)
comandos, enquanto condição for verdadeira
A inicialização será executada apenas uma vez ao iniciar o loop. Em seguida a
condição será testada, se for verdadeira, os comandos serão executados, caso
contrário o loop terminará. Após os comandos serem executados, acontece o
incremento. Chamo sua atenção para o seguinte fato: Esse é o mais poderoso loop
for que existe, pois é possível omitir qualquer um dos itens (inicialização, condição e
incremento), e também colocar qualquer expressão em um deles , contudo, saiba o
que você está fazendo. Exemplo? ok, abaixo:
for (num = 0; num < 10; num++) {
printf("\n Num = %d",num);
}
while e do ... while: estrutura de loop. Os comando serão executados enquanto a
condição for verdadeira. A diferença entre ambos é que o do..while executa os
comandos pelo menos uma vez, mesmo sendo falsa a condição. Dê uma olhada e
você já vai perceber o porquê.
while (condição)
comandos, enquanto condição for verdadeira
Tá bom! Está logo abaixo:
while (num > 10) {
printf(\n Num = %d",num);
num--;
}
Agora o do...while.
do
comandos, enquanto condição for verdadeira
while (condição)
Ok, já sei. O exemplo, o exemplo, o exemplo...:
do {
printf(\n Num = %d",num);
num--;
} while (num > 10)
Existem algumas outras, contudo, estas são as mais importantes. No mais, se
você quiser, consulte o tutorial de C da UFMG disponível na área de downloads, lá
elas estão explicadas mais detalhadamente.
Ponteiros (Apontadores)
Ponteiros ou apontadores (não sei quem foi o infeliz quem pôs este nome), é
uma das características que mais gosto em C/C++ (Assembly também tem, e
adivinha qual a minha segunda linguagem favorita?). Quero que você preste muita
atenção nesse tópico, pois ele é crucialmente importante nesta linguagem.
Sabem, muitos programadores acham que ponteiros são "coisa do demônio",
por este motivo, antes de começarmos gostaria que você, leitor, esqueça tudo que
já ouviu falar sobre ponteiros em toda sua vida, isso mesmo, "delete" esse
conteúdo! E agora que você está 0 km, vamos começar.
Muitos de vocês devem estar perguntando-se: -- para que diabos eu vou utilzar
esta porr...! E eu lhes responderei que porr... é o cacet..., então, nós ficaríamos
discutindo essa merd... até nos cansarmos. Contudo, esta não é minha intenção, por
isso, grave apenas uma coisa: Ponteiros são o poder do C (lembrem-se: Ponteiros
rimam com Power Point, que rima com Power Rangers, que rima com Poder), deste
modo, saber usá-los é incontestavelmente prova de C-abedoria. O importante é que
eles nos permitem realizar tarefas praticamente impossíveis de serem efetuadas
com variáveis.
Em suma, ponteiros são variáveis que "guardam" endereços de memória.
Podem ser tanto endereços de outras variáveis ou constantes, até o endereço de
outros ponteiros. Abaixo, está o esquema de declaração de três variáveis, sendo
uma delas um ponteiro.
int Var1 = 20;
int Var2;
int *ptVar1;
Nas duas primeiras linhas, temos a declaração de duas variáveis do tipo
inteiro, Var1 e Var2, sendo a primeira delas inicializada com o valor 20; em seguida
temos a declaração de um ponteiro. Para tal, é necessário seguir a seguinte regra:
tipo_de_dado* indificador
Então, a única diferença entre a declaração de uma variável e um ponteiro, é a
presença do operador de referência (*). Contudo, tenha em mente uma coisa: você
está declarando um ponteiro para inteiros, e não um ponteiro do tipo inteiro. E
isso é muito importante, sendo que esse ponteiro funcionará corretamente apenas
com variáveis do tipo inteiro e não com qualquer outra. Para compreender a idéia,
veja o trecho de código abaixo:
Var2 = Var1;
ptVar1 = &Var1;
O operador de endereço (&) é utilizado para atribuir o endereço físico de
memória de uma variável a um ponteiro. Contudo, se você tivesse dois ponteiros,
poderia atribuir seus valores normalmente, como se fossem duas variáveis, isso
porque eles são do mesmo tipo. Na figura 2.2, está um esquema de memória
representando o resultado do código acima..
Figura 2.2 - Layout de memória no uso de ponteiros.
Na figura acima, note que: a variável Var1 foi inicializada com o valor 20, seu
endereço na memória é 102 (em azul); e como foi visto no código anterior, Var2
recebeu Var1, e ptVar1 recebeu o endereço de Var1. Concluímos que o conteúdo de
Var2 é o mesmo de Var1, e o conteúdo de ptVar1 é o endereço de Var1. Contudo,
tenha em mente que ponteiros possuem endereço, como toda variável, tanto que,
em C é possível declarar ponteiros para ponteiros, ou até ponteiros para ponteiros
de ponteiros, e assim por diante. Para isso, basta acrescentar mais operadores de
referência ao declará-los. Por exemplo:
int Num;
int *ptNum;
int **ptptNum;
ptNum = &Num;
ptptNum = &ptNum;
Acima, estão declarados uma variável (Num), um ponteiro para aquela variável
(ptNum) e um ponteiro para o ponteiro da variável (ptptNum). Resumindo, para cada
nível de ponteiro, você terá que adicionar um operador (*).
Por fim, para utilizar um ponteiro como se fosse a variável por ele apontada,
utiliza o operador de referência (*) antes do ponteiro. Utilizando o código acima, se
fizéssemos:
Num = 20; /* Num é igua a 20 */
ptNum = &Num; /* ptNum aponta para Num */
*ptNum = 10; /* Num é igual a 10 */
O básico é isso, existem muitos outros recursos que veremos mais a frente. :
-)
Variáveis Multidimensionais (Vetoriais ou Matizes)
Em C, vetores e ponteiros tem uma relação muito íntima, deste modo, é bom
você ter compreendido bem a seção de ponteiros para prossegir com matrizes.
Agora, vou dizer uma coisa que poderá confundi-los um pouco, mas logo depois
vocês irão entender. Em C, matrizes são ponteiros constantes.
A sintaxe básica da declaração de um vetor é a seguinte:
tipo_de_dado indificador [qtde_itens] [qtde_itens]..
Veja o exemplo abaixo e logo depois eu o explicarei:
char Palavra[5] = {'a','b','c','d'}; /* pode ser "abcd" também */
char *ptLetra;
char Letra;
ptLetra = Palavra;
ptLetra = &Palavra[0];
ptLetra = &(*Palavra);
Obs.: Quando você for inicializar uma matriz, como a acima, tenha em mente
que: para vetores do tipo char, usa-se chaves "{" e "}", e os valores entre aspas
simples ('), ou uma palavra entre aspas duplas (") como por exemplo "abcd"; As
chaves são usadas em todos os outros vetores também. : - )
Todas as atribuições acima tem o mesmo efeito (são a mesma coisa), e mais,
note a primeira delas, levando em conta que ponteiros podem receber o conteúdo
apenas de outros ponteiros do mesmo tipo, conclui-se que o vetor unidimensional
Palavra, sem indice (Palavra e não Palavra[]), é um ponteiro e indica a posição de
memória onde inicia o vetor. No entanto, ele é um vetor constante, ou seja, não é possível
modificar seu valor. Abaixo, estão mais atribuições válidas em C:
Letra = *ptLetra; // Letra = 'a'
Letra = ptLetra++; // Letra = 'b'
Letra = Palavra[2]; // Letra = 'c', indice começa em 0, lembra?
Letra = *(Palavra+3); // Letra = 'd'
Passagem de Parâmetros à Funções
Agora, vou introduzí-los (no bom sentido da palavra) um conceito muito
importante, a passagem de argumentos (ou parâmetros, como quiser) à funções.
Apesar de fáceis, muitas pessoas se confundem com os conceitos de passagem por
valor e por referência. Mas antes, é necessário que você tenha entendi bem o
funcionamento dos ponteiros. Se sim, prossiga.
Listagem 2.3, [Link]

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

void P_Valor (int Num1, int Num2)


{
Num1 = 10;
Num2 = 20;
}

void P_Referencia_C (int *Num1, int *Num2)


{
*Num1 = 1;
*Num2 = 2;
}

void P_Referencia_Cpp (int &Num1, int &Num2)


{
Num1 = 16;
Num2 = 32;
}

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


{
int n1 = 3;
int n2 = 4;
int n3;

P_Valor(n1,n2); /* Passagem de parâmetros por


Valor */
n3 = n1 + n2; /* n3 = 3 + 4 */

P_Referencia_C(&n1,&n2); /* Note que eu tive que passar os


endereços de n1 e n2*/
n3 = n1 + n2; /* n3 = 1 + 2 */

P_Referencia_Cpp(n1,n2); /* Bastou passar as variáveis,


como uma chamada comum*/
n3 = n1 + n2; /* n3 = 16 + 32 */

return(0);
}

Como deve ter visto, a primeira função, P_Valor, realiza uma passagem de
parâmetros por valor, e por isso, apenas os valores de n1 e n2 são passados,
deixando-as intactas, ou seja, continuam com seus valores iniciais. Contudo, na
chamada à função P_Referencia_C, são passados os endereços das variáveis n1 e
n2, sendo assim, os ponteiros Num1 e Num2 nesta função passam a referir-se à
estas variáveis respectivamente. Pois caso não lembre-se, quando um ponteiro
possui o operador de referência (*):
*Num1 = 1;
ele passa a ser encarado como a própria variável para qual aponta. Então, ao passar
os endereços de n1 e n2 com o operador de endereço (&):
P_Referencia_C(&n1,&n2);
os ponteiros Num1 e Num2 passam a ser encarados como estas próprias variáveis,
por isso chama-se passagem de valores por referência.
Em C, a única maneira de realizar um passagem por referência é utilizando
ponteiros, por esta razão, é de suma importância que o leitor tenha um bom domínio
sobre eles. No entanto, em C++ é possível realizar essa mesma tarefa dispensando
o uso de ponteiros. Para isso, nós utilizamos o operador de referência (&) ao
declarar a função:
void P_Referencia_Cpp (int &Num1, int &Num2)
então, Num1 e Num2 passam a "ser" n1 e n2 (elas não são ponteiros certo, elas
passam a ser referência a essas duas), por isso, se realizarmos qualquer atribuição
aquelas variáveis, seria como estivéssemos fazendo com essas.
Estruturas de Dados
O princípio de estruturas é exorbitantemente simples, além disso, é a base
para o compreendimento das classes e objetos, que vos mostrarei a seguir (hum,
"vos mostrarei", o cara é bom não?). Todavia, não se enganem, sendo que, apesar
de trivial, este é um recurso muito útil.
Para simplificar, estruturas são nada mais nada menos que um conjunto de
dados (variáveis), atribuídos a uma estrutura comum, ou se preferir, são variáveis
agrupadas em uma estrutura comum. Veja o exemplo abaixo:
Listagem 2.4, [Link]

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

struct SFicha
{
char nome[80];
char endereco[100];
int idade;
};

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


{
SFicha Ficha;
char nome[] = "Guilherme M. Ferreira";
char endereco[] = "Hawaii 90";
strcpy([Link],nome); /* [Link] = nome; */
strcpy([Link],endereco); /* [Link] =
endereco; */
[Link] = 19;
printf("\n nome: %s",[Link]);
printf("\n endereço: %s",[Link]);
printf("\n idade: %d",[Link]);
return(0);
}

Analisando o exemplo acima, vemos basicamente que: para criar uma


estrutura, coloca-se a palavra reservada struct e logo após o nome da estrutura;
Agora, preste atenção que não é possível acessar a estrutura recém criada, pois ela
é um tipo de dado definido por você, tal qual a palavra int, deste modo, é
necessário que você crie uma instância dessa estrutura, para isso, utilize apenas o
esquema padrão de declaração de variáveis:
tipo_de_dado identificador;
No qual tipo_de_dado é o nome do modelo da estrutura o qual você criou, e
identificador será sua estrutura variável por assim dizer. Agora, para acessar os
membros (guarde bem essa definição) desta estrutura é simples, basta colocar-se o
nome da instância da estrutura seguida pelo operador ponto (.), e especificar o
membro da estrutura o qual deseja acessar, como:
[Link]
Essa forma também serve para cessar membros de classes, por isso, quem usa
Pascal deve reconhecer esse operador e esta forma de acesso. No entando, existem
diversas formas de acessar esse mesmo membro, mas este assunto tratarei
juntamente com classes e objetos.

C++ e Programação Orientada a Objetos


Apesar deste assunto estar fora do escopo da linguagem C, ele é a essência do
C++. Além do mais, creio que muitos de vocês não sabem discernir entre Classe de
Objetos, e não se espantem, pois eu mesmo não o sabia. Deste modo, esse capítulo
é no mínimo interessante e lhes aconselho a lê-lo.
Para quem alguma vez já programou em Delphi (todo mundo, eu creio!), irá
compreender muito melhor este conceito, devido ao fato de que esta ferramenta de
programação utiliza exaustivamente o recurso de OOP (Object-Oriented
Programming), deste modo, entendê-lo através do Delphi seria uma maneira fácil.
Como os compiladores disponíveis em nossa área de downloads não tem
suporte ao C++ ANSI, que é o padrão utilizado atualmente, aconselho aos
interessados baixarem Command Line Tools da Borland, que considero o melhor
compilador C++ gratuíto. Se tiverem dúvidas quanto a seu uso, fiz um artigo
ensinando a utilizar essa ferramenta. Excelente!
Classes e Objetos
A base de todo o design de OOP está no conceito de classes e objetos, ele só
parece complicado, pois na verdade não o é. Ambos possuem conceitos
semelhantes, todavia, classe é o projeto do objeto. Vou resumir suas definições para
melhor entendê-las.
Objeto: é a chave do entendimento da programação orientada a objetos. Ele é
a representação de objetos reais ou abstratos em forma de peças de software.
Imagine um carro, ele é um objeto, possui propriedades (cor, tamanho, velocidade)
e ações (aceleração, torque, troca de marchas). Agora, você é o objeto pessoa, que
utiliza o objeto carro, então, através de mensagens que você passa ao carro (como
pisar no acelerador), é possível fazer com que o objeto carro realize ações
(aumentar a velocidade no caso), que em OOP são chamado métodos. Contudo,
você não sabe a maneira pela qual o objeto carro realiza estas fuções, e mais, o
objeto carro pode ser modificado (colocar outro motor) e isso não interferirá em seu
funcionamento (Não leve esse exemplo ao "pé da letra").
As propriedades de um objeto são representadas por variáveis, da mesma
forma que são feitos nas estruturas. Já métodos ou ações, são os membros do objeto
que lhe conferem funcionabilidade. E são representados por funções.
Os objetos tem a propriedade encapsularem seus códigos. Isto significa que
eles comunicam-se, prestam serviços e permitem a modificação de suas
propriedades, contudo, quem os utiliza não está a par do processo interno do objeto,
sendo que este só permite a modificação de alguns métodos e propriedades.
Classe: apesar de parecidos, classes são coisas diferentes de objetos. Bem,
todo o objeto é baseado numa classe, e ela é quem define como serão os objetos
baseados nela. Seguindo o exemplo anterior, imagine novamente seu carro, um Golf
2.0, ele é um objeto correto? Contudo, ele é baseado na classe Automóvel, a qual
possui especificada as seguintes propriedades: quantidade de portas, cor,
velocidade, tipo do motor, e assim por diante; E possui também métodos (ou ações):
aumentar e diminuir velocidade, freiar, buzinar, etc. No entanto, estas são
características comuns a todos os automóveis, e todos os carros (objetos) baseados
na classe automóvel possuem essas propriendades e métodos.
Na linguagem técnica, dizemos que um objeto é uma instância de uma classe.
No exemplo representado na figura 2.3, o objeto Retângulo1 é uma instância da
classe Retângulo, ele possui todas as propriedades e métodos associados à classe,
contudo, ela é apenas o molde, e cada instância desta classe (objeto) possui suas
propriedades particulares.

Figura 2.3 - Exemplo de objeto e classe.


Esse modelo de programação possui diversas vantagens em relação à
programação procedural, como por exemplo, a modularidade. Esta nos permite
modificar o objeto (código fonte) sem que isso prejudique a aplicação ou outros
objetos que o utilizem. Isto se deve ao fato de que objetos se comunicam através de
mensagens, e a maneira como as mensagens são processadas é responsabilidade
apenas do objeto que as recebe. Na listagem abaixo, você verá um exemplo típico
de programação orientada a objetos, e usarei esse exemplo para o restante dos
tópicos:
Listagem 2.5.1, Classe01.h
0: class C3DRect
1: {
2: private:
3: int Comprimento;
4: int Altura;
5: int Profundidade;
6:
7: public:
8: C3DRect();
9: void NovaMedida (int, int);
10: void NovaMedida (int, int, int);
11: float Area();
12: float Volume();
13: };
14:
15: C3DRect::C3DRect()
16: {
17: Comprimento = 1;
18: Altura = 1;
19: Profundidade = 1;
20: }
21:
22: void C3DRect::NovaMedida (int x, int y)
23: {
24: Comprimento = x;
25: Altura = y;
26: }
27:
28: void C3DRect::NovaMedida (int x, int y, int z)
29: {
30: Comprimento = x;
31: Altura = y;
32: Profundidade = z;
33: }
34:
35: float C3DRect::Area()
36: {
37: return (Comprimento * Altura);
38: }
39:
40: float C3DRect::Volume()
41: {
42: return (Area()*Profundidade);
43: }
Listagem 2.5.2, [Link]
0: #include <iostream.h>
1: #include "Classe01.h"
2:
3: int main(int argc, char *argv[])
4: {
5: C3DRect Ret;
6: C3DRect *ptRet;
7:
8: ptRet = &Ret;
9:
10: cout << "\n \n Medidas iniciais" << endl;
11: cout << "\t Area = " << [Link]();
12: cout << "\t Volume = "<< [Link]();
13:
14: [Link](6,10,2);
15: cout << "\n Novas Medidas" << endl;
16: cout << "\t Area = " << ptRet->Area();
17: cout << "\t Volume = "<< (*ptRet).Volume();
18:
19: return(0);
20: }
Conheca agora outro operador, o de seleção de membros (->), que
basicamente, ele equivale à expressão:
(*ponteiro_classe).membro
Na linha 8, você vê que o ponteiro ptRet recebeu o endereço do objeto Ret, no
entanto, se você quiser acessar um membro de Ret através daquele ponteiro, você
poderá ou usar a expressão acima, ou senão o operador de seleção de membro (->).
Caso não tenha entendido, acompanhe meu raciocínio: se eu tiver uma variável X
apontada por um ponteiro Y, então *Y = X certo? Agora, se *ptRet = Ret, então
(*ptRet).membro = [Link]. Entendeu? deste modo, o operador -> substitui a
expressão (*ptRet).membro por ptRet->membro.
Mensagens
Os objetos se comunicam através de mensagens, que são nada mais alem de
propriedades e métodos. Eles permitem que a aplicação e outros objetos interajam
consigo. Por exemplo, na listagem 2.5.2, o comando [Link](6,10,2) (linha
14) é uma mensagem enviada pelo programa ao objeto Ret, e o comando [Link]()
na linha 11 é uma mensagem que o objeto envia ao programa.
Herança
Herança é a capacidade de uma classe herdar características de uma outra.
Voltemos a nosso exemplo do carro, a classe Automóvel herdou características da
Veículos Motorizados, a qual basearam-se as classes Moto, Caminhão, Ônibus, etc.
Todas elas possuem aspectos comuns a sua antecessora (Veículos Motorizados),
contudo, são diferentes entre si.
Há muita coisa a ser vista ainda, porém, não vou saturá-los! >:-0

Alocação Dinâmica de Memória em C e C++


Agora que você está fera em C/C++, nós vamos dar um passo importante em
nosso tutorial. Nós não utilizaremos tão breve o que vamos aprender aqui, contudo,
isto é o que vai separá-lo da programação comum da programação de verdade.
Alocação dinâmica consiste em uma técnica para obter mais memória em
tempo de execução, pois quando o programa é executado, apenas a quantidade de
dados declarada no código é alocada. Não entendeu? Imagine então a seguinte
situação: você quer um vetor de caracteres para guardar nomes, mas algumas
pessoas possuem nomes relativamente curtos como João da Silva, em oposição a
outros mais extensos como Dom Pedro de Alcântara Francisco Antônio João Carlos
Xavier de Paula e lá vai bomba. Agora, declarando esse vetor com um tamanho
definido haverá desperdício de espaço nos nomes pequenos e truncamento nos
grandes. Qual a solução? Definir o número de elementos no vetor de acordo com a
quantidade exigida.
Em síntese, alocação dinâmica é uma maneira de criar variáveis em tempo de
execução, ou seja, alocar memória para novas variáveis.
Há uma diferença entre C e C++ no que diz respeito à maneira como
efetuamos essa tarefa, sendo que em C++ é possível utilizar os métodos que
usamos em C. Neste, dispomos de funções (malloc, calloc, etc) para realizar a
alocação dinâmica, já em C++ existem operadores (new e delete) implementados
na própria linguagem.
Alocação Dinâmica em C
Como mencionei anteriormente, para alocarmos memória dinamicamente em
C, dispomos de funções, as quais estão declaradas na biblioteca stdlib.h.
Tanto as funções malloc quanto calloc servem para alocarmos memória, no
entanto, a primeira é utilizada preferencialmente para variáveis comuns, enquanto a
segunda para alocação de vetores. Contudo, ambas podem ser utilizadas tanto para
vetores como para variáveis simples.
A função malloc possui o seguinte protótipo:
void* malloc(unsigned int numBytes)
Ela simplesmente aloca a número de bytes definidos em numBytes e retorna
um ponteiro para a nova variável alocada. Algo mais ou menos assim:
int* ptNum;
ptNum = (int*) malloc(sizeof(int));
veja que o ponteiro ptNum aponta para a nova variável criada, ou melhor, para um
novo espaço em memória com o tamanho de um inteiro (em geral 4 bytes), para tal
utilizamos a função sizeof()que retorna o tamanho (em bytes) do tipo de dado
passado à ela como argumento. No entanto, geralmente não tem sentido alocar
memória para apenas uma variável, deste modo, a função primordial desse recurso
é reservar memória à matrizes. Sendo assim, o código anterior deverá ser rescrito
da seguinte forma:
int* ptNum;
ptNum = (int*) malloc(numElementos*sizeof(int));
Onde ptNum apontará para o primeiro elemento de um vetor unidimensional
de numElementos, cada um com o tamanho de um inteiro.
Obs.: sempre preste atenção no tipo do seu ponteiro, pois caso você reserve
memória para apenas uma variável, mas o tipo dela seja de um tamanho superior ao
do ponteiro, você estará alocando um vetor. Por exemplo:
char* ptNum;
ptNum = (char*) malloc(sizeof(int));
assumindo que o tamanho de um caracter seja de um(1) byte e o de um inteiro
quatro(4) bytes, o código acima aloca 4 bytes de memória, desta forma, seguindo a
aritmética dos ponteiros, ptNum apontará para o primeiro byte de um vetor de
quatro elementos.
A função calloc funciona de maneira análoga, sendo seu protótipo o seguinte:
void* calloc(unsigned int numElementos, unsigned int numBytes)
ela irá uma quantidade de memória igual a numElementos*numBytes onde cada um
dos numElementos terá numBytes de tamanho, e um ponteiro do tipo void para o
primeiro elemento será retornado pela função.
E se mesmo assim você precisar de mais memória, a função realloc() poderá
reservar.
void* realloc(void* pt, unsigned int novo_numBytes)
Passando a ela o ponteiro para o bloco (pt) e o novo tamanho desse bloco
(novo_numBytes), que poderá ser maior ou menor que o apontado por pt. Todo o
conteúdo existente no antigo bloco será copiado para o novo, caso este seja maior,
ou até o limite do novo bloco, se esse for menor.
Por fim, a função free() serve para liberar a memória alocada, para tal, deve-
se passar o ponteiro como argumento a ela:
void free(void* pt)
E pelo fato dela ter guardado em uma tabela interna a quantidade de bytes
alocados, você não precisa informá-la previamente a quantidade de memória que
você reservou, pois ela já sabe.

Listagem 2.6, [Link]


1: /* Usando malloc() para determinar a quantidade de memória disponível. */
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5:
6: /* Definição de uma estrutura que tem
7: 1024 bytes (1 kilobyte) de tamanho. */
8:
9: typedef struct kilo {
10: struct kilo *next; /* 2 bytes */
11: char dummy[1022]; /* + 1022 bytes = 1024 bytes = 1KB */
12: } KILO, *PTKILO;
13:
14: int FreeMem(void);
15:
16: main()
17: {
18: printf("Espaço Disponível: %d kilobytes.\n", FreeMem());
19: return(0);
20: }
21:
22: int FreeMem(void)
23: {
24: /*Retorna o número de kilobytes (1024 bytes)
25: de memória disponível. */
26:
27: int counter;
28: struct kilo *head; KILO *current; PTKILO nextone; /* Tudo a mesma coisa */
29:
30: current = head = (struct kilo*) malloc(sizeof(struct kilo));
31:
32: /* Checa para ver se há memória livre. */
33: if (head == NULL)
34: return 0;
35:
36: counter = 0;
37: do
38: {
39: counter++;
40: current->next = (struct kilo*) malloc(sizeof(struct kilo));
41: current = current->next;
42: } while (current != NULL);
43:
44: /* Agora counter contém o número estruturas
45: do tipo kilo que nós conseguimos alocar.
46: Em seguida vamos liberar a memória alocada. */
47:
48: current = head;
49:
50: do
51: {
52: nextone = current->next;
53: free(current);
54: current = nextone;
55: } while (nextone != NULL);
56:
57: return counter;
58: }
A função freemem() da listagem anterior utiliza-se de força bruta para obter a
quantidade de memória livre, simplesmente alocando novas estruturas do tipo
definido kilo, com 1024 bytes cada. Mas esse exemplo utiliza uma técnica chamada
linked lists (não vou tentar uma tradução!) – que consiste em um dos elementos de
uma estrutura ser um ponteiro para uma outra estrutura do mesmo tipo.
Enquanto o ponteiro head é mantido intacto – pois aponta para o início da lista
–, current é incrementado sempre com uma nova estrutura do tipo kilo e counter
conta quantas dessas estruturas conseguem ser alocadas. Sendo cada uma delas
1KB (kilobyte) de tamanho, counter possui a quantidade em KB de memória
disponíveis.
Alocação Dinâmica em C++
A diferença entre alocar memória em C e C++ está simplesmente no fato da
linguagem C++ implementar essa funcionalidade através de operadores e não de
funções.
O operador new serve para alocar memória, enquanto o delete serve para
liberar. Mas em suma, eu já mostrei todos os conceitos importantes sobre esse
tópico, deste modo, se você desejasse alocar uma variável, o faria assim:
int* ptNum;
ptNum = new int; // equivalente à ptNum = (int*) malloc(sizeof(int))
ou seja, ptNum receberá o endereço de uma nova variável do tamanho inteiro. E
caso desejasse alocar um vetor, você o faria desta forma:
int* ptNum;
ptNum = new int [numElementos]; // equivalente à ptNum = (int*)
malloc(numElementos*sizeof(int))
sendo que basta indicar o tipo do dado dos elementos da matriz, seguido pelo
número de elementos definido entre conchetes. E para desalocar bastaria fazer o
seguinte:
delete ptNum;
para uma variável simples e:
delete[] ptNum;
para um vetor.
Sumário
Confesso que em todos esses anos de estudo nunca havia visto um tutorial de
C/C++ tão desorganizado. Apesar de não termos visto nem uma fração das
capacidades dessas linguagens (que podem ser consideradas uma só), meu intuito
aqui foi unicamente de mostra-lhes que não precisam ter medo de C/C++. Se você
quiser aprofundar-se no assunto, entre em contato comigo que posso indicar-lhe
livros e apostilas a respeito. E espero que este tutorial tenha ajudado um pouco, pois
vimos os conceitos mais importantes de C/C++ para que você compreenda os
exemplos do curso, e no final você encontrará alguns exercícios sobre C. Falei tanto
em C, que voC deve estar de C-aco Cheio! :-)
Exercícios?
Sei que você deve achar um saco fazer exercícios, mas sem eles você não terá
certeza se realmente aprendeu, mas não ligue, é normal, você é normal!
Exercício 1 - Com os conhecimentos que você tem, crie uma calculadora em C que
realize as quatro funções elementares (Adição, subtração, multiplicação e divisão),
fique a vontade para estabelecer um layout.
Exercício 2 - Faça um programa para ler um número X de nomes, onde cada nome
seja posto em um vetor de caracteres do tamanho certo para o nome. Desta forma
não havendo desperdício nem truncamento.
-- Guilherme M. Ferreira --

Você também pode gostar