Mas a verdade é que, sendo os int limitados em C++, a utilização duma representação não canónica das fracções põe alguns problemas graves de implementação. Suponha-se o seguinte código:
int main()No ecrã deveria aparecer
{
Racional x(1/50000), y(1/50000);
Racional z = x.soma(y);
z.escreve();
cout << endl;
}
1/25000mas a verdade é que, ao se calcular o denominador do resultado, multiplicam-se os dois denominadores, de que deveria resultar 50 000 x 50 000 = 2 500 000 000. Mas, em máquinas em que os int têm 32 bits, esse valor não é representável, pelo que se obtem um valor errado (em Linux i386 obtem-se -1794967296!). Isto apesar de a fracção resultado ser perfeitamente representável.
É pois desejável não só usar uma representação canónica para os racionais, o que de resto simplifica a escrita de operações de comparação, por exemplo, como tentar garantir que os resultados de cálculos intermédios são tão pequenos quanto possível.
// PC: m >= 0 e n > 0 e m = m e n = nÉ fácil verificar que a PC se pode relaxar de modo a admitir n >= 0, muito embora se tenha de continuar a impor que pelo menos uma dos valores é diferente de zero, sem que isso afecte o funcionamento da função:
// CO: n = mdc(m, n)
int mdc(int m, int n)
{
while(m != 0) {
int auxiliar = n % m;
n = m;
m = auxiliar;
}
return n;
}
// PC: m >= 0 e n >= 0 e (m <> 0 ou n <> 0) e m = m e n = nMas seria de todo o interesse que a função aceitasse valores negativos e mesmo os dois valores nulos, nesse caso convencionando-se que mdc(0, 0) = 1, por exemplo. A função abaixo resolve o problema:
// CO: n = mdc(m, n)
int mdc(int m, int n)
{
while(m != 0) {
int auxiliar = n % m;
n = m;
m = auxiliar;
}
return n;
}
// Cálculo do mdc de dois números inteiros estendido de modo a funcionar
// para argumentos negativos ou nulos.
// PC: n = n e m = m
// CO: (n = 0 e m = 0 e n = 1) ou ((m <> 0 ou n <> 0) e n = mdc(|m|, |n|))
int mdc(int m, int n)
{
if(m == 0 && n == 0)
return 1;
if(m < 0)
m = -m;
// Aqui forçosamente m = |m| e m >= 0
if(n < 0)
n = -n;
// Aqui m = |m|, m >= 0, e n = |n|, n >= 0, sendo pelo menos um de m e n
// diferente de zero.
// CI: mdc(m, n) = mdc(|m|, |n|)
while(m != 0) {
int aux = n % m;
n = m;
m = aux;
}
return n;
}
// PC:As mesmas ideias podem ser aplicadas a qualquer outra operação com os racionais. Por exemplo, uma função membro para verificação se um racional é maior que outro poderia ser:
// CO: r = *this + b, sendo *this a instância da classe implícita na execução
// da função.
Racional Racional::soma(Racional b)
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
int termo = (n / l) * (b.d / k) + (b.n / l) * (d / k);
int m = mdc(termo, k);
Racional r;
r.n = (termo / m) * l;
r.d = (k / m) * (d / k) * (b.d / k);
return r;
}
// PC:Uma função para verificar da igualdade poderia ser mais simples, dada a certeza da canonicidade de representação das fracções:
// CO: devolve valor lógico de *this > b, sendo *this a instância da classe implícita
// na execução da função.
Racional Racional::maior(Racional b)
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
return (n / l) * (b.d / k) > (b.n / l) * (d / k);
}
// PC:
// CO: devolve valor lógico de *this = b, sendo *this a instância da classe implícita
// na execução da função.
Racional Racional::igual(Racional b)
{
return n == b.n && d == b.d;
}
// Construtor com dois parâmetros (numerador e denominador):
Racional::Racional(int num, int den)
{
// Se den = 0, então o programa deve assinalar uma falha
// grave! Para já terminar-se-á o programa. Mais tarde
// se aprenderão formas mais elegantes de lidar com estes
// casos:
if(den == 0) {
// É má ideia escrever no ecrã aqui, mas por enquanto
// não conhecemos melhores alternativas:
cerr << "Erro: denominador inválido!" << endl;
exit(1);
} else if(den < 0) {
n = -num;
d = -den;
} else {
n = num;
d = den;
}
// Redução da fracção:
int k = mdc(n, d);
n /= k;
d /= k;
}
#include <iostream>
#include <cstdlib>
using namespace std;// Cálculo do mdc de dois números inteiros estendido de modo a funcionar
// para argumentos negativos ou nulos.
// PC: n = n e m = m
// CO: (n = 0 e m = 0 e n = 1) ou ((m <> 0 ou n <> 0) e n = mdc(|m|, |n|))
int mdc(int m, int n)
{
if(m == 0 && n == 0)
return 1;
if(m < 0)
m = -m;
// Aqui forçosamente m = |m| e m >= 0
if(n < 0)
n = -n;
// Aqui m = |m|, m >= 0, e n = |n|, n >= 0, sendo pelo menos um de m e n
// diferente de zero.
// CI: mdc(m, n) = mdc(|m|, |n|)
while(m != 0) {
int aux = n % m;
n = m;
m = aux;
}
return n;
}class Racional {
private:
int n; // numerador
int d; // denominador
public:
Racional(int num = 0, int den = 1);
void escreve();
Racional soma(Racional b);
bool igual(Racional b);
bool maior(Racional b);
};// Construtor com dois parâmetros (numerador e denominador):
Racional::Racional(int num, int den)
{
if(den == 0) {
cerr << "Erro: denominador inválido!" << endl;
exit(1);
} else if(den < 0) {
n = -num;
d = -den;
} else {
n = num;
d = den;
}
int k = mdc(n, d);
n /= k;
d /= k;
}
// PC:
// CO: aparece no ecrã a representação fraccionária do racional *this, sendo *this
// a instância da classe implícita na execução da função.
void Racional::escreve()
{
cout << n << '/' << d;
}// PC:
// CO: r = *this + b, sendo *this a instância da classe implícita na execução
// da função.
Racional Racional::soma(Racional b)
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
int termo = (n / l) * (b.d / k) + (b.n / l) * (d / k);
int m = mdc(termo, k);
Racional r;
r.n = (termo / m) * l;
r.d = (k / m) * (d / k) * (b.d / k);
return r;
}
// PC:Que, depois de executado, produz no ecrã:
// CO: devolve valor lógico de *this = b, sendo *this a instância da classe implícita
// na execução da função.
bool Racional::igual(Racional b)
{
return n == b.n && d == b.d;
}// PC:
// CO: devolve valor lógico de *this > b, sendo *this a instância da classe implícita
// na execução da função.
bool Racional::maior(Racional b)
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
return (n / l) * (b.d / k) > (b.n / l) * (d / k);
}int main()
{
Racional x = 1, y(7, 15);
Racional z = x.soma(y);
z.escreve();
cout << endl;
if(x.maior(y))
cout << "maior" << endl;
else
cout << "menor" << endl;
if(x.igual(y))
cout << "igual" << endl;
else
cout << "diferente" << endl;
}
22/15
maior
diferente
int main()Se se pudesse escrever o programa como acima, claramente a classe Racional, uma vez equipada com os operadores restantes dos tipos aritméticos, passaria a funcionar para o utilizador como qualquer outro tipo básico do C++.
{
Racional x = 1, y(7, 15);
Racional z = x + y;
z.escreve();
cout << endl;
if(x > y)
cout << "maior" << endl;
else
cout << "menor" << endl;
if(x == y)
cout << "igual" << endl;
else
cout << "diferente" << endl;
}
A solução para este problema passa pela sobrecarga dos operadores do C++ de modo a terem novos significados quando aplicados ao novo tipo Racional, da mesma forma que se tinha visto antes relativamente aos tipos enumerados (ver Secção 5.1). Mas, ao contrário do que se fez então, agora as funções de sobrecarga têm de continuar membros da classe Racional de modo a poderem aceder aos seus membros privados (alternativamente poder-se-iam usar funções membro amigas da classe, ver Secção 1.4). Ou seja, a solução é simplesmente:
#include <iostream>
#include <cstdlib>
using namespace std;// Cálculo do mdc de dois números inteiros estendido de modo a funcionar
// para argumentos negativos ou nulos.
// PC: n = n e m = m
// CO: (n = 0 e m = 0 e n = 1) ou ((m <> 0 ou n <> 0) e n = mdc(|m|, |n|))
int mdc(int m, int n)
{
if(m == 0 && n == 0)
return 1;
if(m < 0)
m = -m;
// Aqui forçosamente m = |m| e m >= 0
if(n < 0)
n = -n;
// Aqui m = |m|, m >= 0, e n = |n|, n >= 0, sendo pelo menos um de m e n
// diferente de zero.
// CI: mdc(m, n) = mdc(|m|, |n|)
while(m != 0) {
int aux = n % m;
n = m;
m = aux;
}
return n;
}class Racional {
private:
int n; // numerador
int d; // denominador
public:
Racional(int num = 0, int den = 1);
void escreve();
Racional operator + (Racional b);
bool operator == (Racional b);
bool operator > (Racional b);
};// Construtor com dois parâmetros (numerador e denominador):
Racional::Racional(int num, int den)
{
if(den == 0) {
cerr << "Erro: denominador inválido!" << endl;
exit(1);
} else if(den < 0) {
n = -num;
d = -den;
} else {
n = num;
d = den;
}
int k = mdc(n, d);
n /= k;
d /= k;
}
// PC:
// CO: aparece no ecrã a representação fraccionária do racional *this, sendo *this
// a instância da classe implícita na execução da função.
void Racional::escreve()
{
cout << n << '/' << d;
}// PC:
// CO: r = *this + b, sendo *this a instância da classe implícita na execução
// da função.
Racional Racional::operator + (Racional b)
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
int termo = (n / l) * (b.d / k) + (b.n / l) * (d / k);
int m = mdc(termo, k);
Racional r;
r.n = (termo / m) * l;
r.d = (k / m) * (d / k) * (b.d / k);
return r;
}
// PC:Note-se que, tal como acontecia com a função membro soma(), a expressão x + y invoca a função membro operator + da classe Racional usando x como instância (variável) implícita! Isto é sempre verdade para sobrecarga de operadores usando funções membro: o primeiro operando (que pode ser o único no caso de operadores unários, i.e., só com um operando) é sempre a instância implícita do método (função membro).
// CO: devolve valor lógico de *this = b, sendo *this a instância da classe implícita
// na execução da função.
bool Racional::operator == (Racional b)
{
return n == b.n && d == b.d;
}// PC:
// CO: devolve valor lógico de *this > b, sendo *this a instância da classe implícita
// na execução da função.
bool Racional::operator > (Racional b)
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
return (n / l) * (b.d / k) > (b.n / l) * (d / k);
}int main()
{
Racional x = 1, y(7, 15);
Racional z = x + y;
z.escreve();
cout << endl;
if(x > y)
cout << "maior" << endl;
else
cout << "menor" << endl;
if(x == y)
cout << "igual" << endl;
else
cout << "diferente" << endl;
}
Racional r;redunda num programa aparentemente funcional mas com um comportamento inesperado. Acontece que a expressão 1/3 é interpretada como a divisão inteira, com resultado zero, que deve ser convertida para um Racional e atribuída à variável r. Logo, r, depois da atribuição, conterá o racional zero!
r = 1/3;
Mas existe uma alternativa elegante, proporcionada pelos construtores das classes, e que funciona quase como se de valores literais se tratasse: os construtores podem ser chamados explicitamente para criar um novo valor dessa classe. Assim, o código anterior deveria ser corrigido para:
Racional r;
r = Racional(1, 3);
Racional x(1, 3);tendo o mesmo significado que
Racional z = x + 1;
Racional x(1, 3);que por sua vez, tendo em conta os valores por omissão dos parâmetros do contrutor dos Racional, significa o mesmo que
Racional z = x + Racional(1);
Racional x(1, 3);que coloca em z o racional (4, 3) (ou 4/3).
Racional z = x + Racional(1, 1);
Em casos em que esta conversão implícita de tipos é indesejável, pode-se preceder o respectivo construtor da palavra chave explicit. Assim, se a classe Racional estivesse definida como
class Racional {o compilador assinalaria erro ao encontrar a expressão x + 1. Neste caso, no entanto, a conversão implícita de int para Racional é útil, pelo que o qualificador explicit é desnecessário.
...
public:
explicit Racional(int num = 0, int den = 1);
...
};
2. Use sobrecarga de operadores C++ para implementar os operadores de atribuição e incrementação e decrementação típicos (i.e., +=, -=, *=, /=, ++, --) para a classe Racional.
3. O cálculo da raiz quadrada de um valor v pode ser feito, duma forma algo ineficiente, procurando a solução da equação f(x) = v, onde f(x) := x2 (em que se usou a notação := com o significado "é definida por"), usando o método de Newton. Este método advoga que se deve construir uma sequência r0, r1, ... de raízes definida, duma forma recorrente, por rn+1 = rn - (rn2 - v) / (2rn). Esta sequência converge para a raiz quadrada (positiva) de v quando n tende para infinito, desde que o valor inicial r0 seja positivo. Escreva uma função Racional raizQuadrada(Racional v) que calcule um valor racional que aproxime a raiz quadrada do racional v. Considere a aproximação razoável quando a diferença entre termos sucessivos da sequência for inferior a 1/100 (ou seja, quando |rn+1 - rn| = |(rn2 - v) / (2rn)| < 1/100).
Se procurar a raiz quadrada de 2 usando o método sugerido, chegará à surpreendente fracção 577/408, que é uma excelente aproximação (577/408 = 1,41421568..., cujo quadrado é 2,000006007...).
4. [difícil] Os inteiros são limitados, pelo que, em máquinas em que os int são representados em complemento para 2 e têm 32 bits, o valor racional mais pequeno (em módulo) representável pela classe Racional tal como desenvolvida é 2-32 (aproximadamente 10-10) e o maior (em módulo) 232 (aproximadamente 1010). Se se pretendesse tornar a classe dos racionais verdadeiramente útil, seria necessário estender a gama dos int consideravelmente. Não sendo isso possível sem mudar de linguagem, compilador, sistema operativo e/ou computador, a solução pode passar por construir uma nova classe Inteiro, à custa da qual a classe Racional pode ser construída, e que represente inteiros de precisão (virtualmente) arbitrária. Construa essa classe, usando sequências de int para representar os inteiros de precisão arbitrária. Pode ter de usar memória dinâmica, a ensinar posteriormente.
Suponha-se que se pretende escrever um programa que leia 10 valores inteiros do teclado e os escreva pela ordem inversa no ecrã. Um pouco de reflexão sobre o problema revela que ele pode ser resolvido com matrizes C++. Mas, antes de aceitar a solução como evidente, convém pensar um pouco no problema.
Ponha-se no lugar do computador e admita que lhe são passadas 10 folhas de papel, com número escritos, e que as deve devolver pela ordem inversa à da entrega. Como organizaria as folhas de papel? A resposta usual a esta pergunta é que os papeis deveriam ser organizados numa pilha (com os números voltados para cima) porque, numa pilha de papeis, o último papel a entrar é o primeiro a sair.
Não há equivalente a uma pilha de papeis na linguagem C++ (embora exista na biblioteca padrão...). Surge então a ideia de construir uma classe para acrescentar esse conceito ao C++. Mas resta uma dúvida: sendo o problema tão simples e facilmente resolúvel usando matrizes directamente, porquê definir uma classe nova?
Mais uma vez não existem respostas taxativas a esta questão. A questão deve ser analisada sob diversos pontos de vista e uma decisão tomada com base nos resultados dessa análise. Por exemplo:
Note-se que, ao contrário das operações mais usuais com inteiros, como a soma, subtracção, etc., as operações de inserção e remoção de valores duma pilha afectam a própria pilha. Seria possível definir as operações de tal modo que a inserção de um valor numa pilha resultasse numa nova pilha, idêntica à original mais com um valor adicional. As vantagens práticas dum tal conceito são poucas, no entanto.
class PilhaInt {
....
public:
PilhaInt(); // construtor da classe.
void põe(int v); // coloca v no topo da pilha.
void tira(); // retira valor do topo da pilha.
int topo(); // devolve valor no topo da pilha.
bool vazia(); // devolve true sse a pilha estiver vazia.
bool cheia(); // devolve true sse a pilha estiver cheia.
int tamanho(); // devolve número de valores na pilha.
};
Como é óbvio, sendo a construção de novos tipos um processo incremental de acrescento de funcionalidade à linguagem C++, é evidente que, para guardar os valores inteiros na pilha, ter-se-á de recorrer a conceitos previamente definidos (alternativamente poder-se-ia construir outro novo tipo, embora correndo o risco de entrar num processo recursivo de definição de novos tipos...). Neste caso pretende-se de algum modo guardar um conjunto de valores todos do mesmo tipo (int). A forma mais simples de o fazer, em C++, é usando o conceito de matriz. Assim, os valores guardados na pilha vão, na realidade, ser guardados numa matriz. Mas as matrizes em C++ têm de ter um tamanho constante. O tamanho escolhido para a matriz é importante porque corresponde ao limite de valores que as pilhas suportarão. Para que se possa facilmente alterar o tamanho limite das pilhas, definir-se-á uma constante para representar esse limite.
Assim,
const int limite = 100;Mas é também necessário saber em cada instante quantos elementos estão na pilha e em que posição. Usando a simples convenção de que o primeiro elemento a entrar na pilha (e o último a sair) é o que está na posição 0 da matriz, encontrando-se todos os outros nos índices subsequentes, é óbvio que uma única variável indica não só o número de elementos na pilha como a posição do próximo elemento a inserir na pilha. Assim:class PilhaInt {
int valores[limite];
public:
PilhaInt(); // construtor da classe.
void põe(int v); // coloca v no topo da pilha.
void tira(); // retira valor do topo da pilha.
int topo(); // devolve valor no topo da pilha.
bool vazia(); // devolve true sse a pilha estiver vazia.
bool cheia(); // devolve true sse a pilha estiver cheia.
int tamanho(); // devolve número de valores na pilha.
};
const int limite = 100;Por exemplo, se quantos tiver valor 4, isso significa que existem 4 elementos na pilha, nas posições 0 a 3 da matriz, e que o próximo valor a colocar ficará na posição 4 da matriz.class PilhaInt {
int valores[limite];
int quantos;
public:
PilhaInt(); // construtor da classe.
void põe(int v); // coloca v no topo da pilha.
void tira(); // retira valor do topo da pilha.
int topo(); // devolve valor no topo da pilha.
bool vazia(); // devolve true sse a pilha estiver vazia.
bool cheia(); // devolve true sse a pilha estiver cheia.
int tamanho(); // devolve número de valores na pilha.
};
É agora possível definir cada uma das funções e procedimentos membro da classe. Como é usual, começa-se pelos construtores (neste caso apenas existe um). O construtor limite-se a colocar quantos a zero, o que é o mesmo que dizer que as pilhas, quando criadas, estão vazias:
PilhaInt::PilhaInt()Note-se que não é necessário inicializar os valores na matriz. Basta que se convencione que, dos limite elementos da matriz, apenas os quantos primeiros são valores colocados na pilha, contendo os restantes limite - quantos valores que são irrelevantes (vulgo lixo).
{
quantos = 0;
}
Em seguida definem-se as funções membro vazia() e cheia(), por exemplo. Como é óbvio, uma pilha está vazia sse quantos = 0 e está cheira sse quantos = limite. Assim:
bool PilhaInt::vazia()A função membro tamanho() é trivial:
{
return quantos == 0;
}bool PilhaInt::cheia()
{
return quantos == limite;
}
int PilhaInt::tamanho()As restantes funções e procedimentos nem sempre estão bem definidas. Que fazer quando o procedimento membro tira() é invocado para uma pilha vazia? É claramente um erro, pelo que, e não se tendo ainda aprendido melhores mecanismos para lidar com este tipo de situação, o melhor é abortar imediatamente o programa. Assim:
{
return quantos;
}
void PilhaInt::põe(int v)É de notar a utilização típica do operador ++ no procedimento membro poe(). O código é equivalente a
{
if(cheia()) {
cerr << "Erro: pilha cheia." << endl;
exit(1);
}
valores[quantos++] = v;
}void PilhaInt::tira()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
quantos--;
}int PilhaInt::topo()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
return valores[quantos - 1];
}
void PilhaInt::põe(int v)mas apenas porque se usou o operador ++ sufixo! A utilização do operador ++ prefixo teria um significado diferente e conduziria a um erro, pois
{
if(cheia()) {
...
}
valores[quantos] = v;
quantos = quantos + 1;
}
valores[++quantos] = v;é o mesmo que
quantos = quantos + 1;
valores[quantos] = v;
class PilhaInt {A razão para a proibição é simples. Sendo limite uma constante membro (de instância), cada instância da classe possuirá a sua cópia privada dessa constante. Mas isso significa que, para ser verdadeiramente útil, essa constante deverá poder tomar valores diferentes para cada instância da classe, i.e., para cada variável dessa classe criada. Daí que não se possam inicializar membros constantes (de instância) na própria declaração. Como, por definição de constante em C++, não é possível atribuir um valor (através do operador =), o C++ proporciona uma forma algo estranha de inicializar membros constantes: colocando os inicializadores após o cabeçalho do construtor (separados por vírgulas e após um :). Por exemplo:
const int limite = 100;// erro!
int valores[limite];
....
}
class Aluno {Este tipo de inicializadores é extremamente útil, sendo utilizado para inicializar não só membros constantes como também membros referência e membros de classes sem construtores por omissão (i.e., que exijam uma inicialização explícita).
const int número;
int nota;
...
public:
Aluno(int n);
};Aluno::Aluno(int n) : número(n) {
...
}
#include <iostream>
using namespace std;const int limite = 100;
class PilhaInt {
int valores[limite];
int quantos;
public:
PilhaInt(); // construtor da classe.
void põe(int v); // coloca v no topo da pilha.
void tira(); // retira valor do topo da pilha.
int topo(); // devolve valor no topo da pilha.
bool vazia(); // devolve true sse a pilha estiver vazia.
bool cheia(); // devolve true sse a pilha estiver cheia.
int tamanho(); // devolve número de valores na pilha.
};
PilhaInt::PilhaInt()
{
quantos = 0;
}
bool PilhaInt::vazia()
{
return quantos == 0;
}bool PilhaInt::cheia()
{
return quantos == limite;
}
int PilhaInt::tamanho()
{
return quantos;
}
void PilhaInt::põe(int v)
{
if(cheia()) {
cerr << "Erro: pilha cheia." << endl;
exit(1);
}
valores[quantos++] = v;
}void PilhaInt::tira()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
quantos--;
}int PilhaInt::topo()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
return valores[quantos - 1];
}int main()
{
PilhaInt pilha;// Coloca 10 valores na pilha:
for(int i = 0; i != 10; i++) {
int v;
cin >> v;
pilha.põe(v);
}// Escreve-os pela ordem inversa:
while(!pilha.vazia()) {
cout << pilha.topo() << endl;
pilha.tira();
}
}
Para definir uma função ou procedimento membro como inline, podem-se fazer uma de duas coisas:
class PilhaInt {ou, alternativamente,
...
int tamanho() {
return quantos;
}
...
}
class PilhaInt {Em geral a segunda alternativa é preferível à primeira, pois torna mais evidente a separação entre a interface e a implementação da classe.
...
int tamanho();
...
}
...
inline int PilhaInt::tamanho()
{
return quantos;
}
Note-se que não só as funções ou procedimentos membro duma classe podem ser inline: qualquer função ou procedimento pode ser definido como inline, bastando para isso usar o qualificador inline. Note-se ainda que a definição de uma função ou procedimento como inline não altera a semântica da sua chamada, tendo apenas consequências em termos da tradução, pelo compilador, para código máquina.
#include <iostream>
using namespace std;const int limite = 100;
class PilhaInt {
int valores[limite];
int quantos;
public:
PilhaInt(); // construtor da classe.
void põe(int v); // coloca v no topo da pilha.
void tira(); // retira valor do topo da pilha.
int topo(); // devolve valor no topo da pilha.
bool vazia(); // devolve true sse a pilha estiver vazia.
bool cheia(); // devolve true sse a pilha estiver cheia.
int tamanho(); // devolve número de valores na pilha.
};
inline PilhaInt::PilhaInt()
{
quantos = 0;
}
inline bool PilhaInt::vazia()
{
return quantos == 0;
}inline bool PilhaInt::cheia()
{
return quantos == limite;
}
inline int PilhaInt::tamanho()
{
return quantos;
}inline void PilhaInt::põe(int v)
{
if(cheia()) {
cerr << "Erro: pilha cheia." << endl;
exit(1);
}
valores[quantos++] = v;
}
inline void PilhaInt::tira()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
quantos--;
}inline int PilhaInt::topo()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
return valores[quantos - 1];
}int main()
{
PilhaInt pilha;// Coloca 10 valores na pilha:
for(int i = 0; i != 10; i++) {
int v;
cin >> v;
pilha.põe(v);
}// Escreve-os pela ordem inversa:
while(!pilha.vazia()) {
cout << pilha.topo() << endl;
pilha.tira();
}
}
const Racional um = 1;e de facto é-o! Podem-se definir constantes de qualquer tipo definido pelo utilizador. O único problema está na sua utilização. Por exemplo:
const Racional zero = 0;
Racional z = um + zero;gera um erro de compilação. Isto deve-se a que o compilador não pode adivinhar que a função membro operator +() da classe Racional não altera a instância que é passada implicitamente por referência, neste caso uma instância constante. Como pode haver a possibilidade de a constante um ser alterada, o que é um contra-senso, o compilador simplesmente proíbe a expressão. Relativamente à constante zero, sendo passada por valor para a função operator +(), o compilador não se queixa.
Para obviar a este comportamento, que impede a soma de uma constante com qualquer outro Racional, é necessário indicar ao compilador que a função membro operator +() é bem comportada, i.e., não altera a instância passada por referência implicitamente. Para isso coloca-se o qualificador const após quer a declaração quer a definição da função membro:
class Racional {Claro está que, se dentro duma função ou procedimento membro declarada como const se tentar alterar alguma variável membro, o compilador gerará uma mensagem de erro.
...
public:
Racional operator + (Racional b) const;
...
};
...
Racional Racional::operator + (Racional b) const
{
...
}
É uma boa regra declarar como const todas as funções e procedimentos membro que não efectem a instância implícita. Assim, apresentam-se abaixo versões melhoradas dos programas desenvolvidos:
#include <iostream>
#include <cstdlib>
using namespace std;// Cálculo do mdc de dois números inteiros estendido de modo a funcionar
// para argumentos negativos ou nulos.
// PC: n = n e m = m
// CO: (n = 0 e m = 0 e n = 1) ou ((m <> 0 ou n <> 0) e n = mdc(|m|, |n|))
int mdc(int m, int n)
{
if(m == 0 && n == 0)
return 1;
if(m < 0)
m = -m;
// Aqui forçosamente m = |m| e m >= 0
if(n < 0)
n = -n;
// Aqui m = |m|, m >= 0, e n = |n|, n >= 0, sendo pelo menos um de m e n
// diferente de zero.
// CI: mdc(m, n) = mdc(|m|, |n|)
while(m != 0) {
int aux = n % m;
n = m;
m = aux;
}
return n;
}class Racional {
private:
int n; // numerador
int d; // denominador
public:
Racional(int num = 0, int den = 1);
void escreve() const;
Racional operator + (Racional b) const;
bool operator == (Racional b) const;
bool operator > (Racional b) const;
};// Construtor com dois parâmetros (numerador e denominador):
Racional::Racional(int num, int den)
{
if(den == 0) {
cerr << "Erro: denominador inválido!" << endl;
exit(1);
} else if(den < 0) {
n = -num;
d = -den;
} else {
n = num;
d = den;
}
int k = mdc(n, d);
n /= k;
d /= k;
}
// PC:
// CO: aparece no ecrã a representação fraccionária do racional *this, sendo *this
// a instância da classe implícita na execução da função.
inline void Racional::escreve() const
{
cout << n << '/' << d;
}// PC:
// CO: r = *this + b, sendo *this a instância da classe implícita na execução
// da função.
Racional Racional::operator + (Racional b) const
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
int termo = (n / l) * (b.d / k) + (b.n / l) * (d / k);
int m = mdc(termo, k);
Racional r;
r.n = (termo / m) * l;
r.d = (k / m) * (d / k) * (b.d / k);
return r;
}
// PC:
// CO: devolve valor lógico de *this = b, sendo *this a instância da classe implícita
// na execução da função.
inline bool Racional::operator == (Racional b) const
{
return n == b.n && d == b.d;
}// PC:
// CO: devolve valor lógico de *this > b, sendo *this a instância da classe implícita
// na execução da função.
bool Racional::operator > (Racional b) const
{
int l = mdc(n, b.n);
int k = mdc(d, b.d);
return (n / l) * (b.d / k) > (b.n / l) * (d / k);
}int main()
{
Racional x = 1, y(7, 15);
Racional z = x + y;
z.escreve();
cout << endl;
if(x > y)
cout << "maior" << endl;
else
cout << "menor" << endl;
if(x == y)
cout << "igual" << endl;
else
cout << "diferente" << endl;
}
#include <iostream>
using namespace std;const int limite = 100;
class PilhaInt {
int valores[limite];
int quantos;
public:
PilhaInt(); // construtor da classe.
void põe(int v); // coloca v no topo da pilha.
void tira(); // retira valor do topo da pilha.
int topo() const; // devolve valor no topo da pilha.
bool vazia() const; // devolve true sse a pilha estiver vazia.
bool cheia() const; // devolve true sse a pilha estiver cheia.
int tamanho() const; // devolve número de valores na pilha.
};
inline PilhaInt::PilhaInt()
{
quantos = 0;
}
inline bool PilhaInt::vazia() const
{
return quantos == 0;
}inline bool PilhaInt::cheia() const
{
return quantos == limite;
}
inline int PilhaInt::tamanho() const
{
return quantos;
}
inline void PilhaInt::põe(int v)
{
if(cheia()) {
cerr << "Erro: pilha cheia." << endl;
exit(1);
}
valores[quantos++] = v;
}inline void PilhaInt::tira()
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
quantos--;
}inline int PilhaInt::topo() const
{
if(vazia()) {
cerr << "Erro: pilha vazia." << endl;
exit(1);
}
return valores[quantos - 1];
}int main()
{
PilhaInt pilha;// Coloca 10 valores na pilha:
for(int i = 0; i != 10; i++) {
int v;
cin >> v;
pilha.põe(v);
}// Escreve-os pela ordem inversa:
while(!pilha.vazia()) {
cout << pilha.topo() << endl;
pilha.tira();
}
}