Trabalho prático de declaração e definição de classes e utilização de classes de biblioteca Mais bolas? 1. 2. 3. 4. 5. 6. 7. 8. Antes de começar este trabalho prático, feche os olhos, e veja se se lembra de todos os passos necessários para criar uma solução, para criar um projecto (de que tipo?), para criar os ficheiros .h e .cpp. Lembra-se de para onde é que vão estas coisas? Se não se lembrar, consulte o primeiro guião. Repare: se o projecto tiver sido criado erradamente, se os ficheiros estiverem nas directorias erradas, ou forem do tipo errado, ou tiverem o nome errado, depois é uma confusão. Neste exercício, você vai declarar de raiz uma classe e programá-la, tal como no exercício anterior. A principal diferença, é que hoje você vai usar também algumas classes já programadas, que lhe vão ser fornecidas. Abra o Visual C++ e crie uma nova solução FiguresSolution e lá dentro um projecto CirclesProject. Verifique com o Explorer que ficou tudo nas directorias certas. (Não sabe quais são as directorias certas? Então releia o primeiro guião.) Copie para a sua directoria Library, os ficheiros das classes Point, Figure e Line, acessíveis através do link para as classes do livro recomendado. Esse link está disponível na página da cadeira, em "Bibliografia & Links". A classe Point é semelhante à da sebenta, mas contém umas “complicações”. As outras duas são “novas”. Mas não se atrapalhe. Você não vai modificar estes ficheiros, apenas os vai usar, tal como estão. Incluí-los-á nos seus projectos, para poder tirar partido da classe Point fornecida, mas sem lhes mexer. Se não respeitar esta regra, isto é, se começar a chafurdar nos ficheiros de biblioteca, depois não se queixe. Mas se detectar algum erro ou alguma incorrecção nestas classes, ou no futuro noutras que serão fornecidas pelo mesmo sistema, por favor avise o seu assistente e siga as suas (do assistente) instruções. Volte ao Visual C++, que na janela Solution Explorer deve exibir a solução FiguresSolution e o projecto CirclesProject, com a pasta do projecto seleccionada faça File>Add Existing Item, seleccione os ficheiros Figures.h, Figures.cpp, Points.h, Point.cpp, Line.h e Line.cpp, na sua directoria Library, e faça Open. Os seis ficheiros devem agora estar no projecto. Abra as pastas Source Files e Header Files para confirmar. Dê uma vista de olhos aos ficheiros, e compile -os, um a um. Dê outra vista de olhos, agora com mais atenção, aos ficheiros Point.h e Point.cpp. Repare nos operadores == e !=. Servem para ver se um ponto é igual a outro, e diferente de outro, respectivamente, escrevendo simplesmente p1 == p2 ou p1 != p2, para dois pontos p1 e p2, como se fossem números. Observe as funções Translate, Scale e Rotate. Os nomes são significativos, mas se não perceber o que fazem, estude as definições. Repare nas funções Write, WriteLine e Read, que têm parâmetros com valores por defeito. Por exemplo, se programarmos p.Write(), para um ponto p, as coordenadas de p aparecem na consola. Se quisermos que apareçam num outro ficheiro, teremos que o indicar explicitamente no argumento. Repare também na função extra, que é só um “mau” exemplo de um “selector-modificador”, o tipo de funções que nós nunca queremos usar. Crie o ficheiro M_Circle.cpp, ou de raiz, como no primeiro guião, ou usando o seguinte expediente: abra o ficheiro M_Parabola.cpp, guarde com o novo nome M_Circle.cpp, apague o que estiver a mais e não valer a pena reutilizar, e junte ao projecto movendo-o da pasta Miscellaneous Files para a pasta Source Files do projecto. Mude os includes. Agora temos que incluir pelo menos o fic heiro com a declaração da classe Point, além do iostream, que deve vir antes (porquê?). Será que o compilador “pede” mais includes? Escreva uma função main vazia (só com return 0;) e compile. Tudo bem? Não deve estar. Ao compilar, o compilador queixa-se de que não consegue encontrar os ficheiros .h. A 1 9. 10. 11. 12. 13. 14. 15. 16. razão é simples: os ficheiros .h estão na directoria Library e o ficheiro M_Circle.cpp está na directoria C++. Se não está, devia estar. O compilador precisa de uma ajudinha nossa para ir buscar os ficheiros à directoria Library. Faça assim: seleccione o projecto no Solution Explorer e faça View>Property Pages. Escolha a pasta C/C++ e dentro desta o item General; no Additional Include Directories escreva o nome completo da sua directoria Library: “C:\…\…\Library”. (Sugestão: abra o Explorer na directoria Library e copie o nome completo da caixa Address.) Você terá que fazer isto para cada novo projecto, não se esqueça! Compile de novo e linque. Agora já deve estar tudo bem. Até pode correr o programa, mas o programa não faz nada (pudera: a função main só tem um return 0.) Agora acrescente uma linha na função main, apenas com a declaração de uma variável de tipo Point. Compila bem? Se sim, é bom sinal. Se não, veja o que se passa. Para começar a fazer qualquer coisa, escreva uma função de teste que aceita um ponto, e depois move-o 2 para a direita 1 para baixo, roda-o de 90 graus e escala -o 0.5. Note que o argumento da função Rotate vem expresso em radianos, não em graus. Para passar de graus para radianos multiplica-se por pi e divide-se por 180, não é? Eis o valor de pi com 20 casas decimais: 3.14159265358979323846. Trate de decorar isto J, pois nem sempre terá este guião à mão, ou então, se for preguiçoso (como convém a um programador), quando for preciso o valor de pi, use o quádruplo do arcotangente de 1. Para o arcotangente use a função ::atan, do <cmath>. Experimente. Escreva agora outra função de teste mais problemática. A função pede um número e calcula os vértices de um polígono regular com esse número de lados, centrado na origem, começando no ponto de coordenadas <1, 0>, e inscrito na circunferência de raio unitário. Como resultado, queremos ver na consola as coordenadas dos vértices, com duas casas decimais. Não se preocupe demasiado com a classe Figure. Hoje ela só é necessária porque a classe Point “deriva” dela (veja a primeira linha da declaração da classe Point), mas nós não tiramos grande partido disso neste trabalho. Por sua vez, a classe Line é precisa porque a classe Figure a usa. Passemos agora à classe de hoje, a classe Circle. A sua tarefa é desenhar e implementar uma classe Circle para representar círculos no plano cartesiano. Cada objecto de tipo Circle tem dois membros de dados, o centro, center, de tipo Point, e o raio, radius, de tipo double. Inclua para começar um construtor por defeito, que inicializa um círculo de raio 1 na origem, um construtor elementar, com argumentos para o centro e para o raio, os selectores para o centro (este tem um resultado de tipo Point retornado por referência constante) e para o raio, as operações Scale, Translate e Rotate, e ainda Write e WriteLine, análogas às da classe Point. Mas repare: rodar um círculo não é rodá-lo em torno do seu centro, que deixaria tudo na mesma, mas sim rodá-lo em torno da origem. Também para escalar um círculo, só damos um factor de escala, e não dois como no caso da classe Point. De facto, um ponto pode ser escalado diferentemente segundo os dois eixos, mas um círculo não. Se o fizéssemos deixávamos de ter um círculo, não era? Finalmente escalar um círculo não é apenas multiplicar o raio por esse factor de escala. Em geral, escalar uma figura, é aplicar o factor de escala (ou os factores de escala, se houver um factor de escala para cada direcção) a cada um dos pontos da figura. Avance pausadamente. Primeiro declare a classe só com os membros de dados, os construtores e o destrutor, no ficheiro Circle.h. Depois, no ficheiro Circle.cpp, primeiro meta só o #include “Circle.h”. Compile. Agora defina os construtores e o destrutor no fiche iro Circle.cpp. Como é que se inicializa o centro, no construtor por defeito e no construtor elementar? Se ainda não sabe, procure um exemplo do género, na sebenta ou no livro. A seguir, declare e defina os selectores para o centro e para o raio. O do raio não tem novidade: não tem argumentos e devolve um double. O do centro também não tem argumentos mas devolve um ponto, isto é, um objecto de tipo Point. Ora Point é um tipoclasse. Normalmente quando uma função devolve um objecto de um tipo-classe que 2 17. 18. 19. 20. 21. 22. existe em memória como “parte” do objecto, devolve-o por referência constante. Se este assunto é novidade para si, procure mais informação sobre ele na sebenta ou no livro. Acrescente as declarações das funções Write e WriteLine, por analogia com as da classe Point. Defina-as. Quando estiver satisfeito com o seu trabalho, e a sua classe compilar sem erros, passe ao ponto seguinte. Observe os ficheiros .h fornecidos: a declaração da classe vem sempre entre as directivas #ifndef, #define (antes) e #endif (depois). Este esquema serve para evitar inclusões repetidas, isto é, para evitar que um ficheiro .h seja incluído mais que uma vez (o que dá erro). Apesar de não ser necessário nestes exemplos simples, pois não há inclusões repetidas (a não ser por distracção) é um bom hábito proteger as declarações das classes com estas directivas, sistematicamente. Faça isso para a sua classe Circle, usando a constante simbólica _H_Circle. Ao ficheiro M_Circle.cpp, que apenas tem os includes e uma função main vazia, acrescente uma função de teste TestCircle pare experimentar os construtores e os selectores básicos. Tudo bem? Declare e defina agora as três funções Scale, Translate e Rotate. Não é muito difícil, pois não? Volte ao ficheiro M_Circle.cpp, e acrescente uma função de teste TestCircleFunctions para experimentar as funções Scale, Translate e Rotate. Por exemplo, faça a sua função ler o centro do círculo e o raio, e depois mova o círculo algumas unidades para a direita e para cima, aumente o tamanho para o dobro e rode-o de -45 graus em torno da origem. Escreva uma terceira função de teste para construir virtualmente o seguinte desenho, para um dado número de círculos, começando com um círculo área 1, sabendo que as áreas dos sucessivos círculos vão duplicando. 23. Acrescente à classe Circle os selectores Area e Perimeter, para calcular a área e o perímetro. Escreva uma função de teste, TestAreaPerimeter, para experimentar. Modifique a função do passo anterior para que ela acrescente o perímetro e a área de cada um dos círculos mostrados na consola. 24. Acrescente à classe Circle a função Equal e os operadores == e !=, como na classe Point. Um círculo é igual a outro se o centro for igual e o raio for igual. Escreve uma função de teste, TestEquality, para experimentar. 25. Acrescente uma função booleana Contains com um argumento de tipo Point que dê true se o ponto pertencer ao círculo, isto é, se estiver sobre a circunferência ou no interior da circunferência. Teste. 26. Acrescente outra função Contains agora com um argumento de tipo Circle. Esta função dá true se o argumento estiver “coberto” pelo objecto. Teste. 3 27. Acrescente uma função booleana Intersects com um argumento de tipo Circle. A função dá true se o objecto tiver pelo menos um ponto em comum com o argumento. Teste. 28. Acrescente uma função de RightMost, com um resultado de tipo Point (retornado por valor). A função calcula e devolve o ponto do círculo mais à direita. Por exemplo, no círculo centrado na origem e raio 1, o ponto mais à direita é o ponto de coordenadas <1, 0>. Teste. “Já agora”, acrescente três funções análogas, LeftMost, TopMost, e BottomMost, que dão os pontos mais à esquerda, mais acima e mais abaixo. Teste de novo. Repare que estas funções têm que devolver o resultado por valor, porque os pontos calculados não existiam em memória como componentes do objecto da função. Por isso têm que ser construídos no seio da função chamada e “copiados” para o contexto da função chamadora. No caso da função Center, já programada, o resultado era um componente do objecto, pré-existente, e a função não precisa duplicar esse objecto: basta devolver uma referência para ele. 29. Acrescente uma função PointAt, com um parâmetro de tipo double representando um ângulo, e um resultado de tipo Point retornado por valor, para calcular o ponto sobre a circunferência que se encontra à distância angular dada do ponto do círculo mais à direita, no sentido contrário ao dos ponteiros do relógio. 30. Acrescente uma função Angle com um parâmetro de tipo Point, para calcular o ângulo (em radianos) formado pela semi-recta horizontal do centro para a direita com a semirecta do centro para o argumento. 31. Programe agora uma função Plot, que escreva num ficheiro de texto n pontos equidistantes ao longo da circunferência do círculo. Em cada linha vem um valor de x e um valor de y. A declaração será assim: void Plot(std::ostream& output, int countPoints) const; O primeiro argumento representa o ficheiro e o segundo o número de pontos. Baseie -se na função PointAt e na função WriteLine da classe Point. 32. Escreva uma função de teste para a função Plot. 33. (Extra) Importe o ficheiro resultante do teste do ponto anterior para o Excel e desenhe o gráfico da circunferência. 34. Acrescente quatro funções SectorArea, SegmentArea, ArcLength e ChordLength, com um parâmetro de tipo double representando um ângulo, para calcular a área do sector circular, a área do segmento circular, o comprimento do arco de circunferência, e o comprimento da corda, definidos pelo ângulo dado. Teste. (Este exercício exige alguma trigonometria, mas nada demais. O próximo também.) 35. Acrescente uma função ChordAngle que calcula o ângulo correspondente à corda cujo comprimento é dado no argumento. Teste. 36. Acrescente uma função IntersectionArea, com um parâmetro de tipo Circle, para calcular a área da intersecção do objecto com o argumento. Quando tiver esta função pronta, copia-a para uma mensagem de correio electrónico e envie -a com os seus cumprimentos para o seu assistente. Ele vai gostar. E você? Gostou? 4