Curso básico de C++ 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 Apostila de C++ Builder Pagina -2- O comando else............................................................................................................................................ 95 if ...else - Continuação ................................................................................................................................. 95 Comandos aninhados e Indentação .............................................................................................................. 97 Operadores lógicos....................................................................................................................................... 98 O operador condicional ternário .................................................................................................................. 99 Funções ...................................................................................................................................................... 100 Chamada de Funções ................................................................................................................................. 102 Definição de uma função ........................................................................................................................... 103 Protótipos de funções ................................................................................................................................. 105 Variáveis locais e globais........................................................................................................................... 107 A palavra-chave extern .............................................................................................................................. 109 A palavra-chave static................................................................................................................................ 111 Parâmetros das funções.............................................................................................................................. 114 O comando return ...................................................................................................................................... 115 Valores Default .......................................................................................................................................... 117 Funções inline ............................................................................................................................................ 118 O comando goto ......................................................................................................................................... 119 O loop while............................................................................................................................................... 119 break e continue ......................................................................................................................................... 122 O loop while sem fim................................................................................................................................. 122 loop while - continuação ............................................................................................................................ 123 O loop do... while....................................................................................................................................... 124 O loop for................................................................................................................................................... 126 loop for - continuação ................................................................................................................................ 127 Omissão e aninhamento no loop for .......................................................................................................... 128 O comando switch...................................................................................................................................... 130 métodos Canvas para desenhar objetos gráficos........................................................................................ 138 propriedades Canvas .................................................................................................................................. 140 TPen::Width............................................................................................................................................... 143 TPen::Mode................................................................................................................................................ 145 TPenRecall ................................................................................................................................................. 146 Usando brushes .......................................................................................................................................... 146 TBrush::Color ............................................................................................................................................ 148 TBrush::Style ............................................................................................................................................. 148 TBrush::Bitmap.......................................................................................................................................... 150 TBrushRecall ............................................................................................................................................. 151 Ler e inserir pixels ..................................................................................................................................... 151 TCanvas::Pixels ......................................................................................................................................... 152 TBitmap::ScanLine .................................................................................................................................... 153 Propriedades e métodos comuns de Canvas .............................................................................................. 154 Conhecendo arrays..................................................................................................................................... 165 Excedendo o limite de um array ................................................................................................................ 167 Matrizes multidimensionais ....................................................................................................................... 168 Arrays de caracteres ................................................................................................................................... 172 Estruturas ................................................................................................................................................... 174 Ponteiros .................................................................................................................................................... 176 A reutilização de um ponteiro.................................................................................................................... 180 Apostila de C++ Builder Pagina -3- Ponteiros apontando para outros ponteiros ................................................................................................ 181 Ponteiros apontando para structs ............................................................................................................... 182 O nome do array......................................................................................................................................... 184 Variáveis dinâmicas ................................................................................................................................... 186 Mais problemas ... ponteiros soltos............................................................................................................ 190 Ponteiros & constantes............................................................................................................................... 193 Referências................................................................................................................................................. 195 Reatribuição de uma referência ................................................................................................................. 196 Apostila de C++ Builder Pagina -4- O bê-a-bá da programação Não fique impressionado, se um dia você descobrir que o aplicativo que você está usando para ler ou tratar algum dado em seu computador foi desenvolvido em linguagem de programação C++ (pronuncia-se "cê mais mais"). Mesmo porque, em poucos dias, você já estará desenvolvendo um processador de texto que poderá, inclusive, ser usado para substituir o bloco de notas do seu Windows. Na escolha de uma linguagem de programação, deve-se considerar não apenas a facilidade de aprendizado, mas principalmente aquilo que se quer e que se pode fazer com ela. Uma escolha errada pode nos trazer prejuízos e frustrações. 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. Afinal, o quê é C++? Segundo Bjarne Stroustrup (criador de C++), "C++ é uma linguagem de uso geral com uma tendência para a programação de sistemas que: é uma C melhor; suporta abstração de dados; suporta programação orientada a objetos; e suporta programação genérica". C++ é um superset de C. Portanto quaisquer códigos escritos para a linguagem C devem funcionar normalmente em C++, porém, não raras vezes, C++ oferece uma maneira melhor, ou mais de uma maneira, de se obter o mesmo resultado, o quê nos proporciona grande flexibilidade em nosso trabalho. 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 Apostila de C++ Builder Pagina -5- e que rodava num computador DEC PDP-7, com sistema operacional UNIX. Ken Thompson, em 1970, efetuou algumas melhorias na linguagem BCPL e a chamou de linguagem "B". Em 1972, Dennis M. Ritchie, no Centro de Pesquisas da Beel Laboratories, implementou diversas melhorias na linguagem "B" que, considerada uma sucessora de "B", foi chamada de "C", rodando pela primeira vez num DEC PDP11, em sistema operacional UNIX. 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. Apostila de C++ Builder Pagina -6- 1º. Linguagens de baixo 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: 543 = (5 x 10²) + (4 x 10¹) + (3 x 10º) pois 543 = (5 x 100) + (4 x 10) + (3 x 1) 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: Apostila de C++ Builder Pagina -7- 1ª. o sistema numérico binário contém apenas os dois valores 0 e 1, em vez de 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9; e 2ª. o sistema numérico binário usa potências de 2, em vez de potências de 10. 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: 1ª. sua conversão para binário é relativamente simples; e 2ª. são razoavelmente compactos. 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. Apostila de C++ Builder Pagina -8- veja a tabela abaixo: decimal binário hexadecimal 1 0001 1 2 0010 2 3 0011 3 4 0100 4 5 0101 5 6 0110 6 7 0111 7 8 1000 8 9 1001 9 10 1010 A 11 1011 B 12 1100 C 13 1101 D 14 1110 E 15 1111 F 16 0001 0000 10 17 0001 0001 11 18 0001 0010 12 19 0001 0011 13 20 0001 0100 14 21 0001 0101 15 22 0001 0110 16 23 0001 0111 17 24 0001 1000 18 25 0001 1001 19 26 0001 1010 1A 27 0001 1011 1B 28 0001 1100 1C 29 0001 1101 1D 30 0001 1110 1E 31 0001 1111 1F 32 0010 0000 20 Apostila de C++ Builder Pagina -9- Existem algumas convenções que devem ser adotadas para se diferenciar os sistemas numéricos: 1ª. pode-se colocar um sufixo "d" num valor decimal; 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. Abaixo, exemplo de instrução em Assembly: a 100 MOV AX, 20 MOV BX, 30 ADD AX, BX NOP Do exemplo, AX e BX são registradores internos da CPU. O comando “a 100” determina o endereço inicial para o programa em 0100h; o programa executa o seguinte: move para o registrador AX o valor 20; move para o registrador BX o valor 30; soma o conteúdo do registrador AX com o conteúdo do registrador BX e deixa o resultado em AX; e a instrução NOP finaliza o programa. 2º. Linguagens de alto nível 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: Apostila de C++ Builder Pagina -10- PRINT " Esta frase que está entre aspas será exibida na tela! " Veja agora, a mesma frase em linguagem C: 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. Apostila de C++ Builder Pagina -11- O bit e o byte O tipo de dado mais utilizado pelos chips da família 80x86 não é o bit, e sim o byte. Um byte equivale a um grupo de oito bits. Para facilitar a leitura e a compreensão, costumam-se adotar algumas convenções: 1ª. completar com zeros os números binários para que eles se tornem um múltiplos de quatro ou de oito bits. Exemplo: 543 = 001000011111; 2ª. cada grupo de quatro bits deve ser separado por espaço. Exemplo: 0010 0001 1111. 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. Apostila de C++ Builder Pagina -12- 1. Deixar o código o mais claro e compreensível possível, inserindo comentários e usando-os adequadamente, bem como tomar bastante cuidado com a grande liberalidade na forma que o formato livre admite. 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: a) comentário no estilo C: /* ...*/ /* Neste tipo de comentário, tudo aquilo que estiver inserido entre a chave de abertura do comentário (barra e asterisco) e a chave de fechamento do comentário (asterisco e barra) será desconsiderado pelo compilador, independentemente da quantidade de linhas usadas. */ b) comentário no estilo C++: // // // // // // Neste tipo de comentário, tudo aquilo que estiver inserido à direita do comentário (barras duplas), até o final da linha será desconsiderado pelo compilador. Neste estilo, a cada nova linha, precisamos iniciar um novo comentário. 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: /* Abrimos fechamento na verdade comentário compilador um comentário no estilo C; /* até a primeira chave de não há problemas, pois a abertura desse novo comentário, não ocorreu, sendo considerada parte integrante do inicial */ aqui começa a parte ilegal do comentário, e o deve acusar o erro */ Cuidado com o formato livre Há uma grande liberalidade na forma em que podemos escrever um código C++, o que significa que o posicionamento de alguns comandos ou caracteres dentro do texto, de regra, não influenciarão no resultado da compilação. Esclarecendo melhor, espaços, tabulações, retornos ou comandos escritos em novas linhas são caracteres desconsiderados pelo compilador. Abaixo transcrevemos um mesmo programa escrito de três formas diferentes: Apostila de C++ Builder Pagina -13- void main(void) { cout << " C++ possui formato livre!!!\n"; getch(); } void main ( void ) { cout << " C++ possui formato livre!!!\n" ; getch ( ) ; } 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. 2. Cuidado com o uso de letras maiúsculas e minúsculas C++ é Case Sensitive 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, Apostila de C++ Builder Pagina -14- bem como com algumas funções pré-existentes. Boa parte desses comandos são digitados com letras minúsculas, como por exemplo: cout, cin, if, else, while, do ... while, for, switch ... case, include, iostream.h, conio.h, open(), getch(), define, undef, \n, \t, \a etc. Mas essa regra, dependendo do compilador, não é absoluta e comporta exceções, como algumas funções ou classes internas do C++ Builder, como ShowMessage() ou AnsiString ou quando, por exemplo no IBM-PC, trabalhamos com os 128 caracteres adicionais da tabela ASCII, pois neste tipo de dado, trabalhamos com o código do símbolo na base hexadecimal. É 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(). O menor programa possivel em C++ é: 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. Em se tratando aplicações tradicionais Windows, necessário far-se-á entendermos a função WinMain. Em capítulos posteriores, estudaremos essa função com detalhes 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 Apostila de C++ Builder Pagina -15- códigos fontes, bem como exportar os mesmos, já formatados, para o padrão .html ou .rtf. Para maiores informações, consulte http://www.fixedsys.com/context 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. Bem, chega de conversa e vamos por as mãos na obra: 1. inicialize o C++Buider; 2. dê um clique no menu File; 3. dê um clique em New... 4. Na janela que se abrir, selecione a opção Console Wizard e clique em OK; 5. selecione Console em Window Type e clique em finish. Apostila de C++ Builder Pagina -16- O Builder C++, abrirá o editor de textos para você. É nesta janela que digitaremos o código-fonte de nosso programa. 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: Apostila de C++ Builder Pagina -17- Na sugestão da Borland: #pragma hdrstop #include <condefs.h> //---------------------------------------------------------------#pragma argsused int main(int argc, char* argv[]) { return 0; } devemos digitar o código-fonte de nosso programa imbricando-o no código pré-estabelecido acima. 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. Delete toda a sugestão da Borland e, no editor de textos, digite: #include <iostream> #include <conio> main() { std::cout << "\n\tEstou fazendo o meu primeiro programa\n\a"; getch(); } Se preferir manter a sugestão da Borland, o código deverá ficar assim: #pragma hdrstop Apostila de C++ Builder Pagina -18- #include <condefs.h> #include <iostream> #include <conio> //--------------------------------------------------------------------------#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. Agora vamos rodar o programa para ver o resultado. 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: Apostila de C++ Builder Pagina -19- Muito bem, salvado o programa, encerre o aplicativo C++Builder. Abra a pasta onde você salvou o projeto PrimProgram.bpr e observe que o compilador colocou lá cinco arquivos: 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. Apostila de C++ Builder Pagina -20- Diretivas de pré-processador O pré-processador C++ é um programa que fornece mecanismos para inclusão de arquivos-textos, cabeçalhos padrão e compilação condicional baseada em macros. Esse programa também pode ser usado para definir e usar macros. 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. As diretivas de compilação mais comuns são: #include #define #line #ifdef #ifndef #if #else #elif #endif #undef #error #pragma Apostila de C++ Builder Pagina -21- Algumas particularidades: 1ª. todas as diretivas iniciam com #; 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. A diretiva #include é um mecanismo de tratamento do código-fonte que reúne, em um único arquivo de compilação, códigos (arquivos de cabeçalho ou hearder files, ou arquivos que contenham funções ou definições de dados) que se encontram dispersos em vários arquivos. 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 Apostila de C++ Builder Pagina -22- partir de então, nenhum sufixo é necessário para cabeçalhos da biblioteca padrão: <iostream>, <conio>, <map> etc. 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 arquivo de cabeçalho iostream Para o nosso programa imprimir a string Estou fazendo o meu primeiro programa no vídeo, nós providenciamos para que a Classe iostream fosse chamada à compilação através da diretiva #include. A biblioteca iostream contém as declarações necessárias ao uso de streams de entrada (teclado) e saída (vídeo), como, por exemplo, o objeto cout e o operador de inserção <<. Obtém-se o operador de inserção << digitando-se o caracter "menor que" duas vezes. 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: #include <iostream> // Cabeçalho padrão sem sufixo .h #include "iostream.h" // Cabeçalho de biblioteca não padrão #include <iostream.h> // Cabeçalho padrão com sufixo .h 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). Em nosso programa: #include<iostream> instrui o pré-processador a incluir os recursos de entrada e saída de iostream padrão, e std::cout determina a inclusão do cout padrão. Apostila de C++ Builder Pagina -23- Se quisermos, podemos tornar todos os nomes std globais, o que nos poupa da tarefa de ter que digitar constantemente o prefixo std:: antes de cout: #include <iostream> using namespace std; 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! O arquivo de cabeçalho conio Nosso programa possui outro cabeçalho, o arquivo conio. Trata-se de um arquivo de inclusão necessário à utilização da função getch(). Quando descrevemos o que o nosso programa faria, afirmamos que, por fim, ele aguardaria que uma tecla qualquer fosse pressionada para encerrar a aplicação. Essa tarefa foi desempenhada pela função getch() que retorna o caracter lido no teclado, sem imprimi-lo no vídeo. Se quisermos que o caracter lido seja impresso no vídeo, podemos usar a função getche(), também integrante de a conio. 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). Como fizemos isso? Apostila de C++ Builder Pagina -24- Alguns caracteres não podem ser inseridos diretamente pelo teclado. Por exemplo, se, durante a elaboração do código-fonte, no meio da string, pressionarmos a tecla Enter pensando que estaremos inserindo uma nova linha, faremos com que o compilador apresente uma mensagem de erro. 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: Escape Nome \a Bell (alerta) 007 emite um som do computador \b Backspace 008 retrocede um caracter \n New Line 010 envia o curso para a próxima linha Carriage 013 move o cursor para o início da linha Horizontal 009 põe o cursor para a próxima tabulação \r Valor ASCII Descrição return \t Tab \xff \0 representação hexadecimal Null 000 exibe um caracter nulo \" Aspas duplas 034 exibe aspas duplas (“) \' apóstrofe 039 exibe apóstrofe (') \? Interrogação 063 exibe ponto de interrogação \\ Barra invertida 092 exibe barra invertida 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" Apostila de C++ Builder Pagina -25- Funções Funções são a base de qualquer programa C++. Elas compõem, juntamente com as Classes, os blocos construtivos da linguagem. Dentro das funções, encontramos todos os comandos executáveis, sendo que as mesmas são a maneira típica de se obter alguma execução em C++. 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. /*Dica: não use o recurso de copiar e colar códigos-fontes quando estiver estudando. Procure digitar todo o código e entender o resultado que cada. instrução conhecida produzirá no programa.*/ #include <iostream> #include <conio> using namespace std; // torna todos o nomes std globais void ModifPrimProg(void); // declara a finção ModifPrimProg int main() // estamos em main() { ModifPrimProg(); // chama a função ModifPrimProg return 0; } // fim de main() void ModifPrimProg(void) // cabeçalho da definição da função { cout << "\n\t\tModificando o primeiro programa." "\n\n\n\t\tDigite dois caracteres mas veja " "s\xa2 o primeiro: "; getche(); // retorna e imprime o caracter lido no vídeo cout << '\a'; // beep - sinal sonoro getch(); // retorna o caracter sem imprimi-lo no vídeo } // fim de ModifPrimProg() Execute o programa e volte aos estudos. Com essa pequena alteração no código, podemos visualizar melhor alguns detalhes sobre as funções. Uma função deve ter a seguinte forma geral: Apostila de C++ Builder Pagina -26- tipo nome(declaração dos parâmetros) { 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 ";": cout << "uma instrução!"; 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. Apostila de C++ Builder Pagina -27- Não se preocupe em entender, por ora, todos os conceitos apresentados, posto que os abordaremos novamente no correr do curso. Até aqui, tivemos apenas a intenção de colocá-los em contado com o ambiente de programação C++Builder e com os fundamentos da linguagem C++. Imprimindo dados nos componentes Se você ainda não se sente familiarizado com o C++Builder, ou não se sente seguro para a respeito da forma correta de se colocar componentes nos formulários (Forms), antes de prosseguir no curso, dê uma olhada nas seguintes lições: Introdução ao BCB(http://www.dicasbcb.com.br/Iniciando_o_BCB/Inicia_o_BCB.htm) Introdução ao BCB - 2ª parte(http://www.dicasbcb.com.br/Iniciando_o_BCB2/Inicia_o_BCB2.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. Exemplos: No Object Inspector, mude o texto da propriedade Caption do Form, de um Label, de um botão, e assim por diante e visualize, já no projeto, o resultado da alteração. 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: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { Canvas->TextOut( Canvas->PenPos.x, Canvas->PenPos.y, "Imprimindo um texto no form com o objeto Canvas" ); Label1 -> Canvas->TextOut( Canvas->PenPos.x, Canvas->PenPos.y, "Imprimindo um texto no Label com o objeto Canvas" ); Canvas->TextOut( Canvas->PenPos.x = 100, Canvas->PenPos.y = 100, "Imprimindo um texto nas coordenadas X = 100 e Y = 100 do Form" ); } Outro exemplo: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) Apostila de C++ Builder Pagina -28- { /* Este exemplo deve ser testado com dois cliques. O primeiro chamará a propriedade Caption; o segundo, o objeto Canvas */ Label1 -> Caption = "Testando o Caption"; /* Na linha de código abaixo, precisamos determinar uma coordenada diferente da default (0, 0), para que esse texto não venha a sobrescrever o Caption de Label */ Label1 -> Canvas->TextOut( Canvas->PenPos.x = 100, Canvas->PenPos.y = 100, "Imprimindo um texto nas coordenas X = 100 e Y = 100 do Label" ); } Outro exemplo: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { Label1 -> Caption = "Testando propriedades"; Edit1->Text = "Continuando o teste ..."; ListBox1->Items-> Add(Label1 -> Caption); ListBox1->Items-> Add("Testando ..."); ComboBox1 -> Text = "Fala, Grande!"; ComboBox1 -> Items -> Add(Label1 -> Caption); ComboBox1 -> Items -> Add("Fala, Grande..."); ComboBox1 -> Items -> Add(Edit1->Text); } Outro exemplo: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { ShowMessage("Alô, Thérbio!"); ShowMessage("Alô, Thérbio!" "\nEstou na segunda caixa de mensagem,\n e na terceira linha"); MessageBox(0, "Agora uma caixa de mensagens \nm\n\ta\n\t\ti\n\t\t\ts\neditada!", "Caixa de Aprendizagem...", 64); } 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. Apostila de C++ Builder Pagina -29- Conhecendo melhor as propriedades que capturam dados Nos exemplos anteriores, conhecemos um modo de inserir textos ou strings nos componentes, sem nos preocuparmos de que modo isso nos pode ser útil. Agora aprofundaremos um pouco mais o estudo para podermos entender que essas propriedades não estão limitadas e pode produzir alguns resultados mais úteis: void __fastcall TForm1::Button1Click(TObject *Sender) /* Neste exemplo, iremos corrigir uma falha referente à cor do primeiro exemplo em que usamos o objeto Canvas do Form. Como você deve ter notado, as cores da frase sairam com um fundo branco (clWhite), enquento que o form manteve a cor original clBtnFace, visto que não procedemos a nenhuma alteração nela. Para visualizar melhor essa situação, dê uma olhada na propriedade Color do Form no Object Inspector. Pois bem, vamos corrigir a cor do fundo da frase para igualá-la ao Form.*/ { // Ok. A cor do fundo igual à cor do Form Canvas -> Brush -> Color = clBtnFace; /* A linha de código abaixo nos mostra como podemos usar propriedades matemáticas diretamente no Form*/ Canvas->TextOut( Canvas->PenPos.x = 10, Canvas->PenPos.y = 100, (5 * 4) + (60 - 50) ); /* Podemos chamar funções que nos mostrem a data e a hora...*/ Canvas->TextOut( Canvas->PenPos.x, Canvas->PenPos.y, Date()/* +Time()*/); } Evidentemente, outros componentes também são férteis para trabalharmos com dados: void __fastcall TForm1::Button1Click(TObject *Sender) { /*Uma caixa de mensagem que pode nos mostrar a data, a hora ou ambas.*/ ShowMessage( Time() /* + Date()*/ ); } Por certo, as operações abaixo já não causam muitas surpresas em você: void __fastcall TForm1::Button1Click(TObject *Sender) { ShowMessage("Alô, Thérbio!\nVocê sabe qual é o resultado de " "286 multiplicado por 132 somado com 412?"); Apostila de C++ Builder Pagina -30- ShowMessage((286 * 132) + 412); Label1 -> Caption = (5 * 4) + 13; // imprime 33 no label AnsiString a = 2 + 12; // AnsiString pode funcionar como inteiro ou ponto flutuante /* neste exemplo, o sinal + neste local do código, serve para concatenar os dados. Só ocorre a concatenação porque usamos o tipo AnsiString. Se tivéssemos usado uma variável inteira ou ponto flutuante, o resultado seria diferente*/ Label2 -> Caption = "Multiplicação de 14 X 14 = " + a * a; /* produz o mesmo resultado no Label. O mesmo resultado teríamos se tivéssemos usados variáveis inteiras ou ponto flutuante*/ Edit1 -> Text = "****Elimina os quatro primeiros caracteres" + 4; } 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. void __fastcall TForm1::BitBtn1Click(TObject *Sender) { /* O código abaixo determina que o programa coloque no Caption de Label1 tudo aquilo que já está nele e mais o número 1. */ Label1 -> Caption = Label1 -> Caption + 1; } 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. C++ aceita esses dois estilos de comentários: /* Esse estilo de comentário é derivado da linguagem C. Ele é aberto com uma barra seguida de um asterisco. Tudo aquilo que estiver após essa chave de abertura, até encontrar uma chave de fechamento (um asterisco e uma barra) será desconsiderado pelo compilador */ // // // // // Essas duas barras significam comentários no estilo C++. Uma vez aberto, esse comentário não precisa ser fechado. Mas ele só vale para a linha em que foi aberto, a partir do ponto em que foi aberto e até o final da linha nada mais será considerado Apostila de C++ Builder Pagina -31- Comentários também podem ser úteis para tirar o efeito de determinadas partes de código, quando estamos fazendo experiências. Você já viu tal fato, nos exemplos que colocamos o código para apresentar a data ou a hora. Se você retirar as chaves de abertura e de fechamento, perceberá que o resultado do programa será diferente. 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: void __fastcall TForm1::Button1Click(TObject *Sender) { // Label1 -> Caption = "Testando propriedades"; // Edit1->Text = "Continuando o teste ..."; /* ListBox1->Items-> Add(Label1 -> Caption); ListBox1->Items-> Add("Testando ..."); */ ComboBox1 -> Text = "Fala, Grande!"; ComboBox1 -> Items -> Add(Label1 -> Caption); ComboBox1 -> Items -> Add("Fala, Grande..."); ComboBox1 -> Items -> Add(Edit1->Text); } 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. Coloque um Memo e um Button no Form. No evento OnClick do botão digite: void __fastcall TForm1::BitBtn1Click(TObject *Sender) { char ValChar = 'a'; int ValInt = 100; float ValFloat = 100.78; // problemas na precisão double ValDoub = 9.9876543; bool ValBool = (2.0000 == (20 - 18)); //Verdade: dois = a vinte menos dezoito Memo1->Lines->Strings[0] = ValChar; // 1ª linha do Memo imprime: a Memo1->Lines->Strings[1] = ValInt; // 2ª linha do Memo imprime: 100 Memo1->Lines->Strings[2] = ValFloat; // 3ª linha do Memo imprime: 100.78... Memo1->Lines->Strings[3] = ValDoub; // 4ª linha do Memo imprime: 9.9876543 // Se a afirmação feita na declaração de ValBool é verdadeira if( ValBool == True) /*5ª linha de Memo imprime: Afirmação Verdadeira*/ Memo1->Lines->Strings[4] = "Afirmação Verdadeira"; Apostila de C++ Builder Pagina -32- else // senão - ou seja, se a afirmação é falsa /* Quinta linha de Memo imprime: Afirmação Mentirosa */ Memo1->Lines->Strings[4] = "Afirmação Mentirosa"; } 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). Um inteiro decimal é formado pela seqüência de dígitos decimais: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Exemplos válidos para decimais são: 0 12345 1 - 89 Os números a seguir não são exemplos válidos para decimais: 00 123.4 1,0 89-8 12 34 045 OBS. Embora alguns compiladores aceitem declarações do tipo: int i = 123.4; todos os números colocados depois do ponto fracionário (nesse caso, o 4) serão desconsiderados. Um inteiro hexadecimal é formado pela seqüência de dígitos decimais: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 combinados com letras que vão de "A" a "F" (A, B, C, D, E, F ou a, b, c, d, e, f). Como não há regras que impeçam que um hexadecimal possua só valores decimais em sua composição, devemos diferenciá-los dos inteiros decimais inserindo 0x em seus primeiros dígitos. Exemplos válidos para hexadecimais são: 0x0 0x1234 0x1 0xA 0xaBcD 0x1A9F Os números a seguir não são válidos para valores hexadecimais: 00x Apostila de C++ Builder 0x123.4 0x1,0 0xAGB Pagina -33- OxaB cD FF12 Um inteiro octal é formado pela seqüência de dígitos decimais: 0, 1, 2, 3, 4, 5, 6, 7. Devemos diferenciálos dos inteiros decimais inserindo 0 em seu primeiro dígito. Exemplos válidos para octais são: 00 012345 01 - 056 Os números a seguir não são exemplos válidos para octais: 0 0123.4 01,0 089 012 34 45 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. Coloque um Label e um Button no Form. No evento OnClick do botão digite: void __fastcall TForm1::Button1Click(TObject *Sender) { // declara um valor inteiro decimal cujo nome será i int i = 43; // podemos substituir o 43 por hexadecimais, //octais ou outros valores decimais Label1 -> Caption = i; // coloca i no Caption de Label1 } 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. veja uma tabela inicial: Apostila de C++ Builder decimais octais hexadecimais 0 00 0x0 1 01 0x1 2 02 0x2 3 03 0x3 4 04 0x4 5 05 0x5 6 06 0x6 7 07 0x7 Pagina -34- 8 010 0x8 9 011 0x9 10 012 0xA 11 013 0xB 12 014 0xC 13 015 0xD 14 016 0xE 15 017 0xF 16 020 0x10 17 021 0x11 18 022 0x12 19 023 0x13 20 024 0x14 21 025 0x15 22 026 0x16 23 027 0x17 24 030 0x18 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. Apostila de C++ Builder Pagina -35- Os tipos ponto flutuante De um modo geral, podemos afirmar que os tipos ponto flutuante são representados pelos números fracionários na base decimal. Ou seja, trata-se de números, de regra, constituídos por um ponto fracionário que não pode ser substituído por uma vírgula. 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. Exemplos válidos para ponto flutuante são: -1.e4 12.345 .1 1.23e-2 1.23E+2 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. void __fastcall TForm1::Button1Click(TObject *Sender) { float f = 3.65e-2; double d = 3.65e-2; Label1 -> Caption = f; // problemas de precisão com o tipo float Label2 -> Caption = d; } 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, Apostila de C++ Builder Pagina -36- quanto pode ser interpretado como um elemento de um conjunto de caracteres da tabela ASCII. O tipo char geralmente tem um byte, o que é suficiente para conter os 256 valores mencionados. Exemplos válidos para caracteres são: 'a' 'A' 'z' '5' '§' '%' '' '*' 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. Abaixo, representaremos algumas seqüências de escape: Controle/Caracter Sequencia de escape Valor ASCII nulo (null) \0 00 campainha (bell) \a 07 retrocesso (backspace) \b 08 tabulacao horizontal \t 09 nova linha (new line) \n 10 tabulacao vertical \v 11 alimentacao de folha (form feed) \f 12 retorno de carro (carriage return) \r 13 aspas (") \" 34 apostrofo (') \' 39 interrogacao (?) \? 63 barra invertida (\) \\ 92 É 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. Apostila de C++ Builder Pagina -37- Coloque um Label no Form e no evento OnClick do Label digite: void __fastcall TForm1::Label1Click(TObject *Sender) { /*Declara uma variável int chamada StrInt e atribui a ela o valor que será digitado no InputBox pelo usuário*/ int StrInt = StrToInt /*converte AnsiString para int*/ (InputBox("Converte int para char","Digite um valor entre 0 e 255...","")); Label1 -> Caption = char(StrInt)/*converte int(StrInt) para char*/; } 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: signed (com sinal); unsigned (sem sinal); long (máxima precisão); short (menor precisão). Tipo bytes possíveis valores char 1 -128 a 127 unsigned char 1 0 a 255 signed char 1 -128 a 127 int 4 -2147483648 a 2147483647 unsigned int 4 0 a 4294967295 signed int 4 -2147483648 a 2147483647 Apostila de C++ Builder Pagina -38- Tipo bytes possíveis valores short int 2 -32768 a 32767 unsigned short int 2 0 a 65535 signed short int 2 -32768 a 32767 long int 4 -2147483648 a 2147483647 signed long int 4 -2147483648 a 2147483647 unsigned long int 4 0 a 4294967295 float 4 -(3.4e38) a 3.4e38 double 8 1.7e308 a -(1.7e308) long double 10 1.7e308 a -(1.7e308) 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; /* Edit1 imprime os números, os caracteres correspondentes da tabela ASCII e mais alguns caracteres para facilitar a leitura*/ Edit1 -> Text = Edit1 -> Text + c + ' ' + '=' + ' ' + ch + ' ' + ' '; } } //------------------------------------------------------------------ Apostila de C++ Builder Pagina -39- void __fastcall TForm1::BitBtn1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { // Adiciona o conteúdo de Edit1 a Memo1 Memo1->Lines->Add(Edit1->Text); } 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 Apostila de C++ Builder Pagina -40- byte; int = 4 bytes; float = 4 bytes; etc), o compilador reservará uma ou mais gavetas seqüencialmente para armazenar o valor correspondente, pois cada tipo de dado possui um tamanho próprio. 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). Por exemplo, a declaração: long double LgDbl; 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. Por exemplo, a declaração: 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": ch = 'b'; // ch agora possui outro valor 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á Apostila de C++ Builder Pagina -41- ativado para atribuir o caracter A à variável ch, exibindo esse valor no Caption de SpeedButton1. Quando o botão do mouse for liberado, o evento OnMouseUp será ativado para alterar o valor da variável para b, exibindo esse valor no Caption de SpeedButton1. //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #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; } //--------------------------------------------------------------------------void __fastcall TForm1::SpeedButton1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { ch = 'b'; // atribui b 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: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { double Dbl; Edit1 -> Text = AnsiString(sizeof(Dbl)) + " bytes"; // retorna pelo nome da variável /* retorna o valor pelo tipo de dados*/ Edit2 -> Text = AnsiString(sizeof(bool)) + " byte"; RichEdit1 -> Clear(); // apaga o conteúdo de RichEdit1 for (int linha = 0; linha <= 3; linha ++) // Em breve estudadremos o loop for Apostila de C++ Builder Pagina -42- RichEdit1 -> Lines -> Insert(linha, ""); RichEdit1 RichEdit1 RichEdit1 RichEdit1 -> -> -> -> Lines-> Lines-> Lines-> Lines-> Strings[0] Strings[1] Strings[2] Strings[3] = = = = "Tamanho "Tamanho "Tamanho "Tamanho // prepara RichEdit1 para receber // as quatro linhas de strings de de de de char = " + String(sizeof(char)); int = " + String(sizeof(int)); String = " + String(sizeof(String)); AnsiString = " + AnsiString(sizeof(AnsiString)); ShowMessage(sizeof(long double)); } //--------------------------------------------------------------------------- Atribuição de valores a variáveis Conforme você já deve ter percebido, para criarmos uma variável, precisamos declarar o seu tipo (char, int, float, ...), seguido pelo nome da variável e por um símbolo de ponto e vírgula: int i; Com o operador de atribuição = atribuímos valores às variáveis: 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: // declararamos 5 variáveis das quais inicializamos duas (i3 e i4) int i, i2, i3 = 10, i4 = 15, i5; 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 compr = 10; int larg = 7; int total = compr * larg; // atribui o resultado da multiplicação a total Apostila de C++ Builder Pagina -43- Existem algumas regras que devem ser respeitadas: Os nomes das variáveis só podem começar com letras (a, A, b, B, c, C, d, D, e, E, f, ...) ou por caracter de sublinhar ( _ ); depois de começado o nome, podemos colocar letras, números ou caracter de sublinhar no nome: 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 /*Coloca no caption de BitBtn1 o título Área =, mais o valor da variável total convertido em AnsiString*/ BitBtn1 -> Caption = "Área = " + AnsiString(total); } //--------------------------------------------------------------------------void __fastcall TForm1::BitBtn1Click(TObject *Sender) { altr = altr + 10; //incrementa a variável altr com + 10, alterando-lhe o valor larg = larg + 30; //incrementa a variável larg com + 30, alterando-lhe o valor BitBtn1 -> Height = altr; //atribui a BitBtn1 -> Height o novo valor de altr; BitBtn1 -> Width = larg; //atribui a BitBtn1 -> Width o novo valor de larg; //atualiza o Caption de BitBtn1 BitBtn1 -> Caption = "Área = " + AnsiString(altr * larg); } //--------------------------------------------------------------------------- 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! Apostila de C++ Builder Pagina -44- Variáveis signed e unsigned (com e sem sinal) Quando tratamos dos modificadores de tipos, tivemos oportunidade de observar que em C++ os tipos char e int possuem as variedades signed (com sinal) e unsigned (sem sinal). Tais modificadores permitem-nos filtrar alguns valores que poderão ser desnecessários para determinados tipos de aplicação, como por exemplo os números negativos, ou os valores estendidos da tabela ASCII. Os tipos inteiros (int, short e long) e os char, de regra, podem armazenar valores positivos ou negativos. Todavia se declarados com o modificador de tipos unsigned, ficarão restritos a números iguais ou maiores do que zero. A tabela abaixo exibe os valores possíveis para os tipos de dados, com ou sem os modificadores de tipo: Tipo bytes possíveis valores char 1 -128 a 127 unsigned char 1 0 a 255 signed char 1 -128 a 127 int 4 -2147483648 a 2147483647 unsigned int 4 0 a 4294967295 signed int 4 -2147483648 a 2147483647 short int 2 -32768 a 32767 unsigned short int 2 0 a 65535 signed short int 2 -32768 a 32767 long int 4 -2147483648 a 2147483647 signed long int 4 -2147483648 a 2147483647 unsigned long int 4 0 a 4294967295 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. O programa abaixo mostra-nos alguns limites de valores: Apostila de C++ Builder Pagina -45- void __fastcall TForm1::SpeedButton1Click(TObject *Sender) { for (char ch = -126; ch > -127; ch++) Memo1->Lines->Add(ch); Memo1 -> Lines -> Add(""); for (signed char Sch = 1 ;Sch > 0; Sch++) Memo1->Lines->Add(Sch); Memo1 -> Lines -> Add(""); for (unsigned char Uch = -1000; Uch > 0; Uch++) Memo1->Lines->Add(Uch); Memo1 -> Lines -> Add(""); for (int i = 2147483600; i > 0; i++) Memo1->Lines->Add(i); Memo1 -> Lines -> Add(""); for (unsigned int ui = 4294967200; ui > 0; ui++) Memo1->Lines->Add(ui); Memo1 -> Lines -> Add(""); for (signed short int SSi = 32768; SSi < -32000; SSi++) Memo1->Lines->Add(SSi); } Excedendo o limite de uma variável Se tentarmos atribuir um valor fora da faixa que a variável pode conter, ela continuará a partir do zero, se for unsigned; porém, se for do tipo signed, continuará a armazenar dados a partir do menor valor negativo. 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 -> ComboBox1 -> Items -> ComboBox1 -> Items -> ComboBox1 -> Items -> unsigned int ComboBox1 -> ComboBox1 -> ComboBox1 -> Add("Limite para int = " + String(i)); Add("Limite + 1 = " + String(i + 1)); Add("Limite + 2 = " + String(i + 2)); Add(""); Ui = 4294967295; Items -> Add("Limite para unsigned int = " + String(Ui)); Items -> Add("Limite + 1 = " + String(Ui + 1)); Items -> Add("Limite + 2 = " + String(Ui + 2)); Apostila de C++ Builder Pagina -46- ComboBox1 -> Items -> Add(""); signed int Si = 2147483647; ComboBox1 -> Items -> Add("Limite para signed int = " + String(Si)); ComboBox1 -> Items -> Add("Limite + 1 = " + String(Si + 1)); ComboBox1 -> Items -> Add("Limite + 2 = " + String(Si + 2)); ComboBox1 -> Items -> Add(""); char ch = ComboBox1 ComboBox1 ComboBox1 ComboBox1 127; -> Items -> Items -> Items -> Items -> -> -> -> Add("Limite para char = " + String(int(char(ch)))); Add("Limite + 1 = " + String(int(char(ch + 1)))); Add("Limite + 2 = " + String(int(char(ch + 2)))); Add(""); signed char Sch = 127; ComboBox1 -> Items -> Add("Limite para signed char = " + String (Sch)); ComboBox1 -> Items -> Add("Limite + 1 = " + String(int(char(Sch + 1)))); ComboBox1 -> Items -> Add("Limite + 2 = " + String(int(char(Sch + 2)))); ComboBox1 -> Items -> Add(""); unsigned char Uch = 255; ComboBox1 -> Items -> Add("Limite para unsigned char = " + String (Uch)); ComboBox1 -> Items -> Add("Limite + 1 = " + String(int(char(Uch + 1)))); ComboBox1 -> Items -> Add("Limite + 2 = " + String(int(char(Uch + 2)))); ComboBox1 -> Items -> Add(""); } //--------------------------------------------------------------------------- Operações matemáticas com unsigned Já sabemos que uma variável unsigned não pode ser atribuída com valores negativos. Se, numa operação matemática, tivermos um resultado menor do que zero para uma variável unsigned, no momento em que o cálculo atingir o primeiro valor negativo (-1) a operação é remetida para o maior valor do tipo do dado, de onde o programa dará continuidade aos cálculos com os valores restantes. 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: Apostila de C++ Builder Pagina -47- //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------unsigned int uiBase = 9; /* declara e inicializa a variável base (observe que poderia ser uma constante)*/ unsigned int uiAcresce = 1; /* declara e inicializa a variável que será incrementada na subtração */ //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Timer1Timer(TObject *Sender) { if (Label1->Visible == true) // se label estiver visivel { Label1->Visible=false; // Label1 ficará invisível /* e altera-se o Caption de Label1, com o resultado da subtração, cada vez que ele ficar invisível*/ Label1 -> Caption = uiBase - uiAcresce; /* incrementa a variável uiAcresce (ela vale 1, depois 2, depois 3, 4, 5, etc)*/ uiAcresce = uiAcresce + 1; } else // ou então ( se Label1 estiver invisível) Label1->Visible=true; // Label1 ficará visível } //--------------------------------------------------------------------------- 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 Apostila de C++ Builder Pagina -48- • data • null string terminator 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!" "Guarulhos é a terra das Pizzarias..." "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 #pragma delphiheader begin #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); Apostila de C++ Builder Pagina -49- // C++ style 'sprintf' (NOTE: Target buffer is the string) // AnsiString& __cdecl sprintf(const char* format, ...); // Returns *this int __cdecl printf(const char* format, ...); // Returns formatted length // static AnsiString __fastcall FormatFloat(const AnsiString& format, const long double& value); static AnsiString __fastcall FloatToStrF(long double value, TStringFloatFormat format, int precision, int digits); static AnsiString __fastcall IntToHex(int value, int digits); static AnsiString __fastcall CurrToStr(Currency value); static AnsiString __fastcall CurrToStrF(Currency value, TStringFloatFormat format, int digits); // 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); __fastcall AnsiString(unsigned int); __fastcall AnsiString(long); __fastcall AnsiString(unsigned long); __fastcall AnsiString(__int64); __fastcall AnsiString(unsigned __int64); __fastcall AnsiString(double src); __fastcall AnsiString(const WideString &src); // Destructor __fastcall ~AnsiString(); // Assignments AnsiString& __fastcall operator =(const AnsiString& rhs); AnsiString& __fastcall operator +=(const AnsiString& rhs); // Comparisons bool __fastcall bool __fastcall bool __fastcall bool __fastcall bool __fastcall bool __fastcall int __fastcall int __fastcall operator ==(const AnsiString& rhs) const; operator !=(const AnsiString& rhs) const; operator <(const AnsiString& rhs) const; operator >(const AnsiString& rhs) const; operator <=(const AnsiString& rhs) const; operator >=(const AnsiString& rhs) const; AnsiCompare(const AnsiString& rhs) const; AnsiCompareIC(const AnsiString& rhs) const; //ignorecase // Accessing character at specified index char __fastcall operator [](const int idx) const { ThrowIfOutOfRange(idx); // Should Range-checking be optional to avoid overhead ?? return Data[idx-1]; } Apostila de C++ Builder Pagina -50- #if defined(ANSISTRING_USE_PROXY_FOR_SUBSCRIPT) // The use of a proxy class optimizes the case where Unique() must be called // when accessing the string via the subscript operator. However, the use of // of the proxy class has some drawbacks. First, it breaks code that apply // operators to the return value. For example, &MyString[i]. Second, it // fails in cases where a implicit conversion was relied upon. For example, // callFuncThatTakesAnObjectWithACharCtr(MyString[i]); // In that case, two implicit conversions would be required... // The first issue can be remedied by enhancing the proxy class to support // all valid operators. The second issue can be lessened but not completely // eliminated. Hence, the use of the PROXY class is not the default! // private: class TCharProxy; friend TCharProxy; class TCharProxy { public: TCharProxy(AnsiString& strRef, int index) : m_Ref(strRef), m_Index(index) {} TCharProxy& operator=(char c) { m_Ref.Unique(); m_Ref.Data[m_Index-1] = c; return *this; } operator char() const { return m_Ref.Data[m_Index-1]; } protected: AnsiString& int }; m_Ref; m_Index; public: TCharProxy __fastcall operator [](const int idx) { ThrowIfOutOfRange(idx); // Should Range-checking be optional to avoid overhead ?? return TCharProxy(*this, idx); } #else char& __fastcall operator [](const int idx) { ThrowIfOutOfRange(idx); // Should Range-checking be optional to avoid overhead ?? Unique(); // Ensure we're not ref-counted return Data[idx-1]; } #endif // Concatenation AnsiString __fastcall operator +(const AnsiString& rhs) const; // C string operator char* __fastcall c_str() const { return (Data)? Data: "";} // Query attributes of string int __fastcall Length() const; bool __fastcall IsEmpty() const; // Make string unique (refcnt == 1) Apostila de C++ Builder Pagina -51- AnsiString& __fastcall Unique(); // Modify string AnsiString& __fastcall Insert(const AnsiString& str, int index); AnsiString& __fastcall Delete(int index, int count); AnsiString& __fastcall SetLength(int newLength); int __fastcall Pos(const AnsiString& subStr) const; AnsiString __fastcall LowerCase() const; AnsiString __fastcall UpperCase() const; AnsiString __fastcall Trim() const; AnsiString __fastcall TrimLeft() const; AnsiString __fastcall TrimRight() const; AnsiString __fastcall SubString(int index, int count) const; int int double __fastcall ToInt() const; __fastcall ToIntDef(int defaultValue) const; __fastcall ToDouble() const; // Convert to Unicode int __fastcall WideCharBufSize() const; wchar_t* __fastcall WideChar(wchar_t* dest, int destSize) const; // MBCS support enum TStringMbcsByteType {mbSingleByte, mbLeadByte, mbTrailByte}; TStringMbcsByteType __fastcall ByteType(int index) const; bool __fastcall IsLeadByte(int index) const; bool __fastcall IsTrailByte(int index) const; bool __fastcall IsDelimiter(const AnsiString& delimiters, int index) const; bool __fastcall IsPathDelimiter(int index) const; int __fastcall LastDelimiter(const AnsiString& delimiters) const; int __fastcall AnsiPos(const AnsiString& subStr) const; char* __fastcall AnsiLastChar() const; protected: void ThrowIfOutOfRange(int idx) const; private: char *Data; }; extern AnsiString __fastcall operator +(const char*, const AnsiString& rhs); #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: Apostila de C++ Builder Pagina -52- unsigned char Len; char Data[sz]; }; ///////////////////////////////////////////////////////////////////////////// // 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); __fastcall SmallString(const AnsiString& src) { long len = src.Length(); Len = (unsigned char)((len > sz)? sz: len); strncpy(Data, src.c_str(), Len); } char& __fastcall operator [](const unsigned char idx) {return Data[idx-1];} SmallString& __fastcall operator =(const SmallString& rhs); __fastcall operator AnsiString() const; }; // used when SmallStrings are in unions (can't have a ctor) // must cast DummySmallString to SmallString to do anything useful template<unsigned char sz> __fastcall SmallString<sz>::SmallString(const char* src) { long len = strlen(src); Len = (unsigned char)((len > sz)? sz: len); strncpy(Data, src, Len); } Apostila de C++ Builder Pagina -53- template<unsigned char sz> __fastcall SmallString<sz>::SmallString(const SmallString& src) { Len = src.Len; for (int i = 0; i < Len; i++) Data[i] = src.Data[i]; } template<unsigned char sz> SmallString<sz>& __fastcall SmallString<sz>::operator =(const SmallString& rhs) { if (this != &rhs) { Len = rhs.Len; for (int i = 0; i < Len; i++) Data[i] = rhs.Data[i]; } return *this; } template<unsigned char sz> inline __fastcall SmallString<sz>::operator AnsiString() const { return AnsiString(Data, Len); } } using namespace System; // The following is provided for backward compatibility. // Otherwise, the new IntToStr(__int64) causes ambiguity for old code // that used 'bool', uint and DWORD. // namespace Sysutils { extern PACKAGE AnsiString __fastcall IntToStr(int Value)/* overload */; extern PACKAGE AnsiString __fastcall IntToStr(__int64 Value)/* overload */; } #pragma option push -w-inl inline AnsiString __fastcall IntToStr(bool value) { return Sysutils::IntToStr(int(value)); } inline AnsiString __fastcall IntToStr(unsigned int value) { return Sysutils::IntToStr(int(value)); } inline AnsiString __fastcall IntToStr(unsigned long value) { return Sysutils::IntToStr(int(value)); } #pragma option pop #pragma delphiheader end. #endif // DSTRING_H Apostila de C++ Builder Pagina -54- Por enquanto não tente entender os fundamentos deste arquivo, pois nele aparecem muitos termos que nos são novidades ainda. No avançar do curso, muitos desses fundamentos serão abordados, tornando a compreensão de partes do arquivo mais ou menos intuitiva. Todavia, vamos verificar alguns tópicos, por dois motivos: 1º - É importante que tenhamos conhecimento de determinadas facilidades que AnsiString nos proporciona; 2º - aprendermos a operar determinadas funções da classe, por intuição. 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); Apostila de C++ Builder Pagina -55- __fastcall __fastcall __fastcall __fastcall __fastcall __fastcall __fastcall AnsiString(unsigned int); AnsiString(long); AnsiString(unsigned long); AnsiString(__int64); AnsiString(unsigned __int64); AnsiString(double src); AnsiString(const WideString &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: AnsiString Raca("Pastor Alemão"); 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; } //--------------------------------------------------------------------------- Também poderá ser usada para preencher um controle Edit: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString Pre_Hist = "Tiranossauro ou 286???"; Edit1->Text = Pre_Hist; } //--------------------------------------------------------------------------- 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, Apostila de C++ Builder Pagina -56- depois que um valor foi fornecido para um controle que fundamenta-se na classe AnsiString como base de seu conteúdo, para executar qualquer operação matemática com tal String, devemos convertê-la para o tipo de dado desejado. Para essa finalidade podemos, por exemplo, usar a função StrToInt(): void __fastcall TForm1::Button1Click(TObject *Sender) { Label1->Caption = "111"; int i = StrToInt(Label1->Caption) - 11; Edit1->Text = i; } Conforme apresentado, a classe AnsiString provê um grande número de construtores que nos permitem a criação de strings de qualquer espécie: Por exemplo, podemos usar: um caracter: AnsiString Simbolo = 'T'; um inteiro AnsiString Int = 120; um long int AnsiString LongoInt = -73495745; um valor ponto flutuante: AnsiString PtoFlut = 675.15; Um double: AnsiString DplPrec = 2.15e28; um string AnsiString AloTher = "Alô, Thérbio"; Qualquer dessas variáveis pode ser declarada usando os construtores equivalentes: //--------------------------------------------------------------------------AnsiString Simbolo('T'); AnsiString Int(120); AnsiString AloTher("Alô, Thérbio"); AnsiString PtoFlut(675.15); AnsiString LongoInt(-73495745); AnsiString DplPrec(2.15e28); //--------------------------------------------------------------------------- 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: Apostila de C++ Builder Pagina -57- um caracter: char Letra = 't'; Edit1 -> Text = AnsiString(Letra); um inteiro: Integer Numero = 256; Edit1->Text = AnsiString(Numero); um long integer: long Valor = 4949431; Panel1->Caption = AnsiString(Valor); um valor ponto flutuante: Single Distancia = 5698.03; Label1->Caption = AnsiString(Distancia); um double: Double Tamanho = 24588; Edit1->Text = AnsiString(Tamanho); uma string AnsiString Servico = "Limpador de Fogões"; 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 ... Por que será? No início do arquivo dstring.h vemos a inclusão do arquivo de cabeçalho sysmac.h: #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: class PACKAGE TVarArray; Apostila de C++ Builder Pagina -58- typedef TVarArray *PVarArray; typedef bool Boolean; // typedef int Integer; // -2147483648..2147484647 typedef char Char; // 0..255 typedef wchar_t WideChar; // Unicode character typedef signed char Shortint; // -128..127 typedef short Smallint; // -32768..32767 typedef unsigned char Byte; // 0..255 typedef unsigned short Word; // 0..65535 typedef unsigned long DWord; // 0..4294967295 typedef void* Pointer; // typedef char AnsiChar; // typedef signed long Longint; // -2147483648..2147484647 typedef unsigned int Cardinal; // 0..2147484647 typedef long double Extended; // 10 byte real typedef float Single; // 4 byte real typedef double Double; // 8 byte real typedef char* const Openstring; typedef void* file; // typedef void* Text; // typedef Text TextFile; // typedef char* PChar; // typedef char* PAnsiChar; // typedef wchar_t* PWideChar; // typedef unsigned char ByteBool; // Apostila de C++ Builder Pagina -59- // D16 string/D32 shortstring formalparm typedef unsigned short WordBool; // typedef Cardinal LongBool; // typedef AnsiString String; // typedef SmallStringBase<255> ShortStringBase; // typedef SmallString<255> ShortString; // typedef ShortString* PShortString; // typedef AnsiString* PAnsiString; // typedef PAnsiString PString; // typedef WideString* PWideString; // typedef Extended* PExtended; // typedef Currency* PCurrency; // typedef Variant* PVariant; // typedef __int64 LONG64; // !! obsolete typedef GUID TGUID; // typedef TGUID* PGUID; // typedef HRESULT HResult; // typedef Integer __fastcall (*TThreadFunc)(Pointer Parameter); typedef void (*TModuleUnloadProc)(HINSTANCE hinstance); typedef bool (*TEnumModuleFunc)(HINSTANCE hinstance, void *data); typedef struct TVarData *PVarData; 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. Nas próximas lições abordaremos várias implementações de AnsiString. Apostila de C++ Builder Pagina -60- Nota: voltamos a insistir para que você não se preocupe quando se deparar com nomes de tipos de dados ainda não estudados (como classe, por exemplo) visto que no momento apropriado do curso abordaremos tais tópicos com a profundidade necessária. Funções que modificam strings Agora conheceremos algumas implementações do tipo AnsiString. Embora tenhamos, para facilidade de compreensão, nos referido às instâncias de classes como variáveis, tecnicamente isso está incorreto. Na verdade, a instância de uma classe (AnsiString, por exemplo) é denominada objeto. E os objetos possuem funções-membro que facilitam sua manipulação. Essas funções-membro podem ser acessadas através do operador ponto “.”. Vejamos alguma funções-membro utilizadas para modificar strings. // Modify string AnsiString& __fastcall Insert(const AnsiString& str, int index); AnsiString& __fastcall Delete(int index, int count); AnsiString& __fastcall SetLength(int newLength); int __fastcall Pos(const AnsiString& subStr) const; AnsiString __fastcall LowerCase() const; AnsiString __fastcall UpperCase() const; AnsiString __fastcall Trim() const; AnsiString __fastcall TrimLeft() const; AnsiString __fastcall TrimRight() const; AnsiString __fastcall SubString(int index, int count) const; 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. void __fastcall TForm1::Label1Click(TObject *Sender) { AnsiString test = "O_ _está_feito"; Label1->Caption = test.Insert("grande_teste", 3); } Delete() AnsiString& __fastcall Delete(int index, int count); Apostila de C++ Builder Pagina -61- Remove um número especificado de caracteres de uma string. Inicia a contagem para a remoção na variável especificada por index, encerrando a exclusão com a remoção do último caracter contado para completar count: void __fastcall TForm1::Label1Click(TObject *Sender) { AnsiString test = "O_grande_teste_está_feito"; Label1->Caption = test.Delete(3, 12); Label1->Caption = test.Insert(" ", 3); } 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: void __fastcall TForm1::Label1Click(TObject *Sender) { AnsiString test = "O_grande_teste_está_feito"; 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”. void __fastcall TForm1::Label1Click(TObject *Sender) { AnsiString test = "O_grande_teste_está_feito"; Apostila de C++ Builder Pagina -62- Label1->Caption = test.Pos("está"); } LowerCase() e UpperCase() AnsiString AnsiString __fastcall LowerCase() const; __fastcall UpperCase() const; LowerCase() transforma todas as letras da string para letras minúsculas e UpperCase() transforma todas para maiúsculas: void __fastcall TForm1::Label1Click(TObject *Sender) { AnsiString test = "O_grande_TESTE_está_FEITO"; Label1->Caption = test; Label2->Caption = test.LowerCase(); Label3->Caption = test.UpperCase(); } Funções que modificam strings AnsiString ... continuação AnsiString AnsiString AnsiString AnsiString __fastcall __fastcall __fastcall __fastcall Trim() const; TrimLeft() const; TrimRight() const; SubString(int index, int count) const; Trim(), TrimLeft()e TrimRight() AnsiString AnsiString AnsiString __fastcall Trim() const; __fastcall TrimLeft() const; __fastcall TrimRight() const; 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()): Apostila de C++ Builder Pagina -63- void __fastcall { Label1->Color = Label2->Color = Label3->Color = Label4->Color = AnsiString test Label1->Caption Label2->Caption Label3->Caption Label4->Caption TForm1::Label1Click(TObject *Sender) clYellow; clYellow; clYellow; clYellow; = " = test; = test.Trim(); = test.TrimLeft(); = test.TrimRight(); O grande teste está feito "; } SubString() AnsiString __fastcall SubString(int index, int count) const; 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: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { // Coloca uma string em Edit1 Edit1 -> Text = "Meu pai se chama Julio Alves"; // Declara e inicializa a variável Frase com a string contida no texto de em Edit1 AnsiString Frase = Edit1 ->Text; /* Declara e inicializa a variável MeuPai com a string que começa no caracter 18 e termina no 28 de Frase*/ AnsiString MeuPai = Frase.SubString(18, 28); // Concatena string com MeuPai, imprimindo no Label1 Label1 -> Caption = "Papai se chama " + MeuPai; } //--------------------------------------------------------------------------- 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: Apostila de C++ Builder Pagina -64- void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString Teste = "abcdefghijlmnopqrstuvxz"; ShowMessage((String)"A posição de 'q' é: " + AnsiPos('q', Teste)); } AnsiString continuação... (dstring.h) Agora já conheceremos diversas funções-membro de AnsiString. Como exercício tente entender a lógica de alguma funções localizadas no arquivo dstring.h. Evidentemente você deverá consultar o HELP do C++Builder. Para tanto, você pode proceder da seguinte maneira. Escolha uma função-membro. Por exemplo: static AnsiString __fastcall IntToHex(int value, int digits); Copie o nome da função para o Editor de Códigos do C++Builder: 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. static AnsiString __fastcall IntToHex(int value, int digits); 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: void __fastcall TForm1::Label1Click(TObject *Sender) { Label1->Caption = IntToHex(100000, 10); } Apostila de C++ Builder Pagina -65- Percebeu como é fácil?!? Sem dúvida alguma, a melhor fonte de pesquisas para compreendermos o funcionamento do C++Builder é o seu próprio HELP. Se você realmente ambiciona aprender programar C++ usando esse excelente compilador, entendemos que não existe melhor fonte de pesquisas. Grande problema que parte dos programadores iniciantes encontram diz respeito ao idioma. Nem todos tem facilidades com inglês. Confesso que eu também não possuo grande facilidade. Procuro supri-la com programas que podem ser baixados na própria NET. Particularmente, eu prefiro o Babylon (considero as versões antigas melhores. Aliás, tenho o ótimo hábito de não atualizar aplicativos com os quais eu me identifico. Várias vezes me arrependi de atualizá-los, em face de “pequenas armadilhas” embutidas nas novas versões do tipo: deixar de ser freeware. Nesses casos, se você não tomou o cuidado de fazer um backup do programa de instalação antigo, a dor de cabeças e o arrependimento poderá ser considerável). 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: Se “A” é maior do que “B”, faça isso; Senão, faça aquilo. No caso acima, o termo maior está representando o operador relacional “>”. Para mais detalhes, dê uma olhadinha no tópico que trata tal assunto. Ansistring implementa métodos para tratamento de operadores relacionais: // Comparisons bool __fastcall bool __fastcall bool __fastcall bool __fastcall bool __fastcall bool __fastcall int __fastcall int __fastcall operator ==(const AnsiString& rhs) const; operator !=(const AnsiString& rhs) const; operator <(const AnsiString& rhs) const; operator >(const AnsiString& rhs) const; operator <=(const AnsiString& rhs) const; operator >=(const AnsiString& rhs) const; AnsiCompare(const AnsiString& rhs) const; AnsiCompareIC(const AnsiString& rhs) const; //ignorecase Vejamos outra implementação de AnsiString: // Query attributes of string int __fastcall Length() const; Apostila de C++ Builder Pagina -66- Length() retorna o número de caracteres de um AnsiString. Logo podemos escrever um código mesclando operadores relacionais com essa função: void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString Str = "Esta é a primeira string"; AnsiString Str2 = "Esta é a segunda string"; if(Str.Length() > Str2.Length()) ShowMessage((String)"A primeira string é maior. Ela possui " + Str.Length() + " caracteres"); else ShowMessage((String)"A segunda string é maior. Ela possui " + Str2.Length() + " caracteres"); } 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: Apostila de C++ Builder Pagina -67- typedef signed short int ssint; 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: void __fastcall TForm1::Button1Click(TObject *Sender) { // cria um sinônimo (ssint) para signed short int typedef signed short int ssint; // declara e inicializa variáveis ssint ssint Altura = 15, Largura = 20, Comprimento = 26; ssint Volume; Volume = Altura * Largura * Comprimento; // centraliza o caption de Label1 Label1 -> Alignment = taCenter; // altera o estilo da font de Label1 para negrito, itálico e sublinhado Label1->Font->Style = Label1->Font->Style<<fsBold<<fsItalic<<fsUnderline; Label1 -> Caption = "******Cálculo do volume de um cubo******\n" "Altura = " + String(Altura) + "\n" "Largura = " + String(Largura) + "\n" "Comprimento = " + String(Comprimento) + "\n" "Volume = " + String(Volume); } 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. #define Kelvin -273 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: void __fastcall TForm1::Button1Click(TObject *Sender) { #define Kelvin -273 #define Frase "Ibirarema é uma pequena cidade do Estado de São Paulo!" #define pi 3.141592653589 #define letra 'A' /* Essas constantes não são tão constantes afinal. Apostila de C++ Builder Pagina -68- Se, mantendo a declaração anterior, fizéssemos uma nova declaração para pi: #define pi 10 o compilador aceitaria esse novo valor desconsiderando o anterior!!!!!*/ Label1 -> Caption = Frase "\n" + AnsiString(Kelvin * pi) + "\n" + letra; } 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) Em capítulos posteriores, estudaremos essa diretiva com detalhes. 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: const int Kelvin = -273; const double pi = 3.141592653589; 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. void __fastcall TForm1::Button1Click(TObject *Sender) { const int km = 170; const AnsiString Itap = "Em Itapetininga faz muito frio!!!!"; const float mtr = 17; const char alf = 'm'; // Erro .... Aqui estamos diante de uma verdadeira constante Itap = "tentando alterar uma constante..."; Label1 -> Caption = Itap + "\n" + AnsiString(km + mtr) + "\n" + alf; } Apostila de C++ Builder Pagina -69- O tipo enum O tipo enum cuida tratar de um tipo cuja sintaxe é bastante elaborada, para conter um conjunto de valores definidos pelo usuário chamados dados de tipos enumerados ou enumeração. 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 } Apostila de C++ Builder Pagina -70- Observe que cada constante enumerada possui um valor inteiro e que podemos atribuir um valor específico a determinada constante. Todavia se não especificarmos tal valor, a primeira constante assumirá o valor 0; a segunda, 1; a terceira, 2; a quarta, 3; e assim sucessivamente. Como, em nosso exemplo optamos por atribuir valores aleatórios às constantes, percebemos que as constantes subseqüentes assumem valores com incremento de 1 a partir das que receberam um valor qualquer (em nosso exemplo, os meses de maio, agosto e novembro). Internamente as constantes enumeradas são representadas como sendo do tipo int. 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 // declara variável do tipo Meses_do_Ano Meses_do_Ano Mes; //declara variável int int iMes; iMes = StrToInt(InputBox ("Caixa para colheita de Valores", "Digite um número entre 1 e 12", "")); Mes = Meses_do_Ano(iMes); switch(Mes) { case 1: Label1 -> Color = clGreen; /*escolha uma cor diferente para cada mês*/ Label1 -> Font -> Name = "Arial"; /*e uma font para cada mês*/ Label1 -> Font -> Color = clYellow; /*e uma cor de font para cada mês*/ Label1 -> Caption = AnsiString(iMes) + " equivale a janeiro..."; break; case 2: Label1 Label1 Label1 Label1 -> -> -> -> Apostila de C++ Builder Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a fevereiro..."; Pagina -71- break; case 3: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a março..."; case 4: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a abril..."; case 5: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a maio..."; case 6: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a junho..."; case 7: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a julho..."; case 8: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a agosto..."; case 9: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a setembro..."; case 10: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a outubro..."; Apostila de C++ Builder Pagina -72- case 11: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a novembro..."; case 12: Label1 Label1 Label1 Label1 break; -> -> -> -> Color = Font -> Font -> Caption clGreen; Name = "Arial"; Color = clYellow; = AnsiString(iMes) + " equivale a dezembro..."; 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); e possui um operador unário: - (menos unário). Os operadores binários operam sobre dois operandos e o unário sobre um. 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): Apostila de C++ Builder Pagina -73- int i = 440; i = -i; Label1 -> Caption = i; // Agora o valor de i é - 440 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 Edit1 -> Edit2 -> Edit3 -> Edit3 -> soltar) Text = ""; Text = ""; Text = ""; ReadOnly = // esvazia Edit1 // Esvazia Edit2 // esvazia Edit3 true; // Edit3 não aceita dados via teclado ou mouse (arrastar e RadioGroup1 -> Caption = "Escolha uma operação"; RadioButton1 -> Caption = "+ (adição)"; RadioButton2 -> Caption = "- (subtração)"; RadioButton3 -> Caption = "* (multiplicação)"; RadioButton4 -> Caption = "/ (divisão)"; RadioButton5 -> Caption = "% (módulo)"; } //--------------------------------------------------------------------------void __fastcall TForm1::RadioButton1Click(TObject *Sender) { /*Na operação de adição precisamos declarar variáveis e realizar as respectivas coversões para evitar erros de concatenação de strings em face do operador + */ double primeiro = StrToFloat(Edit1 -> Text); double segundo = StrToFloat(Edit2 -> Text); Edit3->Text = primeiro + segundo; //realiza o cálculo por via indireta Edit1->SetFocus(); //chama a função SetFocus() para colocar o foco em Edit1 } //--------------------------------------------------------------------------void __fastcall TForm1::RadioButton2Click(TObject *Sender) { Apostila de C++ Builder Pagina -74- Edit3->Text = double((Edit1->Text) - (Edit2->Text)); //realiza o cálculo por via direta Edit1->SetFocus(); //chama a função SetFocus() para colocar o foco em Edit1 } //--------------------------------------------------------------------------void __fastcall TForm1::RadioButton3Click(TObject *Sender) { Edit3->Text = double((Edit1->Text) * (Edit2->Text)); //realiza o cálculo por via direta Edit1->SetFocus(); //chama a função SetFocus() para colocar o foco em Edit1 } //--------------------------------------------------------------------------void __fastcall TForm1::RadioButton4Click(TObject *Sender) { Edit3->Text = double((Edit1->Text) / (Edit2->Text)); //realiza o cálculo por via direta Edit1->SetFocus(); //chama a função SetFocus() para colocar o foco em Edit1 } //--------------------------------------------------------------------------void __fastcall TForm1::RadioButton5Click(TObject *Sender) { /* Essa parte do código foi corrigido com o comando if ...else pois apresentava um bug nas operações em que o divisor era maior que o dividendo*/ //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: -273; // Retorna o valor -273 x = (a + b) - ((c * d) / e); e assim por diante... 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: Apostila de C++ Builder Pagina -75- //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #include <math.h> // Declarar essa biblioteca para funções matemáticas #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 Label1 -> Caption = "*****CALCULA EQUAÇÃO DE SEGUNDO GRAU*****\n" "Dê um clique aqui para digitar os valores \"a\", \"b\" e \"c\" "; } //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { Form1 -> AutoSize = true; // ajusta o tamanho do form ao label try { retornoA: a = InputBox("Caixa de Entrada de Valores", "Digite um valor para 'a'", ""); StrToInt(a); if (a == 0) { MessageBox(0,"Zero não é um valor válido para 'a' !" "\nDigite outro valor... ", "Erro no valor escolhido", 16); goto retornoA; } 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'", ""); Apostila de C++ Builder Pagina -76- StrToInt(b); Label1 -> Caption = Label1 -> Caption + "\n b = " + b; } catch(...) { MessageBox(0, "Erro ... Valor não suportado pelo programa ...", " Erro de digitação", 16); } 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; double x1, x2, delta; delta = (b*b) - ((a * 4) * c); if(delta < 0) MessageBox(0,"delta negativo... não existem raízes reais...\n" "\ntente novamente com outros valores...", "Erro nos valores", 16); else { Label1 -> Caption = Label1 -> Caption + "\n\ndelta = " + FloatToStr(delta); x1 = ((b * (-1)) + (sqrt (delta))) / (a * 2); Label1 -> Caption = Label1 -> Caption + "\nx1 = " + FloatToStr(x1); x2 = ((b * (-1)) - (sqrt (delta))) / (a * 2); Label1 -> Caption = Label1 -> Caption + "\nx2 = " + FloatToStr(x2); } } catch(...) { MessageBox(0, "Por favor, digite os valores corretamente", "Erro de execução...", 16); } } Apostila de C++ Builder Pagina -77- //--------------------------------------------------------------------------void __fastcall TForm1::FormKeyPress(TObject *Sender, char &Key) { if (Key == 13) // se for pressionada a tecla Enter Close(); // encerra o programa } //--------------------------------------------------------------------------- Entendendo melhor o C++Builder Quando você inicia o C++Builder, ele escreve automaticamente algumas linhas de código para a sua aplicação. Tentaremos entender o que significa cada uma dessas linhas: //--------------------------------------------------------------------------#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: Apostila de C++ Builder Pagina -78- #include <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 Vejamos o seu conteúdo: ///////////////////////////////////////////////////////////////////////////////// // 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 Apostila de C++ Builder Pagina -79- // equivalent to including VCLMAX.H) // // INC_CLX This macro is defined by CLX.H to include OFF // the base set of CLX headers // // INC_CLXDB_HEADERS When this macro is defined, CLX.H includes // requires INC_CLX the core Database headers of CLX. OFF // // INC_CLXEXT_HEADERS When this macro is defined, CLX.H includes // requires INC_CLX all CLX headers. OFF // ///////////////////////////////////////////////////////////////////////////////// #ifndef __BASEPCH0_H__ #define __BASEPCH0_H__ // v1.0 of BCB checked for VCL_FULL to avoid LEAN_AND_MEAN support // #if defined(VCL_FULL) // BCB v1.0 compatible #define NO_WIN32_LEAN_AND_MEAN #endif #if !defined(_WINDOWS_) // Don't optimize #if !defined(NO_WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN // #define _VCL_LEAN_AND_MEAN // #endif // #endif // if WINDOWS.H has already been included Enable LEAN_AND_MEAN support BCB v1.0 compatible NO_WIN32_LEAN_AND_MEAN _WINDOWS_ #if !defined(COM_NO_WINDOWS_H) //Make sure COM Headers don't include WINDOWS.H/OLE2.H #define COM_NO_WINDOWS_H #define UNDEF_COM_NO_WINDOWS_H #endif #if !defined(RPC_NO_WINDOWS_H) // Make sure RPC Headers don't include WINDOWS.H #define RPC_NO_WINDOWS_H #define UNDEF_RPC_NO_WINDOWS_H #endif // Core (minimal) Delphi RTL headers // #include <System.hpp> #include <Types.hpp> #include <Windows.hpp> #include <Messages.hpp> #include <SysUtils.hpp> #include <Classes.hpp> // Core (minimal) VCL headers // #if defined(INC_VCL) #include <Controls.hpp> #include <Graphics.hpp> #include <Forms.hpp> #include <Dialogs.hpp> #include <StdCtrls.hpp> #include <ExtCtrls.hpp> Apostila de C++ Builder Pagina -80- // Full set of VCL headers // #if defined(INC_VCLEXT_HEADERS) #include <Buttons.hpp> #include <ChartFX.hpp> #include <ComCtrls.hpp> #include <DdeMan.hpp> #include <FileCtrl.hpp> #include <GraphSvr.hpp> #include <Grids.hpp> #include <MPlayer.hpp> #include <Mask.hpp> #include <Menus.hpp> #include <OleCtnrs.hpp> #include <OleCtrls.hpp> #include <Outline.hpp> #include <Tabnotbk.hpp> #include <Tabs.hpp> #include <VCFImprs.hpp> #include <VCFrmla1.hpp> #include <VCSpell3.hpp> #endif // INC_VCLEXT_HEADERS #endif // INC_VCL // Core (minimal) CLX headers // #if defined(INC_CLX) #include <Qt.hpp> #include <BindHelp.hpp> #include <QConsts.hpp> #include <QStdActns.hpp> #include <QStyle.hpp> #include <QControls.hpp> #include <QTypes.hpp> #include <QGraphics.hpp> #include <QForms.hpp> #include <QDialogs.hpp> #include <QStdCtrls.hpp> #include <QExtCtrls.hpp> #include <QMenus.hpp> #include <QClipbrd.hpp> #include <QMask.hpp> #include <QButtons.hpp> #include <QComCtrls.hpp> // CLX Database related headers // #if defined(INC_CLXDB_HEADERS) #include <QDBCtrls.hpp> #include <DB.hpp> #include <DBTables.hpp> #endif // INC_CLXDB_HEADERS // Full set of CLX headers // Apostila de C++ Builder Pagina -81- #if defined(INC_CLXEXT_HEADERS) #include <QDBGrids.hpp> #include <QDBCtrls.hpp> #include <QDBActns.hpp> #include <QGrids.hpp> #include <QImgList.hpp> #include <QSearch.hpp> #include <QActnList.hpp> #include <QCheckLst.hpp> #include <QPrinters.hpp> #endif // INC_CLXEXT_HEADERS #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 #if defined(UNDEF_RPC_NO_WINDOWS_H) // Clean up MACRO to prevent inclusion of WINDOWS.H #undef RPC_NO_WINDOWS_H #undef UNDEF_RPC_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. Apostila de C++ Builder Pagina -82- #pragma hdrstop A seguir, encontramos a seguinte linha de códigos: #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. O compilador C++Builder suporta as seguintes diretivas #pragma: #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 Apostila de C++ Builder Pagina -83- #pragma pack #pragma package #pragma resource #pragma startup #pragma undefineonoption #pragma warn Sintaxe: #pragma hdrstop 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): #include <vcl.h> // arquivos de header comuns #pragma hdrstop // fim da lista comum #include "Unit1.h" // header files específicos // .... 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: Apostila de C++ Builder Pagina -84- #include “Unit1.h” 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) #pragma package(smart_init, weak) Descrição: smart_init argument Apostila de C++ Builder Pagina -85- A diretiva #pragma package(smart_init) é utilizada durante a inicialização do aplicativo. Ela assegura que qualquer unidade de códigos requisitada pelo programa será inicializada na segqüência correta, ou seja, serão inicializadas na ordem determinada por suas dependências (Incluída por default no pacote arquivo fonte). Tipicamente, você pode usar o #pragma package para arquivos .CPP que são criados como packages. 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. 3. Ordem de prioridade dentro da unit. 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 é: B10 B20 B30 C10 C20 C30 A10 A20 A30 Se, ao invés de units, o arquivo anterior fosse .OBJ a ordem seria: A10 B10 C10 A20 B20 C20 A30 B30 C30 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. Descrição: weak packages 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, Apostila de C++ Builder Pagina -86- o compilador omite a unit de BPLs quando possível, e cria uma cópia local non-packaged da unit quando ela é requerida por outra aplicação ou package. Uma unit compilada com esta diretiva é dita como sendo “weakly packaged”. #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: #pragma resource “*.dfm” 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. Exemplos de forms incluem objetos TLoginDialog e TPasswordDialog. Apostila de C++ Builder Pagina -87- Nota: Quando trabalhamos com objetos form, não chamamos explicitamente os métodos Free ou Destroy para liberá-los. Em vez disso, usamos o método liberar (Release method), o qual espera até que todos event handlers (parte do programa que trata determinadas situações) tenham terminados sua execução. _fastcall, __fastcall Categoria: modificadores C++Builder, extensão de palavra-chave Sintaxe: valor_de_retorno _fastcall nome_da_função(parm-lista) valor_de_retorno __fastcall nome_da_função(parm-lista) 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). Todas as funções-membro da classe Form devem usar a convenção __fastcall. 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++. Para implementação estilo Microsoft VC++ __fastcall, veja __msfastcall e __msreturn. 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. Apostila de C++ Builder Pagina -88- Unit: Classes Descrição: Components são objetos persistentes que possuem as seguintes capacidades: A capacidade de aparecer no Componente palette e ser transferido para o form designer. A capacidade de administrar-se, a si, e a outros componentes. Incrementar o fluxo e armazenar capacidades. A capacidade de ser convertido em um controle ActiveX ou outro objeto COM por wizards na página ActiveX do diálogo New Objects. 1. 2. 3. 4. 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 Indica o componente que é responsável por carregar e liberar seus componentes. __property TComponent* Owner = {read=FOwner}; Descrição: Use Owner para encontrar o proprietário de um componente. O Owner de um componente é responsável por duas coisas: 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. Apostila de C++ Builder Pagina -89- O proprietário de um componente é determinado pelo parâmetro passado ao construtor quando o componente é criado. Pelo fato de os componentes serem criados no form designer, o form é automaticamente nomeado como o proprietário. 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. Operadores de incremento e decremento Você já deve ter percebido que mencionamos expressões do tipo incremento e decremento. Num sentido mais técnico, trata-se de dois operadores de extrema utilidade em C++. 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; produziria o mesmo resultado se fosse substituída por: 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 uiAcresce++; // operador após o nome da variável - sufixo --uiDecresce; // prefixo uiDecresce--; // sufixo Apostila de C++ Builder Pagina -90- Na posição de prefixo, primeiro o operador é aplicado e depois a variável tem seu valor acessado. Na posição de sufixo, o valor da variável é acessado antes de o operador ser aplicado. 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { Memo1->Lines->Add("incrementa Acresce com sufixo " + String(Acresce++) + " " "incrementa ACRESCE com prefixo " + String(++ACRESCE)); Memo1->Lines->Add("decrementa Decresce com sufixo " + String(Decresce--) + " " "decrementa DECRESCE com prefixo " + String(--DECRESCE)); } //--------------------------------------------------------------------------- Operadores relacionais Existem alguns operadores que fazem comparações e, por isso, são chamados de relacionais: Apostila de C++ Builder > maior >= maior ou igual < menor <= menor ou igual == igual a Pagina -91- > maior != diferente 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::FormPaint(TObject *Sender) { /* inicia o loop (laço) for com variável ponto flutuante. Aqui está sendo usado um operador relacional menor ou igual */ for ( float fVar = 0; fVar <= 200; fVar = fVar + 0.1) { TRect Val; // tipo que define um retângulo // função que agrupa as coordenadas do retângulo especificado SetRect((RECT *)&Val,0,(Height*fVar) / 200, Width,(Height*(fVar+1)) / 200); //determina a cor para preenchimento do background. Canvas->Brush->Color=RGB(0,0,255-fVar*1.1); // preenche o retângulo especificado no canvas usando o Brush atual Canvas->FillRect(Val); } } //--------------------------------------------------------------------------void __fastcall TForm1::FormResize(TObject *Sender) { /* determina a repintura do controle depois de outra importante mensagem Windows ser tratada*/ Invalidate(); } //--------------------------------------------------------------------------- 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 Apostila de C++ Builder Pagina -92- comando if ... else sem, contudo, entrar em detalhes sobre o funcionamento do mesmo, exceto por alguma rápida explicação, até mesmo em comentários, que já devem ter lhe dado uma pequena noção acerca do mesmo. Essa é justamente nossa intenção na utilização antecipada dessas instruções. Você vai tendo contato com os comandos e, aos poucos, assimilando sua utilidade e forma de funcionamento. Dessa forma, o estudo deve ser facilitado no momento que tratamos diretamente do assunto, pois, ainda que não se lembre das minúcias, o comando não será algo totalmente estranho para você. 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. Podemos resumir o uso do if da seguinte forma: 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. Também podemos determinar um bloco de comandos entre chaves { }: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { if(Edit1 -> Text == "") { Edit1 -> Text = "Primeiro Comando deste bloco"; Label1 -> Caption = "Segundo Comando deste bloco"; Apostila de C++ Builder Pagina -93- Memo1 -> Lines -> Add("Terceiro Comando e último comando deste bloco"); } } 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"); } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { /* declara e inicializa variável iBusc com o número devolvido por IndexOf() para o item que contém Teste*/ int iBusc = ComboBox1->Items->IndexOf("Teste"); // se a a busca tiver êxito, a variável possui valor diferente de -1 if (iBusc > -1) // chama Delete() para apagar o item buscado ComboBox1->Items->Delete(iBusc); } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -94- O comando else Pode ocorrer de um aplicativo ter de executar uma instrução se determinada condição for verdadeira, e outra instrução se tal condição for falsa. Um recurso oferecido por C++ para essas situação é a combinação if ... else. A forma do uso de if ... else pode ser resumido da seguinte forma: 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: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { if(StrToInt(Edit1 -> Text) >= StrToInt(Edit2 -> Text)) { if((StrToInt(Edit1 -> Text) % StrToInt(Edit2 -> Text)) == 0) { if(StrToInt(Edit1 -> Text) == StrToInt(Edit2 -> Text)) Apostila de C++ Builder Pagina -95- Label1 -> Caption = "O número de Edit1 é igual ao de Edit2!!!"; else Label1 -> Caption = "O número de Edit1 é multiplo do de Edit2"; } else Label1 -> Caption = "O número de Edit1 não é múltiplo do de Edit2"; } else Label1 -> Caption = "O número de Edit2 é maior que o de Edit1"; } //--------------------------------------------------------------------------- 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: //----------------------------------------------------------------------------int x = 45, y = 200, z = 10; if (x == 1) { // início do bloco if (y == 200) z = y - x; } //fim do bloco sem else else // a instrução desse comando será aplicada se x for diferente de 1 z = y + x; Label1->Caption = z; /*Resultado: z = 245, pois o comando else, em virtude das chaves {}, está relacionado com o primeiro if, cuja condição é falsa, porque x é diferente de 1 */ //-----------------------------------------------------------------------------int x = 45, y = 200, z = 10; if (x == 1); //o ponto e vírgula faz a diferença. Não existe instrução para essa condição if (y == 200) z = y - x; // Ok. y é igual a 200 else z = y + x; // essa instrução seria aplicada se y fosse diferente de 200 Label1->Caption = z; /*Resultado: z = 155, porque o primeiro if não é levado em consideração em virtude do ponto e vírgula que deixa a instrução deste comando vazia */ //-----------------------------------------------------------------------------int x = 45, y = 200, z = 10; if (x == 1) if (y == 200) z = y - x; else z = y + x; Label1->Caption = z; /*Resultado: z = 10, pois o programa avalia as duas condições e não encontra uma opção (um segundo else) para o fato de a primeira condição ser falsa */ //------------------------------------------------------------------------------ Apostila de C++ Builder Pagina -96- int x = 45, y = 200, z = 10; if (x == 1) if (y == 200) z = y - x; else z = y + x; else z = 300; Label1 -> Caption = z; /* Resultado: z = 300, pois esse segundo else vai de encontro com o fato de a primeira condição ser falsa */ //-----------------------------------------------------------------------------int x = 45, y = 200, z = 10; if (x == 45) if (y == 200) z = y - x; else z = y + x; Label1 -> Caption = z; // verdadeiras. Resultado: z = 155, porque as duas condições são //------------------------------------------------------------------------------ Comandos aninhados e Indentação Já tivemos oportunidade de mostrar a grande importância do correto uso das chaves {} nos comandos if ... else. Dito isso, é bom realçarmos que esses comandos podem ser aninhados tantas vezes quantas forem necessárias, ou seja, um if ... else pode conter outro if ... else, que pode conter outro if ... else etc, etc, etc. 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: void __fastcall TForm1::Button1Click(TObject *Sender) { if(Edit1->Text == "Olá") Label1->Caption = "Olá"; else if(Edit1->Text == "Pessoal") Label1->Caption = "Pessoal"; else if(Edit1->Text == "da cidade") Label1->Caption = "da cidade"; else if(Edit1->Text == "de Assis") Label1->Caption = "de Assis"; else Label1->Caption = "Olá pessoal de Assis!!!"; Apostila de C++ Builder Pagina -97- } //--------------------------------------------------------------------------Conforme o caso, podemos tornar mais legíveis as construções aninhadas if ... else, sem o recurso da identação: void __fastcall TForm1::Button1Click(TObject *Sender) { if(Edit1->Text == "Olá") Label1->Caption = "Olá"; else if(Edit1->Text == "Pessoal") Label1->Caption = "Pessoal"; else if(Edit1->Text == "da cidade") Label1->Caption = "da cidade"; else if(Edit1->Text == "de Assis") Label1->Caption = "de Assis"; else Label1->Caption = "Olá pessoal de Assis!!!"; }//--------------------------------------------------------------------------- Operadores lógicos Pode ocorrer de um comando precisar receber mais de uma resposta para tomar uma decisão. A linguagem C++ oferece três operadores lógicos que são: && significa: E || significa: OU ! significa: NÃO A sintaxe é: expressão_1 && expressão_2 resulta verdadeiro somente se ambas as expressões forem verdadeiras; expressão_1 || expressão_2 se pelo menos uma das expressões for verdadeira, o resultado será verdadeiro; ! expressão o resultado será verdadeiro somente se a expressão for falsa. 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: Apostila de C++ Builder Pagina -98- ssShift A tecla Shift está segura em baixo. ssAlt A tecla Alt está segura em baixo. ssCtrl A tecla Ctrl está segura em baixo. ssLeft O botão esquerdo do mouse está seguro em baixo. ssMiddle O botão do meio do mouse está seguro em baixo.. ssDouble O mouse recebeu um duplo clique. 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { if(Shift.Contains(ssCtrl) && Shift.Contains(ssShift)) Label1->Caption = "Tecla Ctrl \"e\" tecla Shift pressionadas"; if(!Shift.Contains(ssAlt) && !Shift.Contains(ssCtrl) && !Shift.Contains(ssShift)) ShowMessage("A tecla pressionada não é a tecla Alt, Shift ou Ctrl."); } //--------------------------------------------------------------------------void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if(Shift.Contains(ssDouble) || Shift.Contains(ssLeft)) Label1->Caption = "Evento do mouse detectado: Duplo Clique \"ou\" botão esquerdo."; } //--------------------------------------------------------------------------- O operador condicional ternário C++ possui o operador condicional ternário ? : que nos formece uma maneira concisa de expressar, simplificadamente, uma instrução if ... else: Apostila de C++ Builder Pagina -99- condição? expressão_1 : expressão_2; É 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: int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ); // // // // manipulador da instancia atual manipulador da instancia prévia ponteiro à línha de comando estado de visualização da janela A função WinMain tem quatro parâmetros de entrada: hInstance é um manipulador para a instância do programa que estamos executando. Cada vez que se executa uma aplicação, o Windows cria uma Instância para ela, e lhe passa um manipulador de dita instância para a aplicação hPrevInstance é um manipulador a instâncias prévias da mesma aplicação. Como o Windows é multitarefa, podem existir várias versões da mesma aplicação executando-se varias instâncias. Em Windows 3.1, este parâmetro nos servia para saber se nossa aplicação já estava sendo executada, e desse modo podiam compartilhar os dados comuns a todas as instâncias. Mas isso era antes, já que em Win32 usa um segmento distinto para cada instância e este parâmetro é sempre NULL, sendo conservado apenas por motivo de compatibilidade lpszCmdLine ponteiro a uma cadeia (terminada em zero) que especifica os argumentos Apostila de C++ Builder Pagina -100- hInstance é um manipulador para a instância do programa que estamos executando. Cada vez que se executa uma aplicação, o Windows cria uma Instância para ela, e lhe passa um manipulador de dita instância para a aplicação de entrada da linha de comando da aplicação nCmdShow este parâmetro especifica como se mostrará a janela 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. Apostila de C++ Builder Pagina -101- //-------------------ilustra o uso de uma função ---------------------------#include <vcl.h> #pragma hdrstop #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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { AloCy(); // Chama a função AloCy() } //--------------------------------------------------------------------------// Definição da função AloCy() void AloCy() { Form1 -> Label1 -> Caption = "Alô, Cy!!!! \n" "Como estão todos aí em Araçatuba???" "\nEstou com saudades !!!!!"; } 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 Apostila de C++ Builder Pagina -102- #include "Unit1.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------void AloCy(); // Protótipo da função AloCy() void AloDau(); // Protótipo da função AloDau() //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { AloCy(); // Chama a função AloCy() } //--------------------------------------------------------------------------// Definição da função AloCy() void AloCy() { Form1 -> Label1 -> Caption = "Alô, Cy!!!! \n" "Como estão todos aí em Araçatuba???" "\nEstou com saudades !!!!!"; AloDau(); // Chama AloDau() return; } //-------------------------------------------------------------------------// Definição de AloDau() void AloDau() { // Acrescenta dados no caption de Label1, sem apagar o que já está nele Form1 -> Label1 -> Caption = Form1 -> Label1 -> Caption + "\nAlo, Daury!!!!"; return; } //-------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { Label1->Caption = ""; // Limpa o caption de Label1 } //--------------------------------------------------------------------------- Definição de uma função A definição de uma função se dá por meio de um cabeçalho e de um corpo. O cabeçalho é constituído pelo tipo retornado, pelo nome da função, bem como pelos parâmetros que a mesma recebe. Os parâmetros são declarações dos tipos dos valores ou os dados que serão passados para a função, e sobre os quais a função poderá operar. Os dados ou valores passados são denominados de argumentos. Por exemplo, se desejássemos que uma função efetue uma operação matemática de divisão de dois números Apostila de C++ Builder Pagina -103- inteiros e nos devolva o resultado que poderia ser valor um ponto flutuante, poderíamos ter o cabeçalho da definição assim: float Opera_Divisao( int, int) Já o corpo da função consiste de uma chave de abertura { seguida pelos comandos que executam a tarefa da função, seguidos pela chave de fechamento } Por exemplo: float Opera_Divisao( int Divsr, int Divdnd) { float Result = Divsr / Divdnd; Return Result; } 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"; Label1 -> Caption = "Esta string será devolvida para Edit1"; } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString Valor_Devolvido; // declara variável para receber valor de Recb_Pass() Apostila de C++ Builder Pagina -104- /* recebe valor devolvido pelo comando return de Recb_Pass()*/ Valor_Devolvido = /*Texto de Edit1 será passado como parâmetro para Recb_Pass()*/ Recb_Pass (Edit1 -> Text); /*O valor devolvido por Recb_Pass() será o novo texto de Edit1*/ Edit1 -> Text = Valor_Devolvido; /*Desabilita Button1 para evitar que valores sejam trocados a cada clique do mouse*/ Button1 -> Enabled = false; } //--------------------------------------------------------------------------//Definição de Recb_Pass() //Recb_Pass() devolverá um valor tipo AnsiString //Recb_Pass() receberá AnsiString de Edit1->Text AnsiString Recb_Pass(AnsiString Valor_Recebido) { /*Antes de alterar o caption de Label1, acumula o valor atual na variável Valor_a_Devolver para retorná-lo via comando return*/ AnsiString Valor_a_Devolver = Form1 -> Label1 -> Caption; //Coloca o valor recebido no Caption de Label Form1 -> Label1 -> Caption = Valor_Recebido; //Devolve o valor acumulado do caption de Label1 return Valor_a_Devolver; } 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: double diametro (int raio); 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: double diametro(int raio); // Protótipo da função: com ponto e vírgula após os parênteses double diametro(int raio) // Definição da função: sem ponto e vírgula { // chave de abertura do corpa da função return raio * 3.14; // única instrução dessa função } // chave de encerramento da função Apostila de C++ Builder Pagina -105- O nome da variável passada como parâmetro (em nosso exemplo, a inteira raio) é facultativo, mas o tipo é obrigatório. Quanto ao valor de retorno será um tipo, ou será void (caso não haja valor de retorno): void diametro(int raio); // Protótipo da função 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { // o caption de Label1 chama diametro e passa o caracter 'a' como parâmetro /* IMPORTANTE: em qualquer hipótese, na chamada à função não podemos colocar o tipo de retorno, pois o compilador retornará mensagem de ERRO*/ Label1 -> Caption = /*sem tipo de retorno*/Experimenta_Tipos('a'); } //--------------------------------------------------------------------------Experimenta_Tipos(char raio) /* Definição da função sem tipo especificado para retorno. A função retornará como sendo int */ { // 'a' equivale a 97 na tabela ASCII. return raio * 1000.01; } terá como valor de retorno: 97000. 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). Apostila de C++ Builder Pagina -106- Para esses casos, há duas restrições: 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: //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { char Experimenta_Tipos(char raio); // Protótipo da função Label1 -> Caption = /*sem tipo de retorno*/Experimenta_Tipos('a'); } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { char Experimenta_Tipos(char raio); // Protótipo da função Label2 -> Caption = /*sem tipo de retorno*/Experimenta_Tipos('a'); } //--------------------------------------------------------------------------- Variáveis locais e globais Já tivemos oportunidade de trabalhar com variáveis locais e globais sem, contudo, entrar em detalhes acerca de suas particularidades. Comecemos pelas variáveis locais. Imaginemos o seguinte bloco de códigos com as respectivas mensagens de erro na forma de comentários: void __fastcall TForm1::Button1Click(TObject *Sender) { // início do primeiro bloco (bloco externo) // [C++ Error] Unit1.cpp(20): E2451 Undefined symbol 'a'. Label1->Caption = a; { // início do segundo bloco (bloco interno) int a = 10, b = 20; Label2->Caption = a + b; // Ok. Label2 enxerga as variáveis "a" e "b" // [C++ Error] Unit1.cpp(24): E2451 Undefined symbol 'c'. Label3->Caption = a + b + c; // Erro. Label3 não enxerga "c" // [C++ Error] Unit1.cpp(25): E2238 Multiple declaration for 'c'. int c = 30; } // fim do bloco interno //[C++ Error] Unit1.cpp(27): E2451 Undefined symbol 'c'. Apostila de C++ Builder Pagina -107- Label4->Caption = c; } // fim do bloco externo 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; 3 - Embora a chamada a Label3 se encontre no mesmo bloco, é anterior à declaração da variável c. 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 //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { /*o código abaixo apresentará um erro, pois Button1Click não enxerga global_2 */ Apostila de C++ Builder Pagina -108- Label1->Caption = global_1 + global_2; } //--------------------------------------------------------------------------int global_2; //variável que será visível somente pelas funções abaixo dela void __fastcall TForm1::Button2Click(TObject *Sender) { global_2 = 45; // aqui essa variável adquire característica de global int local_de_Button2Click = 12; // variável local // não haverá erro Label1->Caption = global_1 + global_2 + local_de_Button2Click; } //--------------------------------------------------------------------------void __fastcall TForm1::Button3Click(TObject *Sender) { // ERRO: local_de_Button2Click não pode ser visualizada // pelo evento OnClick de Button3. ELA É LOCAL A Button2Click Label1->Caption = global_1 + global_2 + local_de_Button2Click; } //--------------------------------------------------------------------------- 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. Apostila de C++ Builder Pagina -109- O exemplo a seguir trabalha com dois Forms. O primeiro leva um Label e três Buttons. O segundo leva um Label e um Button. Declaramos duas variáveis inteiras globais no primeiro formulário (Form1), as inicializamos e alteramos seus valores conforme o componente que recebe um clique, exibindo o resultado em Label1. No segundo formulário (Form2), através da palavra-chave extern, manipulamos e exibimos o resultado, conforme o componente que recebe o clique do mouse: 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::Button1Click(TObject *Sender) { a = 1; b = 2; } //--------------------------------------------------------------------------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(); } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -110- Unit2.h //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #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::Label1Click(TObject *Sender) { Label1->Caption = a + b; } //--------------------------------------------------------------------------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: Apostila de C++ Builder Pagina -111- Como sabemos, as variáveis declaradas dentro de um bloco, só existem durante a execução do bloco. Quando o bloco acaba, elas são destruídas. Exemplo: void __fastcall TForm1::Label1Click(TObject *Sender) { int numero = 16; numero+= numero; Label1->Caption = numero; } 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: void __fastcall TForm1::Label1Click(TObject *Sender) { static int numero = 16; numero+= numero; Label1->Caption = numero; } 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) { } Apostila de C++ Builder Pagina -112- //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { numero+= numero; Label1->Caption = numero; } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { Form2->ShowModal(); } //--------------------------------------------------------------------------- esta variável global numero só poderá ser usada dentro do fonte em que foi definida. Uma outra unidade como essa a seguir: //--------------------------------------------------------------------------#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) { } //--------------------------------------------------------------------------void __fastcall TForm2::Label1Click(TObject *Sender) { numero+= numero; Label1->Caption = numero; } //--------------------------------------------------------------------------- não apresentará o resultado esperado para extern int numero, provavelmente apresentando alguma mensagem de erro: [Linker Error] Unresolved external '_numero' referenced from C:\ARQUIVOS DE PROGRAMAS\BORLAND\CBUILDER4\PROJECTS\UNIT2.OBJ. Esse resultado pode ser desejado para reduzir a interferências entre equipes de programação. 3) Em uma definição de classe Apostila de C++ Builder Pagina -113- Em uma definição de classe, a palavra static indica que um dado membro é compartilhado por todas as instâncias da classe. Esse uso para static em C++ será abordado no momento oportuno, quando estivermos estudando as classes. Parâmetros das funções passando argumentos por valor Podemos, de modo errado, supor que toda vez que passamos valores na forma de argumentos para outras funções, estas, ao manipular esses dados, estariam alterando o valor original das mesmas. Observe o exemplo, onde criamos uma função para manipular os dados recebidos de ButtonClick(): //--------------------------------------------------------------------------#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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { int a = 10, b = 40; // declara e inicializa duas inteiras /* imprime os valores de a e b em Label1*/ Label1 -> Caption = "Valor de a = " + String(a) + "\n" + "Valor de b = " + String(b) + "\n"; Impr_Label_1(a, b); // chama a função Impr_Label_1 pasando argumentos por valor /* após o comando retornar para Button1Click(), imprime novamente o valor local de a e b no Label, adicionando esta impressão aos valores que já estavem no Label*/ Label1 -> Caption = Label1 -> Caption + "\n" + "Valor de a após a chamada a Impr_Label_1() = " + String(a) + "\n" + "Valor de b após a chamada a Impr_Label_1() = " + String(b) + "\n"; Apostila de C++ Builder Pagina -114- } //--------------------------------------------------------------------------void Impr_Label_1(int a, int b) { /* A função Impr_Label_1() recebe dois valores e cria cópias dessas variáveis para operar */ a = 100; // altera o valor da variável a (cópia) para 100 b = 400; // altera o valor da variável b (cópia) para 400 /*Imprime o valor das variáveis originais e das cópias no label*/ Form1 -> Label1 -> Caption = Form1 -> Label1 -> Caption + "\n" + "Valor de a em Impr_Label_1() = " + String(a) + "Valor de b em Impr_Label_1() = " + String(b) + "\n" + "\n"; return; // não há valores a retornar } 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 altura * largura; return 412; Podemos retornar uma expressão, cujo valor deve ser compatível com o valor retornado por return: return(ImprLabel_1(int, int)); Apostila de C++ Builder Pagina -115- return (i <= 100); Se não houver valores a retornar, usamos a palavra void indicando a circunstância: void Impr_Diam(int raio); // protótipo 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { int a = 10, b = 40; //declara e inicializa duas int que serão passadas como argumentos da função Form1->Caption = Form_Capt(a, b); // Chama Form_Capt(), passando a e b como argumentos } //--------------------------------------------------------------------------int Form_Capt(int c, int d) { if(c > d) return c * d; // retorna c vezes d se c for maior que d else if(c == d) return c + d; // retorna c mais d se c for igual a d else return d - c; // retorna c menos d se c for menor que d } Apostila de C++ Builder Pagina -116- Valores Default Conforme vimos, na chamada de uma função existe a necessidade de fornecermos um argumento que corresponda a cada parâmetro declarado no protótipo e na definição da função, respeitando-se os tipos que aparecem no protótipo: /*double*/ conta (/*int*/ a, /*double*/ b); Podemos, contudo, estabelecer um valor defaut (padrão) para um ou mais parâmetros: /*protótipo*/ double conta ( int raio, double Pi = 3.1416); Em tais situações, se na chamada à função não fornecermos um valor, o default será usado: conta ( raio); // chamada à função Se, porém, fornecermos um valor, o default não 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { double diametro = conta(6); ShowMessage((String)"O diâmetro com o default é: " + diametro); } Apostila de C++ Builder Pagina -117- //--------------------------------------------------------------------------void __fastcall TForm1::Button2Click(TObject *Sender) { double diametro = conta(6, 3.14); ShowMessage((String)"O diâmetro sem o default é: " + diametro); } //--------------------------------------------------------------------------/*definição*/ double conta ( int i, double Pi) { return 2*Pi*i; } 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) Apostila de C++ Builder Pagina -118- { return(a * b); } //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { int x, y; x = StrToInt(InputBox("Cálculo da área", "entre com o valor de um lado", "")); y = StrToInt(InputBox("Cálculo da área", "entre com o valor do outro lado", "")); Label1->Caption = (String)"Área = " + Area(x, y); } //--------------------------------------------------------------------------- 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). Apostila de C++ Builder Pagina -119- Laços são comandos que fazem com que uma ou mais instruções sejam repetidas enquanto determinada condição não estiver satisfeita. A linguagem C++ possui três formas de laços: 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. Eis a forma do laço while: 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" Apostila de C++ Builder Pagina -120- TForm1 *Form1; //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Label1->Caption = "Dê um clique em mim para iniciar um loop while " "\nonde serão montadas sucessivas tabelas ASCII " "\naté encontrar as letras 'z' ou 'Z' nove vezes!"; } //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { Label1 -> Caption = ""; // Apaga o Caption de Label1 // Declara e inicializa três variáveis para ser incrementadas no loop int contador = 0; // variável que contará o número de loops int paraZ = 0; // variável que contará ocorrências de z e Z unsigned char caract = 0; // variável usada para montar as tabelas ASCII while( paraZ < 9) // enquanto a variável paraZ form menor do que 9 { contador++; // incrementa contador a cada giro do loop caract++; // incrementa caract a cada giro do loop /* A tabela ASCII vai sendo construída no Caption de Label1 */ Label1->Caption = Label1->Caption + char(caract); /* Um comando de decisão switch ... case dentro do corpo do loop*/ switch(caract) /* o comando switch ... case avaliará os valores de caract*/ { /*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; /*caso o valor de caract seja Z ou z, a variável paraZ sofre um incremento e a execução do programa sai do switch*/ case 'Z': paraZ++; break; case 'z': paraZ++; break; } // fim do comando switch ... case } // fim do loop while /*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"; } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -121- O comando goto pode fazer com que a execução do programa pule para qualquer ponto visível do código, e seu uso incorreto ou indiscriminado facilita a introdução de erros. Via de regra, os programadores evitam usar esse comando, mas existem ocasiões em que o mesmo poderá ser útil. 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. //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { int i = 1; while(i <= 1000) { ShowMessage((String)"i é igual a " + i++); if(i <= 5) continue; ShowMessage("Agora o corpo todo do laço será executado, até i ser igual a 10"); if(i == 10) { ShowMessage("Agora i é igual a 10 e, embora i ainda não seja 1000, " "vou embora... Adeus..."); break; } } ShowMessage("Se você está vendo esta mensagem, " "significa que estamos fora do while"); Close(); } //--------------------------------------------------------------------------- O loop while sem fim Podemos inserir uma condição permanentemente verdadeira (igual a 1) para ser testada por um laço while. Nessas condições, o while será executado indeterminadamente, num loop sem fim. Essas situações carecem de um comando break satisfazendo uma condição de encerramento em algum ponto do laço, para evitar o travamento da máquina. Apostila de C++ Builder Pagina -122- É preciso tomar um cuidado especial sempre que se utilizar laços nos programas, pois qualquer erro de lógica ou de condição de encerramento poderá fazer com que o programa entre em um loop sem fim. //--------------------------------------------------------------------------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; } } //-------------------------------------------------------------------------- loop while - continuação A condição avaliada num loop pode ser qualquer expressão válida em C++, incluindo expressões construídas com o uso de operadores lógicos &&, ||, ! : 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { Label1->Caption = ""; Label2->Caption = ""; bool bl = true; // variável que nos dará uma das formas de encerrar o loop Apostila de C++ Builder Pagina -123- int i = 0; // variável para avaliação no primeiro loop int j2 = 0; /* variável que contará o total de vezes que o segundo loop executará */ /*enquanto i for menor ou igual a 10 e bl for verdadeira*/ while(i <= 10 && bl == true) { i++; // incrementa i (i = i + 1) /*Label1 exibirá o número de vezes que o primeiro loop rodará*/ Label1->Caption = Label1-> Caption + " " + i; int j = 10; // variável para avaliação no segundo loop while(j >= 0) /*enquanto j for maior ou igual a zero*/ { j--; // decrementa j (j = j - 1) j2++; // incrementa j2 /*Label1 exibirá o número de vezes que o segundo loop rodará*/ Label2->Caption = Label2->Caption + " " + j2; } /*condição para forçar o encerramento do loop antes de a variável i ser igual a 10*/ if (i >= 4 && j <= 6) { bl = false; } } } //--------------------------------------------------------------------------- O loop do... while A estrutura do laço do... while é muito parecida com a estrutura do laço while. A diferença básica entre elas consiste no fato de, enquanto o teste condicional no comando while é realizado no início do loop (antes das instruções), no laço do... while o teste é realizado no fim do loop (após as instruções). 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 { ... } Apostila de C++ Builder Pagina -124- while(condição) 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 void __fastcall Esperar(int Miliseg); #include "Unit1.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { Label1->Caption = "Aguarde... Após sete segundos o programa será fechado"; Esperar(7000); // tempo que o programa aguardará até ser encerrado Close(); // encerra o programa } //--------------------------------------------------------------------------void __fastcall Esperar (int Miliseg) { TDateTime Hora; // declara variável da classe TDateTime unsigned short Hor, Min, Seg, Miles; // declara vairiáveis int Tempo; Hora = Time(); // atribui a hora do sistema à variável Hora /*todas as instruções abaixo serão executadas ao menos uma vez, (independente de a condição avaliada ser verdadeira) */ do // início do loop { /*DecodeTime() suspende um objeto TdateTime em horas, minutos, segundos e milisegundos*/ (Time() - Hora).DecodeTime (&Hor, &Min, &Seg, &Miles); /*incrementa a variável Tempo*/ Tempo = (Min * 60000) + (Seg * 1000) + Miles; /*interrompe a execução da aplicação possibilitando ao Windows processar determinadas mensagens*/ Application->ProcessMessages(); } /* o loop será executado enquanto Tempo for menor que Miliseg */ Apostila de C++ Builder Pagina -125- while (Tempo < Miliseg); } //--------------------------------------------------------------------------- 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: for( valor inicial; condição; atualizaçã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: for(int i = 0; i < 100; i++); O corpo do loop contém as instruções que serão processadas a cada giro do laço. //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #include <math.h> // biblioteca para floor #include "Unit1.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------/*função para arredondar casas decimais*/ double Arredonda(double valor, int casas_dec); Apostila de C++ Builder Pagina -126- //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { /*variável para receber os números inteiros*/ AnsiString a = InputBox("Caixa de Valores", "Digite um Valor", ""); /*variável para guardar o número de casas decimais*/ AnsiString b = InputBox("Caixa de casas decimais", "Digite as casas decimais", ""); /*chama a função que arredonda casas decimais, atribuindo o valor retornado a b*/ b = Arredonda(StrToFloat(a), StrToInt(b)); /*exibe o valor retornado, já com as casas decimais arredondadas*/ ShowMessage(b); } //--------------------------------------------------------------------------double Arredonda(double valor, int casas_dec) { int i = 1; /* j será incrementada até o número de casas decimais*/ for(int j = 1; j <= casas_dec; j++) i*= 10; // o mesmo que: i = i * 10; // floor é uma função matemática usada para arredondar cifras return floor(valor * i + 0.5) / i; } //--------------------------------------------------------------------------- loop for - continuação O loop for aceita diversas construções como, por exemplo, conter mais de um valor inicial: for(int i = 0, j = 100; ... múltiplas condições: ...; i <= j, j >= i; ... e processar múltiplas instruções de atualização: ...; 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. Apostila de C++ Builder Pagina -127- //--------------------------------------------------------------------------#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::BitBtn1Click(TObject *Sender) { Label1->Caption = ""; int i, j, k; for(i = 0, j i <= i++, // variáveis para o laço for = 50, k = 100; // inicializa as variáveis 10, j <= 100, k >= 0; // estabelece os limites j=j*2, k--) // alterações para as variáveis /****************************** 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 // as variações serão demonstradas no Label Label1-> Caption = (String) Label1->Caption + " i = " + i + " " + " j = " + j + " " + " k = " + k + '\n'; } } //--------------------------------------------------------------------------- Omissão e aninhamento no loop for Podemos deixar de incluir uma ou mais instruções no cabeçalho do loop for. Caso isso ocorra, não podemos nos esquecer de colocar o ponto e vírgula respectivo: for( ; ; ) Apostila de C++ Builder Pagina -128- Dependendo da forma que construímos o laço for, o mesmo poderá funcionar como se fosse um loop while: //--------------------------------------------------------------------------void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i = 1; for(;i < 10;) { ShowMessage((String)"laço for com alguns comandos nulos. i + "°" + " loop"); i++; } } " + //--------------------------------------------------------------------------- É possível construir um loop for com todos os comandos nulos: //--------------------------------------------------------------------------void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i = 1; for(;;) { if(i<10) { ShowMessage((String)"laço for com todos os comandos nulos. " + i + "°" + " loop"); i++; } else break; } } //--------------------------------------------------------------------------- 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. //--------------------------------------------------------------------------void __fastcall TForm1::BitBtn1Click(TObject *Sender) { for(int i = 1; i<10; ShowMessage((String)"laço for com instruções no corpo do laço. i++ + "°" + " loop")); Apostila de C++ Builder Pagina -129- " + } //--------------------------------------------------------------------------- loops for aninhados Os laços for também podem ser aninhados (um dentro do outro): //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { StringGrid1->ColCount = 10; StringGrid1->RowCount = 10; int coluna, linha, celula; celula = 0; for (coluna = 0; coluna < StringGrid1->ColCount; coluna++) { for (linha = 0; linha < StringGrid1->RowCount; linha++) 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. O comando switch consiste de um cabeçalho e de um corpo. O cabeçalho é montado com a palavrachave switch seguida de uma expressão (que pode ser uma constante ou uma variável) entre parênteses: Apostila de C++ Builder Pagina -130- switch (expressão) 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: case valor_x: faz_isso; break; case valor_y: faz_isto; break; case valor_z: faz_aquilo; break; default: faz_outra_coisa; faz_isso_também; 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) { } //--------------------------------------------------------------------------void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key) { if (virgula == false) DecimalSeparator = '\0'; Apostila de C++ Builder Pagina -131- //if(Edit1->Text.Length() != DecimalSeparator) //virgula = true; switch(Key) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': case '.': case ',': case 13: case 8: default: } Key Key Key Key Key Key Key Key Key Key Key Key Key Key Key = = = = = = = = = = = = = = = '1'; break; '2'; break; '3'; break; '4'; break; '5'; break; '6'; break; '7'; break; '8'; break; '9'; break; '0'; break; DecimalSeparator; DecimalSeparator; 13; break; 8; break; '\0'; virgula = false; virgula = false; break; break; } //--------------------------------------------------------------------------- Entendendo o evento OnMouseMove Um evento OnMouseMove ocorre periodicamente quando o usuário move o mouse. O evento inicia-se no objeto que está sob o ponteiro do mouse quando o usuário pressiona o botão. Isto permite ao usuário algumas opções intermediárias enquanto o mouse move. Observe, porém, que podemos chamar esse evento mesmo sem que qualquer botão esteja pressionado. 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: //--------------------------------------------------------------------------void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { } //--------------------------------------------------------------------------- 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) Apostila de C++ Builder Pagina -132- Primeiramente, vamos entender o significado de do tipo TShiftState Shift. O tipo TShiftState indica o estado das teclas Alt, Ctrl e Shift, bem como dos botões do mouse. typedef Set<Classes__1, ssShift, ssDouble> TshiftState; 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 ssShift A tecla Shift está segura em baixo. ssAlt A tecla Alt está segura em baixo. ssCtrl A tecla Ctrl está segura em baixo. ssLeft O botão esquerdo do mouse está seguro em baixo. ssRight O botão direito do mouse está seguro em baixo. ssMiddle O botão do meio do mouse está seguro em baixo.. ssDouble O mouse recebeu um duplo clique. Podemos entender TShiftState como uma espécie de variável que, automaticamente, é nomeada de Shift pelo C++Builder no evento estudado. Quanto às duas variáveis inteiras nomeadas respectivamente de X e Y ( int X, int Y ) pelo C++Builder, nada mais são do que os possíveis valores para as coordenadas X e Y do Form. Então se, por exemplo, inserirmos as seguintes linhas de código no evento acima: //--------------------------------------------------------------------------void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if(Shift.Contains(ssShift) && X == 0 && Y == 0) Close(); } //--------------------------------------------------------------------------- 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. Apostila de C++ Builder Pagina -133- TCanvas A classe TCanvas encapsula um dispositivo de contexto Windows na VCL e um dispositivo de pintura (Qt painter) em CLX, que lida com todo desenho para forms, containers visuais (como panels) e o objeto printer, em ambas as formas. Usando o objeto canvas, você não tem dificuldades para alocar pens, brushes, palettes, e assim por diante — todas as alocações e desalocações são tratadas por você. 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. __property int CopyMode = {read=FCopyMode, write=FCopyMode, default=13369376}; 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. Apostila de C++ Builder Pagina -134- Use CopyMode para executar uma variedade de afeitos quando desenhar uma imagem. Consiga efeitos especiais como fundir imagens e criar partes de um bitmap transparente combinando imagens múltiplas com diferentes CopyModes. A tabela seguinte mostra os possíveis valores de CopyMode. (trata-se de constantes definidas em Windows.hpp.) cmBlackness Preenche o retângulo de destino na tela com negro. cmDstInvert Inverte a imagem na tela e ignora a fonte. cmMergeCopy Combina a imagem na tela e a fonte bitmap usando o operador booleano AND. cmMergePaint Combina a fonte invertida bitmap com a imagem na tela usando o operador booleano OR. cmNotSrcCopy Copia a fonte bitmap invertida para a tela. cmNotSrcErase Combina a imagem na tela e a fonte bitmap usando o operador booleano OR, e inverte o resultado. cmPatCopy Copia a fonte moldando na tela. cmPatInvert Combina a fonte moldando com a imagem na tela usando o operador booleano XOR. cmPatPaint Combina a fonte invertida bitmap com a fonte moldada usando o operador booleano OR. Combina o resultado dessa operação com a imagem na tela usando o operador booleano OR. cmSrcAnd Combina a imagem na tela e a fonte bitmap usando o operador booleano AND. cmSrcCopy Copia a fonte bitmap na tela. cmSrcErase Inverte a imagem na tela e combina o resultado com a fonte bitmap usando o operador booleano AND. cmSrcInvert Combina a imagem na tela e a fonte bitmap usando o operador booleano XOR. cmSrcPaint Combina a imagem na tela e a fonte bitmap usando o operador booleano OR. cmWhiteness Preenche o retângulo de destino na tela com branco. Apostila de C++ Builder Pagina -135- O seguinte exemplo usa CopyMode para deixar branco fora da imagem quando o usuário escolher “Cortar” no menu . void __fastcall TEdtDsnh::Cortar1Click(TObject *Sender) { TRect ARect; // copia a figura para o Clipboard, através do método copiar. Copiar1Click(Sender); // preenche a seção cortada com branco. CmBlackness determinaria preto Image1->Canvas->CopyMode = cmWhiteness; // dimensiona a seção da tela copiada que será preenchida com branco ARect = Rect(0, 0, Image1->Width, Image1->Height); // complementa as duas linhas acima, retirando a figura (cortando) Image1->Canvas->CopyRect(ARect, Image1->Canvas, ARect); // restaura o modo default Image1->Canvas->CopyMode = cmSrcCopy; } TRect, Rect, TextRect, Brush e FrameRect Rect cria uma estrutura TRect para colocar as coordenadas. 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. void __fastcall TForm1::Button1Click(TObject *Sender) { // TRect, Rect, TextRect, Brush e FrameRect 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; Apostila de C++ Builder Pagina -136- // TCanvas::TextRect - escreve uma string // dentro de um recorte retangular // coordenadas do texto: X = 85 e Y = 100 Canvas->TextRect(ARect, 85, 100, "Texto no recorte retangular"); // clicar uma vez deixará a borda será vermelha; // duas vezes, toda a área dentro do retângulo Canvas->Brush->Color = clRed; // A função FrameRect desenha uma borda em torno do retângulo // especificado, usando o brush especificado. A largura e altura // da borda será sempre uma unidade lógica. Canvas->FrameRect(ARect); } 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. void __fastcall CopyRect(const Windows::TRect &Dest, TCanvas* Canvas, const Windows::TRect &Source); 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. A porção da tela inicial é copiada usando o modo especificado por CopyMode. Apostila de C++ Builder Pagina -137- O exemplo a seguir ilustra como usufruir a diferença entre CopyRect e BrushCopy. O bitmap gráfico “meu_desenho.bmp” é carregado dentro de Bitmap e exibido no Canvas de Form1. BrushCopy substitui a cor negra no gráfico com o brush de canvas, enquanto CopyRect mantém a cor intacta. void __fastcall TForm1::Button1Click(TObject *Sender) { Form1->WindowState = wsMaximized; // amplia a janela Graphics::TBitmap *Bitmap; // declara Bitmap TRect ARect, A2Rect; // declara ARect = Rect(30, 30, 480, 300); // inicializa ARect determinado as coordenadas A2Rect = Rect(30, 310, 480, 570); // inicializa o retângulo A2Rect Bitmap = new Graphics::TBitmap; // alocação dinâmica de memória para Bitmap Bitmap->LoadFromFile("c:\\meu_desenho.bmp"); // carrega o arquivo especificado // BrushCopy carregará o desenho substituindo as cores negras do mesmo Form1->Canvas->BrushCopy(ARect, Bitmap, ARect, clBlack); // CopyRect carregará o arquivo sem alterá-lo Form1->Canvas->CopyRect(A2Rect, Bitmap->Canvas, ARect); // desaloca a memória dinâmica delete Bitmap; } métodos Canvas para desenhar objetos gráficos. Esta seção mostra a forma de usar alguns métodos comuns para desenhar objetos gráficos. Eles envolvem: desenhar linhas e múltiplas-linhas; desenhar figuras; desenhar retângulos arredondados; desenhar polígonos. desenhar linhas e múltiplas-linhas 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 Apostila de C++ Builder Pagina -138- Canvas possui métodos diferentes para desenhar diferentes espécies de figuras. Você pode usar pen para desenhar uma figura e brush (pincel) para preencher o interior da figura. A linha que firma a borda da figuras é controlada pelo corrente objetos Pen. Esta parte envolve: desenhar retângulos e elipses; desenhar retângulos arredondados; desenhar polígonos. desenhar retângulos e elipses 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: void __fastcall TForm1::FormPaint(TObject *Sender) { // 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); } desenhando retângulos arredondados Para desenhar um retângulo arredondado na tela, chame o método RoundRect. 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. O seguinte método desenha um retângulo arredondado no quadrante superior esquerdo do form, arredondando os cantos com seções de um círculo de 30 pixels de diâmetro: Apostila de C++ Builder Pagina -139- void __fastcall TForm1::FormPaint(TObject *Sender) { 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. void __fastcall TForm1::PaintBox1Paint(TObject *Sender) { TPoint ponto[6]; ponto[0] = Point(0,0); ponto[1] = Point(30,80); ponto[2] = Point(280,130); ponto[3] = Point(280,220); ponto[4] = Point(340,320); ponto[5] = Point(440,380); // ((TPaintBox *)Sender)->Canvas->Brush->Color = clBlue; /*opcional*/ ((TPaintBox *)Sender)->Canvas-> Polygon(ponto, 5 /*número de lados: ou 4, ou 3, ou 2 ...*/); } 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. As próximas seções descrevem: Usando pens (canetas); Usando brushes (pincéis); Lendo e inserindo pixels. Apostila de C++ Builder Pagina -140- Usando pens A propriedade Pen de um controle canvas é o meio de fazer linhas aparecerem, incluindo linhas trabalhadas como linhas de figuras. Desenhar uma linha linha reta é alterar um grupo de pixels que estão entre dois pontos. De si mesmo, pen possui quatro propriedades que você pode alterar: Color, Width, Style, e Mode. Color muda a cor de pen. Width muda a largura de pen. Style muda o estilo de pen. Mode muda a forma de pen. 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. __property TColor Color = {read=GetColor, write=SetColor, default=0}; 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. //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -141- #include <vcl.h> #pragma hdrstop #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) { } //--------------------------------------------------------------------------void __fastcall TForm1::FormActivate(TObject *Sender) { WindowState = wsMaximized; // janela maximizada Timer1->Interval = 50; // intervalo de tempo para Timer randomize(); } //--------------------------------------------------------------------------void __fastcall TForm1::Timer1Timer(TObject *Sender) { // inicializa variáveis com valores aleatórios x = random(Screen->Width - 10); y = random(Screen->Height - 10); // Cor aleatória para pen Canvas->Pen->Color = (Graphics::TColor) random(65535); // 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 case 1: Canvas->Pen->Style = psDash; Canvas->Brush->Color = random(65535); break; // cor aleatória case 2: Canvas->Pen->Style = psDot; Canvas->Brush->Color = random(65535); break; case 3: Canvas->Pen->Style = psDashDot; Canvas->Brush->Color = random(65535); break; case 4: Canvas->Pen->Style = psDashDotDot; Canvas->Brush->Color = clAqua; break; } // desenha elípses com tamanhos diversos escolhidos aleatoriamente Canvas->Ellipse(x, y, x + random(400), y + random(400)); } Apostila de C++ Builder Pagina -142- //--------------------------------------------------------------------------- TPen::Width Especifica a largura máxima de um pen em pixels. __property int Width = {read=GetWidth, write=SetWidth, default=1}; 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. Nota: O valor de Width influencia os possíveis valores válidos de Style. O exemplo a seguir desenha muitos grupos, geralmente sobrepostos, de retângulos, retângulos arredondados e elipses de vários tamanhos e cores em um form maximizado, preenchendo totalmente o screen: //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #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) { } //--------------------------------------------------------------------------void __fastcall TForm1::FormActivate(TObject *Sender) { WindowState = wsMaximized; // janela maximizada Timer1->Interval = 600; // intervalo de tempo para Timer randomize(); } //--------------------------------------------------------------------------void __fastcall TForm1::Timer1Timer(TObject *Sender) { x = random(Screen->Width - 10); y = random(Screen->Height - 10); Canvas->Pen->Color = (Graphics::TColor) random(65535); Canvas->Pen->Width = random(50); // Pen terá largura variável Apostila de C++ Builder Pagina -143- int Dx = random(400); // variáveis para os pixels int Dy = random(400); // do arredondamento do retângulo Canvas->Brush->Color = random(65535); // cor aleatória p retângulo arredondado Canvas->RoundRect(x, y, x + Dx, y + Dy, Dx/2, Dy/2); Canvas->Brush->Color = random(65535); // cor aleatória para o retângulo Canvas->Ellipse(x, y, x + random(400), y + random(400)); Canvas->Brush->Color = random(65535); // cor aleatória para a elípse Canvas->Rectangle(x, y, x + random(400), y + random(400)); } //--------------------------------------------------------------------------- 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. psDash Uma linha composta de uma série de traços. psDot Uma linha feita com uma série de pontos. psDashDot Uma linha construída alternando traços e pontos. psDashDotDot Uma linha feita com uma série de combinações traço-ponto-ponto. 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. Apostila de C++ Builder Pagina -144- Veja o exemplo do tópico Tpen::Color. 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. pmWhite Sempre branco. pmNop Imutável. pmNot Inverso à cor de canvas background. pmCopy Cor de Pen especificada na propriedade Color. pmNotCopy Inverso da cor de pen. pmMergePenNot Combinação da cor de pen e o inverso de canvas background. pmMaskPenNot Combinação de cores comuns para ambos, pen e inverso de canvas background. pmMergeNotPen Combinação da cor de canvas background e o inverso da cor de pen. pmMaskNotPen Combinação de cores comuns para ambos, canvas background e o inverso de pen. pmMerge Combinação da cor de pen e da cor de canvas background. Apostila de C++ Builder Pagina -145- Mode Cores Pixels pmNotMerge Inverso de pmMerge: combinação da cor de pen e cor de canvas background. pmMask Combinação de cores comuns em ambos, pen e canvas background pmNotMask Inverso de pmMask: combinação de cores comuns em ambos, pen e canvas background pmXor Combinação de cores em um ou em outro (pen ou canvas background), mas não em ambos. pmNotXor Inverso de pmXor: combinação de cores em um ou outro (pen ou canvas background), mas não juntos. 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. Brush possui três propriedades que podemos manipular: Color Muda a cor de preenchimento. Style muda o estilo de brush. Bitmap usa um bitmap como um modelo para brush. Apostila de C++ Builder Pagina -146- O valor dessas propriedades determina o jeito que o canvas preenche figuras ou outras áreas. Por default, qualquer brush inicia branco, com um estilo sólido e modelo não bitmap. 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. __property TBrush* Brush = {read=FBrush, write=SetBrush}; 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: Graphics::TBitmap *BrushBmp = new Graphics::TBitmap; 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. void __fastcall FillRect(const Windows::TRect &Rect); 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. Apostila de C++ Builder Pagina -147- O exemplo a seguir cria um retângulo no form, preenchendo-o de branco (cor default para Brush), ou vermelho (se você remover o símbolo: // ). void __fastcall TForm1::Button1Click(TObject *Sender) { TRect NewRect = Rect(20, 30, 350, 290); // Canvas->Brush->Color = clRed; Canvas->FillRect(NewRect); } TBrush::Color Indica a cor do brush. __property TColor Color = {read=GetColor, write=SetColor, default=16777215}; 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: void __fastcall TForm1::Button1Click(TObject *Sender) { 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, Apostila de C++ Builder Pagina -148- bsBDiagonal, bsCross, bsDiagCross __property TBrushStyle Style = {read=GetStyle, write=SetStyle, default=0}; 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: //--------------------------------------------------------------------------void __fastcall TForm1::FormClick(TObject *Sender) { Form1->Height = 430; Form1->Width = 410; TCanvas *pCanvas = Form1->Canvas; pCanvas->Brush->Color = clBlue; Apostila de C++ Builder Pagina -149- pCanvas->Brush->Style = bsDiagCross; pCanvas->Ellipse(0, 0, 100, 200); 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. __property TBitmap* Bitmap = {read=GetBitmap, write=SetBitmap}; 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. Apostila de C++ Builder Pagina -150- Mudar a imagem não afeta brush até que TBitmap seja redefinido para a propriedade Bitmap. Deve-se liberar o TBitmap depois de finalizado com brush, visto que TBrush não pode fazê-lo. 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. void __fastcall TForm1::Button1Click(TObject *Sender) { Graphics::TBitmap *BrushBmp = new Graphics::TBitmap; 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. Ler e inserir pixels Você pode reparar que cada tela possui uma propriedade Pixels que representa pontos individuais coloridos que compõem a imagem na tela. Raramente você precisará acessar Pixels direta ou individualmente, sendo que eles estão disponíveis apenas, por conveniência, para executar pequenas ações como, por exemplo, buscar ou inserir uma cor. Apostila de C++ Builder Pagina -151- Nota: Manipular pixels individuais é muito mais lento do que executar operações gráficas por regiões. Não use a propriedade array Pixel para ter acesso a pixels de imagem de um array geral. Para acesso de alto-desempenho a pixels de imagem, veja a propriedade TBitmap::ScanLine. TCanvas::Pixels Especifica as cores dos pixels dentro do atual ClipRect. __property TColor Pixels[int X][int Y] = {read=GetPixel, write=SetPixel}; 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. void __fastcall TForm1::Button1Click(TObject *Sender) { for (int i = 10; i <= 300; i++) { Canvas->Pixels[i][10] = clRed; Canvas->Pixels[i][14] = clBlue; Canvas->Pixels[i][18] = clYellow; Canvas->Pixels[i][22] = clWhite; Canvas->Pixels[i][26] = clFuchsia; Canvas->Pixels[i][30] = clGreen; 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; Apostila de C++ Builder Pagina -152- Canvas->Pixels[i][292] = clWhite; Canvas->Pixels[i][296] = clFuchsia; Canvas->Pixels[i][300] = clGreen; 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. __property void * ScanLine[int Row] = {read=GetScanline}; 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]; for (int x = 0; x < pBitmap->Width; x++) ptr[x] = (Byte)y; } Canvas->Draw(0,0,pBitmap); } catch (...) { ShowMessage("Não foi possível carregar ou alterar bitmap"); } Apostila de C++ Builder Pagina -153- delete pBitmap; } Propriedades e métodos comuns de Canvas A tabela a seguir exibe as propriedades do objeto Canvas geralmente usadas. Propriedades descrições Font Especifica a fonte a ser usada quando escrever textos na imagem. Use as propriedades do objeto TFont para especificar a aparência da fonte, como cor, tamanho e estilo da fonte. Brush Determina a cor e a forma que Canvas usa para preencher figuras gráficas e backgrounds. Use as propriedades do objeto TBrush para especificar a cor e a forma ou bitmap em uso quando preencher espaços no canvas. Pen Especifica a espécie de caneta que o canvas usa para desenhar linhas e esboçar figuras. Use as propriedade do objeto TPen para especificar a cor, estilo largura e modo da caneta. PenPos Especifica a posição atual de pen. Pixels Especifica a cor de pixels da área dentro do atual ClipRect. Estas propriedades são descrevidas em maiores detalhes em Using the properties of the Canvas object. Abaixo a lista de vários métodos que você pode usar: Métodos Descrições Arc void __fastcall Arc(int X1, int Y1, int X2, int Y2, int X3, int Y3, int X4, int Y4); Desenha um arco na imagem ao longo do perímetro da elipse limitada pelo retângulo especificado. 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: void __fastcall TForm1::FormPaint(TObject *Sender) { TRect R = GetClientRect(); // Coordenadas retangulares // da janela atual Apostila de C++ Builder Pagina -154- Métodos Descrições Canvas->Arc(R.Left, R.Top, R.Right, R.Bottom, R.Right, R.Top, R.Left, R.Top); } 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. void __fastcall TForm1::Button1Click(TObject *Sender) { TRect R = Rect(20, 5, 350, 350); Canvas->Chord(20, 5, 350, 350, 10, 10, 200, 200); } CopyRect void __fastcall CopyRect(const Windows::TRect &Dest, TCanvas* Canvas,const Windows::TRect &Source); Copia parte de uma imagem de um canvas diferente para dentro do Canvas. 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. A porção da tela fonte é copiada usando o modo especificado por CopyMode. Exemplo: void __fastcall TForm1::Button1Click(TObject *Sender) { Graphics::TBitmap *Bitmap; TRect Figura_Copiada; Figura_Copiada = Rect(0,0,300,300); Bitmap = new Graphics::TBitmap; Bitmap->LoadFromFile("c:\\indio2.bmp"); Form1->Canvas->CopyRect(Figura_Copiada, Bitmap->Canvas, Figura_Copiada); Apostila de C++ Builder Pagina -155- Métodos Descrições delete Bitmap; } Draw void __fastcall Draw(int X, int Y, TGraphic* Graphic); Desenha o objeto gráfico especificado pelo parâmetro Graphic na tela na localização determinada pelas coordenadas (X, Y). 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. Gráficos podem ser bitmaps, ícones, ou metafiles. Se o gráfico é um objeto TBitmap, o bitmap é desenhado usando o valor de CopyMode. Exemplo: void __fastcall TForm1::Button1Click(TObject *Sender) { Graphics::TBitmap* figura = new Graphics::TBitmap; figura->LoadFromFile("c:\\meu_desenho.bmp"); Canvas->Draw(10, 10, figura); delete figura; } Ellipse void __fastcall Ellipse(int X1, int Y1, int X2, int Y2); Desenha a elipse definida pelos limites de um retângulo na tela. 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. A elipse é desenhada usando o valor de Pen, e preenchida usando o valor de Brush. Exemplo: TCanvas *pCanvas = Canvas; pCanvas->Brush->Color = clRed; pCanvas->Brush->Style = bsSolid; pCanvas->Ellipse(0, 0, Width*2, Height/2); pCanvas->Brush->Color = clYellow; pCanvas->Brush->Style = bsVertical; pCanvas->Ellipse(0, 0, Width, Height); pCanvas->Brush->Color = clBlue; pCanvas->Brush->Style = bsHorizontal; pCanvas->Ellipse(200, 0, Width*2, Height); FillRect void __fastcall FillRect(const Windows::TRect &Rect); Apostila de C++ Builder Pagina -156- Métodos Descrições Preenche o retângulo especificado na tela usando o brush (pincel) 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. O exemplo a seguir cria um retângulo no form, preenchendo-o de azul. void __fastcall TForm1::Button1Click(TObject *Sender) { TRect NewRect = Rect(20, 30, 350, 290); Canvas->FBrush->Color = ClBlue; Canvas->FillRect(NewRect); } FloodFill enum TFillStyle {fsSurface, fsBorder}; void __fastcall FloodFill(int X, int Y, TColor Color, TFillStyle FillStyle); Preenche a área da tela usando o brush atual. 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. void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TCanvas *pCanvas = Canvas; pCanvas->Ellipse(0, 0, 100, 400); } //---------------------------------------------------------------void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) Apostila de C++ Builder Pagina -157- Métodos Descrições { Canvas->FloodFill(ClientWidth/2, fsBorder); } FrameRect ClientHeight/2, clBlack, void __fastcall FrameRect(const Windows::TRect &Rect); Desenha uma borda de retângulo usando o Brush do canvas. Use FrameRect para desenhar um 1 pixel de borda ao redor de uma região retangular. FrameRect não enche o interior do retângulo com o Brush padrão. Para desenhar um limite usando Pen, use o método Polygon. void __fastcall TForm1::Button1Click(TObject *Sender) { TRect ARect; Canvas->Brush->Color = clFuchsia; ARect.Top = 10; ARect.Left = 10; ARect.Bottom = 200; ARect.Right = 200; Canvas->FrameRect(ARect); } LineTo void __fastcall LineTo(int X, int Y); 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. void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Canvas->LineTo(X, Y); } MoveTo void __fastcall MoveTo(int X, int Y); Altera, determinado a posição atual do desenho de acordo com as coordenadas X e Y. 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. Apostila de C++ Builder Pagina -158- Métodos Descrições int p_X, p_Y; // Variáveis para trabalhar com o ponto (X,Y) void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { // Canvas->FillRect(ClientRect); Canvas->MoveTo(p_X, p_Y); Canvas->LineTo(X, Y); } //-------------------------------------------------------------void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { p_X = X; p_Y = Y; } Pie void __fastcall Pie(int X1, int Y1, int X2, int Y2, int X3, int Y3, int X4, int Y4); Desenha a figura seccionada em direção da elipse como se dela houvera sido retirada uma fatia (como num bolo redondo). O seccionamento (ou cunha) é determinado por duas linhas que radiam do centro da elipse pelos pontos (X3, Y3) e (X4, Y4). A cunha é desenhada com Pen, e preenchida com Brush. Exemplo: Canvas->Pie(50, 50, 300, 200, 50, 30, 100, 50); Polygon void __fastcall Polygon(const Windows::TPoint * Points, const int Points_Size); Desenha uma série de linhas na tela, ligando os pontos passados e fechando a figura ao ligar o último ponto ao primeiro ponto. 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). O exemplo a seguir desenha uma estrela de cinco pontas no Form, preenchendo as pontas da mesma com amarelo: void __fastcall TForm1::FormPaint(TObject *Sender) { TPoint points[5]; points[0] = Point(40,10); points[1] = Point(20,60); points[2] = Point(70,30); points[3] = Point(10,30); points[4] = Point(60,60); Apostila de C++ Builder Pagina -159- Métodos Descrições Canvas->Brush->Color = 0xffff; // Color = clYellow Canvas->Polygon(points, 4); } PolyLine void __fastcall Polyline(const Windows::TPoint * Points, const int Points_Size); 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::FormPaint(TObject *Sender) { TPoint points[6]; Canvas->Pen->Color = 0xff0000; // clBlue points[0].x = 40; points[0].y = 10; points[1].x = 20; points[1].y = 60; points[2].x = 70; points[2].y = 30; points[3].x = 10; points[3].y = 30; points[4].x = 60; points[4].y = 60; points[5].x = 40; points[5].y = 10; Canvas->Polyline(points,5); } //----------------------------------------------------------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; Apostila de C++ Builder Pagina -160- Métodos Descrições points[2].y = 30; points[3].x = 10; points[3].y = 30; points[4].x = 60; points[4].y = 60; points[5].x = 40; points[5].y = 10; ((TPaintBox *)Sender)->Canvas->Polyline(points,5); // A linha acima é equivalente a: // pPB->Canvas->Polyline(points,5); } 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. O exemplo a seguir desenha muitos retângulos de vários tamanhos e cores no form maximizado preenchendo janela. Para executar o exemplo, inclua o cabeçalho stdlib.h, coloque um componente TTimer no Form e use o object inspector para criar o evento OnTimer. 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)); if(x > 700) Timer1->Enabled = false; } Apostila de C++ Builder Pagina -161- Métodos Descrições RoundRect void __fastcall RoundRect(int X1, int Y1, int X2, int Y2, int X3, int Y3); 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. Este exemplo desenha retângulos de cantos arredondados de vários tamanhos e cores no form maximizado preenchendo a janela. Para executar o exemplo, coloque um componente TTimer no form e use o object inspector para criar o evento OnTimer. 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; } } StretchDraw void __fastcall StretchDraw(const Windows::TRect &Rect, TGraphic* Graphic); Desenha o gráfico especificado no parâmetro Graphic no retângulo especificado pelo parâmetro Rect. Chame StretchDraw para desenhar um gráfico na tela de forma que a imagem encaixe-se no retângulo especificado. StretchDraw chama o método Draw do gráfico. O objeto gráfico determina a forma de preenchimento do Apostila de C++ Builder Pagina -162- Métodos Descrições retângulo. Isto talvez envolva mudança de tamanho e/ou relação entre altura e largura. Para desenhar o gráfico em seu tamanho natural, use o método Draw. Graphics podem ser bitmaps, ícones ou metafiles. se o gráfico é um objeto TBitmap, o bitmap desenhado usando o valor de CopyMode. O código a seguir estica o bitmap para preencher a área cliente de Form1. void __fastcall TForm1::Button1Click(TObject *Sender) { Graphics::TBitmap* figura = new Graphics::TBitmap; figura->LoadFromFile("c:\\Meu_Desenho.bmp"); // Canvas->Draw(10, 10, figura); Canvas->StretchDraw(ClientRect, figura); delete figura; } TextHeight, int __fastcall TextHeight(const AnsiString Text); TextWidth 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: void __fastcall TForm1::FormCreate(TObject *Sender) { int x = Canvas->TextHeight(" Curso de C++Builder"); Edit1->Text = String(x) + String(" pixels em height"); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int __fastcall TextWidth(const AnsiString Text); 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 Apostila de C++ Builder Pagina -163- Métodos Descrições imagem. TextWidth indica se determinada string encaixa-se no espaço disponível. Outros elementos gráficos na imagem como linhas, ou strings adicionais podem ser posicionadas para acomodar a largura (width) do texto. TextWidth retorna o mesmo valor que TextExtent(Text).cx. Esse exemplo determina a largura (width) de uma string especificada, e se a string é demasiadamente grande para ser exibida no Edit, o Edit é redimensionado para acomodar a string. A string é exibida no edit box. void __fastcall TForm1::Button1Click(TObject *Sender) { long int t; String str("Tutorial de C++Builder"); t = Canvas->TextWidth(str); if(t > Edit1->Width) Edit1->Width = t + 10; Edit1->Text = str; } TextOut void __fastcall TextOut(int X, int Y, const AnsiString Text); 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: void __fastcall TForm1::Button1Click(TObject *Sender) { Canvas->Brush->Color = clBtnFace; Canvas->Font->Name = "Courier New"; Canvas->TextOut(20, 100, "Este curso C++Builder destina-se a facilitar o aprendizado de C++ "); } TextRect void __fastcall TextRect(const Windows::TRect &Rect, int X, int Y, const Text); Apostila de C++ Builder Pagina -164- AnsiString Métodos Descrições 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). void __fastcall TForm1::Button1Click(TObject *Sender) { 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]; Apostila de C++ Builder Pagina -165- declara uma matriz de vinte e seis ([26]) caracteres (char), cujo nome é Abcdario. Quando o compilador se depara com essa declaração, ele reserva vinte e seis bytes (1 byte para cada char), enfileirados seqüencialmente na memória do computador. O exemplo a seguir usa matrizes para imprimir o abecedário num Label: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { 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, char Abcdario[0] é a primeira variável; char Abcdario[1] é a segunda variável; char Abcdario[2] é a terceira variável; ... char Abcdario[25] é a última variável; 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. No exemplo anterior (Abcdario[]), temos que: Abcdario[0] = a (1ª posição) Abcdario[1] = b (2ª posição) Abcdario[2] = c (3ª posição) Abcdario[3] = d (4ª posição) Apostila de C++ Builder Pagina -166- Abcdario[4] = e (5ª posição) ... Abcdario[25] = z (26ª posição) Vamos reescrever o exemplo para acessar apenas um caracter: //--------------------------------------------------------------------------// 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]; } } //--------------------------------------------------------------------------- Agora o programa acessa apenas a 11ª variável, ou seja, a letra k. Excedendo o limite de um array Quando uma matriz é declarada, o espaço necessário em memória é calculado pelo compilador com base no tipo da variável, multiplicado pelo índice: a declaração float Notas[26] faz que o compilador reserve 104 bytes seqüenciais em memória para armazenar as 26 variáveis, visto que cada variável float ocupa 4 bytes. Logo, 4 * 26 = 104. 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. Apostila de C++ Builder Pagina -167- O exemplo a seguir, que lógica de programação. pode causar danos ao sistema, procura demonstrar esse tipo de erro na //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { 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): char Abcdario[26]; // um par de colchetes Podemos, também, ter matrizes com duas ou mais dimensões: bidimensionais: unsigned char Bid_Vetor[3][4]; // dois pares de colchetes tridimensionais: int Trid_Array[2][3][2]; // três pares de colchetes e assim por diante, cada par de colchetes representa 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) | Apostila de C++ Builder Pagina -168- As coordenadas das linhas localizam-se no lado esquerdo do interior dos parênteses e as coordenadas das colunas localizam-se no lado direito do interior dos parênteses. Logo, na primeira linha, a coordena horizontal são os zeros; na segunda, os um; na terceira, os dois. Na primeira coluna, a coordenada vertical são os zeros; na segundo, os um; na terceira, os dois; e na quarta, os três. Nós podemos inicializar as matrizes na mesma instrução onde a declaramos: char { a, { e, { i, }; Bid_Array[3][4] = { b, c, d } f, g, h } j, l, m } No exemplo acima, o caracter 'g' ocupa as coordenadas (1, 2). Então, Bid_Array[1][2] == 'g'. Nas matrizes de três dimensões, cada elemento é uma matriz de duas dimensões: char { { // { a, { e, } // Trid_Vetor[3][2][4] = início do primeiro elemento[0] b, c, d }, // primeira linha [0] f, g, h } // segunda linha [1] fim do primeiro grupo { // início do segundo grupo [1] { i, j, k, l }, // primeira linha [0] { m, n, o, p } // segunda linha [1] } // fim do segundo grupo { // início do terceiro grupo [2] { q, r, s, t } // primeira linha [0] { u, v, x, z } // segunda linha [1] } // fim do terceiro grupo }; // fim da matriz 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] Trid_Vetor[0][0][1] Trid_Vetor[0][0][2] Trid_Vetor[0][0][3] Trid_Vetor[0][1][0] Trid_Vetor[0][1][1] Trid_Vetor[0][1][2] Trid_Vetor[0][1][3] == == == == == == == == 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' e assim sucessivamente... Apostila de C++ Builder Pagina -169- Outra maneira de dizermos: '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. 'a', 'e', 'i', 'n', 'q', 'u', estão na primeira coluna; 'b', 'f', 'j', 'n', 'r', 'v', estão na segunda coluna; 'c', 'g', 'k', 'o', 's', 'x', estão na terceira coluna; e '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(): //--------------------------------------------------------------------------void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { ShowHint = true; Application->HintColor = RGB(X%255, Y%255, (X+Y)%255); Hint = (String)"Coordenada X = " + X + '\n' + "Coordenada Y = " + Y +"\n" + "RGB(" + X%255 + ", " + Y%255 + ", " + (Y+X)%255 + ")"; } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -170- Conforme dissemos, essas coordenadas são preenchidas com pontos coloridos que podem ser visualidas e acessadas. O exemplo a seguir nos possibilita visualizar a cor do pixels que receber o evento mouse down: //--------------------------------------------------------------------------#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::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Form1->Color = clRed; } //--------------------------------------------------------------------------void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { ShowHint = true; Hint = ColorToString(Form1->Canvas->Pixels[X][Y]); } //--------------------------------------------------------------------------- Agora vamos criar uma matriz bi-dimensional que receberá o valor do RGB (cor) do local do form onde ocorrer o evento mouse down: //--------------------------------------------------------------------------void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Form1->Color = clPurple; short Ponto[800][600]; Ponto[X][Y] = ColorToRGB(Canvas->Pixels[X][Y]); Label1->Caption = Ponto[X][Y]; } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -171- Arrays de caracteres Podemos declarar e inicializar strings (arrays de caracteres) de mesma forma que fazemos com outros tipos de matrizes. 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 Na memória a estrutura de armazenamento que se forma é: 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: Apostila de C++ Builder Pagina -172- //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { Label1 -> Caption = "Tamanho de \'A\' = " + String(sizeof('A')) + " byte" "\nTamanho de \"A\" = " + sizeof("A") + " bytes"; } //--------------------------------------------------------------------------- Certamente você já percebeu que a diferença se dá em virtude do caracter NULL (nulo) colocado a mais automaticamente pelo compilador. Existem outras formas de declarar e inicializar constantes de caracteres: int i[ ] = {0, 1, 2, 3, 4, 5, 6, 7}; // inteiros não precisam do caracter nulo char ch[ ] = {'T', 'h', 'e', 'r', 'b', 'i', 'o', '\0'}; // essa não é a melhor forma... char* ch = "Que saudades do Rio Paraná!!!!"; // Ponteiros... logo os entenderemos!!! Nessas declarações, [ ] significa "array de" e * significa "ponteiro para". Obs. A expressão char ch[3] = "Ola"; 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. Uma forma sempre preferível é a notação: char ch[ ] = "Ola"; 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: //--------------------------------------------------------------------------void __fastcall TForm1::Label1Click(TObject *Sender) { /*O exemplo imprime a string referente ao mês de março e demonstra o preenchimento de alguns elementos restantes com 0*/ char nome [12] [10] = {"janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"}; Apostila de C++ Builder Pagina -173- Label1 -> Caption = AnsiString(nome[2][0]) + AnsiString(nome[2][1]) + AnsiString(nome[2][2]) + AnsiString(nome[2][3]) + AnsiString(nome[2][4]) + // até aqui, imprime março int(nome[2][5]) + // imprime zero int(nome[2][6] + 'a') + // imprime 97 int(nome[2][6] + 1) + // imprime 1 int(nome[2][7] - 1); // imprime -1 } //--------------------------------------------------------------------------- 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; Acessamos os membros individuais através do operador . (ponto): DeLima.Nome = "Therbio"; DeLima.Rua = "Rua Central"; DeLima.Numero = 163; Podemos inicializar estruturas da mesma forma que inicializamos um array: struct dados // define uma estrutura { char *Nome; // sem problemas Apostila de C++ Builder Pagina -174- char rua[12]; // não esquecer de colocar o índice int Numero; char *Cidade; char Estado[3]; int Cep; long Idade; float Peso; }; // o ponto-e-vírgula é necessário // inicializa a estrutura igual a uma matriz dados DeLima = { "Therbio", // essa inicialização não trabalha com AnsiString "Rua Central", 163, "Ibirarema", // também não trabalha com String "SP", 7073, 40, 91.6 }; // para imprimir num label, precisamos converter os dados em AnsiString ou String Label1 -> Caption = AnsiString(DeLima.Nome) + '\n' + String(DeLima.rua) + '\n' + AnsiString(DeLima.Numero) + '\n' + String(DeLima.Cidade) + '\n' + AnsiString(DeLima.Estado) + '\n' + AnsiString(DeLima.Cep) + '\n' + AnsiString(DeLima.Idade) + '\n' + String(DeLima.Peso); Porém, esse tipo de inicialização acima tem dificuldade para inicializar dados do tipo AnsiString. Eis outra forma de inicializar os membros individuais através do operador . (ponto): struct dados // define uma estrutura { AnsiString Nome; char *rua; // sem problemas int Numero; char *Cidade; // não trabalha com dados do tipo ch[ ] ou ch[i] String Estado; int Cep; long Idade; float Peso; }; // o ponto-e-vírgula é necessário dados DeLima; // declara variável do tipo dados // inicializa as variáveis que trabalha com AnsiString e String DeLima.Nome = "Therbio"; DeLima.rua = "Rua Central"; DeLima.Numero = 163; DeLima.Cidade = "Ibirarema"; DeLima.Estado = "SP"; Apostila de C++ Builder Pagina -175- DeLima.Cep = 07073; DeLima.Idade = 40; DeLima.Peso = 91.6; // ok. Nesse exemplo não precisamos converter pra String ou AnsiString Label1 -> Caption = DeLima.Nome + '\n' + DeLima.rua + '\n' + DeLima.Numero + '\n' + DeLima.Cidade + '\n' + DeLima.Estado + '\n' + DeLima.Cep + '\n' + DeLima.Idade + '\n' + DeLima.Peso; Observação: Ainda que duas struct possuam os mesmos membros: struct aluno { char* Nome; int Nota; float media; }; struct __aluno { char* Nome; int Nota; float media; }; elas sempre serão diferentes tipos de dados entre si. Portanto a declaração: aluno Paulo; __aluno Paulo_Jose = Paulo; fará com que o compilador acuse um erro, uma vez que se trata de tipos incompatíveis entre si: [C++ Error] Unit 1.cpp(36): E2034 Cannot convert 'aluno' to '__aluno'. 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 Apostila de C++ Builder Pagina -176- 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 re-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. Conforme exposto, 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 cabe ao compilador relacionar o nome de cada variável com sua posição na memória. 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. Considerando-se o poder que os ponteiros representam na linguagem, a compreensão e o uso correto desses recursos são fundamentais para a criação de muitos programas em C++. 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 Apostila de C++ Builder Pagina -177- Quando realizamos operações com ponteiros, basicamente desejamos conhecer o endereço de uma variável e manipular o seu conteúdo. Para tanto, C++ nos fornece dois operadores especiais, o operador de endereços & (determina o primeiro byte do bloco ocupado pela variável) e o operador indireto de conteúdos * (retorna o conteúdo, ou seja, o valor do dado armazenado no endereço apontado). 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: tipo *nome = &nome1; onde nome1 é o nome da variável apontada: char c; int p; float flt; Apostila de C++ Builder Pagina -178- char *ch = &c; int *pi = &p; float *flot = &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: int *p, p2; float* flot, *flot2; 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; Vamos escrever um simples programa Console Wizard abordando o tema: #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 ... " Apostila de C++ Builder Pagina -179- << *Pi2 << "\n"; getch(); //exibe o conteúdo de i2 via ponteiro } Veja um exemplo equivalente no ambiente visual do C++Builder: //--------------------------------------------------------------------------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 } //--------------------------------------------------------------------------- O código abaixo exibe o endereço da variável: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { double r = 65; Label1 -> Caption = IntToHex(int(&r), 8); } //--------------------------------------------------------------------------- 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" //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -180- #pragma package(smart_init) #pragma resource "*.dfm" int i_1 = 100; // declara e inicializa variável int global int *P_i = 0; // declara e inicializa com 0, ponteiro para int TForm1 *Form1; //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Label1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { P_i = &i_1; // atribui o endereço0 de i_1 ao ponteiro P_i Label1 -> Caption = *P_i; // exibe o conteúdo da variável (100) via ponteiro } //--------------------------------------------------------------------------void __fastcall TForm1::Label1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { int i_2 = 200; // declara e inicializa variável int local P_i = &i_2; // atribui o endereço da variável local ao ponteiro Label1 -> Caption = *P_i; // exibe o conteúdo da variável (200) via ponteiro } //--------------------------------------------------------------------------void __fastcall TForm1::Label1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { int i_3 = 300; // declara e inicializa variável int local P_i = &i_3; // atribui o endereço da variável local ao ponteiro Label1 -> Caption = *P_i; // exibe o conteúdo da variável (300)via ponteiro } //--------------------------------------------------------------------------- Ponteiros apontando para outros ponteiros Como sabemos, um ponteiro pode apontar para qualquer tipo de variável. void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString Alvo = "AnsiString é uma variável ou uma classe?"; AnsiString *Aponta_Alvo = &Alvo; Edit1 -> Text = *Aponta_Alvo; } Ele pode, inclusive apontar para outro ponteiro: Apostila de C++ Builder Pagina -181- //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { char Alvo[ ] = "Alvo[ ] é uma matriz ou um ponteiro?"; // logo saberemos char *Aponta_Alvo = &*Alvo; // igual a: char *Aponta_Alvo = &Alvo[0]; Edit1 -> Text = *Aponta_Alvo; // experimente: Edit1 -> Text = Aponta_Alvo; } //--------------------------------------------------------------------------- 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. veja um exemplo, usando três ponteiros: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { char *ch = "testando ponteiros"; char **ch_Ptr = &ch; char ***Ptr_ch_Ptr = &ch_Ptr; Label1 -> Caption = "*ch = " + String(*ch) + '\n' + "ch = " + ch + '\n' + '\n' + "**ch_Ptr = " + **ch_Ptr + '\n' + "*ch_Ptr = " + *ch_Ptr + '\n' + '\n' + "***Ptr_ch_Ptr = " + ***Ptr_ch_Ptr + '\n' + "**Ptr_ch_Ptr = " + **Ptr_ch_Ptr; } //--------------------------------------------------------------------------- Ponteiros apontando para structs Diferente do que ocorre com matrizes (das quais podemos acessar seus elementos individuais por meio de índices), não podemos usar valores numéricos para acessar os membros de uma estrutura com base em sua ordem. Isso ocorre porque, enquanto todos os elementos de uma matriz são constituídos de um mesmo tipo de dado, os de uma estrutura podem ser constituídos de dados de tipos diferentes. 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; Apostila de C++ Builder Pagina -182- }Marta; // outra forma de declarar uma variável struct 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: 1 - Substituição do nome da estrutura pelo nome do ponteiro; 2 - Substituição do operador . "membro de" pelo operador -> ("aponta para"), que é formado por um hífen “ - ” mais o símbolo "maior que “ > ”: No exemplo anterior, altere o evento OnClick, apenas, de Label2: void __fastcall TForm1::Label2Click(TObject *Sender) { Endereco *PtrNome = &Marta, *PtrIdade = &Marta; (*Label2).Caption = PtrNome -> Nome + '\n' + (*PtrIdade).idade; /* Ops!! Parece que já vi essa notação*/ } Você já deve ter entendido. Veja: 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! Apostila de C++ Builder Pagina -183- Por aí, podemos perceber a grande importância que os ponteiros representam na linguagem C++, especialmente no C++Builder. Você, mesmo sem consciência, já tem trabalhado com esses conceitos ora apresentado, desde os nossos primeiros exemplos. Um completo domínio sobre ponteiros, pode fazer a diferença para qualquer pessoa que se aventure nesse ramo da programação. 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: void __fastcall TForm1::Label1Click(TObject *Sender) { char Abcdario[26] = {65, 66, 67, 68, 69}; // A, B, C, D, E - na tabela ASCII Label1 -> Caption = *Abcdario; } 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 Apostila de C++ Builder Pagina -184- Label1 -> Caption = *Abcdario; // imprime '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' e assim por diante. Considere a seguinte declaração: void __fastcall TForm1::Label1Click(TObject *Sender) { int i = 6; char Abcdario[26] = {65, 66, 67, 68, 69, 70, 71}; Label1 -> Caption = Abcdario[i]; // imprime 'G' Label2 -> Caption = *(Abcdario + i); // imprime 'G' } 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 Agora temos, também, que Abcdario + i é equivalente a &Abcdario[i]. Vejamos, agora, um exemplo onde um ponteiro é declarado e inicializado apontando para um vetor: void __fastcall TForm1::Button1Click(TObject *Sender) { Label1 -> Caption = ""; Apostila de C++ Builder Pagina -185- char Ch[] = "O Osmarino é um grande amigo de infância"; char *ApontCh; ApontCh = &Ch[0]; for(int i = 0; i < 42; i++) { Label1 -> Caption = Label1 -> Caption + *ApontCh; ApontCh++; } } Quando estudamos arrays de caracteres, aprendemos algumas formas de declarar e inicializar constantes de caracteres, entre elas: char* ch = "Que saudades do Rio Paraná!!!!"; // Ponteiros... logo os entenderemos!!! 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. Apostila de C++ Builder Pagina -186- Operador new: O operador new oferece um meio para alocação de espaço na memória livre, de uma forma parecida, porém muito superior àquela alocação oferecida pela função malloc() da livraria padrão da linguagem C. O operador new precisa, necessariamente, ser suprido com o tipo de dados para o qual está alocando memória, a fim de que o compilador saiba exatamente quanta memória deverá reservar para colocar os dados no heap. Por exemplo: new double aloca 8 bytes, suficientes para conter um double. O operador new retorna um endereço de memória a ser atribuído a um ponteiro: double *dbl = 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). Sintaxe (para tipos pré-definidos – baseado no Help do BCB): <::> new <placement> type-name <(initializer)> <::> new <placement> (type-name) <(initializer)> 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'); :: new placement (tipo) (inicializador) // char *ch = new (char) (65); Parênteses envolvendo o tipo é opcional. Os argumentos opcionais podem ser os seguintes: :: , 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. Apostila de C++ Builder Pagina -187- O inicializador, se aparece, é usada para assinalar valores iniciais para a memória reservada com new, mas não pode ser usado com arrays. 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. Nota: Classes de arrays requerem um construtor default. Há uma regra de ouro, quando se usa a área de alocação dinâmica: "toda memória reservada durante a execução do programa deve ser liberada antes do encerramento do programa". A memória é liberada através do operador delete. 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; d->e = 123.456; d->f = 0.5; Label1 -> Caption = "a = " + String(*a) + '\n' + "\nb = " + *b + '\n' + "\nc = " + *c + '\n' + "\nd = (" + d->e + " " + (*d).f + ")"; delete a; delete b; delete c; delete d; } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -188- Não seguir a regra de liberar a memória alocada antes do encerramento do programa é uma atitude extremamente irresponsável, e na maior parte dos casos acarretará conseqüências desastrosas, tais como: Tenha muito cuidado: se um ponteiro perde uma variável reservada dinamicamente (perda de memória), esta não poderá mais ser liberada. Exemplo: void __fastcall { int *a; a = new int; // *a = 10; a = new int; // *a = 20; delete a; // só TForm1::Button1Click(TObject *Sender) variável dinâmica nova variável dinâmica. Perde-se a anterior é possível liberar a última reserva } 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 <cast-expression> <::> delete [ ] <cast-expression> delete <array-name> [ ]; Acima adotamos a mesma regra usada na sintaxe de new: Apostila de C++ Builder Pagina -189- ::delete expressão //expressão normalmente será um ponteiro. :: delete [ ] expressão // delete[] expressão é usado para arrays dinamicos como sabemos, em muitos casos, :: é opcional. 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. int *i = new (int) ('\0'); 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). Mais problemas ... ponteiros soltos Outra forma de se pedir problemas, é tentar reutilizar um ponteiro cujo espaço apontado na memória foi liberado pelo operador delete, sem antes atribuir um novo endereço ou uma nova variável ao ponteiro: //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" int * i = new int (100); // cria variável dinâmica 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 } Apostila de C++ Builder Pagina -190- //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { /*************deleta a variável dinâmica. **************Se você clicar Button1 e ENCERRAR O PROGRAMA **************sem clicar Button2, **************ou clicar Button2 antes de clicar Button1, **************não haverá bug *******************************************************/ delete i; } //--------------------------------------------------------------------------void __fastcall TForm1::Button2Click(TObject *Sender) { /********* SE VOCÊ JÁ DEU UM CLIQUE EM Button1******** *********E CLICAR Button2, O BICHO VAI PEGAR**********/ *i = 200; /* coloca um novo valor no endereço apontado pelo ponteiro... ... só que, se Button1 foi já clicado, não existirá uma variável no endereço para receber o novo valor... então!!!!! */ Label1 -> Caption = *i; } //--------------------------------------------------------------------------- O melhor resultado produzido nos meus testes pelo exemplo acima foi o seguinte: Vamos entender o que aconteceu. A instrução: int * i = new int (100); de certa forma, combina várias instruções numa só, pois, na verdade, cria um ponteiro: Apostila de C++ Builder Pagina -191- int * i uma variável: new int e atribui um valor a essa variável: int (100); 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. Para melhor compreensão, veja a mesma instrução montada de forma diferente: int * i; // cria naturalmente um ponteiro i = new int; // cria variável dinâmica e atribui seu endereço ao ponteiro *i = 100; // inicializa a variável, via ponteiro 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" int * i = new int (100); TForm1 *Form1; //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -192- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::FormCreate(TObject *Sender) { Label1 -> Caption = "valor de i = " + String(*i) + "\nendereço = " + IntToHex(int(&*i), 8); } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { delete i; } //--------------------------------------------------------------------------void __fastcall TForm1::Button2Click(TObject *Sender) { i = new int; *i = 200; Label1 -> Caption = "valor de i = " + String(*i) + "\nendereço = " + IntToHex(int(&*i), 8); // NÃO ESQUEÇA DE DESALOCAR A MEMÓRIA, // CLICANDO NOVAMENTE (Button1Click) // ANTES DE ENCERRAR O PROGRAMA } //--------------------------------------------------------------------------- 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); } //--------------------------------------------------------------------------- Ponteiros & constantes Veja algumas declarações usando const com ponteiros, e tente visualizar as possíveis diferenças de resultados, em virtude da posição do prefixo const: AnsiString * const Const_Str = &Str; // o ponteiro é constante const int* Const_i = &i; // a variável é constante para *Const_i Apostila de C++ Builder Pagina -193- const double alterados * const Const_dbl = &dbl; // Nem Const_i, nem *Const_i podem ser Vejamos agora um exemplo com respectivos comentários abordando cada situação: //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" //////////////GRUPO DO PONTEIRO CONSTANTE ////////////////// AnsiString Str = "Testando Ponteiros e Constantes"; AnsiString Str_1 = "Um novo endereço não pode ser atribuído" " a um ponteiro const"; AnsiString * const Const_Str = &Str; // ponteiro const para Str //////////////////////////////////////////////////////////////////////////////// ////////////GRUPO DO DADO APONTADO CONSTANTE ////// int i = 100; // obs. i pode ter seu valor alterado, mas não por *Const_i int i_1 = 200; const int *Const_i = &i; // o ponteiro (*Const_i) não consegue alterar i //////////////////////////////////////////////////////////////////////////////// // PONTEIRO E VARIÁVEL CONSTANTES VIA PONTEIRO double dbl = 0.123456789; // dbl é variável, mas não via ponteiro *Const_dbl double dbl_1 = 9.876543210; const double * const Const_dbl = &dbl; // Const_dbl e *Const_dbl constantes //////////////////////////////////////////////////////////////////////////////// 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) Apostila de C++ Builder Pagina -194- { // altera a variável via ponteiro. Ok *Const_Str = "alterando a variável via ponteiro - operação permitida."; Label1 -> Caption = *Const_Str; Const_i = &i_1; // altera endereço apontado via ponteiro. Ok Label2 -> Caption = "Alterando endereço via ponteiro. " "Operação permitida. *Const_i = " + String() + *Const_i; Const_i = &i; // volta apontar para a variável inicial } //--------------------------------------------------------------------------void __fastcall TForm1::Button2Click(TObject *Sender) { // tenta redirecionar ponteiro const para outra variável... ERRO!!!! Const_Str = &Str_1; // [C++ Error] Unit1.cpp(57): E2024 Cannot modify a const object. Label1 -> Caption = *Const_Str; } //--------------------------------------------------------------------------void __fastcall TForm1::Button3Click(TObject *Sender) { // tenta alterar variável const para ponteiro ... ERRO!!!! *Const_i = 400; // [C++ Error] Unit1.cpp(63): E2024 Cannot modify a const object. Label2 -> Caption = *Const_i; } //--------------------------------------------------------------------------void __fastcall TForm1::Button4Click(TObject *Sender) {// tenta alterar variável / ponteiro const e redirecionar ponteiro const ... ERRO!!!! *Const_dbl = 5.43; // [C++ Error] Unit1.cpp(69): E2024 Cannot modify a const object. Const_dbl = &dbl_1; // [C++ Error] Unit1.cpp(70): E2024 Cannot modify a const object. Label3 -> Caption = *Const_dbl; } //--------------------------------------------------------------------------- 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: //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString esposa = "Marta"; // declara e inicializa variável esposa AnsiString &ref_esposa = esposa; // cria e inicializa uma referência para esposa ref_esposa = "Marta é minha esposa!"; // altera variável esposa através do "apelido" 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); } //--------------------------------------------------------------------------- Apostila de C++ Builder Pagina -195- Analisando o código, percebemos que uma referência deve ser criada com o mesmo tipo de dados do dado alvo (AnsiString, no exemplo) e com o operador & antecedendo o "apelido", bem como ser inicializada com o nome do dado original (esposa, no exemplo). Outra observação importantíssima é que referências devem ser inicializadas na mesma instrução de sua declaração. Instruções do tipo: AnsiString &ref_esposa; ref_esposa = esposa; Retornarão uma mensagem de erro, avisando que a referência deve ser inicializada: [C++ Error] Unit1.cpp(19): E2304 Reference variable 'ref_esposa' must be initialized. Reatribuição de uma referência Desde minha infância, tenho um grande amigo chamado Wanderley. Desde que o conheço (trinta anos, ou mais), o Wanderley tem um apelido: "Churrasco". Esse apelido está tão difundido na personalidade do Wanderley, que uma vez ele me falou que eu seria a única pessoa que o chamava pelo nome verdadeiro. Todas as outras pessoas tratavam-no por seu apelido. Muitos nem sabem seu verdadeiro nome. Agora vamos supor uma hipótese meio absurda. Amanhã eu chego até o Wanderley para, formalmente, comunicar-lhe de que, a partir daquele momento, o apelido não será mais dele. Que o apelido será meu. E saio da presença do Wanderley convencido de que, a partir de então, todos me chamarão de Churrasco, enquanto tratarão o antigo Churrasco por Wanderley. É possível???? NÃO DÁ PRA TROCAR APELIDO!!!!! E, POR MAIS QUE SE POSSA QUERER, NÃO SE PODE PEGAR O APELIDO DE NINGUÉM!!!! 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); AnsiString irma_1 = "Daurylene também é minha irmã"; // variável irma_1 Apostila de C++ Builder Pagina -196- irma_1 = ref_irma; /* parece variável. passa ser O apelido que está atribuindo a referência para a outra Mas está alterando o conteúdo de irma_1, que igual a irma. continua pertencendo a irma (v. endereços) */ //ref_irma = irma_1; // eis outra possibilidade de verificação no lugar da anterior 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 Autor: Thérbio de Lima Alves (DeLima) Agradecimentos especiais: Lucas Santos Sanches ( mais conhecido por scorpio) em nosso Fórum. 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! Apostila de C++ Builder Pagina -197-