Escolar Documentos
Profissional Documentos
Cultura Documentos
DicasBCB
http://www.dicasbcb.com.br
Conteúdo
O bê-a-bá da programação ............................................................................................................................. 5
Afinal, o quê é C++?...................................................................................................................................... 5
Um pouco de história ..................................................................................................................................... 5
Conceitos........................................................................................................................................................ 6
O bit e o byte................................................................................................................................................ 12
Cuidados gerais ............................................................................................................................................ 12
Documentar o trabalho................................................................................................................................. 13
O programa main()....................................................................................................................................... 15
Primeiro programa ....................................................................................................................................... 15
Imprimindo dados nos componentes ........................................................................................................... 28
Comentários ................................................................................................................................................. 31
Tipos fundamentais ...................................................................................................................................... 32
O tipo inteiro ................................................................................................................................................ 33
Os tipos ponto flutuante ............................................................................................................................... 36
O tipo caracter.............................................................................................................................................. 36
Modificadores de tipos................................................................................................................................. 38
Variáveis ...................................................................................................................................................... 40
Atribuição de valores a variáveis ................................................................................................................. 43
Variáveis signed e unsigned......................................................................................................................... 45
Excedendo o limite de uma variável ............................................................................................................ 46
Operações matemáticas com unsigned ........................................................................................................ 47
AnsiString .................................................................................................................................................... 48
Funções que modificam strings ................................................................................................................... 61
Funções que modificam strings ................................................................................................................... 63
AnsiString continuação... (dstring.h) ........................................................................................................... 65
AnsiString continuação... ............................................................................................................................. 66
AnsiString continuação... ............................................................................................................................. 67
A palavra-chave typedef .............................................................................................................................. 67
A diretiva #define ........................................................................................................................................ 68
A palavra-chave const.................................................................................................................................. 69
Operadores matemáticos .............................................................................................................................. 73
Expressões.................................................................................................................................................... 75
Entendendo melhor o C++Builder ............................................................................................................... 78
#include <vcl.h> .......................................................................................................................................... 78
#pragma hdrstop........................................................................................................................................... 83
“Unit1.h”...................................................................................................................................................... 84
“#pragma package(smart_init)” ................................................................................................................... 85
#pragma resource ......................................................................................................................................... 87
_fastcall, __fastcall ...................................................................................................................................... 88
TComponent ................................................................................................................................................ 88
TComponent::Owner ................................................................................................................................... 89
Operadores de incremento e decremento ..................................................................................................... 90
Operadores relacionais................................................................................................................................. 91
O comando if................................................................................................................................................ 92
Certamente C++ não é a linguagem cujos conceitos mais facilmente são assimilados, mas também não é
uma linguagem difícil. Na verdade, trata-se de uma linguagem de programação poderosa, para qualquer
tipo de desenvolvimento computacional, e quando se começa compreender os seus conceitos fica bastante
interessante e divertido o seu aprendizado. E não há nenhum inconveniente ou dificuldade em se aprender
C++ como primeira linguagem de programação. O que é importante é que existam a vontade de aprender,
a dedicação e a familiaridade com computadores.
C++ é uma linguagem de programação usada para a criação de uma infinidade de programas como
sistemas operacionais, processadores de texto como o Microsoft Word, planilhas eletrônicas como o
Excel, comunicação, automação industrial, bancos de dados, jogos, games, aplicativos multimídia,
navegadores Web como o Internet Explorer etc; é uma linguagem que permite acesso aos recursos do
hardware e vai por aí afora, sem deixar de lado os sistema embutidos usados nos automóveis, celurares
etc.
Um pouco de história
Ocorreu em junho de 1983, a primeira utilização de C++ fora de uma organização de pesquisa. Podemos
considerar C++ um resultado evolutivo da linguagem de programação BCPL, criada por Martin Richards
O poder da linguagem "C" logo foi demonstrado, em sua primeira aplicação de peso, quando foi usada
para reescrever o sistema operacional UNIX, até então escrito em linguagem assembly.
Com o tempo, a linguagem "C" tornou-se bastante popular e importante. Contribuiriam para o sucesso o
fato de essa linguagem possuir tanto características de baixo nível quanto de alto nível; a portabilidade da
linguagem, ou seja, poder ser usada em máquinas de diferentes portes e diferentes sistemas operacionais;
bem como o fato de, em meados de 1970, o sistema operacional UNIX ser liberado para as Universidades,
deixando de ficar restrito aos laboratórios. Por volta de 1980, várias empresas já ofereciam diversas
versões de compiladores "C", compatíveis com outros sistemas operacionais, além do original UNIX.
Bjarne Stroustrup criou C++. Claramente, como pondera Stroustrup, C++ deve muito a "C" que foi
mantida como um subconjunto. Também foi mantida a ênfase de "C" em recursos que são suficientemente
de baixo nível para enfrentar as mais exigentes tarefas de programação de sistemas. Outra fonte de
inspiração para C++ foi Simula67, da qual C++ tomou emprestado o conceito de Classes.
Desde 1980, versões anteriores da linguagem, conhecidas como "C com Classes" têm sido utilizadas. O
nome C++, criado em 1983 por Rick Mascitti, representa as mudanças evolutivas a partir de "C", onde
"++" é o operador de incremento em "C".
Bjarne Stroustrup projetou C++ basicamente para poder programar sem ter de usar Assembler, "C" ou
outras linguagens de alto nível. Seu principal objetivo era tornar a escrita de bons programas mais fácil e
mais agradável para o programador individual.
Em março de 1998, o American National Standards Institute (ANSI) aprovou e publicou um padrão para
a linguagem C++. A padronização melhora a portabilidade e a estabilidade dos programas. Usando a
biblioteca Standart de C++, podemos, rapidamente, construir aplicações confiáveis, bem como mantê-las
com menos custo e esforço.
Desde seu desenvolvimento por Dr. Bjarne Stroustrup, C++ foi extensamente usado na construção de
grandes e complexas aplicações como telecomunicações, finanças, negócios, sistemas embutidos e
computação gráfica. A padronização final da biblioteca de C++ torna mais fácil o seu aprendizado,
facilitando seu uso por uma grande variedade de plataformas, o que, por outro lado, significa garantia de
colocação profissional permanente para os bons programadores da linguagem.
Conceitos
Podemos classificar as linguagens de programação em dois grupos: aquelas consideradas de baixo nível e
aquelas consideradas de alto nível.
Quando nascemos, à medida que crescemos, nossa percepção e o contato com a realidade do mundo, nos
anexa às coisas como elas já foram sedimentadas na cultura de um povo. Assim também ocorre com o
idioma e, de um modo geral, com algumas ciências que assimilamos em sequer darmos conta disso.
Por exemplo, quando uma criança conta para outra que possui trinta ou quarenta figurinhas, não percebe,
mas está fazendo uso do sistema numérico decimal (base 10). E o sistema numérico decimal está
incrustado em nosso cotidiano. Via de regra, todos os valores que usamos e todas as contas que fazemos
têm esse sistema numérico como base. Por exemplo, quando escrevo 543, você entende 543 e não tem
dúvidas, pois sabe o que 543 significa. Raciocinando em sistema numérico decimal, também não é difícil
compreender que:
pois
pois
543 = 500 + 40 + 3.
Infelizmente, os sistemas computacionais não representam valores usando o sistema numérico decimal.
Internamente, os computadores representam valores usando dois níveis de voltagem (normalmente 0v e
+5v). Com esses dois níveis de voltagem, nós podemos representar dois valores. Por convenção, adota-se
o zero e o um. Então, para todos os efeitos, os computadores só conhecem uma linguagem que é
constituída por zeros e uns, ou código de máquina, mais conhecida por linguagem binária. Nesse contexto,
se quisermos representar o número 543, devemos escrever:
1000011111
Qualquer quantia de dígitos binários com valor igual a zero pode anteceder um número binário sem
alterar-lhe o valor. Por exemplo, podíamos representar o número 543 assim:
00000000000000000000000000000000001000011111.
Ante o exposto, não fica difícil compreender que a menor unidade de dados num computador é um dígito
binário. Um dígito binário é o mesmo que um bit, abreviatura de binary digits.
Complicado? No início da era dos computadores (aqueles gigantes à válvula), os programas eram escritos
basicamente em binário, mas, felizmente, longe se vão os dias em que se precisava programar diretamente
em binário, embora ainda exista quem trabalhe nesta base.
O sistema numérico binário tem um modo de funcionamento semelhante ao sistema numérico decimal,
porém com duas diferenças básicas:
Embora um bit seja capaz de assumir apenas dois valores (zero e um), o número de elementos que se pode
representar com um único bit é infinitamente grande. Por exemplo, pode-se representar quaisquer dois
valores diferentes com um único bit: falso ou verdadeiro, maior ou menor, redondo ou quadrado e assim
por diante, numa lista incomensurável. Todavia, esse tipo de construção não é usual.
Um sério problema com o sistema binário é o tamanho de suas representações. Veja bem, enquanto a
versão decimal de 543 contém apenas três dígitos: 5, 4 e 3, a representação binária do mesmo valor
contém dez dígitos: 1000011111. Disso decorre que, ao se trabalhar com valores grandes, os números
binários rapidamente se tornam difíceis de ser controlados. Embora seja possível converter binários em
decimal e vice-versa, tal tarefa não é simples nem trivial. Mas é essa a linguagem que a máquina entende.
Alternativamente, tentou-se programar em hexadecimal, que pode representar dezesseis valores entre 0 e
15 decimais. Os números hexadecimais possuem duas características positivas:
O sistema hexadecimal é uma base de numeração que começa a ser contado no "0" e termina em "F":
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.
O sistema hexadecimal possui uma relação simples com o sistema binário, uma vez que para cada grupo
de quatro dígitos binários temos um em hexadecimal.
2ª. a letra "b" deve ser colocada no final de todo e qualquer valor binário. Exemplo: 1011 0001b
3ª. a letra "h" deve ser colocada ao final de todo e qualquer valor hexadecimal. Exemplo 4D5Fh;
Não há necessidade de enfatizar que a programação em hexadecimal, embora menos complexa que a
binária, continuava sendo demasiadamente complicada e demorada.
Nesse contexto, surge uma nova linguagem de programação: o Assembly, uma linguagem de símbolos
designados mnemónicas, que são instruções Assembly. Cada mnemónica tem a sua correspondência em
um comando elementar inteligível pelo computador. Nesse tipo de programação, o programador trabalha
diretamente com registradores da CPU e com a memória, entre outras coisas.
Programar em Assembly significa conversar quase que diretamente com a CPU, tal qual os sistemas
binário e hexadecimal. Ou seja, nesses tipos de programação, o programa é escrito em uma linguagem
muito próxima àquela que a máquina entende, e por isso são as linguagens ditas de baixo nível.
a 100
MOV AX, 20
MOV BX, 30
ADD AX, BX
NOP
Quando nos referimos às linguagens de alto nível, estamos falando daquelas que são escritas em um
distanciamento relativo da linguagem máquina. Ou seja, as linguagens de alto nível são aquelas que se
aproximam da linguagem humana, como a Linguagem Basic, C++, Pascal, Java etc.
Por exemplo, Para fazer uma frase aparecer na tela no DarkBASIC, basta digitar:
main()
printf (" Esta frase que está entre aspas será exibida na tela! ");
exit (0);
Em Pascal:
program
begin
write (" Esta frase que está entre aspas será exibida na tela! ");
end
Visualmente, o que diferencia estas linguagens de programação daquelas ditas de baixo nível é justamente
o fato de nestas linguagens podermos encontrar algum sentido naquilo que estamos lendo ou escrevendo.
Um simples programa como este que foi apresentado nessas linguagens de alto nível, seria praticamente
ininteligível para nós, se fosse escrito em binário.
De uma forma simplista, podemos dizer que nas linguagens de alto nível, o próprio programa tradutor
(compilador ou interpretador) que usamos se encarrega de conversar com a máquina, transformando todos
os comandos que inserimos no programa para uma linguagem que a máquina compreenda qual a resposta
a ser dada. Ou seja, o tradutor converte o código-fonte em um código executável pela máquina.
1ª. completar com zeros os números binários para que eles se tornem um múltiplos de quatro ou de oito
bits.
2ª. cada grupo de quatro bits deve ser separado por espaço.
Um byte pode representar até 256 valores diferentes, pois são oito bits em potência de dois:
28 = 256.
Geralmente um byte é usado para representar valores compreendidos entre 0 e 255 não sinalizados ou
valores entre -127 a 128 sinalizados.
Para os tipos de dados, como os caracteres (letras ou símbolos), que não possuem mais do que 256
elementos, um byte normalmente é suficiente.
Embora sejam visualizadas como letras e símbolos, as constantes caracteres são armazenadas
internamente pelo computador como um número inteiro entre 0 e 255. O caracter A, por exemplo, tem
valor 65; o B, 66; o C, 67; e assim por diante. Os valores numéricos dos caracteres estão padronizados em
uma tabela chamada de American Standard Code for Information Interchange Table ou simplesmente
tabela ASCII.
Um grupo de 16 bits denomina-se word, ou palavra. Com 16 bits podemos representar até 65.536 (216)
valores diferentes: 0 a 65.535 não sinalizados e -32.767 a 32.767 sinalizados. Um dos principais usos para
a palavra são para os valores de inteiros.
Um grupo de 32 bits denomina-se double words. Com 32 bits podemos representar uma infinidade de
tipos de dados, como por exemplo valores ponto flutuante de 32 bits. São 4.294.967.296 (232) valores
diferentes: 0 a 4.294.967.295 não sinalizados e -2.147.483.647 a 2.147.483.647 sinalizados.
Cuidados gerais
Ao escrever um código-fonte em C++, algumas precauções extras devem ser adotadas.
Documentar o trabalho
Em princípio, comentários são textos que inserimos no programa para esclarecer qual é a tarefa que
determinado comando realiza. Comentários são muito importantes em nossos códigos C++. A linguagem
C++ suporta dois estilos de comentários:
Outra aplicação que podemos dar aos comentários, é a de tirar, temporariamente, a validade de uma parte
do programa:
cout << " Esta mensagem aparece na tela! \n";
// cout << " Esta mensagem não aparece na tela! \n";
/* cout << Esta mensagem não aparece na tela! \n"; *
Uma vez iniciado um comentário no estilo C, não podemos inserir outro comentário dentro deste, pois o
compilador considerará apenas a primeira chave de fechamento, o quê ocasionará um erro:
void
main
(
void
)
{
cout
<<
" C++ possui formato livre!!!\n"
;
getch
(
)
;
}
O resultado apresentado pelo programa acima é rigorosamente o mesmo em qualquer das três hipóteses.
Evidentemente, se, quando trabalharmos num projeto, não tivermos cuidado de escrever um bom e legível
código, o resultado poderá apresentar-se bagunçado, dificultando sua compreensão e, até mesmo, a
localização e conserto de bugs (erros no código que afetam diretamente o resultado) no programa.
Certamente em pouco tempo você já estará desenvolvendo sua própria técnica para escrever bons
programas, usando o formato livre a seu favor.
C++ é Case Sensitive, isto é, letras maiúsculas e minúsculas são interpretadas de forma diferente pelo
compilador. Dentre outras implicações, isso significa que se, por acaso, declararmos uma variável com o
nome Temp, os seguintes nomes terão significados diferentes: TemP; TEMP; temp; tEmp; TeMp. Atenção
especial devemos ter quando escrevemos os comandos da linguagem nas instruções dos códigos-fontes,
É bastante comum erros de compilação por causa da sensitividade de C++, mas esse não é um erro difícil
de se corrigir.
O programa main()
Todo programa C++ deve ter uma função principal denominada main(), não podendo haver outra função
com esse mesmo nome, sendo que a execução do programa sempre será iniciado em main().
main() {}
Ele define a função main, que não recebe nenhum argumento e não devolve nada. O programa logo
encerra e o máximo que se percebe é uma janela piscando rapidamente na tela.
Primeiro programa
Diversos programas-exemplo serão apresentados durante este curso, sendo que os mesmos foram
processados no c++builder 5.02, c++builder 3, c++builder 4 e no c++builder 6, todos da borland. Alguns
desses programas foram escritos com o intuito de ilustrar os tópicos apresentados; outros, para deixar o
curso mais interessante. Sendo assim, procuraremos apresentar tutoriais sobre a confecção de programas,
que irão se completando aos poucos, à medida que avançamos no curso. No final, tudo deverá fazer
sentido.
O código-fonte de um programa C++ pode ser escrito em qualquer editor de texto simples que possa
salvar seu conteúdo em formato de texto puro. Podemos, inclusive, usar o Bloco de Notas do Windows.
Porém, no lugar da extensão .txt, devemos salvar os códigos do programa com extensão .cpp. Contudo, a
forma mais rápida e sensata de se escrever um programa C++ é usando o próprio editor de códigos que
acompanha o ambiente de desenvolvimento C++. Esclarecemos aqui que muitos exemplos deste livro
estão redigidos com fonte formatada (itálica, negrito, colorida etc) apenas para fins didáticos e para
facilitar uma eventual localização no mesmo. Um código-fonte C++ não deverá ser escrito com este
formato e sim, conforme já exposto, em formato de texto puro, não formatado. Se você está procurando
um bom editor de códigos para seus programas, nos atrevemos a indicar o ConTEXT. Usamos esse editor
grandemente na elaboração destas páginas. Com ele ficou muito fácil padronizar a visualização dos
Inicialmente, para compreendermos melhor o conceito dos comandos e das instruções em C++,
trabalharemos um programa definido como aplicação Console Win32, que gera programas parecidos com
o DOS quando estão rodando, mas, na realidade, trata-se de programas de 32 bits executados no ambiente
Windows.
1. inicialize o C++Buider;
3. dê um clique em New...
Observe o próprio BuiderC++, por default (padrão), já digita parte do código que usaremos. Porém essa
sugestão da Borland, neste tipo de aplicação Console Wizard, em princípio, pode ser ignorada, conforme
demonstramos abaixo. Você pode optar por qualquer uma das duas formas para desenvolver seus
programas:
//----------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
return 0;
}
Outra opção que temos é deletar todo esse código e simplesmente iniciar nosso programa a partir do
código já visto do menor programa possível em C++:
main()
{
}
Esse modelo é aconselhável, se você estiver usando outro tipo de compilador. Caso haja algum conflito
entre o exemplo apresentado e o seu compilador, procure ajuda no manual do mesmo.
Nosso primeiro programa, ao ser compilado, escreverá na saída de vídeo uma string (cadeia de caracteres)
com um espaço de tabulação na segunda linha da tela, levará o cursor para a terceira linha da tela, emitirá
um beep e, por fim, aguardará que uma tecla qualquer seja pressionada para encerrar a aplicação.
#include <iostream>
#include <conio>
main()
{
std::cout << "\n\tEstou fazendo o meu primeiro programa\n\a";
getch();
}
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
std::cout << "\n\tEstou fazendo o meu primeiro programa\n\a";
getch();
return 0;
}
Para Salvar, clique o menu File, escolha a opção Save e, na pasta de sua preferência, salve o programa
com o nome PrimProgram.bpr.
Compilar significa converter o código-fonte em linguagem que o processador (CPU) entenda, ou seja, em
código de máquina, reunindo todos os comandos em um só arquivo. Linkeditar é transformar esse código
fonte em um executável - um .exe (programa pronto).
Para executar o programa, nós podemos pressionar a tecla F9, ou podemos dar um clique em Run no
menu Run, ou podemos clicar a seta verde (Run F9) sob o botão Open Project.
Feito isso, seu primeiro programa produzirá um executável semelhante à figura abaixo:
PrimProgram.bpr
PrimProgram.cpp
PrimProgram.exe
PrimProgram.obj
PrimProgram.tds
Ora, mas vejam só, nós já temos um arquivo .exe (PrimProgram.exe) em nossa pasta! Inicialize-o e
perceba que o nosso primeiro programa já roda sem auxílio do compilador. Pois bem, pressione uma tecla
qualquer para encerrar o aplicativo e dê um (ou dois) clique(s) em PrimProgram.bpr para ver o que
acontece: ... Estamos de volta no ambiente de desenvolvimento do primeiro programa.
A seguir, procuraremos entender como conseguimos criar esse programa. Na explanação, aparecerão
nomes como Classes, objetos, macros, funções etc, que talvez lhe pareçam estranhos e incompreensíveis.
Não se preocupe, pois, no momento certo, tudo ficará claro.
Talvez você esteja se perguntando: afinal, o quê quer dizer esse monte de coisas?
Por enquanto, apenas para elucidar nosso primeiro programa sem tumultuar nosso entendimento, vamos
nos contentar com uma explicação bem singela:
O pré-processador C++ é um programa que roda antes do compilador e, baseando-se em certas instruções
denominadas diretivas de compilação, examina o programa fonte e implementa-lhe algumas
modificações.
#include
#define
#line
#ifdef
#ifndef
#if
#else
#elif
#endif
#undef
#error
#pragma
2ª. devemos respeitar a regra de colocar somente uma diretiva em cada linha. Ou seja, não podemos
colocar outro comando qualquer na linha em que há uma diretiva.
a diretiva #include
Imagine que muitos dos resultados que esperamos obter de nossos programas já estão, de certa forma,
prontos, na biblioteca padrão, para ser inseridos em nosso código-fonte. Ou seja, não temos necessidade
de desenvolver determinados arquivos (classes, funções ou gabaritos) porque eles já existem e podem ser
usados em qualquer momento que precisemos deles.
Quando o pré-processador encontra a diretiva #include em nosso código fonte, substitui a mesma pelo
conteúdo do arquivo indicado (no nosso exemplo, o conio e o iostream). O resultado é que o compilador
encontrará e processará o arquivo implementado, e não a diretiva de compilação.
Para incluir os herder files em nosso código-fonte, devemos usar os símbolos < e > ou " e " para
envolver o arquivo de cabeçalho:
#include <iostream>
ou
#include "iostream.h"
Se deixarmos espaço entre o caracter < e o nome do arquivo de cabeçalho, ou entre o caracter " e o nome
do arquivo de cabeçalho, o compilador indicará erro:
< iostream.h> // [C++ Error] Project2.cpp(4): E2209 Unable to open include file ' iostream'.
Convencionou-se usar para os arquivos de cabeçalho o sufixo .h e para outros tipos de arquivos que
contenham funções ou definições de dados, o sufixo .hpp, .c, ou .cc, .cpp, .cxx, .C, conforme o
compilador. No caso do C++Builder, o arquivo que se encontra associado é o .hpp, responsável pela
interface com a VCL, que foi escrita em Object Pascal.
Historicamente, o sufixo .h era padrão para todos os arquivos de cabeçalho. Entretanto, para evitar
problemas de compatibilidade com cabeçalhos de versões redefinidas de biblioteca padrão, ou com novos
recursos de biblioteca que foram surgindo, resolveu-se suprimir o sufixo .h dos nomes de cabeçalho
padrão (podemos dizer que cabeçalho padrão é o arquivo de cabeçalho definido na biblioteca padrão). A
A principal diferença entre a biblioteca padrão e a não padrão é que, enquanto uma biblioteca não padrão
tem de ser incorporada manualmente, a padrão é incorporada automaticamente pelo compilador. É
importante saber, ainda, que os recursos da biblioteca padrão estão definidos num ambiente de nomes
chamado std e apresentados como uma série de cabeçalhos.
O objeto cout é o responsável pela saída, no console ou na tela, da string que digitamos: Estou fazendo o
meu primeiro programa.
O operador de inserção << (envie para) escreve o argumento da direita (a string) no da esquerda (no
cout).
Além do cout e do operador de inserção << a Classe iostream contém outras definições de impressão e
leitura (I/O), que estudaremos mais adiante.
Você deve ter notado que demonstramos a inclusão dos arquivos de cabeçalho da Classe iostream de três
modos diferentes:
Não há necessidade de inserirmos nenhum sufixo para cabeçalhos da biblioteca padrão, porque os
símbolos < > já denotam o chamamento de cabeçalhos padrão. Contudo, as declarações dos cabeçalhos
mais antigos, são colocadas no ambiente de nomes global, carecendo do sufixo .h.
Os cabeçalhos colocados entre aspas " " determinam que o pré-processador procure o cabeçalho primeiro
no diretório atual, e depois na biblioteca padrão (Include).
#include <iostream>
Se algum dos nossos programas não rodar, podemos usar uma versão mais tradicional. Para isso
colocamos o sufixo .h nos cabeçalhos e removemos o prefixo std::. Exemplo:
#include <iostream.h>
#include <conio.h>
main()
{
cout << "\n\tEstou fazendo o meu primeiro programa\n\a";
getch();
}
Boa parte dos programadores, talvez por praticidade, parece preferir essa segunda versão. Daqui para
frente, escolha é sua!
Lembra-se do menor programa possível em C++, quando afirmamos que tudo o que veríamos seria um
breve piscar da janela na tela e o encerramento do programa? Pois bem, poderíamos usar getch() para
segurar a janela na tela até que uma tecla fosse pressionada!
Caracteres especiais
Antes que nossa string fosse imprimida no vídeo, o programa pulou uma linha (caracter de nova linha) e
imprimiu uma caracter de tabulação horizontal. Depois da string, o programa colocou o cursor na linha de
baixo (nova linha) e emitiu um beep (sinal sonoro).
A solução é usarmos algumas combinações de caracteres que realizam essas tarefas. Essas combinações,
conhecidas como seqüência de escape, são representadas por uma barra invertida \ e um caracter. Por
exemplo, a combinação \n representa nova linha (Enter), \t representa tabulação horizontal (Tab) e \a
representa o beep. Essas seqüências de escape, nós já conhecemos. Vejamos outras:
String(s) de texto
Constante caracter é uma letra ou símbolo colocado entre apóstrofes: 'a', 'b', '~', 'A', 'B', '\n, '\r', '\xa0',
'\xa2', 'xa3' etc.
Uma importante atividade nos programas em geral é a manipulação de textos como palavras, frases,
nomes etc. Na realidade, esses textos são constantes string(s), que se compõem de um conjunto de
caracteres colocados entre aspas: "Estou fazendo o meu primeiro programa",
"constante string", "\tOuviram do Ipiranga as margens pl\xa0"
"cidas\n\tde um povo her\xa2ico o brado retumbante\n\te o sol da
liberdade em raios f\xa3lgidos,\n\tbrilhou no c\x82u da P\xa0tria nesse
instante.\n"
Vamos reescrever o primeiro programa. Por enquanto, não queira compreender todos os detalhes da
alteração, uma vez que, por ora, nossa preocupação é apenas esclarecer, sem pormenores, quais são os
elementos básicos de uma função. Em outra seção deste curso, abordaremos as funções de uma forma
mais abrangente e minuciosa.
Inicialize PrimProgram.bpr, caso não esteja com ele aberto. No compilador, clique em Edit e depois em
Select All; Clique novamente em Edit e depois em Delete. Agora copie o código abaixo no editor de
textos do compilador.
#include <iostream>
#include <conio>
using namespace std; // torna todos o nomes std globais
void ModifPrimProg(void); // declara a finção ModifPrimProg
} // fim de ModifPrimProg()
Com essa pequena alteração no código, podemos visualizar melhor alguns detalhes sobre as funções.
Primeira_Instrução;
Segunada_Instrução;
Etc_instrução;
Nosso programa agora possui quatro funções: main(), ModifPrimProg(), getche() e getch(). O nome
delas é: main, ModifPrimProg, getche e getch respectivamente. A função main(), como sabemos, é a
principal, é aquela onde a execução do programa se inicia; ModifPrimProg() é uma função que não
estava em biblioteca alguma, tendo sido criada por nós; getche() e getch() nós também já conhecemos do
arquivo de cabeçalho conio.
Precedendo o nome da função main() temos o tipo int, que informa ao compilador que main() pode
retornar um tipo int. Já a função ModifPrimProg() é do tipo void. O tipo void significa que a função
não possui valor de retorno. Quando nenhum tipo de retorno for especificado, presume-se o valor de
retorno int para a função. As funções retornam valores por meio do comando return.
Após o nome de qualquer função temos parênteses de abertura "(" e de fechamento ")". Nesses parênteses,
informamos ao compilador a lista de parâmetros que a função pode receber. Logo,
ModifPrimProg(void) não pode receber nenhum parâmetro por causa do tipo void.
Entende-se por cabeçalho da definição da função o conjunto tipo nome(declaração dos parâmetros) que
antecede a chave "{" de abertura do corpo da função. Entre a chave de abertura "{" e a de fechamento "}"
do corpo da função, encontramos as tarefas (instruções) que a mesma realiza.
Instruções são as tarefas que determinados comandos realizam. Toda instrução é encerrada com um ponto
e vírgula ";":
getch();
return 0;
A falta do ponto e vírgula ensejará mensagem de erro pelo compilador. Porém, não se coloca ponto e
vírgula após as chaves de abertura e fechamento das funções.
Introdução ao BCB(http://www.dicasbcb.com.br/Iniciando_o_BCB/Inicia_o_BCB.htm)
Podemos inserir palavras, textos ou frases em diversos componentes através códigos ou de suas
propriedades no Object Inspector. Essas propriedades, uma vez demarcadas, determinam o modo que o
programa iniciará a sua execução. Podemos, posteriormente, em tempo de execução, alterar essas
propriedades de diversas formas, conforme o caso.
Outros componentes, derivados de outros ramos, possuem outras formas de ser alterados. Por exemplo,
altere a propriedade Text de um Edit, ou de um MaskEdit; ou a propriedade Items de um ListBox; ou as
propriedades Text e Items de um ComboBox etc.
Vejamos algumas formas de inserir textos em componentes, através do código, em tempo de execução:
//---------------------------------------------------------------------------
Outro exemplo:
//---------------------------------------------------------------------------
Outro exemplo:
//---------------------------------------------------------------------------
Outro exemplo:
//---------------------------------------------------------------------------
ShowMessage("Alô, Thérbio!"
"\nEstou na segunda caixa de mensagem,\n e na terceira linha");
Nota introdutória. No avançar deste curso, você perceberá que, propositadamente, usamos alguns nomes,
comandos ou tipos de dados com os quais você poderá não estar habituado a trabalhar. Não se assuste e
nem se preocupe em querer entender imediatamente todos os detalhes apresentados, pois tudo deverá ficar
bastante claro no tempo certo. Concentre-se, basicamente, em entender a manipulação dos dados
referentes ao tópico apresentado.
Evidentemente, outros componentes também são férteis para trabalharmos com dados:
Vamos aproveitar para conhecer a base do código que será usado para construirmos uma calculadora.
Usaremos um Label e um Botão qualquer no Form.
Comentários
Quando estamos empenhados na tarefa de escrever uma aplicação, é fundamental tomarmos algumas
providências para que não nos percamos nas diversas linhas de código que por ventura utilizaremos. Uma
coisa muito útil que podemos, ou melhor, que devemos fazer é colocar comentários que nos esclareçam a
utilidade das diversas partes do código. Evidentemente, você já tem percebido que, junto às linhas de
código, nós colocamos algumas frases esclarecedoras dentro de um par de barras com asterisco /* */, ou
imediatamente após duas barras //. Essas frases são os comentários.
Abaixo apresentamos um exemplo de como podemos usar comentários para retirar efeitos de um código
que já usamos anteriormente. Compile e execute para ver que o resultado do programa será outro:
Tipos fundamentais
A linguagem C++ possui tipos fundamentais de dados que representam valores como caracteres, inteiros,
pontos flutuantes e booleanos. Os tipos inteiro (int) e pontos flutuantes (float e double) representam os
números de um modo geral. Caracteres (char) representam as letras ou símbolos e o tipo booleano (bool)
oscila entre dois valores, geralmente para representar duas situações inversas como, por exemplo, falso ou
verdadeiro. Podemos, ainda, considerar um tipo básico de dados, o tipo enum, ou enumerados, o qual nos
permite definir conjuntos de constantes, normalmente do tipo int, chamados tipos de dados enumerados.
As variáveis declaradas deste tipo poderão adquirir somente entre os valores definidos.
Nota: Se o programa acima não imprimir todas as linhas, pressione a tecla Enter alguma vezes para criar
linhas vazias no Memo. Desta forma, as strings serão colocadas no espaço correto. Depois (no tópico
Variáveis) veremos outra solução mais adequada para o problema!
A linguagem C++ ainda nos oferece um tipo especial que serve para indicar, justamente a ausência de
tipo: o void. No exemplo acima percebemos o uso deste tipo junto à função do evento chamado,
indicando que tal função não devolverá nenhum valor. O void também será é usado em funções que não
requerem parâmetros.
O tipo inteiro
O tipo inteiro é um número de valor inteiro, ou seja, não fracionário. De um modo geral, trata-se de
seqüências de dígitos que representam números inteiros, que podem ser escritos na base 8 (octal), na base
10 (decimal) ou na base 16 (hexadecimal).
int i = 123.4;
todos os números colocados depois do ponto fracionário (nesse caso, o 4) serão desconsiderados.
Via de regra, os dados inteiros são devolvidos pelo programa no formato decimal. Desta forma, podemos
testar uma aplicação, onde colocaremos valores inteiros em vários formatos, porém os mesmos sempre
serão exibidos em formato decimal. Você poderá testar valores válidos e valores não válidos para cada
base de dados e visualizar os resultados.
Compile e execute a aplicação. O Label imprimirá o número 43. Feche a aplicação e substitua o valor 43
(decimal) no Editor de Códigos por 0x2b (hexadecimal). Rode o programa e dê um clique no botão para
ver o resultado. Agora faça a mesma experiência substituindo o 0x2b por 053 (octal). Se você fez tudo
direitinho, qualquer dos testes que você executar deverá imprimir o número 43 no Label do Form. Isso
porque esses valores são equivalentes. Baseando-se nas explicações introdutórias sobre esses valores
inteiros e na tabela abaixo, faça mais alguns testes para compreender melhor o que ocorre a nível
equivalência desses dados.
25 031 0x19
26 032 0x1A
27 033 0x1B
28 034 0x1C
29 035 0x1D
30 036 0x1E
31 037 0x 1F
32 040 0x20
Tudo pode estar parecendo muito complicado, mas não se preocupe porque o uso de valores octais ou
hexadecimais não é comum. Além do mais, no avançar do curso (ou talvez na seção de tutoriais), nós
aprenderemos a construir uma calculadora que nos apresentará como resultado o valor de conversão entre
esses dados. Por enquanto, contentemo-nos com a calculadora do Windows que pode realizar esse tipo de
cálculos.
Um número de ponto flutuante pode apresentar-se na forma de notação científica. Por exemplo: 4.32 vezes
dez elevado a quarta. Neste caso, o vezes dez ( X 10) é substituído por e ou E. Logo o número
supramencionado será representado por 4.32e4, que vale 43200.
OBS. Embora o C++Builder aceite a notação científica para inteiros, todos os valores que estiverem
situados após um eventual ponto fracionário, no resultado, serão desconsiderados.
Não podemos colocar vírgulas para representar pontos flutuantes: 12,345 // ilegal.
Os números ponto flutuante se dividem basicamente nos tipos float e double. Posteriormente
entenderemos melhor a que se referem esses nomes.
Eis um código para visualizarmos a matéria tratada neste tópico. Para executar esse código, precisaremos
de um Button e dois Label(s) no Form. De quebra, visualizaremos um problema de precisão com o tipo
float.
O tipo caracter
O tipo caracter é uma letra ou um símbolo colocado entre aspas simples. Embora sejam visualizados
como letras ou símbolos, é importante ter em mente que, internamente, os computadores armazenam os
caracteres como um número inteiro entre 0 e 255. Por exemplo, a letra a é associada ao número 97, b ao
98, c ao 99, d ao 100 e assim sucessivamente. Os valores numéricos dos caracteres estão padronizados em
uma tabela chamada de American Standard Code for Information Interchange Table ou simplesmente
tabela ASCII. Então concluímos que um char tanto pode ser interpretado como um número entre 0 e 255,
Seqüências de escape
Certos caracteres da tabela ASCII devem ser representados pela combinação especial de outros
caracteres. Essas combinações, conhecidas como seqüência de escape, são representadas por uma barra
invertida ( \ ) e outro caracter. Esse grupo de dois caracteres é interpretado como uma seqüência simples.
Por exemplo, nós já usamos em exemplo anterior, o '\n' e o '\t'. Essas combinações são interpretadas como
nova linha (Enter) e Tab, respectivamente.
É bom notarmos que, embora alguns desses caracteres possam ser compilados sem a barra invertida ( \ )
em algumas aplicações pelo C++Builder (", ?), noutras a barra poderá fazer falta.
Agora rode a aplicação e faça algumas experiências, dando um clique em Label para inserir valores entre
0 e 255 e visualizar a equivalência de valores entre os números e os elementos da tabela ASCII.
Modificadores de tipos
Podemos alterar o significado de um tipo básico de dados, adaptando-o a uma necessidade específica, por
meio de modificadores de tipos precedendo os tipos na declaração:
O exemplo a seguir usa um Edit, um Memo e um Button no Form. Quando o usuário dá um clique no
botão, um loop começa a contar uma seqüência de valores numéricos de caracteres para a variável
unsigned char c e só a encerra quando contar o último caracter, o de número 254. No corpo desse loop,
nós declaramos uma variável do tipo char, que converterá os valores numéricos da variável c para os
elementos correspondentes aos números na tabela ASCII, atribuindo esses valores a ch. Feito isso os
valores obtidos serão imprimidos no Edit, exibindo as correspondências encontradas. Concomitantemente,
quando o dedo do usuário deixa de fazer pressão com o mouse sobre o botão, configurando o evento
OnMouseUp, o conteúdo do Edit (no caso os valores 0 a 254 da tabela ASCII e mais alguns caracteres
para facilitar a visualização e a leitura) é adicionado a Memo1.
void __fastcall TForm1::BitBtn1Click(TObject *Sender)
{
/* Declara e inicializa com 0 uma variável do tipo unsigned char
dentro de um loop for; estabelece o limite de 254 para o loop;
determina o incremento da variável. */
for(unsigned char c = 0; c < 255; c++)
{
/*Declara uma variável que converterá os
números para o caracter da tabela ASCII*/
char ch = c;
//------------------------------------------------------------------
O exemplo acima pode ser modificado para exibir somente alguns caracteres ASCII. Por exemplo, após
rodar a aplicação, o programa apresentará para você o número de cada caracter. Com base nestas
informações, você poderá, facilmente, montar um programa que imprima somente as letras minúsculas do
alfabeto etc.
Variáveis
Podemos entender um microcomputador como um sistema de cinco unidades de funcionamento: unidade
de entrada (teclado, mouse, drive de CD-ROM, drive de disquetes etc), unidade de saída (impressora,
monitor etc), unidade de memória (memória RAM -escrita e leitura-, memória ROM - leitura), e as
unidades aritmética e lógica que se encontram agrupadas na CPU (Unidade Central de Processamento, o
processador).
O chip responsável pelo controle de todo o computador é o processador. Outro circuito de extrema
importância é a memória RAM, que podemos imaginar como um grupo de células usadas para
armazenamento temporário das instruções e dos dados que são acessados e processados pelo
microprocessador em altíssima velocidade. Trata de uma memória volátil pois seus dados perdem-se no
momento em que são desligadas, o que não chega a ser um problema, visto que esses dados, de regra, após
salvos, ficam guardados em algum disco de armazenamento permanente, como os discos rígidos ou os
disquetes, sendo copiados novamente para a memória na ocasião de seu processamento.
A memória RAM é constituída por uma imensa seqüência de células de armazenamento (localizações)
com o tamanho de oito bits (um byte) cada, o que permite que cada uma dessas localizações possa assumir
um entre 256 valores diferentes. Ressalte-se, ainda, que cada célula possui um endereço único e
inconfundível, expresso por um valor numérico que define a exata localização desse byte, bem como que,
apesar do limitado tamanho de cada célula, podemos acessar dois bytes consecutivos (word) ou quatro
bytes consecutivos (doubleword) simultaneamente com um único endereçamento.
Disso decorre que durante a execução de um programa, as instruções e os dados processados ficam
armazenados na memória do computador. Cada informação é representada por certo grupo de bytes (char
- 1 byte, float - 4 bytes, double - 8 bytes etc) e possui um local determinado na memória, um endereço que
pode ser expressado por um valor hexadecimal. Não há necessidade de o programador conhecer o
endereço absoluto de cada dado, pois o compilador relaciona o nome de cada variável com sua posição na
memória, cuidando dessa tarefa da melhor maneira possível.
Para facilitar o entendimento, podemos imaginar a memória do computador como um enorme armário de
gavetas. Cada gaveta (célula de armazenamento) é numerada seqüencialmente e possui o tamanho de 1
byte. Esse número seqüencial é o endereço da gaveta. Conforme o tipo de variável declarada (char = 1
Imagine um armário imenso, com um milhões de gavetas iguais. Seria demasiadamente complicado
localizar determinado objeto numa dessas gavetas se não possuíssemos uma forma de diferenciá-las entre
si. Então, o nome da variável funciona como uma inscrição que individualiza a gaveta (endereço na
memória).
informa ao compilador que ele deverá reservar 10 bytes seqüenciais (dez gavetas dispostas em seqüência)
para uma variável do tipo long double cujo nome é LgDbl. Observe que as "gavetas" estão reservadas,
porém nenhum "objeto" foi colocado nelas.
Um ponto bastante importante sobre o tamanho das variáveis é que seu tamanho pode variar de máquina
para máquina, ou sistema operacional para sistema operacional. O tipo int, por exemplo, ocupa 2 bytes no
sistema operacional MS-DOS e 4 bytes no Windows.
Com esses conceitos, já temos informações suficientes para entender o que vem a ser uma variável.
Podemos definir uma variável como um local na memória do computador que a cada momento pode
possuir um valor diferente, porém do mesmo tipo de dados.
char ch;
faz com que o compilador reserve espaço suficiente para um caracter. Já a declaração abaixo é um pouco
mais completa, pois inicializa a variável, colocando um caracter A no espaço reservado:
char ch = 'A';
O conceito de variáveis decorre justamente do fato de que podemos substituir o "conteúdo" dessas
"gavetas":
O exemplo a seguir usa um SpeedButton no Form. Declararemos uma variável do tipo char num lugar
onde ela será visível tanto pelo evento OnMouseDown quanto pelo evento OnMouseUp de
SpeedButton1. Quando o botão do mouse for pressionado sobre o botão, o evento OnMouseDown será
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
// declara uma variável char visível pelos eventos de SpeedButton1
char ch;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButton1MouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
ch = 'A'; // atribui A para a variável ch
SpeedButton1 -> Caption = ch;
}
//---------------------------------------------------------------------------
O C++Builder implementa a função sizeof() que nos permite visualizar o tamanho, em bytes, de uma
variável ou de um tipo de dados:
//---------------------------------------------------------------------------
double Dbl;
Edit1 -> Text = AnsiString(sizeof(Dbl)) + " bytes"; // retorna pelo nome da variável
ShowMessage(sizeof(long double));
}
//---------------------------------------------------------------------------
int i;
i = 43;
Nada impede que, no momento da declaração, também inicializemos a variável com algum valor:
int i = 43;
Podemos, inclusive, declarar mais de uma variável na mesma instrução, bem como misturar declarações
com inicializações:
O nome de uma variável deve ser sugestionável, nos indicando o tipo de dados com o qual ela trabalhará.
Por exemplo, suponhamos um problema onde será calculada a área de um retângulo de 10 metros de
comprimento por 7 metros de largura. Poderíamos definir as variáveis assim:
int i, i1, i_, _i, _2, i_2 ; // Ok. Todas as variáveis possuem nomes aceitáveis
float 7_i; // nome inválido, pois começa com número
char _AF$G; // nome inválido - caracter ilegal $
Coloque um botão BitBtn no Form sem alterar-lhe o tamanho. O exemplo exibirá o valor área do botão
que será ampliada a cada clique do mouse. Eis o código:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
int altr = 25, larg = 75; // declara e inicializa duas variáveis globais
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
/* O código deste bloco será executado imediatamente
(e somente) quando o aplicativo for inicializado*/
int total = altr * larg; // declara e inicializa uma variável local
Nota: No exemplo acima, essa não é a melhor forma de se conseguir o resultado, pois o exemplo foi
apenas didático para ilustrar a declaração, a inicialização e a substituição de valores em variáveis. Como
exercício, procure conseguir o mesmo resultado sem declarar nenhuma variável, o que poupará memória
durante a execução do programa. Você já possui conhecimentos suficientes para tanto!
A tabela abaixo exibe os valores possíveis para os tipos de dados, com ou sem os modificadores de tipo:
Observe que o tipo não tem o seu tamanho alterado (número de bytes) em virtude da presença dos
modificadores signed ou unsigned. O resultado direto desse fato é que, para um mesmo tipo de dados, o
valor máximo que pode ser atribuído a um unsigned é o dobro do maior valor positivo que pode ser
atribuído a um signed.
for (signed short int SSi = 32768; SSi < -32000; SSi++) Memo1->Lines->Add(SSi);
O Exemplo abaixo leva um SpeedButton e um ComboBox no Form. Quando o usuário clicar o botão, o
programa mostrará o resultado de se tentar atribuir valores não contemplados pela variável.
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ComboBox1 -> Text = "Excedendo limites de variáveis ...";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
ComboBox1 -> Text = "Veja abaixo os novos valores ...";
int i = 2147483647;
ComboBox1 -> Items -> Add("Limite para int = " + String(i));
ComboBox1 -> Items -> Add("Limite + 1 = " + String(i + 1));
ComboBox1 -> Items -> Add("Limite + 2 = " + String(i + 2));
ComboBox1 -> Items -> Add("");
char ch = 127;
ComboBox1 -> Items -> Add("Limite para char = " + String(int(char(ch))));
ComboBox1 -> Items -> Add("Limite + 1 = " + String(int(char(ch + 1))));
ComboBox1 -> Items -> Add("Limite + 2 = " + String(int(char(ch + 2))));
ComboBox1 -> Items -> Add("");
}
//---------------------------------------------------------------------------
O exemplo a seguir usa um Label e um Timer no Form. São declaradas duas variáveis globais do tipo
unsigned int, sendo que uma funcionará como base do cálculo da subtração e a outra como valor a ser
subtraído. O Label ficará piscando no Form, ou melhor, o Timer fará com que o Label fique visível e
depois de alguns instantes invisível, e depois visível, e depois invisível e assim sucessivamente.
Ajustamos o intervalo de tempo para 500, mas você pode alterar o valor para mais ou menos, no Code
Editor ou no Object Inspector. Haverá um decremento (diminuição do valor em razão de 1) do número
apresentado no Label, a cada piscada. Ou seja, o primeiro valor apresentado será 10; o segundo, 9; o
terceiro, 7; e assim por diante. Observe bem o que acontecerá depois que 0 (zero) for apresentado no
Label, onde, equivocadamente, poderíamos estar esperando algum valor negativo:
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
AnsiString
O C++Builder implementa o tipo AnsiString como uma classe. AnsiString é projetado para funcionar
como o tipo long string Delphi. Adequadamente, AnsiString fornece as seguintes características de
tratamento de strings que são requeridas quando você chama funções do tipo VCL que usam qualquer
tipo long string Delphi:
• reference count
• string length
Se você não fornecer um valor inicial, as variáveis AnsiString são iniciadas com instância zero.
Podemos conceituar uma string como um caracter, ou uma seqüência de caracteres colocados entre aspas
duplas:
"Alô, Thérbio!"
"Oba!"
"ô"
A classe AnsiString possui um bom nível de independência e flexibilidade nos controles onde é usada,
uma vez que não é descendente de Tobject, permitindo-nos realizar diversas operações úteis com strings.
A implementação desse arquivo pode ser visualizada no diretório include/vcl/dstring.h da pasta de
instalação de seu compilador:
#ifndef DSTRING_H
#define DSTRING_H
#include <sysmac.h>
namespace System
{
class TVarRec;
class RTL_DELPHIRETURN Currency;
class RTL_DELPHIRETURN WideString;
/////////////////////////////////////////////////////////////////////////////
// AnsiString: String class compatible with Delphi's Native 'string' type
/////////////////////////////////////////////////////////////////////////////
class RTL_DELPHIRETURN AnsiString
{
friend AnsiString __fastcall operator +(const char*, const AnsiString& rhs);
public:
// the TStringFloatFormat enum is used by FloatToStrF
enum TStringFloatFormat
{sffGeneral, sffExponent, sffFixed, sffNumber, sffCurrency};
static AnsiString __fastcall StringOfChar(char ch, int count);
static AnsiString __fastcall LoadStr(int ident);
static AnsiString __fastcall LoadStr(HINSTANCE hInstance, int ident);
static AnsiString __fastcall FmtLoadStr(int ident, const TVarRec *args,
int size);
AnsiString& __fastcall LoadString(HINSTANCE hInstance, int ident);
// Delphi style 'Format'
//
static AnsiString __fastcall Format(const AnsiString& format,
const TVarRec *args, int size);
// Destructor
__fastcall ~AnsiString();
// Assignments
AnsiString& __fastcall operator =(const AnsiString& rhs);
AnsiString& __fastcall operator +=(const AnsiString& rhs);
// Comparisons
bool __fastcall operator ==(const AnsiString& rhs) const;
bool __fastcall operator !=(const AnsiString& rhs) const;
bool __fastcall operator <(const AnsiString& rhs) const;
bool __fastcall operator >(const AnsiString& rhs) const;
bool __fastcall operator <=(const AnsiString& rhs) const;
bool __fastcall operator >=(const AnsiString& rhs) const;
int __fastcall AnsiCompare(const AnsiString& rhs) const;
int __fastcall AnsiCompareIC(const AnsiString& rhs) const; //ignorecase
protected:
AnsiString& m_Ref;
int m_Index;
};
public:
TCharProxy __fastcall operator [](const int idx)
{
ThrowIfOutOfRange(idx); // Should Range-checking be optional to avoid overhead ??
return TCharProxy(*this, idx);
}
#else
#endif
// Concatenation
AnsiString __fastcall operator +(const AnsiString& rhs) const;
// C string operator
char* __fastcall c_str() const { return (Data)? Data: "";}
// Modify string
AnsiString& __fastcall Insert(const AnsiString& str, int index);
AnsiString& __fastcall Delete(int index, int count);
AnsiString& __fastcall SetLength(int newLength);
// Convert to Unicode
int __fastcall WideCharBufSize() const;
wchar_t* __fastcall WideChar(wchar_t* dest, int destSize) const;
// MBCS support
enum TStringMbcsByteType
{mbSingleByte, mbLeadByte, mbTrailByte};
protected:
void ThrowIfOutOfRange(int idx) const;
private:
char *Data;
};
#if defined(VCL_IOSTREAM)
ostream& operator << (ostream& os, const AnsiString& arg);
istream& operator >> (istream& is, AnsiString& arg);
#endif
/////////////////////////////////////////////////////////////////////////////
// SmallStringBase
/////////////////////////////////////////////////////////////////////////////
template <unsigned char sz> class SmallStringBase
{
protected:
/////////////////////////////////////////////////////////////////////////////
// SmallString
/////////////////////////////////////////////////////////////////////////////
template <unsigned char sz> class SmallString : SmallStringBase<sz>
{
#if defined(VCL_IOSTREAM)
friend ostream& operator <<(ostream& os, const SmallString& arg)
{
os << AnsiString(arg);
return os;
}
friend istream& operator >>(istream& is, SmallString& arg)
{
AnsiString s;
is >> s;
arg = s;
return is;
}
#endif
public:
__fastcall SmallString() { Len = 0; }
__fastcall SmallString(const SmallString& src);
__fastcall SmallString(const char* src);
Nota: Caption é o rótulo que pode ser estampado no componente, suportando mudanças em
tempo de execução. Controles que exibem textos, fazem-no através da propriedade Caption
ou da propriedade Text. A propriedade a ser usada dependerá do tipo do controle. De um
modo geral, Caption é usado por textos que aparecem como títulos de uma janela ou um
rótulo (estampa), enquanto Text é usado por textos que aparecem como conteúdo de um
controle. Via de regra, Text podem ser editados pelo usuário, enquanto Caption é uma
propriedade que não recebe o foco da aplicação, tendo como característica a finalidade básica
de enviar uma informação ao usuário.
Muitos controles usam propriedades da classe AnsiString. Por exemplo, todos os controles que possuem
rótulo (forms, edits, panels, labels) usam AnsiString através da propriedade Caption. Outros controles
como o EditBox usam a classe AnsiString como base de seus textos (propriedade Text). Se repararmos
bem, notaremos que nós já temos usado e implementado objetos AnsiString sem qualquer espécie de
declaração. Em outra situação qualquer, a declaração e inicialização de uma string sempre será necessária
antes do uso respectivo.
A declaração de uma String é análoga à declaração de um tipo básico, porém usando a palavra
AnsiString seguida de um nome válido C++. Eis um exemplo:
AnsiString Pais;
Em dstring.h podemos observar que AnsiString é uma classe com seu próprio construtor e destruidor:
// Constructors
__fastcall AnsiString(): Data(0) {}
__fastcall AnsiString(const char* src);
__fastcall AnsiString(const AnsiString& src);
// __fastcall AnsiString(const char* src, unsigned char len);
__fastcall AnsiString(const char* src, unsigned int len);
__fastcall AnsiString(const wchar_t* src);
__fastcall AnsiString(char src);
__fastcall AnsiString(short);
__fastcall AnsiString(unsigned short);
__fastcall AnsiString(int src);
// Destructor
__fastcall ~AnsiString();
Logo você também pode declarar uma variável dela com parênteses vazios, determinando a chamada do
construtor da classe. Eis um exemplo:
AnsiString Animal();
Há dois modos principais para você iniciar uma variável AnsiString. Depois de declará-la, pode-se
determinar o valor desejado para a variável usando o nome escolhido. Eis um exemplo:
AnsiString Especie;
Especie = "Cachorro";
também podemos, tal qual nos tipos básicos, inicializar a variável String na sua declaração, fornecendo o
valor desejado para o nome escolhido. Eis um exemplo:
Uma vez definida, a String poderá ser usada, por exemplo, para alterar o Caption de controles:
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
AnsiString Meu_Cao;
Meu_Cao = " O Tobby é um vira-lata, que só sabe latir, latir e latir...";
Label1->Caption = Meu_Cao;
}
//---------------------------------------------------------------------------
Um texto introduzido num Edit box, por exemplo, é, por padrão, considerado uma String, visto que o
compilador não pode deduzir que espécie de dado ou valor o usuário deseja manipular. Por essa razão,
Conforme apresentado, a classe AnsiString provê um grande número de construtores que nos permitem a
criação de strings de qualquer espécie:
um caracter:
AnsiString Simbolo = 'T';
um inteiro
AnsiString Int = 120;
um long int
AnsiString LongoInt = -73495745;
Um double:
AnsiString DplPrec = 2.15e28;
um string
AnsiString AloTher = "Alô, Thérbio";
Baseado na configuração dos construtores AnsiString, podemos converter qualquer valor e torná-lo
disponível para um controle que use as propriedades AnsiString. Por exemplo, podemos converter e
exibir:
um inteiro:
um long integer:
um double:
uma string
Button2->Caption = AnsiString(Servico);
Nota: Você deve ter notado o uso de nomes de tipos diferentes dos apresentados anteriormente.
Por exemplo:
chamamos o tipo int de Integer, o tipo float de Single; o tipo double de Double ...
#include <sysmac.h>
NOTA: Logo abordaremos o uso da palavra typedef para criar outro nome para um tipo de
dados:
Vejamos um trecho do arquivo sysmac.h:
Os nomes à direita representam os sinônimos que podem ser usados em substituição aos tipos básicos. Por
exemplo, Boolean pode ser usado em substituição ao tipo bool.
// Modify string
AnsiString& __fastcall Insert(const AnsiString& str, int index);
AnsiString& __fastcall Delete(int index, int count);
AnsiString& __fastcall SetLength(int newLength);
Insert()
AnsiString& __fastcall Insert(const AnsiString& str, int index);
insere uma string especificada dentro de AnsiString, iniciando a inserção na posição determinada pela
variável index.
O exemplo abaixo leva um Label no Form. Quando o usuário dá um clique no Label, o programa
providencia a inserção de uma string dentro de outra.
Delete()
AnsiString& __fastcall Delete(int index, int count);
SetLength()
AnsiString& __fastcall SetLength(int newLength);
Determina um novo tamanho para a string, especificado por newLength, desde que esse novo
comprimento seja menor do que o tamanho inicial. SetLength não pode aumentar o tamanho da string:
Label1->Caption = test.SetLength(14);
Pos()
int __fastcall Pos(const AnsiString& subStr) const;
a posição do primeiro caracter de uma substring especificada na string. Se a substring não for
encontrada na string, Pos() retorna “zero”.
LowerCase() e UpperCase()
AnsiString __fastcall LowerCase() const;
AnsiString __fastcall UpperCase() const;
LowerCase() transforma todas as letras da string para letras minúsculas e UpperCase() transforma
todas para maiúsculas:
Label1->Caption = test;
Label2->Caption = test.LowerCase();
Label3->Caption = test.UpperCase();
Podemos usar essas funções-membro para eliminar caracteres em branco no início (TrimLeft()), no
final (TrimRight()) e no início e no final da string (Trim()):
SubString()
Retorna uma substring especificada de uma string. A substring inicia a contagem dos caracteres em
index e termina de contá-los em count.
O exemplo abaixo possui um Button, um Edit e um Label no Form. Quando o usuário der um clique no
botão, a função membro SubString() da classe AnsiString será chamada para acessar uma substring
(parte da frase) contida na variável Frase:
//---------------------------------------------------------------------------
Nota: Como você deve ter observado, já temos utilizado AnsiString em diversas oportunidades. Também
já utilizamos a denominação String que é análoga ao uso de AnsiString (Lembre-se da palavra-chave
typedef).
Como sabemos, o C++Builder possui várias funções para manipulação de strings. Veja abaixo um
exemplo com AnsiPos(), uma função que retorna a posição de um caracter dentro de uma String:
IntToHex
Dê um clique para que o cursor fique situado sobre o nome da função e tecle F1.
O HELP do BCB deverá abrir-se automaticamente mostrando as eventuais opções para essa função. Eis
uma delas:
AnsiString::IntToHex
AnsiString
Description
Converts a number into a string containing the number's hexadecimal (base 16) representation.
Value is the number to convert. Digits indicates the minimum number of hexadecimal digits.
Percebemos que a função em questão trata da conversão de valores inteiros para hexadecimais,
devolvendo AnsiString, onde int value é o valor a ser convertido e int digits é o número de
dígitos devolvido pela função na conversão:
Um dos trabalhos que estaremos disponibilizando, sempre que possível, neste site é a tradução de partes
que consideramos fundamentais no HELP do BCB.
AnsiString continuação...
(operadores relacionais)
Embora ainda não tenhamos abordado o tema (fato reservado para lições futuras), já tivemos oportunidade
de apreciar exemplos com os comandos if e else. Por enquanto tenhamos em mente que esses comandos
são usados basicamente para efetuar comparações do tipo:
No caso acima, o termo maior está representando o operador relacional “>”. Para mais detalhes, dê uma
olhadinha no tópico que trata tal assunto.
// Comparisons
bool __fastcall operator ==(const AnsiString& rhs) const;
bool __fastcall operator !=(const AnsiString& rhs) const;
bool __fastcall operator <(const AnsiString& rhs) const;
bool __fastcall operator >(const AnsiString& rhs) const;
bool __fastcall operator <=(const AnsiString& rhs) const;
bool __fastcall operator >=(const AnsiString& rhs) const;
int __fastcall AnsiCompare(const AnsiString& rhs) const;
int __fastcall AnsiCompareIC(const AnsiString& rhs) const; //ignorecase
Depois que você estudar os comandos if e else, e os operadores relacionais, procure fazer alguns
exercícios implementando outros tipos de comparações suportadas por AnsiString , com outros
operadores relacionais.
AnsiString continuação...
(concatenation)
// Concatenation
AnsiString __fastcall operator +(const AnsiString& rhs) const;
Podemos usar o operador + para concatenar strings (colocar uma após a outra):
void __fastcall TForm1::Label1Click(TObject *Sender)
{
AnsiString AS1 ("\tTestando");
AnsiString AS2 ("\n\tAnsiString");
String S1 ("\n\nTestando");
String S2 ("\nString");
Label1 -> Caption = AS1 + AS2 + "\n\t\t\tOutro teste" + S1 + S2 + " !!!!";
}
A partir de agora assumimos que você já conhece razoavelmente a classe AnsiString. Porém ainda há
muito mais a conhecer sobre strings. Dedique algumas horas ao estudo, sempre procurando documentar
os estudos.
A palavra-chave typedef
Podemos usar a palavra-chave typedef para criar um sinônimo para um tipo de dados existente qualquer.
Isso poderá nos auxiliar quando estivermos lidando com nomes de tipos muito longos e, portanto, sujeito a
erros de digitação. Por exemplo, na área dos cabeçalhos, digitamos:
e, a partir daí, todas as vezes que quisermos declarar uma variável signed short int poderemos fazê-lo
através da declaração: ssint:
A diretiva #define
Podemos criar um nome para definir valores constantes através da diretiva de preprocessador #define.
Esse nome é conhecido por constante simbólica.
Embora Kelvin não tenha sido declarado como nenhum tipo em particular (char, float, AnsiString etc), o
compilador saberá lidar com os dados da melhor forma possível em virtude do estilo da declaração. Por
exemplo:
#define pi 10
Além da habilidade de definir constantes simbólicas, a diretiva #define pode ser usada para definir
macros, desde que se lhe forneça algum argumento:
#define SOMA(X, Y) (X + Y)
A palavra-chave const
Outra forma (melhor) que a linguagem C++ nos oferece para definir constantes é através da palavra-chave
const.
A diferença básica entre os dois tipos de declaração é o fato de que naquela (#define), é o tipo de
declaração quem informa ao compilador qual será o provável tipo, enquanto nesta (const), o tipo (char,
int etc) é declarado:
Este método é o mais recomendado na maioria dos casos, pois além de tornar o código mais fácil de ler e
manter, dificulta a introdução de bugs, posto que o compilador pode checar se a constante está sendo
usada de acordo com seu tipo.
Usando a palavra-chave enum, podemos definir um conjunto de constantes agrupadas sob um nome como
um novo tipo de dados, somando-se uma nova maneira às duas formas apresentadas anteriormente para
definição de constantes. A essa coleção de constantes, dá-se o nome de constantes enumeradas.
As constantes enumeradas possibilitam-nos a criação de novos tipos de dados, bem como a definição de
variáveis desses tipos, sendo que os valores assumidos ficam restritos a determinada coleção de valores.
Podemos, por exemplo, declarar uma enumeração representando os meses do ano:
enum Meses_do_Ano
{
janeiro,
fevereiro,
marco,
abril,
maio,
junho,
julho,
agosto,
setembro,
outubro,
novembro,
dezembro
}; // Fim de enum Meses_do_Ano
Feito isso, podemos definir variáveis do tipo Meses_do_Ano, que podem assumir apenas valores inteiros:
enum Meses_do_Ano
{
janeiro = 0,
fevereiro = 1,
marco = 2,
abril = 3,
maio = 20,
junho = 21,
julho = 22,
agosto = 56,
setembro = 57,
outubro = 58,
novembro = 200,
dezembro = 201
}
O exemplo a seguir contém um Label no Form. Quando o usuário dá um clique no Label, uma caixa de
inserção de dados aparece para que seja digitado um valor entre 1 e 12. Dependendo do valor digitado,
teremos a resposta dos mês correspondente no Label:
//---------------------------------------------------------------------------
void __fastcall TForm1::Label1Click(TObject *Sender)
{
enum Meses_do_Ano
{
janeiro = 1, // especifica o valor 1 para a primeira constante
fevereiro, // vale 2
marco, // vale 3 ...
abril,
maio,
junho,
julho,
agosto,
setembro,
outubro,
novembro,
dezembro
}; // Fim de enum Meses_do_Ano
case 2:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a fevereiro...";
case 3:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a março...";
break;
case 4:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a abril...";
break;
case 5:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a maio...";
break;
case 6:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a junho...";
break;
case 7:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a julho...";
break;
case 8:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a agosto...";
break;
case 9:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a setembro...";
break;
case 10:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a outubro...";
break;
case 12:
Label1 -> Color = clGreen;
Label1 -> Font -> Name = "Arial";
Label1 -> Font -> Color = clYellow;
Label1 -> Caption = AnsiString(iMes) + " equivale a dezembro...";
break;
default:
ShowMessage(AnsiString(iMes) + " não é um valor válido...");
break;
}
}
//---------------------------------------------------------------------------
Operadores matemáticos
A linguagem C++ possui cinco operadores matemáticos binários representados pelos seguintes
operadores:
+ (adição);
- (subtração);
* (multiplicação);
/ (divisão); e
% (módulo);
- (menos unário).
Os operadores de adição, subtração, multiplicação e divisão funcionam normalmente como nos cálculos
matemáticos usuais. Já o operador módulo fornece-nos o resto de uma divisão inteira (sem eventuais
partes fracionárias) como resultado. Por exemplo, quando fazemos a divisão de 9 por 5, o resultado é 1 e o
resto é 4.
O operador menos unário serve para trocar o sinal de seu operando (positivo para negativo ou negativo
para positivo):
O exemplo a seguir (calculadora básica) possui, no Form, três componentes Edit e cinco RadioButton(s)
dentro de um RadioGroup. Todas as propriedades do programa serão geradas em tempo de execução (no
código), e não diretamente no Object Inspector. O usuário deverá digitar um valor no Edit1, outro valor
no Edit2 e escolher uma operação matemática no RadioGroup para visualizar o resultado no Edit3.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Form1 -> Caption = "Calculadora Básica";
Form1 -> BorderStyle = bsSizeToolWin; // altera a borda de Form1
//se o valor convertido para ponto flutuante de Edit1 for maior do que o de Edit2
if (StrToFloat(Edit1 -> Text) > StrToFloat(Edit2 -> Text))
// Edit3 apresentará o resto da divisão de Edit1->Text por Edit2->Text
Edit3 -> Text = double((Edit1 -> Text) % (Edit2 -> Text));
// caso contrário
else
// Edit3 apresentará o valor 0 (zero) em seu texto
Edit3 -> Text = 0;
Edit1 -> SetFocus(); // chama a função SetFocus() para colocar o foco em Edit1
}//---------------------------------------------------------------------------
Expressões
Todo comando que ao ser efetuado retorna um valor, é considerado uma expressão em C++.
São expressões:
x = (a + b) - ((c * d) / e);
Observe as expressões no exemplo a seguir que leva apenas um Label no Form e será usado para calcular
o resultado de uma equação de segundo grau:
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
AnsiString a, b, c;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Form1->BorderStyle = bsNone; //retira as bordas e barra de títulos do programa
else
Label1 -> Caption = " a = " + a;
Label1 -> Font -> Color = clRed;
Label1 -> Font -> Style = TFontStyles()<< fsBold;
}
catch(...)
{
MessageBox(0, "Erro ... Valor não suportado pelo programa ...",
"Erro de digitação", 16);
}
try
{
b = InputBox("Caixa de Entrada de Valores",
"Digite um Valor para 'b'", "");
try
{
c = InputBox("Caixa de Entrada de Valores",
"Digite um Valor para 'c'", "");
StrToInt(c);
Label1 -> Caption = Label1 -> Caption + "\n c = " + c;
}
catch(...)
{
MessageBox(0, "Erro ... Valor não suportado pelo programa ...",
"Erro de digitação", 16);
}
try
{
Label1 -> Font -> Color = clBlue;
Label1 -> Font -> Style = TFontStyles()<< fsBold;
else
{
Label1 -> Caption =
Label1 -> Caption + "\n\ndelta = " + FloatToStr(delta);
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Inicialmente, na parte superior da janela, podemos contemplar uma linha de comentários, constituída
basicamente de vários hífens seguindo //. Como sabemos, em C++ tudo o que segue a dupla de caracteres
// será ignorado pelo compilador até o final da linha. O compilador insere essas linhas, basicamente, para
separar seções de códigos ou funções. Eles estão ali, apenas, para melhorar a visualização.
Perceba que, existem outras linhas iguais a essa no código e que sempre que codificarmos um um método
de algum evento, através da chamada no Object Inspector, essas linhas de comentário estarão lá, criadas
automaticamente para melhorar a visualização dos códigos.
#include <vcl.h>
Já sabemos que quando o pré-processador encontra a diretiva #include em nosso código fonte, ele
substitui a mesma pelo conteúdo do arquivo indicado, sendo que o compilador encontrará e processará o
arquivo implementado, e não a diretiva de compilação. Avançado no estudo do código inicial inserido
automaticamente pelo C++Builder, encontramos essa diretiva de determina a inclusão do arquivo
chamado vcl.h:
Se você instalou o C++Builder no diretório padrão, poderá abrir e visualizar esse arquivo em
C:\Arquivos de programas\Borland\CBuilder6\Include\Vcl\vcl.h
/////////////////////////////////////////////////////////////////////////////////
// VCL.H - Borland C++ Builder pre-compiled header file
// $Revision: 1.12.1.0.1.1 $
// Copyright (c) 1997, 2002 Borland Software Corporation
/////////////////////////////////////////////////////////////////////////////////
#ifndef VCL_H
#define VCL_H
#define INC_VCL
#include <basepch0.h>
#endif // VCL_H
A vcl.h é parte da Visual Component Library usada para linkar a definição de componentes como botões,
menus etc. Perceba que no arquivo em questão aparece novamente um include. Vejamos o conteúdo do
arquivo basepch0.h:
/////////////////////////////////////////////////////////////////////////////////
// BASEPCH0.H - Borland C++ Builder pre-compiled header file
// $Revision: 1.0.1.0.1.0 $
// Copyright (c) 1997, 2002 Borland Software Corporation
//
// BASEPCH0.H is the core header that includes VCL/CLX headers. The headers
// included by BASEPCH0.H are governed by the following macros:
//
// MACRO DESCRIPTION DEFAULT
// ======= ============= =======
//
// NO_WIN32_LEAN_AND_MEAN When this macro is defined, BASEPCH.H does OFF
// not define WIN32_LEAN_AND_MEAN.
//
// INC_VCL This macro is defined by VCL.H to OFF
// include the base set of VCL headers
//
// VCL_FULL Same as NO_WIN32_LEAN_AND_MEAN OFF
// (NOTE: This macro is for BCB v1.0 backward
// compatibility)
//
// INC_VCLDB_HEADERS When this macro is defined, VCL.H includes
// requires INC_VCL the core Database headers of VCL. OFF
// (Defining this macro is functionally
// equivalent to including VCLDB.H)
//
// INC_VCLEXT_HEADERS When this macro is defined, VCL.H includes
// requires INC_VCL all VCL headers. OFF
// (Defining this macro is functionally
#endif // INC_VCL
#endif // INC_CLX
#if defined(INC_OLE_HEADERS)
#include <cguid.h>
#include <dir.h>
#include <malloc.h>
#include <objbase.h>
#include <ole2.h>
#include <shellapi.h>
#include <stddef.h>
#include <tchar.h>
#include <urlmon.h>
#include <AxCtrls.hpp>
#include <OleCtnrs.hpp>
#include <OleCtrls.hpp>
#endif
// Using ATLVCL.H
//
#if defined(INC_ATL_HEADERS)
#include <atl\atlvcl.h>
#endif
#if defined(UNDEF_COM_NO_WINDOWS_H) //Clean up MACRO to prevent inclusion of WINDOWS.H/OLE2.H
#undef COM_NO_WINDOWS_H
#undef UNDEF_COM_NO_WINDOWS_H
#endif
#endif // __BASEPCH0_H__
Veja quantos include. Por exemplo, #include <Buttons.hpp>, #include <Menus.hpp> etc. Um bom
conhecimento dessas bibliotecas, pode nos facilitar bastante a compreensão. Todavia, por enquanto, não
se preocupe com isso, mesmo porque o compilador cuida muito bem da tarefa, à margem de nosso
profundo, ou escasso, conhecimento de suas particularidades. Quanto ao estudo das diretivas, no avançar
do curso procuraremos conhecer pelo menos um pouco de cada uma delas.
#pragma hdrstop
Através da diretiva #pragma, o C++Builder pode definir diretivas que podem ser passadas ao compilador.
Se o compilador não identificar o nome da diretiva, ele simplesmente ignora #pragma sem emitir qualquer
aviso ou mensagem de ERRO. A sintaxe de pragma é #pragma nome_da_diretiva.
#pragma alignment
#pragma anon_struct
#pragma argsused
#pragma checkoption
#pragma codeseg
#pragma comment
#pragma defineonoption
#pragma exit
#pragma hdrfile
#pragma hdrstop
#pragma inline
#pragma intrinsic
#pragma link
#pragma message
#pragma nopushoptwarn
#pragma obsolete
#pragma option
#pragma package
#pragma resource
#pragma startup
#pragma undefineonoption
#pragma warn
Descrição: Esta diretiva termina a lista de hearde files escolhidos para pré-compilação. Você pode usá-lo
para reduzir a quantia de espaço em disco usados por headers de pré-compilação.
Hearder files pré-compilados podem ser compartilhados entre os arquivos fonte de seu projeto sendo que
somente as diretivas #include antes de #pragma hdrstop são idênticas (comuns a todos os arquivos do
projeto). Então, você adquire o melhor desempenho do seu compilador se você incluir os header files
comuns de seu projeto antes de #pragma hdrstop, e os específicos depois disto. Certifique-se de ter
inserido as diretivas #include antes de #pragma hdrstop de forma indêntica em todo o código fonte, ou
que haja apenas pequenas variações.
O ambiente de desenvolvimento integrado gera o código para aumentar desempenho de header pré-
compilados. Por exemplo, depois de uma Aplicação Nova, o arquivo fonte " Unit1.cpp" terá a seguinte
aparência (comentários adicionados):
// ....
Só se usa essa diretiva pragma em arquivos fonte. Ela não tem nenhum efeito quando usada em um
arquivo header (.h, .hpp etc).
“Unit1.h”
A seguir, percebemos mais um enunciado include inserido automaticamente pelo BCB:
O arquivo de cabeçalho “Unit1.h” é criado simultaneamente com o arquivo “Unit1.cpp” que é o arquivo
que contém o código principal de um aplicativo (aquele onde temos efetuado a maioria das inclusões de
códigos até agora). Já sabemos que os cabeçalhos colocados entre aspas “...” determinam que o pré-
processador procure o cabeçalho primeiro no diretório atual, e depois na biblioteca padrão (Include).
Entende-se por diretório atual, aquele onde salvamos o projeto, ou seja, onde se encontram os arquivos do
projeto gerados pelo sistema no desenvolvimento do programa. Eventualmente, se o arquivo não se
encontrar em nenhum desses dois diretórios, podemos indicar o seu caminho completo entre as aspas:
“C:\\Meu_aplicativo\\Unit1.h”
Evidentemente que Unit1.h é o nome padrão dado pelo C++Builder ao arquivo. Se, num projeto,
salvarmos os arquivos com nomes diferentes, esses nomes serão aqueles que encontraremos
acompanhados do “.h” e do “.cpp”. Por exemplo, “Edit_Texto.h” e “Edit_Text.cpp”. Outra questão
importante diz respeito ao número 1 entre Unit e o .h ou o .cpp. Esse número refere-se ao número do
componente na aplicação. Se colocarmos um segundo Form no aplicativo, o arquivo principal dele será
nomeado de Unit2.cpp; um terceiro, de Unit3.cpp , e assim por diante. Evidentemente, conforme exposto,
não precisamos concordar com nomes padrão.
Em outra seção, abordaremos técnicas de como dar nomes a nossas aplicações e aos componentes nelas
inseridos. Porém, no nosso curso, raramente trocaremos o nome padrão fornecido pelo compilador. Por
um motivo muito simples. O nome padrão facilita a compreensão para estudantes de línguas diferentes,
que não compreendem nosso idioma. Quando eu fazia pesquisas na internet, várias vezes pude aproveitar
códigos, cujos autores eu nem poderia, de mim mesmo, imaginar a nacionalidade, de tão complexa a
forma de escrever (tipo chinês, russo, grego etc). Mesmo assim, quando os códigos eram mantidos no
default fornecido pelo C++Builder, eram-me bastante claros (exceto strings e comentários,
evidentemente).
Penso que é o mínimo que posso fazer, uma vez que a maior parte do que consegui aprender sobre
programação foi através da Internet, através de uma infinidades de generosos professores anônimos.
Porém, quando eu aproveitar exemplos que já fiz anteriormente, alterando os nomes, manterei estes
nomes.
“#pragma package(smart_init)”
Sintaxe:
#pragma package(smart_init)
Esta diretiva pragma afeta a ordem de inicialização desta unidade de compilação. Para units, a
inicialização ocorre na seguinte ordem:
1. Pelas suas dependências de "usos", quer dizer, se unitA depende de unitB, unitB deve ser inicializa
antes de unitA.
2. A ordem de vínculo.
para arquivos .OBJ regulares (aqueles não construídos como units), a inicialização acontece primeiro de
acordo com a ordem de prioridade e, então, de acordo com a ordem de vínculo. Mudando a ordem de
vínculo dos arquivos .OBJ muda-se a ordem na qual os objetos construtores globais são chamados.
Os exemplos seguintes mostram como a inicialização difere entre units e arquivos .OBJ regulares.
Tome como um exemplo três arquivos unit, A, B e C que são “inteligentemente inicializados” com
#pragma package(smart_init) e possuem valor de prioridade (definidas no parâmetro prioridades do
#pragma startup) setados de 10, 20 e 30. As funções são nomeadas de acordo com os seus valores de
prioridades e o parent .OBJ. assim os nomes são a10, a20, a30, b10, e assim por diante.
Desde que todas as três são units, e se A usa B e C e a ordem de vínculo é A, B e então C, a ordem de
inicialização é:
Os arquivos .CPP que usam #pragma package(smart_init) também requerem que algum #pragma link
referencie outro arquivo .OBJ desde um arquivo .CPP que declara #pragma package(smart_init), e deve
estar resolvido na unit. Se #pragma link fizer referências para arquivos não .OBJ pode ainda ser
resolvido por bibliotecas etc.
A diretiva #pragma package(smart_init, weak) afeta o modo que um arquivo .OBJ é armazenado num
package’s .BPI e arquivos .BPL. Se #pragma package(smart_init, weak) aparece em um arquivo de unit,
#pragma package(smart_init, weak) é usado para eliminar conflitos entre packages que possivelmente
dependem da mesma biblioteca externa.
Arquivos Unit contendo a diretiva #pragma package(smart_init, weak) não devem ter variáveis globais.
para maiores informações sobre uso weak packages, veja Weak packaging.
#pragma resource
Sintaxe:
Descrição: Esta diretiva pragma faz o arquivo ser marcado como uma unidade de Form e requer
combinação .dfm. Ou seja, ela determina que o compilador utilize tal arquivo (.dfm) de definição do
Form, requisitado por essa Unit. Tais arquivos são administrados pelo IDE.
Se seu Form requer qualquer variável, elas devem ser declaradas depois da inscrição do pragma resource.
As declarações devem ser pertencentes ao Form.
TFormName *Formname;
TForm
TForm representa uma janela padrão da aplicação (Form).
Unit: Forms
Descrição:
Quando você cria formulários no Form designer em tempo de projeto, eles são implementados como
descendentes de TForm. Forms podem representar a janela principal da aplicação, caixas de diálogo, ou
janelas filhas (MDI children). Um form pode conter outros objetos como TButton, TCheckBox,
TcomboBox etc.
_fastcall, __fastcall
Categoria: modificadores C++Builder, extensão de palavra-chave
Sintaxe:
Descrição:
use o modificador __fastcall para declarar funções que aguardam parâmetros para serem passados em
registros. os três primeiros parâmetros são passados (da esquerda para a direita) em EAX, EDX, e ECX,
se eles se ajustarem no registro. Os registros não são usados se o parâmetro é um ponto flutuante ou uma
estrutura (struct type).
O compilador trata essa convenção chamada como uma nova language, ao longo das linhas of _cdecl e
_pascal
Funções declaradas usando _cdecl ou _pascal não podem ter os modificadores _fastcall porque eles usam
a pilha para passar os parâmetros. Igualmente, o modificador __fastcall não pode ser usado junto com
_export.
O compilador antepõe o nome da função __fastcall com um a-sinal (at-sign) ("@"). Este prefixo aplica-se
a desqualificação de nomes de função C e para C++.
Nota: o modificador __fastcall está sujeito desqualificação do nome da função. Veja a descrição do -VC
option.
TComponent
TComponent é o ancestral comum de todos os componentes que podem aparecer no form designer.
Descrição:
1.A capacidade de aparecer no Componente palette e ser transferido para o form designer.
2.A capacidade de administrar-se, a si, e a outros componentes.
3.Incrementar o fluxo e armazenar capacidades.
4.A capacidade de ser convertido em um controle ActiveX ou outro objeto COM por wizards na página
ActiveX do diálogo New Objects.
Não crie instâncias de TComponent. Use TComponent como uma classe base quando declarar um
componente não-visual que pode mostrar-se no component palette e ser usado no form designer.
Propriedades e métodos de TComponent fornecem comportamentos básicos que classes dependentes
herdam e também comportamentos que componentes podem ativar para ajustar seu comportamento.
Para criar componentes os quais são visíveis em tempo de execução para os usuários, use TControl ou
seus descendentes como base. Para criar controles baseados em objetos janelas, use TWinControl ou seus
descendentes como base.
TComponent::Owner
Owner = Proprietário
Descrição:
A memória referente ao componente é liberada assim que a memória de seu proprietáro é liberada.
O Owner é responsável por carregar e salvar as propriedades published de seu controle proprietário.
Por default, um form possui todos os componentes que estão nele. Por sua vez, o form é pertencente à
aplicação. Assim, quando a aplicação é encerrada e a memória correspondente é liberada, a memória para
todos os forms (e todos os seus componentes) também é liberada. Quando um form é carregado na
memória, automaticamente são carregados todos os componentes que nele estão instalados.
Aviso: Se um componente tem um proprietário diferente de um form ou módulo de dados, ele não será
salvo ou carregado com seu proprietário, a menos que você o identifique como um subcomponente. Para
identificar um componente como um subcomponente, chame o método SetSubComponent.
O operador de incremento é formado por dois sinais de adição ++ e adiciona 1 ao valor da variável à qual
é aplicado. Num exemplo anterior (Operações matemáticas com unsigned), a expressão:
uiAcresce = uiAcresce + 1;
uiAcresce++;
Já o operador de decremento é formado por dois sinais de subração -- e subtrai 1 do valor da variável à
qual é aplicado:
uiDecresce--;
é equivalente a:
uiDecresce = uiDecresce - 1;
É relevante a posição dos operadores de incremento (++) ou decremento (--) em relação à variável:
++uiAcresce; // operador antecedendo do nome da variável - prefixo
--uiDecresce; // prefixo
uiDecresce--; // sufixo
O exemplo abaixo usa um Button e um Memo no Form. Quando o usuário dá cliques no botão, as
variáveis são, conforme o caso, incrementadas ou decrementadas, com operadores pós e pré-fixados. A
diferença dos resultados que tais operadores operam nas variáveis nessas diversas situações é demonstrada
no Memo:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
int Acresce = 100, ACRESCE = 100,
Decresce = 100, DECRESCE = 100;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
Operadores relacionais
Existem alguns operadores que fazem comparações e, por isso, são chamados de relacionais:
> maior
>= maior ou igual
< menor
<= menor ou igual
== igual a
Os operadores relacionais são bastante usados pelas partes dos programas que trabalham com laços e
comandos de decisão. O exemplo a seguir usa um desses operadores num programa que coloca um efeito
degradê no form:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
O comando if
No início deste curso informamos que usaríamos alguns nomes, comandos ou tipos de dados com os quais
você poderia não estar habituado a trabalhar. Em algumas ocasiões, já tivemos oportunidade de usar o
Dentro de cada bloco de códigos, o fluxo de execução de um programa faz com que as linhas sejam
executadas na ordem em que aparecem. Por exemplo:
Edit1 -> Text == "";
Edit1 -> SetFocus();
Nestas duas linhas acima, primeiro será esvaziado o Text de Edit1 que depois receberá o foco.
Todavia, é bastante comum um programa precisar quebrar esse fluxo na execução, seguindo por um
caminho diferente, deixando de executar determinadas instruções em resposta a certas condições. O
comando if permite testar uma condição (por exemplo, se o valor de uma variável booleana é verdadeiro -
true) e seguir para uma ou outra parte do código em virtude do resultado desse teste.
if(expressão)
comando;
É mais comum que a expressão avaliada pelo comando if seja do tipo relacional, mas, em princípio, pode
ser de qualquer tipo. Feita a avaliação pelo comando, se o resultado da expressão for zero, ela é
considerada falsa, e o comando não será executado. Se o valor da expressão for diferente de zero, ela é
considerada verdadeira, e o comando será executado.
No exemplo:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(Edit1 -> Text == "")
Edit1 -> SetFocus();
}
Quando o usuário der um clique no botão, o foco ira para Edit1 somente se Edit1 estiver vazio.
O exemplo a seguir usa um ComboBox, um Label e um Button no Form. Quando o usuário dá um clique
no botão, IndexOf() faz uma busca pelo item que contém a string Teste no ComboBox. Se o item é
encontrado, o valor numérico do item é atribuído à variável que passará a ter um valor igual ou maior do
que zero (zero corresponde ao primeiro item da relação no ComboBox, 1 ao segundo e assim por diante).
Feito isso, o programa chama Delete() para apagar o item correspondente à busca. Consecutivamente, o
programa informa o valor da variável no Label.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
/* Adiciona cabeçalho (texto) e items no
ComboBox na inicialização do programa*/
ComboBox1 -> Text = "Fatos e Testes";
ComboBox1 -> Items -> Add("Hoje é 22 de julho de 2002");
ComboBox1 -> Items -> Add("Teste");
ComboBox1 -> Items -> Add("Está fazendo um pouco de frio");
ComboBox1 -> Items -> Add("A Marta é professora");
ComboBox1 -> Items -> Add("Ela está trabalhando no Jd. Álamo");
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
if(expressão)
comando;
else
outro_comando;
O Exemplo a seguir usa um Edit, um Label e um Button no Form. Quando o usuário dá um clique no
botão, o programa fará uma busca pelo arquivo, cujo caminho estiver especificado no Edit. O resultado da
busca será informado no Label.
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
/* Declara e inicializa variável BuscArquiv com o resultado da busca
por um arquivo, cujo caminho completo é especificado em Edit1 */
AnsiString BuscArquiv = FileSearch(Edit1->Text, GetCurrentDir());
// Se BuscArquiv estivar vazia (pelo fato de a busca fracassar)
if (BuscArquiv == "")
// O Label informará que não encontrou o arquivo
Label1 -> Caption = "Não foi possível encontrar " + Edit1->Text + ".";
// caso contrário (Se houver algum dado em BuscArquiv)
else
// Label informará que encontrou o arquivo
Label1 -> Caption = "Encontrado " + BuscArquiv + ".";
}
//---------------------------------------------------------------------------
if ...else - Continuação
O comando if ... else comporta em seu interior quaisquer outros comandos, incluindo combinações else if,
if ... else:
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
Nota: O comando else nem sempre estará associado ao comando if mais recente. Um comando if contido
com todas as suas instruções num bloco de chaves {} deverá remeter o else (fora desse bloco) para o if
antecedente. Um bloco de código, em virtude de "pequenas" alterações, poderá apresentar resultados
totalmente diferentes. Por exemplo:
//-----------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
Esse tipo de codificação pode se tornar difícil de escrever e interpretar, induzindo a erros. Daí a
necessidade do adequado uso das chaves {}, bem como de recursos que facilitem a escrita como a
indentação do código.
Promover à indentação do código significa indicar os níveis de aninhamento de suas diversas partes,
afastando-se gradualmente (através de espaços ou tabulações) as linhas (ou blocos) de código da margem
da página:
Operadores lógicos
Pode ocorrer de um comando precisar receber mais de uma resposta para tomar uma decisão.
&& significa: E
|| significa: OU
! significa: NÃO
A sintaxe é:
expressão_1 || expressão_2
se pelo menos uma das expressões for verdadeira, o resultado será verdadeiro;
! expressão
Como os dois primeiros operadores avaliam duas expressões, são classificados como binários; já o último,
unário.
Nota: O tipo TShiftState é usado para eventos de teclado e eventos do mouse para determinar o estado das teclas
Alt, Ctrl e Shift, bem como dos botões do mouse, quando ocorrer um evento. Trata-se de um grupo de flags que
indica o seguinte:
O exemplo a seguir leva um label no Form. Conforme o usuário produzir um dos eventos acima, uma
resposta diferente será dada pelo programa:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
É feita uma avaliação da condição. Se ela for verdadeira, o programa retornará a expressão_1; se falsa,
a expressão_2. A nomenclatura ternário decorre do fato de o operador manipular três termos. Embora o
operador condicional ternário seja constituído de dois caracteres (? :), trata-se de um único operador.
O exemplo a seguir leva um Edit no Form e é usado para permitir a edição de valores numéricos inteiros:
//---------------------------------------------------------------------------
void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key)
{
isdigit(Key)? Key<48 || Key>57 : Key = 0;
}
//---------------------------------------------------------------------------
Funções
Todos os aplicativos escritos em linguagem C ou C++ iniciam a execução do programa através de uma
função principal. Nas aplicações tipo Console, essa função denomina-se main. Aplicações Win32 GUI
chamam WinMain para iniciar a execução:
Não entraremos em detalhes sobre essa função, visto que tal assunto está reservado para outro curso, sobre
WinAPI, que em breve esperamos estar disponibilizando neste Sítio:
Eis um arquivo básico de um Project.cpp do C++Builder, onde podemos visualizar a função principal:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
USERES("Project2.res");
USEFORM("Unit1.cpp", Form1);
//---------------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
//---------------------------------------------------------------------------
A função acima encontra-se num arquivo em separado daqueles que estamos acostumados a trabalhar.
Via de regra, em C++, é irrelevante se a função inicial está colocada no início ou no fim do arquivo, pois o
programa ignorará o fato, executando do mesmo jeito, tanto numa quanto noutra posição. Evidentemente,
no BCB não devemos alterar, sem prévio conhecimento, o conteúdo ou disposição da função WinAPI.
As instruções em C++ aparecem dentro de alguma função, ou seja, de um grupo de comandos que executa
alguma tarefa, sendo que as funções podem conter instruções que chamam outras funções, bem como
retornar algum valor no seu encerramento para a instrução chamadora.
Criamos a função AloCy() no exemplo abaixo. Quando o usuário der um clique no botão colocado no
Form, AloCy() será chamada para imprimir uma mensagem no Label1.
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
void AloCy(); // Protótipo da função
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
NOTA: Esclarecemos que neste e nos próximos tópicos estaremos trabalhando com funções de uma
maneira que, embora não seja incorreta, não é a convencional para o C++Builder. Basicamente
estaremos preocupados em conhecer os fundamentos das funções em C++, sem nos preocupar se essa é a
forma ideal de se trabalhar com o BCB.
Chamada de Funções
Já tivemos oportunidade de, em tópicos anteriores, verificar que todo programa C++ deve ter uma função
principal denominada main() ou, conforme o caso, WinMain(), não podendo haver outra função com
esse mesmo nome, sendo que a execução do programa sempre será iniciado por essa função. As funções
main() e WinMain() são diferentes das outras funções, visto que estas são chamadas após iniciada e ao
longo da execução do programa.
Evidente que uma função, quando é chamada, pode também chamar a uma outra função:
//-------------------ilustra chamada de funções-----------------------------
#include <vcl.h>
#pragma hdrstop
seguida pelos comandos que executam a tarefa da função, seguidos pela chave de fechamento
Por exemplo:
O Exemplo abaixo possui um Button, um Edit e um Label. Preste bastante atenção nos comentários, pois
eles explicam os códigos com detalhes:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
/* Protótipo da função com o tipo retornado,
o nome da função e o parâmetro entre parênteses*/
AnsiString Recb_Pass(AnsiString);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
/* Cria um texto para Edit1 e um caption para Label1 em tempo de execução*/
Edit1 -> Text = "Essa string será passada para"
" a função Recb_Pass() que a imprimirá em Label1";
Protótipos de funções
Não podemos usar uma função sem declará-la previamente. Trata-se duma instrução geralmente colocada
no início do programa ou do arquivo, obrigatoriamente antecedendo a definição e a chamada da função.
O protótipo informa ao compilador o tipo que a função retorna, o nome da função, bem como os
parâmetros que ela recebe. Eis um exemplo:
Esse protótipo está declarando uma função chamada diametro, que recebe um valor inteiro (a declaração
int dentro dos parênteses: diametro(int raio)) e devolve um valor ponto flutuante (o double que
antecede o nome da função: double diâmetro()). Poderiam ser quaisquer outros tipos.
O protótipo e a definição da função devem conter o mesmo nome, o mesmo tipo retornado e a mesma lista
de parâmetros:
Se nenhum tipo for especificado como valor de retorno, o compilador retornará o tipo int:
O exemplo:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
Experimenta_Tipos(char raio); // Protótipo da função
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Experimente, nesse mesmo exemplo, colocar como valor de retorno o tipo char. Depois faça o mesmo
como o tipo double. Com essas pequenas pequenas alterações, você poderá visualizar os diferentes
comportamentos do compilador.
Além da forma de declaração apresentada (protótipo externo, pois é escrito fora e antes das funções),
existe a possibilidade de declararmos o protótipo dentro do corpo de cada função chamadora (protótipo
interno).
1ª. A função é visível apenas dentro do bloco onde estiver o seu protótipo;
2ª. devemos declarar o tipo retornado no protótipo (ou void), para que o compilador não interprete o
protótipo como uma chamada à função e, conseqüentemente, retorne uma mensagem de erro:
//---------------------------------------------------------------------------
Comecemos pelas variáveis locais. Imaginemos o seguinte bloco de códigos com as respectivas
mensagens de erro na forma de comentários:
Analisemos o código. No exemplo acima visualizamos dois blocos de código: um externo e outro interno.
1 - O componente Label1 não enxerga a variável porque sua chamada encontra-se antes e fora do bloco da
declaração de a;
2 - não temos qualquer problema com o componente Label2 porque sua chamada se encontra no mesmo
bloco e após as declarações das variáveis a e b;
4 - Label4 não enxerga a variável c pelo fato de estar fora do bloco onde tal variável é declarada.
Conclusão: podemos entender como locais as variáveis que se encontram declaradas dentro de
determinado bloco, com a ressalva de que essas variáveis são visíveis apenas dentro desse bloco e a partir
do local de sua declaração:
Já as variáveis globais devem ser declaradas antes e fora de qualquer função, uma vez que elas serão
visíveis somente pelas instruções posteriores à sua declaração. Logo se você quiser que uma variável seja
"vista" por todas as funções de uma determinada Unit, pode declará-la na região dos include. A seguir
ilustramos um exemplo que demonstra a o relacionamento existente entre as variáveis globais e locais:
//---------------------------------------------------------------------------
// Unit1.cpp
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
int global_1 = 50; // declara e inicializa variável global
//---------------------------------------------------------------------------
int global_2; //variável que será visível somente pelas funções abaixo dela
}
//---------------------------------------------------------------------------
Muito importante: as variáveis locais são criadas pelo aplicativo cada vez que sua declaração é
encontrada pela execução do programa e são destruídas cada vez que a execução sai do bloco em que elas
se encontram; já as variáveis globais são criadas uma única vez no início do programa e destruídas uma
única vez no encerramento do programa.
O resultado da observação acima é que, cada vez que uma variável local for destruída, se ela for criada
novamente, não terá conservado o valor que possuía no momento em que foi destruída. Será inicializada
novamente com o valor que lhe for atribuído no momento apropriado.
Quanto às variáveis globais, seus valores vão sendo alterados durante a execução do programa, sendo que
cada vez que tal variável for chamada trará em si o valor resultante da última operação em que foi tratada.
A palavra-chave extern
As palavras-chaves extern e static são recursos oferecidos pela linguagem C++ para alterar o
comportamento dos conceitos local e global, abordados no tópico anterior.
A palavra-chave extern pode ser usada para que um arquivo fonte possa usar uma variável global que se
encontra definida em outro arquivo fonte.
Unit1.h
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
int a, b;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
a = 100;
b = 200;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Label1Click(TObject *Sender)
{
Label1->Caption = a + b;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
Form2->ShowModal();
}
//---------------------------------------------------------------------------
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
//---------------------------------------------------------------------------
extern int a;
extern int b;
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
void __fastcall TForm2::Button1Click(TObject *Sender)
{
a = 1000;
b = 2000;
}
//---------------------------------------------------------------------------
O recurso extern é um modo de trabalhar uma mesma variável em mais de um arquivo, guardando sempre
o último valor atribuído à variável, não importando em qual arquivo se tenha dado tal atribuição.
Em C++ , extern não é mais tão necessário por causa do conceito de namespace, o qual é mais facilmente
gerenciado em sistemas grandes. Futuramente abordaremos esse conceito.
A palavra-chave static
As palavra-chave static, usada junto a variáveis, possui uma amplitude maior, visto que possui três
significados distintos. Vejamos cada um deles:
1 - Dentro de um bloco:
O exemplo acima sempre exibirá o número 32 no label. Isto acontece porque cada vez que a função é
chamada, a variável numero é criada com o valor dezesseis, e quando a função acaba, ela é destruída. Se
alterarmos a forma de declarar a variável, inserindo a palavra-chave static:
tudo muda de figura. Declarada deste modo, a variável numero é criada uma única vez, e não é destruída
até o encerramento do programa. Note que ela não poderá ser usada fora do bloco onde foi declarada, mas,
exceto por esse fato, essa variável comporta-se exatamente como uma variável global. Deste modo, na
primeira vez que chamamos Label1Click(), o label exibe 32; na segunda, 64; na terceira, 128; e assim
por diante... a cada nova chamada, a variável numero já está lá, com o valor deixado na última chamada.
2 - Em variável global.
Em uma variável global, a palavra static esconde a variável de acessos através de declarações extern.
Assim se definimos a variável global numero como:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
static int numero = 10;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
esta variável global numero só poderá ser usada dentro do fonte em que foi definida.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
//---------------------------------------------------------------------------
extern int numero;
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
não apresentará o resultado esperado para extern int numero, provavelmente apresentando alguma
mensagem de erro:
Esse resultado pode ser desejado para reduzir a interferências entre equipes de programação.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
void Impr_Label_1(int a, int b); // Protótipo da função
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Na execução deste programa. ocorre o seguinte: Quando o usuário dá um clique no botão, o valor das
variáveis a e b são imprimidos no Label e é chamada a função Impr_Label_1(a, b) que recebe os
valores de a e b como argumentos. Impr_Label_1() cria uma cópia para cada argumento que lhe foi
passado e opera sobre essas cópias, alterando e imprimindo os novos valores no Label. Quando a função
retorna o controle para a função chamadora, todas as cópias criadas em Impr_Label_1(a, b) são
destruídas e, então, ButtonClick() imprime novamente no Label os valores das variáveis a e b que, nessa
função, permanecem inalterados.
Passar argumentos dessa forma, onde a função cria cópias dos valores transmitidos, dá-se o nome de:
passar argumentos por valor.
Podemos, então, concluir que os valores passados dessa forma a uma função funcionam como variáveis
locais a essa função.
O comando return
Quando tivermos que retornar um valor de alguma função, devemos fazê-lo através do comando return,
seguido do valor a ser retornado:
return 412;
Podemos retornar uma expressão, cujo valor deve ser compatível com o valor retornado por return:
return(ImprLabel_1(int, int));
O comando return funciona como uma porta de saída da função. Assim que é executado, a expressão que
o segue é retornada, sendo desconsideradas todas as instruções posteriores. Contudo, o fato de um
comando return ser encontrado, não significa necessariamente que o mesmo será executado, pois poderá
haver um desvio na linha de execução do código. Daí, podemos ter um ou mais comandos return numa
função:
if(a > b) return a;
else return b;
Exemplo:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
int Form_Capt(int, int); // protótipo - recebe dois inteiros e devolve um
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Em tais situações, se na chamada à função não fornecermos um valor, o default será usado:
double Pi = 3.14;
conta (raio, Pi); // chamada à função
Não há limites para a quantia de parâmetros com valores default. A única exigência é que os parâmetros
com valores default devem ser, sempre, os últimos na lista de parâmetros de uma função.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
/*protótipo*/ double conta ( int raio, double Pi = 3.1416);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Funções inline
Quando uma função é chamada, a execução do programa salta para o grupo de instruções constantes dessa
função na memória. Encerrada a função, a execução do programa retorna para a linha de código seguinte
àquela que chamou a função.
Esse trabalho de saltar para o conjunto de instruções onde se encontra definida a função e depois retornar
para a linha de código seguinte à instrução chamadora à função denota um trabalho que ocupa certo
espaço em memória, acarretando determinada quantidade de tempo para ser executada.
Para ganhar um pouco de tempo, podemos colocar a palavra-chave inline como primeira palavra do
cabeçalho da definição de uma função, para que seja inserida uma cópia da função em todo lugar onde a
mesma é chamada. Contudo esse procedimento só se justifica se a função chamada for muito pequena
(uma ou duas linhas de instruções), pois nesses casos as instruções necessárias à chamada da função
podem ocupar mais espaço na memória do que as instruções do seu próprio código.
Outro cuidado a ser tomado é que a definição (e não o protótipo apenas) da função deve anteceder à sua
primeira chamada.
Não devemos esquecer que se uma função inline for chamada muitas vezes, a mesma quantia de vezes
será realizada uma cópia de seu código no programa, o que poderá significar um aumento considerável no
tamanho do executável.
O exemplo a seguir leva um Label no Form e é usado para devolver o valor da área de um retângulo:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
inline int Area(int a, int b)
O comando goto
A instrução goto é formada por duas partes: o comando goto e um rótulo:
goto Erro;
A instrução goto causa um desvio na execução normal do programa para a instrução seguinte ao rótulo.
Um rótulo é um nome (que respeita a convenção para a nomenclatura de variáveis) que deve ser colocada
em dois locais: imediatamente após a instrução goto e, completada por dois pontos, antecedendo a
instrução para a qual o goto desviará a execução do programa:
Erro:
ShowMessage("Erro... Não é possível efetuar divisão por zero...");
// ...
if(Divid / 0)
goto Erro;
// ...
No exemplo, se o usuário tentar efetuar uma divisão por zero, o comando goto remeterá a execução do
programa para " Erro: " que chamará uma caixa de mensagem.
Existem códigos que possibilitam usar o goto como um laço (loop), mas é desaconselhável, uma vez que
os laços for , while e do... while fazem com muito mais segurança a tarefa.
O loop while
LAÇOS - Algumas vezes um aplicativo precisa executar operações repetitivas. A fim de evitar que um
programador tenha de repetir uma diversidade de vezes um mesmo código, criou-se o conceito de loop
(ou laço).
for
while
do ... while
Podemos imaginar os laços como uma roda gigante que fica girando até que determinada condição se
verifique. E a cada giro da roda, determinada tarefa é realizada. A ocorrência da última tarefa dar-se-ia
com a satisfação da condição.
Quando não possuímos prévio conhecimento acerca do número de vezes que o corpo de um loop deverá
ser executado, o while se mostra como opção mais correta, que, basicamente, fará o seguinte: testará uma
condição definida inicialmente; enquanto o comando verificar que a condição continua verdadeira, o
conjunto de instruções contido no corpo do laço continuará sendo executado. No instante em que o
comando verificar um valor de retorno falso para a condição, o processamento será desviado para fora do
laço.
Voltando ao exemplo da roda gigante, no while não haveria a necessidade de uma catraca contando o
número de giros, mas um funcionário aguardando a ocorrência de determinado fato para desligar o
aparelho, como, por exemplo, uma pessoa pedir para descer do aparelho.
Se a condição avaliada for falsa logo no início do loop, o conjunto de instruções será ignorado e o laço
não será executado.
while(condição)
corpo_do_loop;
O exemplo a seguir usa um Label no Form. Quando o usuário dá um clique no label, inicia-se um loop
onde o comando while irá procurar nove caracteres z, ou Z, em quantas tabelas ASCII forem necessárias,
sendo que essas tabelas serão imprimidas no label. Depois informará o resultado da busca no próprio
label.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
/*caso o valor de caract seja 100, 200 ou 256, a tabela ASCII continuará
sendo imprimida na linha de baixo do Label1*/
case 100: Label1->Caption = Label1->Caption + '\n';
break; /* o break remete o processamento para fora do comando switch*/
case 200: Label1->Caption = Label1->Caption + '\n';
break;
case 256: Label1->Caption = Label1->Caption + '\n';
break;
/*o label usará as variáveis paraZ e contador para informar os resultados da busca*/
Label1->Caption = Label1->Caption + "\n\nEncontrei os caracteres "
"\"Z\" e \"z\" " + String(paraZ) + " vezes!";
Label1->Caption = Label1->Caption + "\n\nPara encontrar esses caracteres, "
"o loop foi executado " + String(contador) + " vezes";
}
//---------------------------------------------------------------------------
break e continue
Existem dois comandos que podemos usar no loop while. O comando continue causa uma interrupção na
execução das instruções, remetendo a execução para o topo do laço; já o comando break determina a
imediata saída do laço, independentemente de a condição ter sido satisfeita. Ao encontrar a instrução
break, a execução do programa é enviada para primeira instrução após a chave de fechamento } do corpo
do laço.
//---------------------------------------------------------------------------
if(i <= 5)
continue;
if(i == 10)
{
ShowMessage("Agora i é igual a 10 e, embora i ainda não seja 1000, "
"vou embora... Adeus...");
break;
}
}
Close();
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int i = 0;
while(1) // também poderia ser: while(true)
{
ShowMessage((String)"Mensagem número: "+ ++i);
if(i == 10)
break;
}
//--------------------------------------------------------------------------
Uma das instruções que compõem um bloco de códigos do corpo de um loop while pode ser
outro laço while. A essa situação denominamos laços while aninhados.
O exemplo a seguir leva um Button e dois Labels no Form. Quando o usuário der um clique no
Button, uma sequência de loops será executada, mostrando o resultado final nos Labels:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
bool bl = true; // variável que nos dará uma das formas de encerrar o loop
}
//---------------------------------------------------------------------------
Dessa forma, o programador garante que pelo menos uma vez as instruções contidas no corpo do laço
serão processadas, mesmo que a condição seja falsa.
eis a sintaxe:
do
...
O exemplo a seguir leva um Button e um Label no Form. Quando o usuário dá um clique no Button, uma
mensagem é exibida no Label e, após sete segundos (7000 milisegundos), o programa é encerrado.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
O loop for
Retornando à idéia de que poderíamos comparar os laços com uma roda-gigante, o laço for funcionaria do
seguinte modo: O aparelho começaria a girar e cada volta seria registrada num contador (ou catraca).
Quando o aparelho atingisse o número de voltas determinado no controlador da catraca (na condição), o
aparelho automaticamente se desligaria.
Isso significa que quando possuímos prévio conhecimento acerca do número de vezes que um loop deverá
ser executado (ver while), a melhor opção será o laço for.
A sintaxe do for é um cabeçalho onde encontramos o valor inicial, a condição e a atualização separados
por ponto e vírgula dentro de um par de parênteses, antecedendo o corpo do laço que contém os
comandos da instrução:
comando;
Geralmente o valor inicial, também conhecido como inicialização, é uma instrução de atribuição a uma
variável (geralmente inteira), que será executado apenas uma vez no início do loop.
A condição é uma instrução que é avaliada sempre que o loop inicia ou reinicia. Se o valor retornado pela
condição for verdadeiro (ver while), as instruções do corpo do laço são executadas; caso contrário, se
falsa, a execução do programa sai do loop e vai para a instrução seguinte.
A atualização contém a instrução de acordo com que a variável será alterada cada vez que o loop repetir.
Geralmente a variável será incrementada. exemplo:
O corpo do loop contém as instruções que serão processadas a cada giro do laço.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
/*chama a função que arredonda casas decimais, atribuindo o valor retornado a b*/
b = Arredonda(StrToFloat(a), StrToInt(b));
//---------------------------------------------------------------------------
múltiplas condições:
...; i = i + 3, j = j - 7)
Esses múltiplos comandos são separados entre si por vírgulas e das demais instruções por ponto e vírgula.
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
/******************************
NOTA: O EXEMPLO NOS DEIXA CLARO QUE O LIMITE A SER OBEDECIDO PELO LOOP
SERÁ O LIMITE MAIOR. OU SEJA, EMBORA A VARIÁVEL "i" DEVA SER INCREMENTADA
SOMENTE DE "0" ATÉ "10", ELA CONTINUARÁ SENDO INCREMENTADA ATÉ A VARIÁVEL "k"
SER SATISFEITA PLENAMENTE O SEU DECREMENTO (DE 100 A 0).
QUANTO À VARIÁVEL "k", NÓS PROCURAMOS DEMONSTRAR UMA FORMA DE INTERFERIR
EM SUA VARIAÇÃO DENTRO DO LAÇO, ATRAVÉS DO COMANDO if
******************************/
{
if(j > 900000000) // um novo limite para "j"
j = 53; // "j" agora é igual a 53
}
//---------------------------------------------------------------------------
for( ; ; )
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
Por incrível que pareça, às vezes podemos colocar instruções que estariam no corpo do loop dentro do
cabeçalho. Nessas situações, se o corpo do loop não for usado pelo comando, será necessário colocar-se a
instrução de nulo (um ponto e vírgula) no corpo do laço.
//---------------------------------------------------------------------------
for(int i = 1;
i<10;
ShowMessage((String)"laço for com instruções no corpo do laço. " +
i++ + "°" + " loop"));
//---------------------------------------------------------------------------
StringGrid1->ColCount = 10;
StringGrid1->RowCount = 10;
celula = 0;
StringGrid1->Cells[coluna][linha] = IntToStr(++celula);
//---------------------------------------------------------------------------
O comando switch
Existem situações onde a execução do programa deverá avaliar uma opção entre várias alternativas. Uma
forma de resolvermos essas situações, seria através de várias estruturas if ... else; outra forma, através
do comando switch() ... case.
Em tais situações, o uso do comando switch() ... case é, de longe, o mais apropriado pois o uso de
vários if ... else pode tornar o código confuso, sujeito a erros e cansativo de depurar.
após o parênteses de fechamento, não há ponto e vírgula, seguindo-se imediatamente a abertura do corpo
do comando com a chave {.
O corpo do comando consiste de várias opções rotulando a palavra-chave case, bem como os comandos
para os quais será desviado o controle do programa. Via de regra, essas ações conterão o comando break,
remetendo o processamento do programa para fora do switch (primeira linha de código após o comando).
Sempre que o comando não encontrar uma opção para processar nos case, a opção default, caso exista,
será processada; se, porém, nesses casos, a opção default não existir, a execução do programa
atravessará o switch sem executar instrução alguma:
O exemplo a seguir leva um Edit no Form. O código está voltado a impedir a entrada de caracteres não
numéricos no Text do Edit.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
bool virgula = true;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
if (virgula == false)
DecimalSeparator = '\0';
switch(Key)
{
case '1': Key = '1'; break;
case '2': Key = '2'; break;
case '3': Key = '3'; break;
case '4': Key = '4'; break;
case '5': Key = '5'; break;
case '6': Key = '6'; break;
case '7': Key = '7'; break;
case '8': Key = '8'; break;
case '9': Key = '9'; break;
case '0': Key = '0'; break;
case '.': Key = DecimalSeparator; virgula = false; break;
case ',': Key = DecimalSeparator; virgula = false; break;
case 13: Key = 13; break;
case 8: Key = 8; break;
default: Key = '\0';
}
}
//---------------------------------------------------------------------------
Para responder aos movimentos do mouse, defina um evento handler para o evento OnMouseMove.
Num Form vazio, selecione a guia Events do Object Inspector. No setor esquerdo das opções dessa
guia, procure pelo evento OnMouseMove. Dê um duplo clique no campo situado ao lado direito desse
evento para abrir a janela de edição de códigos. Imediatamente você entrará em contato com as seguintes
linhas de código:
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
Já temos conhecimento prévio do significado de várias partes do código acima. Mas há algumas
novidades: TShiftState Shift, int X, int Y)
O tipo TShiftState indica o estado das teclas Alt, Ctrl e Shift, bem como dos botões do mouse.
Descrição: O tipo TShiftState é usado para eventos de teclado e eventos do mouse para determinar o
estado das teclas Alt, Ctrl e Shift, bem como dos botões do mouse, quando ocorrer um evento. Trata-se
de um grupo de flags que indica o seguinte:
Valor Significa
Podemos entender TShiftState como uma espécie de variável que, automaticamente, é nomeada de
Shift pelo C++Builder no evento estudado.
Então se, por exemplo, inserirmos as seguintes linhas de código no evento acima:
//---------------------------------------------------------------------------
Não ficará difícil entender que: se a tecla Shift (Shift.Contains(ssShift)) estiver pressionada
enquanto o mouse mover-se sobre as coordenadas 0 e 0 ( X == 0 && Y == 0 ), ou seja, no extremo
esquerdo do topo de Form1, o programa será encerrado.
TCanvas inclui um grande número de gráficos primitivos, rotinas para desenhar linhas, shapes (figuras),
polygons (polígonos), fonts (fontes), etc para qualquer controle que contém um canvas.
O exemplo abaixo trata um evento através do clique de um botão, determinando o desenho de uma linha
desde as coordenadas X = 100 e Y = 100 até as coordenadas X = 200 e Y = 200, bem como a colocação de
um texto, cujo início dar-se-á nessas últimas coordenadas:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Canvas->Pen->Color = clBlue;
Canvas->MoveTo( 100, 100 );
Canvas->LineTo( 200, 200 );
Canvas->Brush->Color = clBtnFace;
Canvas->Font->Name = "Arial";
Canvas->TextOut( Canvas->PenPos.x, Canvas->PenPos.y,"Este é o fim da linha"
);
}
O objeto TCanvas também protege você contra erros gráficos comuns Windows, como por exemplo
restaurar dispositivos contexto, pens, brushes, e assim por diante de acordo com os valores que eles
possuiam antes da operação desenho. TCanvas é usado em qualquer lugar no C++Builder onde um
desenho seja requerido ou possível, e faz gráficos facilmente.
TCanvas::CopyMode
Especifica como uma imagem gráfica é copiada sobre a tela.
Descrição:
Use CopyMode para afetar o modo como imagens gráficas são desenhadas sobre a tela. CopyMode é
usado quando copiamos uma imagem de outra tela usando o método CopyRect. O CopyMode também é
usado por objetos TBitmap para desenhar sobre a tela.
Use Rect para criar uma TRect que representa o retângulo segundo as coordenadas especificadas. Use Rect
para construir parâmetros para funções que requerem TRect, especialmente para colocar variáveis locais
para cada parâmetro.
O exemplo a seguir usa um Button no Form que, ao ser clicado, exibe um texto em um retângulo definido
pelas coordenadas (10 e 10) e (300 e 300). Após exibir o texto por meio do método TextRect, o
código desenha uma linha definida pelo método FrameRect em volta do retângulo.
TRect ARect;
// coordenadas do retângulo.
//O mesmo que: ARect = Rect(10,10,300,300);
ARect.Top = 10;
ARect.Left = 10;
ARect.Bottom = 300;
ARect.Right = 300;
CopyRect
A função CopyRect copia as coordenadas de um retângulo em outro.
BOOL CopyRect(
LPRECT lprcDst, // aponta para a estrutura do retângulo de destino
CONST RECT *lprcSrc // aponta para a estrutura com o retângulo inicial
);
O parâmetro lprcDst aponta para a estrutura RECT que recebe as coordenada lógicas do retângulo inicial
e o parâmetro lprcSrc aponta para a estrutura RECT à qual pertencem as coordenadas que estão sendo
copiadas.
Se a função lograr êxito, produzirá um valor de retorno diferente de zero; se fracassar, o valor de retorno
será zero. Para obter informações de erro estendidas, use GetLastError.
TCanvas::CopyRect
Copia partes de uma imagem de um a outro canvas.
Use CopyRect para transferir parte da imagem de outro canvas para a imagem do objeto TCanvas. Dest
especifica o retângulo no canvas onde a imagem inicial será copiada. O parâmetro Canvas especifica a
tela com a imagem fonte. Source especifica um retângulo que limita a porção de tela fonte que será
copiada.
desenhar figuras;
desenhar polígonos.
Canvas pode ser usado para desenhar linhas e múltiplas-linhas. uma linha reta pode ser entendido como
uma linha de pixels conectando dois pontos. Múltiplas-linhas pode ser interpretado como uma série de
linhas retas, conectando cada ponto inicial com o seu ponto final. O canvas desenha todas as linhas
usando pen (caneta).
desenhar figuras
desenhar polígonos.
Para desenhar retângulo ou elipse na tela, chame o método Rectangle ou o método Ellipse, passando as
coordenadas dos limites do retângulo.
O método Rectangle desenha um retângulo; Ellipse desenha uma elipse que toca todos os lados das
coordenadas de um retângulo que lhe fornece os limites.
O seguinte método desenha um retângulo no canto superior esquerdo do form, colocando, depois, uma
elipse no interior do mesmo:
{
// ClientWidth especifica a largura da área do controle em pixels
Canvas->Rectangle(0, 0, ClientWidth/2, ClientHeight/2);
// ClientHeight especifica a altura da área do controle em pixels
Canvas->Ellipse(0, 0, ClientWidth/2, ClientHeight/2);
}
Os quatro primeiros parâmetros passados para RoundRect referem-se aos limites do retângulo
(coordenadas), da mesma forma que nos métodos Rectangle ou Ellipse. RoundRect recebe mais dois
parâmetros que indicam como desenhar cantos arredondados.
{
Canvas->RoundRect(0, 0, ClientWidth/2, ClientHeight/2, 30, 30);
}
desenhando polígonos
Para desenhar polígonos com qualquer número de linhas na tela, use o método Polygon de canvas, que
desenha uma série de linhas na tela conectando os pontos passados, providenciando, por fim, o
fechamento da figura por unir o último ponto com o primeiro.
Polygon recebe um array de pontos como seu único parâmetro e conecta os pontos com pen, até
conectar o último ponto com o primeiro para fechar o polígono. Depois de estipular os limites, Polygon
usa o brush para preencher a parte interior do polígono.
O exemplo a seguir leva um PaintBox no Form. Quando o programa é executado, desenha um polígono
irregular no form, cujas coordenadas são especificadas pelo array. Observe que, uma das maneiras pela
qual você pode alterar o resultado da execução, é variando as possibilidades de acordo com o exposto nos
comentários.
propriedades Canvas
Com o objeto Canvas, você pode colocar as propriedades de uma pen para desenhar linhas, um brush
para preencher figuras, uma font para escrever um texto, e um array de pixels para representar a imagem.
De si mesmo, pen possui quatro propriedades que você pode alterar: Color, Width, Style, e Mode.
O valor dessas propriedades determinam como pen pode alterar os pixels numa linha. Por default, cada
pen inicia com cor negra, com largura (width) de 1 pixel, um estilo sólido e um modo chamado copy
que escreve qualquer coisa elaboradamente na tela.
Você pode usar TPenRecall para rapidamente salvar e restaurar as propriedades de pens.
TPen::Color
Color determina a cor usada para desenhar linhas na tela.
Você pode usar Color para mudar a cor usada para desenhar linhas ou figuras. O jeito que Color é usado
depende das propriedades Mode e Style.
O exemplo a seguir desenha uma infinidade de elipses de vários estilos, cores e tamanhos (pen e brush)
no form para preencher inteiramente a tela. Para executar o código, coloque um componente TTimer no
form e use o Object Inspector para criar os eventos handlers OnTimer e OnActivate.
//---------------------------------------------------------------------------
#include "Unit1.h"
#include <stdlib.h> // para random() e randomize(): valores aleatórios
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
int x, y;
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
// comando de decisão
switch (random(5))
{
case 0: Canvas->Pen->Style = psSolid; // estilo da linha
Canvas->Brush->Color = clLime; break; // cor da figura: clLime
TPen::Width
Especifica a largura máxima de um pen em pixels.
Você pode usar a propriedade Width para dar à linha maior ou menor espessura. Se Width for colocado
para um valor menor do que 1, o valor de pen automaticamente será remetido para 1.
#include "Unit1.h"
#include <stdlib.h> // para random() e randomize(): valores aleatórios
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
int x, y;
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
TPen::Style
Determina o estilo que pen desenha linhas.
enum TPenStyle
{psSolid, psDash, psDot, psDashDot, psDashDotDot, psClear, psInsideFrame};
__property TPenStyle Style = {read=GetStyle, write=SetStyle, default=0};
Você pode usar Style para escolher entre várias opções de como desenhará linhas, como por exemplo,
desenhar uma linha pontilhada ou tracejada, ou para omitir a linha que aparece como uma moldura em
torno de uma figura. Eis os possíveis valores para Style:
psSolid Uma linha sólida.
psClear Linhas não são desenhadas (usado para omitir a linha em volta de figuras
que traça um desenho usando a atual pen).
psInsideFrame Uma linha sólida, mas que pode usar uma cor diferente se Width é maior
do que 1.
Nota: Só o estilo psInsideFrame produz cor diferente para adaptar à propriedade Color que não está na
tabela de cores. Todos os outros escolhem cores na tabela de cores do Windows.
Nota: estilos pontilhados ou tracejados não estão disponíveis quando a propriedade Width é diferente de
1.
TPen::Mode
Determina como pen desenha linhas na tela.
enum TPenMode
{pmBlack, pmWhite, pmNop, pmNot, pmCopy, pmNotCopy, pmMergePenNot,
pmMaskPenNot, pmMergeNotPen, pmMaskNotPen, pmMerge, pmNotMerge, pmMask,
pmNotMask, pmXor, pmNotXor};
__property TPenMode Mode = {read=FMode, write=SetMode, default=4};
Use Mode para determinar como a cor de pen atua sobre a cor sobre o canvas. Os efeitos de Mode estão
descritos na seguinte tabela:
Mode Cores Pixels
pmBlack Sempre negro.
pmNop Imutável.
TPenRecall
TPenRecall salva e restaura as propriedades de um objeto TPen.
Use TPenRecall para armazenar o estado atual de um objeto TPen. Então você estará livre para mudar
esse estado atual armazenado do objeto pen. Para restaurar o objeto pen para o seu estado original
armazenado, basta destruir o objeto TPenRecall o que fará com que o objeto pen seja automaticamente
restaurado para as propriedades e valores salvos.
Você pode atualizar a instância TPenRecall para refletir as propriedades atuais do objeto TPen chamando
o método Store. Você pode prevenir a destruição de TPenRecall da atualização do objeto TPen pela
chamada do método Forget.
Usando brushes
A propriedade Brush de um controle canvas é o meio pelo qual podemos preencher o interior de áreas,
incluindo figuras. Preencher uma área com um brush é, nada mais, do que um meio de alterarmos um
grande número de pixels na área especificada, ou seja, um pincel.
Você pode usar TBrushRecall para rapidamente salvar e restaurar as propriedades de brushes.
Tcanvas::Brush
Determina a cor e o padrão de preenchimento para figuras gráficas e backgrounds.
Use a propriedade Brush para especificar a cor e o padrão quando desenhar o background ou preencher
em figuras gráficas. O valor de Brush está num objeto TBrush. Use as propriedades do objeto TBrush
para especificar a cor e o modelo ou bitmap quando preencher espaços no canvas.
Nota: Fixando a propriedade Brush nomeie o objeto TBrush especificado, no lugar de substituir o objeto
TBrush atual.
O código a seguir carrega um bitmap desde um arquivo, destinando-o ao Brush do Canvas de Form1:
try
{
BrushBmp->LoadFromFile("C:\meu_desenho.bmp");
Form1->Canvas->Brush->Bitmap = BrushBmp;
Form1->Canvas->FillRect(Rect(0,0,100,100));
}
__finally
{
Form1->Canvas->Brush->Bitmap = NULL;
delete BrushBmp;
}
TCanvas::FillRect
Preenche o retângulo especificado no canvas usando o brush atual.
Use FillRect para preencher uma região retangular usando o pincel atual. A região preenchida inicia-se
com as coordenadas do topo esquerdo do retângulo até as coordenadas da base direita do mesmo.
TBrush::Color
Indica a cor do brush.
A propriedade Color determina a cor de brush. Esta cor é usada para traçar a forma representada pela
propriedade Style, e não a cor background do brush (a menos que Style seja sólido).
Nota: Se o valor da propriedade Style for bsClear, a propriedade Color é ignorada. Mais ainda,
qualquer valor marcado para a propriedade Color é perdido quando Style é marcado para bsClear.
O exemplo a seguir leva um Button e um Image no Form. Quando o usuário clicar o botão, uma elipse é
desenhada em Image1 (TImage sobre o form), adotando o estilo bsDiagCross para Brush, ou seja,
linhas cruzadas, as quais serão vermelhas:
{
TCanvas *pCanvas = Image1->Canvas;
pCanvas->Brush->Color = clRed;
pCanvas->Brush->Style = bsDiagCross;
pCanvas->Ellipse(0, 0, Image1->Width, Image1->Height);
}
TBrush::Style
Especifica a forma para brush.
enum TBrushStyle {bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal,
A propriedade Style determina a forma pintada por brush, a menos que um valor seja determinado por
Bitmap. Os possíveis valores de Style são:
bsSolid
bsCross
bsClear
bsDiagCross
bsBDiagonal
bsHorizontal
bsFDiagonal
bsVertical
O exemplo a seguir demonstra o uso dos possíveis valores da propriedade Style de Brush, pintando os
exemplos no Form que é redimensionado quando o mesmo recebe um clique do mouse:
//---------------------------------------------------------------------------
pCanvas->Brush->Color = clRed;
pCanvas->Brush->Style = bsSolid;
pCanvas->Rectangle(101, 0, 200, 200);
pCanvas->Brush->Color = clYellow;
pCanvas->Brush->Style = bsDiagCross;
pCanvas->RoundRect(201, 0, 300, 200, 50, 90);
pCanvas->Brush->Color = clLime;
pCanvas->Brush->Style = bsBDiagonal;
pCanvas->Ellipse(301, 0, 400, 200);
pCanvas->Brush->Color = clFuchsia;
pCanvas->Brush->Style = bsHorizontal;
pCanvas->Rectangle(0, 200, 100, 400);
pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsFDiagonal;
pCanvas->RoundRect(101, 200, 200, 400, 90, 50);
pCanvas->Brush->Color = clGreen;
pCanvas->Brush->Style = bsVertical;
pCanvas->Ellipse(201, 200, 300, 400);
pCanvas->Brush->Color = clGreen;
pCanvas->Brush->Style = bsClear;
pCanvas->Rectangle(301, 200, 400, 400);
}
//---------------------------------------------------------------------------
Dica: Coloque a propriedade Style para bsClear para eliminar instabilidade na tela (pisca-pisca) quando
o objeto é repintado.
TBrush::Bitmap
Especifica uma imagem bitmap externa que define uma composição para brush.
Bitmap aponta para um objeto TBitmap que guarda uma imagem bitmap. Se Bitmap não estiver vazio, a
imagem bitmap define a composição para brush’s. Se a imagem é maior do que oito pixels por oito
pixels, somente a região do topo esquerdo oito-por-oito é usado.
O exemplo a seguir leva um Button no Form. Quando o usuário clicar o botão, um bitmap é carregado do
disco e destinado para o Brush do Canvas de Form1. O form é totalmente preenchido, mesmo que a
figura tenha um dimensionamento menor. Nesse caso as imagens serão colocadas lado a lado.
try
{
TRect ARect = Rect(0, 0, Width, Height);
BrushBmp->LoadFromFile("C:\\Meu_desenho.bmp");
Canvas->Brush->Bitmap = BrushBmp;
Canvas->FillRect(ARect);
}
__finally
{
Canvas->Brush->Bitmap = NULL;
delete BrushBmp;
}
}
TBrushRecall
TBrushRecall salva e restaura um objeto TBrush.
Use TBrushRecall para guardar o atual estado de um objeto TBrush. Você então está livre para mudar o
estado atual do objeto brush. Para restaurar o objeto brush ao seu estado original, basta destruir o objeto
TBrushRecall e o objeto brush é automaticamente restaurado para os valores da propriedade salvos.
Você pode atualizar a instância TBrushRecall para refletir as propriedades atuais do objeto TBrush pela
chamada do método Store. Você pode impedir o destruidor de TBrushRecall pela atualização do objeto
TBrush pela chamada do método Forget.
TCanvas::Pixels
Especifica as cores dos pixels dentro do atual ClipRect.
Use Pixels para descobrir a cor na superfície do desenho em uma posição específica dentro da região
selecionada. Se a posição estiver fora do retângulo selecionado, a leitura do valor de Pixels retornará -1;
use Pixels para mudar a cor individual de pixels na superfície do desenho; e use Pixels para detalhar
efeitos em uma imagem. Pixels também pode ser usado para determinar a cor que deveria ser usada pelo
método FillRect.
Nem todo contexto de dispositivo suporta a propriedade Pixels. Ler a propriedade Pixels para cada
contexto de dispositivo retorna um valor de -1. Colocar a propriedade Pixels para cada contexto de
dispositivo não realiza ação alguma.
O exemplo a seguir desenha várias linhas coloridas formando uma moldura, quando o usuário pressiona o
botão.
Canvas->Pixels[10][i] = clRed;
Canvas->Pixels[14][i] = clBlue;
Canvas->Pixels[18][i] = clYellow;
Canvas->Pixels[22][i] = clWhite;
Canvas->Pixels[26][i] = clFuchsia;
Canvas->Pixels[30][i] = clGreen;
Canvas->Pixels[i][280] = clRed;
Canvas->Pixels[i][284] = clBlue;
Canvas->Pixels[i][288] = clYellow;
Canvas->Pixels[280][i] = clRed;
Canvas->Pixels[284][i] = clBlue;
Canvas->Pixels[288][i] = clYellow;
Canvas->Pixels[292][i] = clWhite;
Canvas->Pixels[296][i] = clFuchsia;
Canvas->Pixels[300][i] = clGreen;
}
}
Para entender o que cada bloco faz, você pode tirá-lo da execução, colocando-o entre os símbolos de
comentário C: /* */
TBitmap::ScanLine
Provê acesso indexado a cada linha de pixels.
ScanLine é usado somente com DIBs (Device Independent Bitmaps) para imagens que são editadas em
ferramentas que fazem trabalhos de pixels de baixo nível.
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Graphics::TBitmap *pBitmap = new Graphics::TBitmap();
// Este exemplo mostra edição diretamente no Bitmap
Byte *ptr;
try
{
pBitmap->LoadFromFile
("C:\\meu_desenho.bmp");
for (int y = 0; y < pBitmap->Height; y++)
{
ptr = (Byte *)pBitmap->ScanLine[y];
Estas propriedades são descrevidas em maiores detalhes em Using the properties of the Canvas object.
Use Arc para desenhar uma linha encurvada elipticamente com a Pen (caneta)
atual. O arco atravessa o perímetro de uma elipse que é limitada pelos pontos
(X1,Y1) e (X2,Y2). O arco é desenhado seguindo o perímetro da elipse, no sentido
anti-horário (à esquerda), do ponto de partida para o ponto final. O ponto inicial é
definido pela intersecção da elipse e uma linha definida pelo centro da elipse e
(X3,Y3). O ponto final é definido pela intersecção da elipse e uma linha definida
pelo centro da elipse e (X4, Y4). Exemplo:
Chord void __fastcall Chord(int X1, int Y1, int X2, int Y2,
int X3, int Y3, int X4, int Y4);
Desenha figura fechada representada pela intersecção de uma linha e uma elipse.
Use Chord para criar uma forma que é definida por um arco e uma linha que une
os pontos finais do arco. A corda (chord) consiste numa porção da elipse que é
limitada pelos pontos (X1,Y1) e (X2,Y2). A elipse é dividida por uma linha que liga
os pontos (X3,Y3) e (X4,Y4).
O perímetro de chord vai da direita (X3, Y3), para a esquerda, ao longo da elipse
para (X4,Y4), e para trás (X3,Y3). Se (X3,Y3) e (X4,Y4) não estiver na superfície da
elipse, os cantos correspondentes em chord serão os pontos mais íntimos do
perímetro que cruza a linha. O desenho da corda (chord) é feito usando valores
tirados de Pen, e a figura é preenchida usando valores de Brush.
Use CopyRect para transferir parte da imagem de outra tela para a imagem do
objeto TCanvas. Dest especifica o retângulo na tela onde a imagem de fonte será
copiada. O parâmetro de Canvas especifica a tela com a imagem fonte. Source
especifica um retângulo que limita a porção da tela fonte que será copiada.
Chame Draw para colocar um gráfico na tela. Draw chama o método Draw da
imagem. A imagem é desenhada em um retângulo determinado pelo tamanho de
Graphic, com o canto esquerdo superior nos pontos X e Y.
Ellipse void __fastcall Ellipse(int X1, int Y1, int X2, int Y2);
Use Ellipse para desenhar um círculo ou elipse na tela. O ponto do topo superior
direito do retângulo é o pixel representado pelas coordenadas X1 e Y1 e o ponto
inferior direito pelas coordenadas X2 e Y2. Se os pontos do retângulo formam um
quadrado, um círculo é desenhado.
Use FillRect para preencher uma região retangular usando o pincel atual. A região
preenchida inicia-se com as coordenadas do topo esquerdo do retângulo até as
coordenadas da base direita do mesmo. O exemplo a seguir cria um retângulo no
form, preenchendo-o de azul.
Use FloodFill para encher uma região possivelmente não retangular da imagem
com o valor de Brush. Os limites da região para ser preenchidos são determinados
pelo passar ao lado externo do ponto (X,Y) quando o limite de cor que envolve o
parâmetro Color é encontrado.
FillStyle determina qual tipo de mudança de cor define os limites, como indicado
na seguinte tabela.
fsSurface Enche toda a área que tem a cor indicada pelo parâmetro Color. Para
quando outra cor é encontrada.
fsBorder Enche toda a área que não tem a cor indicada pelo parâmetro Color. Para
quando Color é encontrado.
Use a propriedade Pixels para adquirir o valor exato da cor no ponto (X,Y) quando
usar fsSurface de FillStyle. Semelhantemente, quando FillStyle é fsBorder, use
Pixels para adquirir o valor exato da cor limite se um ponto no limite é conhecido.
Desenha uma linha no canvas desde PenPos para o ponto especificado por X e Y,
colocando a posição atual de Pen para (X, Y).
Use LineTo para desenhar uma linha desde PenPos até o ponto X e Y, mas não
incluindo o mesmo. LineTo altera o valor de PenPos para X e Y. A linha é
desenhada usando Pen.
Muda a posição atual do desenho para o ponto X e Y. Use MoveTo para colocar o
valor de PenPos antes de LineTo. Chamar MoveTo é equivalente a setar a
propriedade PenPos.
Pie void __fastcall Pie(int X1, int Y1, int X2, int Y2,
int X3, int Y3, int X4, int Y4);
Use Polygon para desenhar uma figura com vários lados fechados na tela, usando o
valor de Pen. Depois de completar o desenho da figura, Polygon preenche a figura
usando o valor de Brush. Para desenhar um polígono na tela, sem preenchê-lo, use
o método Polyline, especificando o primeiro ponto uma segunda vez no fim (após
o último ponto).
Desenha uma série de linhas na tela com a atual caneta (pen), conectando cada um
dos pontos passados de acordo com Points. Use Polyline para ligar um grupo de
pontos na tela. Se existem apenas dois pontos, Polyline desenha uma linha linha
simples.
Chamando a função MoveTo com o valor do primeiro ponto, e então chame LineTo
repetidamente com todos os pontos subseqüentes para desenhar uma imagem na
tela. De qualquer forma, diferente de LineTo, Polyline não altera o valor de
PenPos.
O exemplo a seguir, que procura demonstrar sutis diferenças, desenha duas estrelas
com linhas coloridas. Uma estrela no Form e outra num PaintBox:
//-----------------------------------------------------------
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
TPaintBox *pPB = (TPaintBox *)Sender;
TPoint points[6];
pPB->Canvas->Pen->Color = clFuchsia; // Color = 0xff00ff;
points[0].x = 40;
points[0].y = 10;
points[1].x = 20;
points[1].y = 60;
points[2].x = 70;
Rectangle void __fastcall Rectangle(int X1, int Y1, int X2, int Y2);
Desenha um retângulo na tela com o canto superior esquerdo no ponto (X1, Y1) e o
canto inferior direito no ponto (X2, Y2). Use Rectangle para desenhar um retângulo
com Pen e preenchendo-o com Brush. Para preencher a região retangular externa
desenhando a borda no corrente pen, use FillRect. Para desenhar uma área
retangular externa adicional, use FrameRect ou Polygon. Para desenhar um
retângulo com cantos arredondados, use RoundRect.
int x, y;
//---------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
{
WindowState = wsMaximized;
Canvas->Pen->Width = 20;
Canvas->Pen->Style = psDot;
Timer1->Interval = 50;
randomize();
}
//----------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
x+= 4;
y+=4;
Canvas->Pen->Color = random(65535);
Canvas->Rectangle(x, y, x + random(400), y + random(400));
Desenha um retângulo na tela com o canto superior esquerdo no ponto (X1, Y1) e o
canto inferior direito no ponto (X2, Y2). Use Rectangle para desenhar um retângulo
com Pen e preenchendo-o com Brush. Para preencher a região retangular externa
desenhando a borda no corrente pen, use FillRect. Para desenhar uma área
retangular externa adicional, use FrameRect ou Polygon. Para desenhar um
retângulo com cantos arredondados, use RoundRect.
int x, y;
void __fastcall TForm1::FormActivate(TObject *Sender)
{
WindowState = wsMaximized;
Canvas->Pen->Width = 20;
Canvas->Pen->Style = psInsideFrame;
Timer1->Interval = 100;
randomize();
}
//----------------------------------------------------------------
-
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
x+=4;
y+=4;
Canvas->Pen->Color = random(65535);
Canvas->RoundRect(x, y, x + random(400), y + random(400),
40, 40);
if(x == 600)
{
x = 400;
y = 4;
}
}
Retorna a altura (height), em pixels, de uma string desenhada na atual font. Use
TextHeight para determinar o height que uma string ocupa na imagem. Outros
elementos na imagem como linha, boxes, ou linhas adicionais de textos podem ser
posicionadas para acomodar o height do texto. TextHeight retorna o mesmo valor
de TextExtent(Text).cy.
Este exemplo exibe o height de um texto string na atual font do canvas num edit
box no form:
{
int x = Canvas->TextHeight(" Curso de C++Builder");
Edit1->Text = String(x) + String(" pixels em height");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Retorna a largura (width), em pixels, de uma string desenhada na atual font. Use
TextWidth para determinar o comprimento (length) que uma string ocupa numa
{
long int t;
String str("Tutorial de C++Builder");
t = Canvas->TextWidth(str);
Escreve uma string na tela, iniciando nos pontos X e Y, e envia o PenPos para o
fim da string. Use TextOut para escrever uma string na tela. A string pode ser
escrita usando a font atual, ou não. Use o método TextExtent para determinar o
espaço ocupado pelo texto na imagem. Para escrever somente o texto que ajusta
dentro do retângulo, use TextRect.
Este exemplo exibe uma string na posição especificada no form quando o usuário
clica o botão no form:
Escreve uma string dentro de um retângulo. Use TextRect para escrever uma string
dentro de uma limitada região retangular. Qualquer porção da string que ultrapasse
o retângulo passado no parâmetro é cortado e não aparece. O canto superior
esquerdo do texto é colocado no ponto (X, Y).
O código a seguir insere u texto num retângulo definido pelas coordenadas (10, 10)
e (100, 100).
{
TRect TheRect;
TheRect.Top = 10;
TheRect.Left = 10;
TheRect.Bottom = 40;
TheRect.Right = 100;
Canvas->TextRect
(TheRect, 20, 20, String("Wilson e Milton... bons amigos!!!"));
}
Conhecendo arrays
As palavras matriz, array ou vetor são sinônimos utilizados para um mesmo tipo de dados utilizado na
linguagem C++ para representar uma coleção de variáveis do mesmo tipo, referenciadas por um mesmo
nome e identificadas através de um índice numérico.
Essas variáveis ficam alocadas seqüencialmente na memória, seguindo o sentido da primeira variável
(índice 0 - zero, o endereço mais baixo) para a última variável (índice n - endereço mais alto).
Para declarar uma matriz, devemos escrever um tipo, seguido do nome da matriz, seguidos pelo índice,
que nada mais é do que um número, indicando a quantidade de elementos de array, contido entre
colchetes.
A instrução:
char Abcdario[26];
//---------------------------------------------------------------------------
Label1->Caption = "";
char Abcdario[26];
for(int i = 0; i < 26; i++)
{
Abcdario[i] = 97 + i;
Label1 -> Caption = (String)Label1 -> Caption +
"Abcdario[" + i + "] = " + Abcdario[i] + '\n';
}
}
//---------------------------------------------------------------------------
As variáveis de uma matriz são numeradas a partir do zero (índice). No exemplo anterior,
...
Para acessar uma variável determinada de um array, indicamos o nome do array e o índice; ou seja, o
número que está entre colchetes [ ] e indica a posição da mesma na memória.
...
//---------------------------------------------------------------------------
// reescrito
void __fastcall TForm1::Button1Click(TObject *Sender)
{
char Abcdario[26];
for(int i = 0; i < 26; i++)
{
Abcdario[i] = 97 + i;
Label1 -> Caption = Abcdario[10];
}
}
//---------------------------------------------------------------------------
A utilização de arrays traz uma uma responsabilidade a mais para o programador: a de verificar se o
limite da matriz não foi excedido, uma vez que a linguagem C++ não realiza esse tipo de verificação.
No exemplo anterior, se no código houvesse alguma instrução para que fosse escrito algum dado em
Notas[26], o fato de que não existe espaço reservado em memória para essa variável seria ignorado pelo
compilador que implementaria a instrução (lembre-se de que a primeira variável de vetores inicia-se com
o índice zero).
Uma vez que os valores do array estão armazenados seqüencialmente na memória, o compilador, sem
fazer qualquer verificação, colocará esse elemento imediatamente após Notas[25] (endereço mais alto do
exemplo).
Teoricamente, qualquer dado, instrução ou comando poderá estar ocupando esse endereço, o que poderá
produzir um resultado totalmente desastroso.
//---------------------------------------------------------------------------
Label1->Caption = "";
char Abcdario[26];
for(int i = 0; i <= 26; i++)
{
Abcdario[i] = 97 + i;
Label1 -> Caption = (String)Label1 -> Caption +
"Abcdario[" + i + "] = " + Abcdario[i] + '\n';
}
}
//---------------------------------------------------------------------------
Matrizes multidimensionais
Até agora, temos visto vetores unidimensionais (pois apresentam apenas uma dimensão):
Matrizes com mais de uma dimensão significa: os elementos da matriz são outras matrizes.
Considere uma matriz int bidimensional, cujos índices são [3] e [4]. Vejamos quais seriam as
coordenadas:
| (0 0), (0 1), (0 2), (0 3) |
| (1 0), (1 1), (1 2), (1 3) |
| (2 0), (2 1), (2 2), (2 3) |
Nas matrizes de três dimensões, cada elemento é uma matriz de duas dimensões:
char Trid_Vetor[3][2][4] =
{
{ // início do primeiro elemento[0]
{ a, b, c, d }, // primeira linha [0]
{ e, f, g, h } // segunda linha [1]
} // fim do primeiro grupo
Na declaração char Trid_Vetor[3][2][4], o índice [3] declara o número de grupos; o índice [2], o
número de linha; e i índice [4], o número de colunas.
Logo,
Trid_Vetor[0][0][0] == 'a'
Trid_Vetor[0][0][1] == 'b'
Trid_Vetor[0][0][2] == 'c'
Trid_Vetor[0][0][3] == 'd'
Trid_Vetor[0][1][0] == 'e'
Trid_Vetor[0][1][1] == 'f'
Trid_Vetor[0][1][2] == 'g'
Trid_Vetor[0][1][3] == 'h'
e assim sucessivamente...
'a', 'b', 'c', 'd', 'e', 'f', 'g' e 'h' estão no primeiro grupo;
'i', 'j', 'k', 'l', 'm', 'n', 'o' e 'p' estão no segundo grupo; e
'q', 'r', 's', 't', 'u', 'v', 'x' e 'z' estão no terceiro grupo.
'a', 'b', 'c', 'd', i', 'j', 'k', 'l', 'q', 'r', 's' e 't' estão na primeira linha; e
e', 'f', 'g', 'h', 'm', 'n', 'o', 'p', 'u', 'v', 'x' e 'z' estão na segunda linha.
'd', 'h', 'l', 'p', 't', 'z', estão na quarta e última coluna.
Para projetar imagens na tela, o computador trabalha com as coordenas X e Y, dentro de uma matriz
bidimensional denominada Pixels. No Windows 98, se você der um clique com o botão direito do mouse
sobre a área de trabalho (Desktop) e escolher a opção Propriedades no menu PopUp que aparecer, poderá
acessar a aba Configurações na caixa que se abrir. No campo Área da tela, encontram-se as opções 640
por 480 pixels, 720 por 480 pixels, 800 por 600 pixels, 1024 por 600 pixels e 1024 por 768 pixels.
Basicamente o computador projeta imagens na tela preenchendo-a com pontos coloridos. Por exemplo, a
opção 800 por 600 pixels significa que serão colocados 800 pontos (pixels) na linha horizontal
(coordenada X) por 600 na vertical (coordenada Y). Logo podemos supor que esses pontos estão
localizados dentro da matriz Pixels de tamanho [800] por [600]: Pixels[800][600];
Através do exemplo abaixo podemos conhecer cada coordenada do Form, através do evento
OnMouseDown, e, de quebra, conhecer uma forma de mudar a cor do Hint através de parâmetros
passados para RGB():
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Agora vamos criar uma matriz bi-dimensional que receberá o valor do RGB (cor) do local do form onde
ocorrer o evento mouse down:
//---------------------------------------------------------------------------
Recapitulando um pouco, podemos entender uma constante caracter como sendo uma letra ou símbolo
colocado entre aspas simples: 'A', 'b', 'c', ']'. Também já sabemos que tais dados são armazenados
internamente pelo computador como um número inteiro entre 0 e 255.
Já uma seqüência de caracteres colocados entre aspas duplas constitui uma constante string, ou constante
de caracteres (no plural):
"Assis e Palmital"
"D. Pedro I deu um grito que ecôa até hoje!"
"A"
"b"
Essa "cadeia" ordenada de caracteres contém um caracter a mais do que parece ter, pois é encerrada com
o caracter nulo '\0'. Por exemplo:
char Nome[8] = "Therbio"; // visualmente Therbio possui apenas sete caracteres
Nome
T h e r b i o \0
0 1 2 3 4 5 6 7
//---------------------------------------------------------------------------
void __fastcall TForm1::Label1Click(TObject *Sender)
{
char Nome[ ] = "Therbio"; // não fornecemos um índice
Label1 -> Caption = sizeof(Nome);
}
//---------------------------------------------------------------------------
É diferente a interpretação que o compilador faz quando encontra um caracter entre aspas simples ou entre
aspas duplas:
Certamente você já percebeu que a diferença se dá em virtude do caracter NULL (nulo) colocado a mais
automaticamente pelo compilador.
Obs. A expressão
incorretamente declara uma matriz de índice três e a inicializa com os três caracteres 'O', 'l' e 'a' sem a
terminação usual com o caracter nulo, '\0'. Embora essa notação, algumas vezes, pareça funcionar, deve
ser evitada.
pois inicializa a matriz com a mesma facilidade e implementa, automaticamente, a terminação usual com
o caracter nulo '\0'.
Se na inicialização fornecermos elementos a menos do que o índice comporta, o valor 0 será atribuído
inicialmente para os elementos restantes da matriz:
//---------------------------------------------------------------------------
Estruturas
Conforme estudamos, uma matriz consiste num agrupamento de elementos do mesmo tipo, enquanto uma
struct (estrutura) consiste num agrupamento de tipos de dados arbitrários, denominados membros, sob um
único novo tipo e nome. Por exemplo:
struct dados
{
AnsiString Nome; // conforme a inicialização, aqui haverá um ERRO
char* rua;
int Numero;
String Cidade; // e aqui também.
char Estado[3]; // ou então aqui, se a inicialização for de outro tipo
int Cep;
long Idade;
float Peso;
}; // o ponto-e-vírgula é necessário
define um novo tipo de dados agrupados sob o nome dados, onde podemos anotar alguns dados de alguma
pessoa. Observe que existem itens que requerem diferentes tipos de dados. Enquanto alguns armazenarão
strings (sob suas diversas formas, escolhidas aleatoriamente), outros armazenarão números inteiros e, por
fim, o último, valores numéricos que poderiam conter uma fração.
Feito isso, podemos declarar variáveis do tipo dados como faríamos para declarar outra variável qualquer:
dados DeLima;
Porém, esse tipo de inicialização acima tem dificuldade para inicializar dados do tipo AnsiString.
Observação:
struct __aluno
{
char* Nome;
int Nota;
float media;
};
elas sempre serão diferentes tipos de dados entre si. Portanto a declaração:
aluno Paulo;
fará com que o compilador acuse um erro, uma vez que se trata de tipos incompatíveis entre si:
Ponteiros
Podemos entender um microcomputador como um sistema de cinco unidades de funcionamento: unidade
de entrada (teclado, mouse, drive de CD-ROM, drive de disquetes etc), unidade de saída (impressora,
monitor etc), unidade de memória (memória RAM -escrita e leitura-, memória ROM - leitura), e as
unidades aritmética e lógica que se encontram agrupadas na CPU (Unidade Central de Processamento, o
processador).
O chip responsável pelo controle de todo o computador é o processador. Outro circuito de extrema
importância é a memória RAM, que podemos imaginar como um grupo de células usadas para
A memória RAM é constituída por uma imensa seqüência de células de armazenamento (localizações)
com o tamanho de oito bits (um byte) cada, o que permite que cada uma dessas localizações possa assumir
um entre 256 valores diferentes. Ressalte-se, ainda, que cada célula possui um endereço único e
inconfundível, expresso por um valor numérico que define a exata localização desse byte, bem como que,
apesar do limitado tamanho de cada célula, podemos acessar dois bytes consecutivos (word) ou quatro
bytes consecutivos (doubleword) simultaneamente com um único endereçamento.
Em muitas ocasiões é preferível trabalhar com os endereços das variáveis a acessá-las pela maneira
convencional. A linguagem C++ dispõe do operador unário & que permite que os ponteiros (do inglês
pointer), um tipo especial de variável, armazenem o endereço de outras variáveis.
Podemos citar vários motivos para isso: são a única forma de implementar determinadas operações;
produzem código compacto robusto e eficiente; constituem ferramenta bastante poderosa para
manipulação da informação ou de elementos de arrays; constituem um meio para que as funções possam
realmente modificar os argumentos da função chamadora; são usados na alocação e desalocação da
memória do sistema; são usados para passar strings de uma função para outra; podem ser usados no lugar
de arrays, o que proporciona aumento de eficiência.
Mas nem tudo é simples na utilização dos ponteiros. Em primeiro lugar, a sintaxe de ponteiros pode ser
nova para muitos programadores de outras linguagens que estão iniciando seus estudos em C++; e, para
complicar, o uso incorreto ou descuidado dos ponteiros pode causar o travamento imediato do programa,
do sistema ou algo pior, como, por exemplo, a formatação do disco rígido.
Conceitualmente, ponteiro é uma variável que contém o endereço de localização na memória de outro
objeto. Normalmente, esse endereço é a localização de alguma variável declarada no programa. Então
dizemos que o ponteiro aponta para determinada variável. Logo também é correto chamá-lo de
apontador.
Declaração
Como qualquer variável, os apontadores precisam ser declarados. Ao declará-los, tomemos o cuidado de
observar o tipo do bloco que será apontado. Por exemplo, se queremos um ponteiro apontando para uma
variável char (bloco de 1 byte), devemos declará-lo como char; se o queremos apontando para uma
variável int (bloco de 2 (ou 4) bytes, conforme a máquina), também devemos declará-lo como int, e assim
por diante.
A declaração de um ponteiro é parecida com a declaração de outra variável qualquer. Eis a sintaxe:
tipo *nome;
onde tipo é o grupo a que pertence a variável e nome é o nome que escolhemos para a variável. Exemplo:
char *ch;
int *pi;
float *flot;
O operador unário *, mais conhecido como operador indireto, nos permite acessar o conteúdo do objeto
apontado; permite, também, que o compilador saiba que a variável guardará um endereço, e não outro
dado qualquer.
Quando declaramos um ponteiro, devemos inicializá-lo com algum "valor" que, de regra, será o
endereço de alguma variável. Conforme vimos, C++ dispõe do operador de endereço & que permite que
um apontador guarde o endereço da variável a qual se refere:
char c;
int p;
float flt;
Para compreendermos melhor, vamos supor que a variável inteira p seja inicializada com o valor 257 (int
p = 257;) e que esteja localizada no endereço 0065FDF8. Então, após a atribuição do ponteiro (int *pi
= &p;), pi estará apontado para o endereço de memória 0065FDF8 e *pi será igual a p, ou seja, seu
valor será exatamente os mesmos 257.
Veja as instruções:
A primeira instrução declara um ponteiro p (para int) e uma variável p2 do tipo int. Já a segunda
instrução declara dois ponteiros (flot e flot2) do tipo float. Observe o operador indireto * determinando
os elementos da lista que serão ponteiros. Observe também que os três ponteiros ainda não foram
inicializados, o que significa que eles apontam para um endereço qualquer da memória do micro. Um
ponteiro, nessas circunstâncias, pode acarretar sérios problemas. Se não existir nenhum "valor" para
inicializar o ponteiro, devemos inicializá-lo com 0 (zero), o que indicará que o ponteiro não se refere a
nenhum objeto:
tipo *nome = 0;
#include <iostream>
#include <conio>
using namespace std;
main()
{
int i = 97; //variável int
int *Pi = &i; //ponteiro para int
int *Pi2 = 0; //Ponteiro apontando para null
cout << "\n\tConteudo via variavel = " << i;
cout << "\n\tEndereco via variavel = " << &i;
cout << "\n\tConteudo via ponteiro = " << *Pi;
cout << "\n\tEndereco via ponteiro = " << Pi;
cout << "\n\n";
int i2 = 98; //variável int
Pi2 = & i2; //inicializa Pi2 com endereço de i2
cout << "\n\tAgora podemos acessar *Pi2 ... "
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int i = 97; //variável int
int *Pi = &i; //ponteiro para int
int *Pi2 = 0; //Ponteiro apontando para null
Label1 -> Caption = "\tConteúdo via variável = " + String(i) +
"\n\tEndereço via variável = " + IntToHex(int(&i), 8) +
"\n\tConteúdo via ponteiro = " + *Pi +
"\n\tEndereço via ponteiro = " + IntToHex(int(Pi), 8);
int i2 = 98; //variável int
Pi2 = & i2; //inicializa Pi2 com endereço de i2
Label2 -> Caption = "\tAgora podemos acessar *Pi2 ... " +
String(*Pi2); //exibe o conteúdo de i2 via ponteiro
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
Os exemplos acima ilustram bem o fato de que toda variável possui um endereço e de que, mesmo sem
saber o endereço específico da variável, podemos armazenar esse endereço em um ponteiro.
A reutilização de um ponteiro
Uma vez declarado e inicializado, podemos redefinir o conteúdo de um ponteiro indefinidamente. Basta
atribuirmos um novo endereço para o ponteiro, descartando o endereço anterior (que será perdido pelo
ponteiro). O exemplo a seguir usa um Label no Form. Conforme o evento do mouse, (down, move ou
up), um ponteiro apontará para variáveis diferentes, exibindo os valores respectivos no Label:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
A notação acima pode parecer estranha. Ocorre que o nome de um array é um ponteiro que aponta para o
primeiro elemento da matriz (conforme explicado em outra seção mais adiante). Logo, usamos essa
característica para montar o exemplo acima, onde um ponteiro aponta para outro ponteiro.
Uma vez que cada membro da estrutura possui um nome simbólico, podemos usá-lo no acesso aos dados,
separado do nome do membro através do operador . (ponto - que significa "membro de"):
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
struct Endereco
{
AnsiString Nome;
int idade;
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
/*inicializa variáveis na criação do Form*/
Marta.Nome = "Marta C. Plaça";
Marta.idade = 36;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Label1Click(TObject *Sender)
{
(*Label1).Caption = /*Breve você entenderá essa notação*/
Marta.Nome + '\n' + Marta.idade;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Label2Click(TObject *Sender)
{
Marta.Nome = "Marta C. Plaça Alves"; // casou!!!
Marta.idade = 432; // se for variável, aceita estes novos valores
Label1 -> Caption = "Será que Marta.Nome e Marta.idade são variáveis?";
(*Label2).Caption = Marta.Nome + '\n' + Marta.idade + " meses";
}
//---------------------------------------------------------------------------
A notação de ponteiros para estruturas segue um procedimento semelhante, com duas diferenças:
2 - Substituição do operador . "membro de" pelo operador -> ("aponta para"), que é formado por um hífen
“ - ” mais o símbolo "maior que “ > ”:
Label1 -> Caption é igual a (*Label1).Caption, assim como, obviamente, PtrNome -> Nome será igual a
(*PtrNome).Nome, então essas notações têm muito em comum!
O nome do array
Veja o seguinte trecho de código:
void __fastcall TForm1::Label1Click(TObject *Sender)
{
char Abcdario[26];
for(int i = 0; i < 26; i++)
{
Abcdario[i] = 65 + i;
Label1 -> Caption = Abcdario[0];
}
}
Ao ser executado, o programa imprimirá o caracter 'A' no label. Como sabemos, os elementos de um vetor
ficam enfileirados seqüencialmente na memória do computador. Supondo que Abcdario[0], o primeiro
elemento da matriz, ocupe a posição 651256 na mémória, Abcdario[1] ocuparia a posição 651257;
Abcdario[2], a posição 651258; Abcdario[3], a posição 651259; Abcdario[4], a posição 65125A; e
assim sucessivamente.
Veja agora o próximo exemplo, usando o nome da matriz, que também imprime 'A' no Label:
Pergunta: Se não colocamos o índice, porque essa aplicação imprime o caracter 'A' no Label? E porque
usamos a notação de ponteiros?
Resposta: Simplesmente porque o nome de um array representa o seu endereço de memória, sendo que tal
endereço é o do elemento que ocupa o índice 0 (zero) da matriz. Ou, de um modo mais objetivo: o nome
de um array é um ponteiro que aponta para o primeiro elemento da matriz.
Raciocine. Se
Label1 -> Caption = Abcdario[0]; // imprime 'A'
equivale a
então,
Label1 -> Caption = Abcdario[1]; // imprime 'B'
equivale a
Label1 -> Caption = *(Abcdario + 1); // imprime 'B'
e
Label1 -> Caption = Abcdario[2]; // imprime 'C'
equivale a
Label1 -> Caption = *(Abcdario + 2); // imprime 'C'
do exposto temos que a expressão *(Abcdario + i) sempre terá o mesmo valor de Abcdario[i].
Vamos, agora, montar dois exemplos que imprimem os caracteres 'C', 'D', 'E', 'F' e 'G':
int i = 2; /* cada valor de i determina inícios de impressão de diferentes*/
{
char Abcdario[26] = {65, 66, 67, 68, 69, 70, 71};
Label1 -> Caption = Abcdario + i; /* se i = 0, Label1 -> Caption = Abcdario;
*/
} // fim do primeiro exemplo
{
char Abcdario[26] = {65, 66, 67, 68, 69, 70, 71};
Label2 -> Caption = &Abcdario[i];
} // fim do segundo exemplo
Vejamos, agora, um exemplo onde um ponteiro é declarado e inicializado apontando para um vetor:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Label1 -> Caption = "";
Quando estudamos arrays de caracteres, aprendemos algumas formas de declarar e inicializar constantes
de caracteres, entre elas:
Agora já possuímos conceitos suficientes para entende o porquê de tal declaração. Veja um exemplo:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
char *ch = "Que saudades do Rio Paraná!!!!"; // Ponteiros... logo os entenderemos!!!
Label1 -> Caption = *ch; // imprime: Q
Label2 -> Caption = ch; // imprime: Que saudades do Rio Paraná!!!!
Label3 -> Caption = *ch + 1; // imprime: 82
Label4 -> Caption = ch + 1; // imprime: ue saudades do Rio Paraná!!!!
Label5 -> Caption = *(ch + 1); // imprime: u
}
Conclusão: Na linguagem C++, o relacionamento entre arrays e ponteiros é muito grande, a ponto de, sob
muitos aspectos, poderem ser tratados juntos.
Variáveis dinâmicas
Quando um programa é executado, o sistema operacional reserva um espaço de memória para o código
(ou instruções do programa) e outro espaço para as variáveis usadas durante a execução. Grosso modo,
esses espaços ocupam uma mesma região, que podemos denominar memória local. Também existem
outras zonas de memória, como a pilha, usada, entre outras coisas, para realizar o intercâmbio de dados
entre as funções. O resto, a memória que não estiver em uso por nenhum programa, é o que se conhece
por memória livre (área de alocação dinâmica, heap ou free store). Quando um programa usa a área de
alocação dinâmica, naturalmente estará usando parte desse resto de memória.
O maior poder esperado na utilização de ponteiros decorre, justamente, de seu uso junto a esse conceito de
alocação de memória no free store.
C++ dispõe de dois operadores para acesso à memória dinâmica: new e delete.
new double
Feito isso, o que acontece é que dbl passará a apontar para uma variável double na área de alocação
dinâmica. Logo, podemos inserir algum valor nessa variável:
*dbl = 563.9082;
Se a reserva de memória não for bem sucedida, o operador new devolverá um ponteiro nulo (NULL).
Na sintaxe acima, os símbolos < > significam que os dados inseridos não são obrigatórios. Ex:
placement. Continuemos:
:: new placement tipo ( inicializador) // char *ch = :: new char ('A');
:: , placement e ( inicializador)
O operador ::, relacionado com a sobrecarga de operadores, executa a versão global de new.
O mesmo se aplica a placement, que pode ser usado para fornecer argumentos adicionais para new. Mas
você poderá usar essa sintaxe somente se você tiver uma versão sobrecarregada de new que seja
compatível com os argumentos opcionais.
Um pedido para uma alocação para um dado não-array usa apropriadamente o operador new(). Qualquer
pedido, porém, para alocação array deve chamar o apropriado operador new[]:
:: new tipo[ ]
Alocação de memória para um objeto não-array é feito pelo uso de ::new(). Note que essa alocação é
sempre usada para tipos pré-definidos. Ela também é possível para sobrecarga de operador de função
global. De qualquer forma, isso geralmente não é aconselhável.
"toda memória reservada durante a execução do programa deve ser liberada antes do encerramento do
programa".
Vejamos um exemplo:
//---------------------------------------------------------------------------
void __fastcall TForm1::Label1Click(TObject *Sender)
{
int *a;
char *b = new char;
float *c = new float (123.4);
struct stPunto {
double e,f;
} *d;
a = new int;
d = new stPunto;
*a = 65;
*b = 65;
Neste exemplo vemos como é impossível liberar a alocação de memória feita em primeiro lugar. Se não
necessitamos mais dela, deveríamos tê-la liberado antes de reservá-la outra vez, e se ainda necessitamos,
devemos guardar sua posição, por exemplo com outro ponteiro.
Operador delete:
Como sabemos, em C++, devemos desalocar a memória usada por qualquer coisa que tenha sido criada
pelo operador new. Existe uma exceção a essa regra que é o filho de um form MDI, criado como filho de
um parent MDI, pois esses objetos serão deletados, automaticamente, pelo sistema quando do
encerramento do programa. Logo, os blocos de memória reservados com o operador new serão válidos até
que sejam liberados com delete ou, em certos casos, até o fim do programa. Mas é sempre aconselhável
liberar-se a memória reservada com new usando delete.
Sintaxe: ::
delete <array-name> [ ];
Quando se usa o operador delete com um ponteiro nulo, nenhuma ação será realizada. Essa
característica permite usar o operador delete com ponteiros sem a necessidade de se perguntar antes se
o mesmo é nulo.
delete i;
Nota: Os operadores new e delete são próprios de C++. Em C, usamos as funções malloc e free para
reservar e liberar memória dinâmica. Liberar um ponteiro nulo com free pode provocar conseqüências
desastrosas.
Em tópicos mais avançados abordaremos arrays dinâmicos (alocados através de new e liberados através
de delete).
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Label1 -> Caption = *i; // código implementado na criação de Form1
}
O melhor resultado produzido nos meus testes pelo exemplo acima foi o seguinte:
de certa forma, combina várias instruções numa só, pois, na verdade, cria um ponteiro:
uma variável:
new int
Só que essa variável não possui um nome próprio. Observe que o ponteiro não foi criado por new. O
operador new criou apenas a variável int inominada. Logo o ponteiro não estará sujeito às regras de
criação e destruição new / delete, visto ter sido criado naturalmente pelo programa, como outro ponteiro
qualquer.
Podemos acessar e modificar essa variável inominada através do ponteiro * i. Quando chamamos o
operador delete ele destrói apenas a variável criada pelo operador new, desalocando a memória
respectiva. A partir desse momento, o sistema fica livre para colocar qualquer tipo de dado nesse
endereço. O ponteiro, que não foi destruído pelo operador delete, visto que não foi criado por new,
continua existindo e apontando para o mesmo endereço.
Então, tentar usar esse ponteiro poderá ocasionar o imediato travamento do programa, ou a derrubada de
todo o sistema. Ou pior, o programa poderá continuar funcionando normalmente, e só travar
posteriormente, o que tornará bastante difícil a solução do bug.
Vamos refazer o exemplo, retirando o bug através da colocação de uma nova variável no endereço:
Nota: No exemplo abaixo, primeiro dê um clique em Button1, e depois em Button2. E depois, para liberar
a memória, clique novamente em Button1 antes de encerrar o programa.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
Ao rodar o exemplo, você observará que o ponteiro continuará apontando sempre para o mesmo endereço
de memória. Poderíamos, também, retirar o bug fazendo com que o ponteiro apontasse para outro
endereço. Para isso alteraríamos apenas o código de Button2Click:
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
int j = 1000; // essa variável não foi criada por new
i = &j;
Label1 -> Caption = "valor de i = " + String(*i) +
"\nendereço = " + IntToHex(int(&*i), 8);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
////////////////////////////////////////////////////////////////////////////////
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{ // eventos na criação de Form1
Label1 -> Caption = *Const_Str;
Label2 -> Caption = *Const_i;
Label3 -> Caption = *Const_dbl;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
Referências
É possível criar um segundo nome para determinado tipo de dados, ou seja, um "apelido". O nome
original e o "apelido" serão, exatamente, o mesmo dado, ocupando, inclusive, o mesmo endereço. Toda
modificação que implementarmos através do "apelido" estará afetando, diretamente, o dado original:
//---------------------------------------------------------------------------
Label1 -> Caption = esposa + '\n' + // imprime esposa (modificada por ref_esposa)
// demonstra que se trata do mesmo dado, através do endereço
"Endereço de esposa = " + IntToHex(int(&esposa), 8) + '\n' +
"Endereço de ref_esposa = " + IntToHex(int(&ref_esposa), 8);
}
//---------------------------------------------------------------------------
Instruções do tipo:
AnsiString &ref_esposa;
ref_esposa = esposa;
Retornarão uma mensagem de erro, avisando que a referência deve ser inicializada:
Certamente você já entendeu onde queremos chegar: Uma referência, uma vez inicializada no momento
de sua criação, apontará sempre para o mesmo dado. Não podemos pegar o "apelido" daquele dado e
transferi-lo para outro dado:
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
AnsiString irma = "Cybele"; // declara e inicializa variável irma
AnsiString &ref_irma = irma; // cria e inicializa uma referência para irma
ref_irma = "Cybele é minha irmã!"; // altera variável esposa através do "apelido"
Label1 -> Caption = irma + '\n' + // imprime irma (modificada por ref_irma)
// demonstra que se trata do mesmo dado, através do endereço
"Endereço de irma = " + IntToHex(int(&irma), 8) + '\n' +
"Endereço de ref_irma = " + IntToHex(int(&ref_irma), 8);
Label2 -> Caption = irma_1 + '\n' + // imprime irma_1, seja qual for o conteúdo
"Endereço de irma_1 = " + IntToHex(int(&irma_1), 8) + '\n'
+
// mostra que ref_irma mantém o antigo endereço
"Endereço de ref_irma = " + IntToHex(int(&ref_irma), 8) +
"\n\n\n" +
irma + '\n' + // mostra que irma foi alterada
// demonstra que se trata do mesmo dado, através do endereço
"Endereço de irma = " + IntToHex(int(&irma), 8) + '\n' +
ref_irma + '\n' +
"Endereço de ref_irma = " + IntToHex(int(&ref_irma), 8);
}
//---------------------------------------------------------------------------
Pelo exemplo, percebemos que a referência não pode apontar para outro dado. O que ela faz é alterar o
valor dos dados. E pode alterar tanto o valor do alvo, como do outro tipo que estivermos trabalhando,
atribuindo a esse segundo, o valor do alvo, ou vice-versa.
Este curso está disponível, online, no DicasBCB, o Site dos Programadores C++Builder
http://www.dicasbcb.com.br
nosso fórum:
http://www.dicasbcb.com
Agradecimentos especiais:
Lucas, em nome de toda nossa comunidade, muito obrigado por viabilizar esta edição em PDF de nosso
curso, antiga reivindicação de grande parte dos freqüentadores de nosso site!