Ao longo deste capítulo reforçámos a ideia de que a abstração procedimental é extremamente útil na programação:
Usando de novo a abstração procedimental, vamos desenvolver um programa para imprimir um retângulo de carateres. O retângulo da figura abaixo é composto por 6 linhas com 15 asteriscos cada uma:
***************
***************
***************
***************
***************
***************
Vamos começar por construir um método que imprime uma linha com 15 asteriscos:
x/**
* Imprimir uma linha com 15 asteriscos no standard output
*/
static void imprimeAsteriscos () {
for (int i = 1 ; i <= 15 ; i++) {
System.out.print('*');
}
System.out.println();
}
A última instrução é para provocar a mudança de linha.
Agora é preciso invocar este método seis vezes para obter o retângulo desejado. O seguinte programa obtém o efeito pretendido:
xxxxxxxxxx
public class EscreveRetanguloAsteriscos {
public static void main(String[] args) {
for (int i = 1 ; i <= 6 ; i++) {
imprimeAsteriscos();
}
}
}
E se quisermos imprimir o seguinte retângulo, com 7 asteriscos em cada linha?
xxxxxxxxxx
*******
*******
*******
*******
*******
*******
Ou ainda este, com 25 asteriscos em cada linha?
xxxxxxxxxx
*************************
*************************
*************************
*************************
*************************
*************************
Na verdade, o método imprimeAsteriscos
não serve para muita coisa… Teríamos que ter vários métodos quase iguais a este para podermos imprimir os dois retângulos acima, pois o número de carateres em cada linha é diferente.
O ideal era ter um método que permitisse imprimir um asterisco um qualquer número de vezes. Como podemos fazer isso?
Usando abstração de parâmetro – adicionamos um parâmetro que representa o número de asteriscos a imprimir.
Sempre que o método é invocado, um valor específico deve ser indicado para esse parâmetro. Assim obtemos um método mais geral e, portanto, mais útil, pois pode ser usado em várias circunstâncias.
xxxxxxxxxx
/**
* Imprimir no standard output uma linha com um dado numero
* de asteriscos
* @param n - O numero de asteriscos a imprimir
* @requires n > 0
*/
static void imprimeAsteriscos (int n) {
for (int i = 1 ; i <= n ; i++) {
System.out.print('*');
}
System.out.println();
}
O seguinte programa imprime os três retângulos pretendidos:
xxxxxxxxxx
public class EscreveRetangulos {
public static void main(String[] args) {
for (int i = 1 ; i <= 6 ; i++) {
imprimeAsteriscos(15);
}
for (int i = 1 ; i <= 6 ; i++) {
imprimeAsteriscos(7);
}
for (int i = 1 ; i <= 6 ; i++) {
imprimeAsteriscos(25);
}
}
}
E se quisermos imprimir o seguinte retângulo?
xxxxxxxxxx
@@@@@@@@@
@@@@@@@@@
@@@@@@@@@
@@@@@@@@@
Ou ainda este?
xxxxxxxxxx
€€€€€€€€€€€€€€€€€€€€€€€€€
€€€€€€€€€€€€€€€€€€€€€€€€€
€€€€€€€€€€€€€€€€€€€€€€€€€
O ideal era ter um método que permitisse imprimir um qualquer caráter um qualquer número de vezes.
Usando outra vez abstração de parâmetro, adicionamos um segundo parâmetro que representa o caráter a escrever.
Quando o método é invocado, indicamos um valor específico para cada um desses parâmetros. Alteramos também o nome do método para ficar mais de acordo com a sua funcionalidade.
xxxxxxxxxx
/**
* Imprimir no standard output uma linha com um dado carater
* repetido um dado numero de vezes
* @param n - O numero de carateres a imprimir
* @param carater - O carater a imprimir
* @requires n > 0
*/
static void imprimeCarateres (int n, char carater) {
for (int i = 1 ; i <= n ; i++) {
System.out.print(carater);
}
System.out.println();
}
O seguinte programa imprime vários dos retângulos pretendidos:
xxxxxxxxxx
public class EscreveRetangulos {
public static void main(String[] args) {
for (int i = 1 ; i <= 6 ; i++) {
imprimeCarateres(15, '*');
}
for (int i = 1 ; i <= 4 ; i++) {
imprimeCarateres(9, '@');
}
for (int i = 1 ; i <= 3 ; i++) {
imprimeCarateres(25, '€');
}
}
}
Se observarmos melhor o método main
, vemos que a instrução for
que permite repetir a invocação do método imprimeCarateres
é praticamente igual nos três casos. As diferenças são:
i
, o qual define a altura do retângulo;imprimeCarateres
.Além disso, cada um desses ciclos tem como objetivo a escrita de um retângulo no standard output.
Justifica-se então a criação de um método cujo objetivo é o de escrever no standard output um retângulo formado por um dado número de linhas, em que cada linha será formada por um dado número de um qualquer caráter.
O seguinte método faz o que pretendemos:
xxxxxxxxxx
/**
* Imprimir no standard output um retangulo com uma dada altura, em que
* em cada linha e' repetido um carater dado, um dado numero de vezes
* @param altura - O numero de linhas do retangulo
* @param largura - O numero de carateres a imprimir em cada linha
* @param carater - O carater a imprimir
* @requires n > 0
*/
static void imprimeRetangulo (int altura, int largura, char carater) {
for (int i = 1 ; i <= altura ; i++) {
imprimeCarateres(largura, carater);
}
}
O programa EscreveRetangulos
fica então simplificado e com maior legibilidade:
xxxxxxxxxx
public class EscreveRetangulos {
public static void main(String[] args) {
imprimeRetangulo(6, 15, '*');
imprimeRetangulo(4, 9, '@');
imprimeRetangulo(3, 25, '€');
}
}
Na verdade, o método imprimeRetangulo
poderia ter sido implementado da seguinte forma:
xxxxxxxxxx
static void imprimeRetangulo (int altura, int largura, char carater) {
for (int i = 1 ; i <= altura ; i++) {
for (int j = 1 ; j <= largura ; j++) {
System.out.print(carater);
}
System.out.println();
}
}
Repare que nesta versão tivemos que substituir a variável de progresso do ciclo interno para j
de modo a ficar diferente de i
, pois não podemos ter dois ciclos encaixados declarando variáveis de progresso com nomes iguais.
Qual das duas versões é a melhor?
imprimCarateres
– que pode ser útil por si só; além disso o código é mais legível e mais perto da nossa forma de raciocinar;
Agora imagine que queremos um triângulo retângulo de asteriscos em que a base contém n
asteriscos. Se n
for 6, queremos:
xxxxxxxxxx
*
**
***
****
*****
******
Como fazer? Repare que queremos escrever n
linhas, cada uma com um número de asteriscos que varia conforme a linha. Ou seja, reaproveitando o método imprimeAsteriscos
queremos algo parecido com:
xxxxxxxxxx
/**
* Imprimir um triangulo de carateres com um dado numero de linhas
* @param n - o numero de linhas
* @param carater - O carater a imprimir
* @requires n > 0
*/
static void imprimeTriangulo (int n, char carater){
for (int i = 1 ; i <= n ; i++) {
imprimeCarateres(???, carater);
}
}
Coloca-se então a questão: o que devemos escrever em vez dos ???
? Depende.
i
é 1, significando que vamos escrever a 1ª linha do triângulo, queremos escrever um caráter;i
é 2 (para escrever a 2ª linha do triângulo) queremos escrever dois carateres;i
tem um dado valor x (que corresponde à linha x), queremos escrever x carateres;Na verdade queremos escrever tantos carateres quantos o valor de i
!
Então, a nossa função deve ser:
xxxxxxxxxx
static void imprimeTriangulo (int n, char carater){
for (int i = 1 ; i <= n ; i++) {
imprimeCarateres(i, carater);
}
}
Se optarmos por outra versão sem a invocação do método imprimeCarateres
(à semelhança do que fizemos acima com o retângulo), teremos:
xxxxxxxxxx
static void imprimeTriangulo (int n, char carater) {
for (int i = 1 ; i <= n ; i++) {
for (int j = 1 ; j <= i ; j++) {
System.out.print(carater);
}
System.out.println();
}
}
Descubra como imprimir um triângulo retângulo invertido (com a base em cima e o vértice em baixo).
E se agora quisermos imprimir n
x m
números sucessivos, organizados em n
linhas com m
números cada? Por exemplo, se n
for 3 e m
for 4 queremos isto:
xxxxxxxxxx
1 2 3 4
5 6 7 8
9 10 11 12
Raciocinemos de forma semelhante ao que já fizemos: vamos imprimir uma linha de m
números sucessivos, n
vezes.
Então vamos ter dois ciclos for
encaixados:
m
números,n
vezes.xxxxxxxxxx
/**
* Imprimir uma tabela de numeros sucessivos
* @param n - o numero de linhas da tabela
* @param m - o numero de valores por linha
* @requires n > 0 && m > 0
*/
static void imprimeTabelaNumeros (int n, int m) {
for (int i = 1 ; i <= n ; i++) {
for (int j = 1 ; j <= m ; j++) {
System.out.print(???);
}
System.out.println();
}
}
O que deverá estar no lugar dos ???
? Depende do número de vezes que cada ciclo já foi executado.
Como queremos imprimir um número que, começando em 1, resulta do anterior somando-lhe um, podemos utilizar uma variável para memorizar o número que deve ser impresso a seguir.
Então, é o valor dessa variável que deve ser impresso no corpo do ciclo interno. Essa variável deve ser inicializada com o valor 1, que é o primeiro número que queremos imprimir, e, para que o seu valor progrida, devemos incrementá-la de cada vez que executamos a instrução System.out.print
.
xxxxxxxxxx
static void imprimeTabelaNumeros (int n, int m) {
int count = 1;
for (int i = 1 ; i <= n ; i++) {
for (int j = 1 ; j <= m ; j++) {
System.out.print(count);
count++;
}
System.out.println();
}
}
Um pequeno exercício – o que imprimiria a seguinte versão?
xxxxxxxxxx
static void imprimeTabelaNumerosA (int n, int m) {
for (int i = 1 ; i <= n ; i++) {
int count = 1;
for (int j = 1 ; j <= m ; j++) {
System.out.print(count);
count++;
}
System.out.println();
}
}
Anterior: 6.3. Ciclos for encaixados
Seguinte: 6.5. Ainda o âmbito das variáveis