Ir para conteúdo
Faça parte da equipe! (2024) ×
Conheça nossa Beta Zone! Novas áreas a caminho! ×
  • Quem está por aqui   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.

Entendendo C++ - Introdução


iMelo~'
 Compartilhar

Posts Recomendados

Entendendo C++ - Introdução

Para muitos a transição de C para C++ não é fácil. De fato, essa transição é freqüentemente acompanhada de muita ansiedade porque C++ é popularmente envolto em uma aura de inacessibilidade. Por exemplo, você pode pegar um livro sobre C++, abri-lo em uma página qualquer e deparar-se com um parágrafo mais ou menos assim:Entendendo C++ Introdução

 

Do ponto de vista de projeto, derivação privada é equivalente a compartimentação, exceto pela, ocasionalmente importante, questão de sobreposição. Um uso importante disso é a técnica de derivar uma classe publicamente a partir de uma classe base abstrata, definindo uma interface, e privativamente de uma classe concreta provendo uma implementação. Porque a herança implícita na derivação privada é um detalhe de implementação que não é refletido no tipo da classe derivada, ela é algumas vezes denominada "herança de implementação" e contrasta com a declaração pública, onde a interface da classe base é herdada e a conversão implícita para o tipo da classe é permitida. O restante é algumas vezes referido como uma a sub-tipificação ou "herança de interface". (Trecho extraído de "The C++ Programming Language, second edition, by Bjarne Stroustrup, page 413)

É realmente difícil iniciar-se em C++ com uma literatura assim tão rebuscada, tão hermética.

Essa série de tutoriais respondem a três questões bastante comuns:


  • [*=center]Porque C++ existe e quais são suas vantagens sobre o C?
    [*=center]Que recursos estão disponíveis no C++ para materializar idéias orientadas a objeto?
    [*=center]Como você projeta e implementa código usando os princípios da orientação a objeto?

Uma vez que você tenha compreendido os recursos básicos disponíveis no C++, e saiba como e porque usá-los, você se tornará um programador em C++. Essas série de tutoriais vão iniciá-lo nessa direção, e tornar outros textos sobre C++, inclusive os de Stroustrup, mais fáceis de entender.

Esses tutoriais presumem que você conhece a linguagem C. Se esse não é o seu caso, gaste uma semana ou duas aprendendo C e então retorne a esses tutoriais. C++ é um superset do C, portanto quase tudo que você souber sobre C vai encontrar conexão nessa nova linguagem.

Porque C++ existe?

As pessoas que são novatas em C++, ou aqueles que lêem livros sobre C++, geralmente tem duas perguntas:


  • [*=center]"Tudo o que leio tem um vocabulário maluco: encapsulamento, herança, funções virtuais, classes, sobrecarga, amigos... De onde vem tudo isso?
    [*=center]Essa linguagem - e programação orientada a objeto de um modo geral - obviamente implicam em uma mudança de mentalidade, então como eu faço para aprender a pensar em modo C++?

Ambas essas questões podem ser respondidas, e o projeto do C++ como um todo é facilmente inteligível, se você souber o que os projetistas do C++ pretendiam atingir quando criaram essa linguagem. Se você entender porque os projetistas fizeram as opções que fizeram, e porque introduziram certos recursos específicos na linguagem, então será muito mais fácil compreender a linguagem integralmente.

O projeto de linguagens de programação é um processo evolucionário. Uma nova linguagem é criada a partir de lições aprendidas com linguagens antigas, ou na tentativa de introduzir novos recursos e facilidades a uma linguagem existente. Por exemplo, a linguagem Ada foi projetada originalmente para resolver um problema aflitivo enfrentado pelo Pentágono. Os programadores, escrevendo código para diferentes sistemas de defesa militar, tinham usado centenas de linguagens de programação diferentes, o que tornaria, de fato, impossível manter ou aprimorar esses sistemas no futuro. Ada tenta resolver alguns desses problemas combinando os bons recursos de várias linguagens em uma única linguagem de programação.

Um outro bom exemplo é o processo evolucionário que ocorreu com as linguagens de programação a partir do desenvolvimento das linguagens estruturadas. Essas linguagens vieram em resposta a um grande problema não previsto pelos projetistas das linguagens de programação mais antigas: o uso abusivo do comando goto em programas muito grandes. Em um programa pequeno o comando goto não causa maiores problemas. Mas em um programa muito grande, especialmente quando desenvolvido por alguém viciado no comando goto, os problemas tornam-se terríveis. O código torna-se absolutamente incompreensível por um outro programador que tente lê-lo pela primeira vez. As linguagens de programação evoluíram para resolver esse problema, eliminando o comando goto inteiramente, e tornando simples subdividir um programa muito grande em módulos ou funções pequenas, compreensíveis e manejáveis.

C++ é uma linguagem orientada a objeto. Programação orientada a objeto é uma reação a problemas que foram percebidos pela primeira vez em programas muito grandes desenvolvidos na década de 70. Todas as linguagens orientadas a objeto tentam atingir três objetivos, como uma forma de impedir que ocorram os problemas inerentes a projetos muito grandes:

Todas as linguagens de programação orientadas a objeto implementam data abstraction de uma forma clara usando um conceito denominado classes. Vamos examinar data abstraction em maiores detalhes mais adiante, até porque esse é um conceito central, muito importante, em C++. Em poucas palavras, data abstraction é um modo de combinar dados e as funções usadas para manipulá-los, de tal forma que os detalhes da implementação fiquem ocultos para outros programadores. Data abstraction possibilita o desenvolvimento de programas mais fáceis de manter e de aprimorar. Todas as linguagens orientadas a objeto tentam tornar facilmente reutilizáveis e extensíveis cada uma das partes que compõem os programas. Aqui é que o termo objeto começa a fazer sentido. Os programas são quebrados, subdivididos, em objetos reutilizáveis. Esse objetos podem ser agrupados de diferentes maneiras para formar novos programas. Objetos existentes podem ainda ser estendidos. Dando aos programadores um modo muito simples de reutilizar código, e virtualmente forçando os programadores a escrever códigos para serem reutilizados, torna-se muito mais fácil desenvolver novos programas remontando peças já existentes.

Linguagens orientadas a objeto visam tornar um código existente facilmente modificável sem, na realidade, alterar fisicamente o código. Esse é um conceito único e muito poderoso, porque a primeira vista parece não ser possível modificar alguma coisa sem de fato alterá-la. Entretanto, é plenamente possível fazer isso usando dois novos conceitos: herança e polimorfismo. O objeto existente permanece o mesmo, e as alterações são como que assentadas sobre ele. A habilidade dos programadores para manter e aprimorar código de um modo livre de erros é drasticamente melhorada usando essa abordagem.

Como C++ é uma linguagem orientada a objeto, ela contém os três benefícios da orientação a objeto recém apresentados. C++ acrescenta ainda a tudo isso duas outras importantes melhorias, para eliminar problemas existentes na linguagem C original, e para tornar a programação em C++ mais fácil que em C.

C++ acrescenta um conceito denominado sobrecarga de operador. Esse recurso permite que você especifique em seus programas novos modos de uso para operadores padrões, tais como + e >>. Por exemplo, se você quiser adicionar um novo tipo de dado, como um tipo número complexo em um programa em C, tal implementação não será simples. Para somar dois números complexos, você terá que criar uma função denominada, por exemplo, soma, e então escrever c3=soma(c1,c2);, onde c1, c2 e c3 são valores do novo tipo número complexo. Em C++ você pode, ao invés de criar uma nova função, sobrecarregar os operadores + e =, de tal forma que você pode escrever c3=c1+c2. Dessa maneira, novos tipos de dados podem ser adicionados à linguagem de uma maneira clara, simples, sem ajeitamentos. O conceito de sobrecarga aplica-se a todas as funções criadas em C++.

C++ ainda simplifica a implementação de várias partes da linguagem C, principalmente no que se refere a operações de I/O e alocação de memória. Essas novas implementações foram criadas contemplando-se a sobrecarga de operadores, de tal modo que tornou-se fácil adicionar novos tipos de dados e providenciar operações de I/O e alocação de memória para os novos tipos, sem truques ou artifícios.

Vamos examinar alguns problemas que você provavelmente enfrentou usando a linguagem C, e então ver como eles são resolvidos pelo C++

O primeiro problema pode ser visto em toda biblioteca de programas construída em C. O problema é demonstrado no código a seguir, o qual atribui um valor a um string e então concatena esse valor a um outro string:

char s[100];

strcpy(s, "hello ");

strcat(s, "world");

Essa codificação não é muito bonita, mas o seu formato ilustra muito tipicamente o que se encontra em bibliotecas criadas em C. O tipo string é construído a partir do tipo matriz-de-caracteres, que é nativo em C. Devido ao fato de que o novo tipo, string, não é parte integrante da linguagem original, o programador é forçado a usar chamadas de funções para fazer qualquer operação com o novo tipo de dado. O desejável é que, ao invés disso, se possa criar novos tipos de dados e lidar com eles naturalmente, com os próprios recursos da linguagem. Alguma coisa como

string s;

 

s = "hello ";

s += "world";

Se algo assim é possível, a linguagem pode ser estendida ilimitadamente. C++ suporta esse tipo de extensão através da sobrecarga de operadores e de classes. Repare ainda que usando um novo tipo string, a implementação tornou-se completamente oculta. Ou seja, você não precisa saber como, ou se ,o tipo string foi criado usando uma matriz de caracteres, uma lista ligada, etc. ou ainda se string tem um tamanho máximo. Melhor ainda, é fácil alterar-se, no futuro, a implementação do tipo string, sem afetar negativamente os códigos que estiverem utilizando-o

Outro exemplo usando uma biblioteca pode ser visto na implementação de uma biblioteca para tratamento de pilhas.

Os protótipos das funções para uma típica biblioteca de tratamento de pilhas - normalmente encontrado no header file - é mostrado a seguir:

void stack_init(stack s, int max_size);

int stack_push(stack s, int value);

int stack_pop(stack s, int *value);

void stack_clear(stack s);

void stack_destroy(stack s);

O programador usuário dessa biblioteca pode usar funções para push, pop e clear a pilha, mas antes, e para que qualquer uma dessas operações seja válida, de inicializar a pilha com a função stack_init. Ao concluir com a utilização da pilha, deve destruí-la com a função stack_destroy. E o que acontece se você se esquece da inicialização ou da destruição? Em um caso real, o código não vai funcionar e pode ser bem difícil rastrear o problema, a menos que todas as rotinas dessa biblioteca detectem a falta da inicialização e indiquem isso especificamente. A omissão da etapa de destruição da pilha pode causar um problema denominado

É necessário se cadastrar para acessar o conteúdo.
, que é também difícil de rastrear. C++ resolve esse problema usando construtores e destrutores, que automaticamente manejam a inicialização e a destruição de objetos, inclusive de pilhas.

Continuando ainda com o exemplo da pilha, note que a pilha, uma vez definida, pode push e pop números inteiros. O que acontece se você quer criar uma outra pilha para lidar com números reais, ou uma outra ainda para caracteres? Você terá que criar três bibliotecas separadas, ou, alternativamente, usar uma union e deixar a union manejar todos os tipos de dados possíveis. Em C++, um conceito denominado modelo permite que você crie uma única biblioteca para tratar a pilha e redefina os tipos de dados a serem armazenados na pilha quando esta for declarada.

Um outro problema que você já deve ter tido programando em C envolve a alteração de bibliotecas. Digamos que você esteja usando a função printf definida na biblioteca stdio, mas você quer modificá-la para manejar um novo tipo de dado que você criou em seu programa. Exemplificando, você deseja modificar printf para que ela imprima números complexos. Você não tem como fazer isso, a menos que você tenha o código fonte da implementação de printf. Mas ainda que você tenha o código fonte de printf, essa pode ser uma péssima estratégia porque você pode gerar um código não portável. Não há realmente um modo de extender facilmente uma biblioteca C uma vez que ela tenha sido compilada. Para resolver o problema de impressão de números complexos em C, como no nosso exemplo, você teria que criar uma nova função com a mesma finalidade que printf. Se você tiver vários novos tipos de dados, você terá que criar várias e diferentes novas funções de saída, à semelhança de printf. C++ lida com todos esses problemas com uma nova técnica para saída padrão. Uma combinação de sobrecarga de operador e classes permite integrar novos tipos de dados ao esquema padrão de I/O do C++.

Ainda pensando sobre a função printf, reflita sobre seu projeto e pergunte-se a si mesmo: Esse é um bom modo de se projetar um código? Dentro do código de printf há um comando switch, ou uma cadeia de if-else-if que avalia um string de formatação da saída. Um %d é usado para números decimais, um %c é usado para caracteres, um %s é usado para strings, e assim por diante. Há, no mínimo, três problemas com essa implementação:

O programador da implementação de printf tem que manter o comando switch, ou cadeia de if-else-if, e modificá-lo para cada novo tipo de formatação que se quiser implementar. Modificações sempre significam a possibilidade de se introduzir novos bugs.

Não há qualquer garantia de que o programador usuário de printf vai combinar corretamente o tipo do dado com string de formatação, o que significa que a função contém um risco de falha.

Essa implementação não é extensível, a menos que você possua o código fonte, você não pode ampliar as capacidades de printf.

C++ resolve esses problemas completamente porque força o programador a estruturar o código de uma nova maneira. O comando switch é ocultado e manejado automaticamente pelo compilador através da sobrecarga de função. Torna-se assim impossível combinar erradamente os parâmetros ao invocar uma função, primeiro porque eles não são implementados como parâmetros em C++, e segundo porque o tipo da variável controla automaticamente o mecanismo de switch que é implementado pelo compilador.

C++ resolve ainda vários outros problemas. Por exemplo, resolve o problema de código comum replicado em vários lugares, permitindo que você controle código comum em uma terceira dimensão. Resolve o problema eu quero alterar o tipo de dado passado para uma função sem alterar a função, permitindo que você sobrecarregue o mesmo nome de função com múltiplas listas de parâmetros. Resolve o problema eu quero fazer uma pequena alteração no modo como isso funciona, mas eu não tenho o código fonte, e ao mesmo tempo resolve o problema eu quero reformular essa função inteiramente, sem alterar o restante da biblioteca usando o conceito de herança.

C++ torna a criação de bibliotecas mais simples e melhora drasticamente a tarefa de manutenção do código. E muito mais.

Você precisará mudar um pouco o seu modo de pensar em programação para obter as vantagens de todos esses recursos poderosos, e isso significa que você vai ter que dedicar-se um pouco mais ao projeto de seu código. Sem isso, você perderá vários dos benefícios do C++. Como em tudo na vida, a migração para C++ significa custos e benefícios, mas nesse caso o conjunto dos benefícios supera em muito os custos.

 

Entendendo C++ Extensões ao C

 

[TABLE=width: 500]

[TR]

[/TR]

[TR]

[TD=align: center]2.0 - Introdução

[/TD]

[/TR]

[TR]

[TD=align: left]


[/TD]

[/TR]

[TR]

[TD=align: left]

Tudo o que você já escreveu em C funciona em C++. No entanto, em muitos casos C++ oferece um modo melhor de se realizar o mesmo trabalho. Em outros casos C++ oferece mais de uma maneira de se fazer a mesma coisa, e isso lhe dá maior flexibilidade no desenvolvimento de programas. Nessa seção vamos examinas as extensões do C++ em relação ao C. Muitas dessas extensões não foram adicionadas gratuitamente, mas sim para viabilizar programação orientada a objeto, conforme se verá mais adiante em outros tutoriais dessa série.

 

Esse tutorial em especial apresenta muitos detalhes do C++. Nada de pânico. Se preferir apenas percorra o texto e volte mais tarde estudando cada seção, na medida de sua necessidade. Os conceitos apresentados a seguir foram todos reunidos aqui para simplificação de referência, já que serão usados em vários outros pontos dessa série de tutoriais.

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.1 - Comentários

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]C++ suporta o antigo modo multi-linha de comentário, bem como a nova forma linha-única representado pelo símbolo //. Por exemplo:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]// get_it function reads in input values

void get_it()

{

// do something.

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]O C++ ignora tudo o que estiver escrito após o // e até o final da linha. Você pode usar ambas as formas de comentário em um programa C++.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

É necessário se cadastrar para acessar o conteúdo.
[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.2 - Conversão de tipos de dados[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]Em C você pode determinar a conversão de tipos de dados, colocando o nome do tipo entre parêntesis imediatamente antes do nome da variável, como no exemplo seguinte:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int i;

float f;

 

f = (float) i;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]C++ suporta um segundo modo de conversão de tipos, que faz com que a especificação de conversão se pareça mais com a chamada de uma função:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int i;

float f;

 

f = float(i);[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Vamos ver mais adiante, quando começarmos a tratar de classes, que há uma razão para esse novo formato.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.3 - Input/Output

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]2.3.1 - I/O em Terminal[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Uma das mais óbvias diferenças entre C e C++ é a substituição da biblioteca stdio do C pela iostream do C++. A biblioteca iostream beneficia-se de um grande número de extensões do C++ voltadas a orientação a objeto, conforme veremos mais adiante. Mais ainda, torna mais fácil a adição de novos tipos de dados definidos pelo programador. A biblioteca iostream contém todas as capacidades encontradas na stdio, mas as viabiliza de uma nova forma. Portanto, é importante conhecer como usar as funções básicas da iostream se você estiver convertendo seu código de C para C++. O uso de iostream para as funções básicas de input/output é de compreensão quase imediata. Veja os seguintes exemplos:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << "hello\n";[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]ou o equivalente:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << "hello" << endl;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

Ambos os exemplos produzem a mesma saída, e fazem com que a palavra hello seguida de um newline seja escrita na unidade padrão de saída. A palavra cout indica stdout como destinação para a operação de saída, e o operador << (que significa inserção) é usado para transferir os itens de dados. Há duas outras saídas-padrão pré-definidas: cerr para notificações imediatas de erro, e clog para acumulação de notificações de erro.

Pode-se escrever em qualquer saída padrão usando-se as técnicas mostradas no exemplo anterior. Múltiplos itens de dados podem ser enfileirados em uma única linha de comando, ou empilhados em várias linhas. Por exemplo:

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int i = 2;

float f = 3.14

char c = 'A';

char *s = "hello";

 

cout << s << c << f << i << endl;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

produz a saída:

helloA3.142

da mesma forma que

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << s << c;

cout << f;

cout i << endl;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]O mecanismo cout compreende valores de endereços de memória automaticamente, e os formata para apresentação em hexadecimal. Por exemplo, se i é um inteiro, então o comando[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << &i << endl;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Há casos, no entanto, em que essa regra de conversão para hexadecimal não se aplica. Imprimir s, onde s é um pointer para um caracter, produz a impressão do string apontado por s em lugar do endereço contido em s. Para remediar essa situação, converta s para um pointer void, como mostrado a seguir, se o que você quer é a impressão do endereço contido de s:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << (void *) s;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Agora o endereço contido em s será apresentado em formato hexadecimal. Se você quiser o endereço apresentado em formato decimal, converta-o para um inteiro longo:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << long(& i);[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Essa linha imprime o endereço de i em formato decimal.

 

Da mesma forma, uma conversão para int é usada para imprimir o valor inteiro de um caracter:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << int('A'); // produces 65 as output[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Você pode estranhar que o operador << - conhecido em C como shift left operator - tenha sido roubado para manejar operações de saída em C++. Se você quiser usá-lo para operação de shift left dentro de uma linha de saída, use-o entre parêntesis:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << (2 << 4); // produces 32 as output[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Você pode usar várias técnicas para formatar a saída. Informações podem ser espaçadas acrescentando-se espaços, tabs ou strings literais, como mostrado a seguir:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int i = 2;

float f = 3.14

char c = 'A';

char *s = "hello";

 

cout << s << " " << c << "\t" << f

<< "\t" << i << endl;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Há ainda vários outros manipuladores que podem ser inseridos em um fluxo de saída (em alguns sistemas você precisará incluir iomanip.h para poder usá-las): [/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: left]

[TABLE=width: 100%]

[TR]

[TD=width: 30%, align: center]dec [/TD]

[TD=width: 70%, align: center]Usa base decimal [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]oct[/TD]

[TD=width: 70%, align: center]Usa base octal [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]hex[/TD]

[TD=width: 70%, align: center]Usa base hexadecimal [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]endl[/TD]

[TD=width: 70%, align: center]Finaliza a linha [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]ends[/TD]

[TD=width: 70%, align: center]Finaliza o string ('\0') [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]flush[/TD]

[TD=width: 70%, align: center]Descarrega o buffer de saída [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]setw(w)[/TD]

[TD=width: 70%, align: center]Estabelece a largura da saída para o valor de w

(0 é o default) [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]setfill©[/TD]

[TD=width: 70%, align: center]Estabelece o caracter de preenchimento para conteúdo de c (blank é o default) [/TD]

[/TR]

[TR]

[TD=width: 30%, align: center]setprecision(p)[/TD]

[TD=width: 70%, align: center]Estabelece a precisão flutuante para o valor de p [/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]O código[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]cout << "[" << setw (6) << setfill('*') << 192;

cout << "]" << endl;

cout << hex << "[" << setw (6);

cout << setfill('*') << 192 << "]" << endl;

cout << setprecision(4) << 3.14159 << endl;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]produz

 

[***192]

[****c0]

3.142[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

A saída de números em ponto flutuante pode truncar ou não zeros a direita, independente de como você estabeleça a precisão. Essa é uma característica determinada pelo compilador que você estiver usando.

Você deve ter notado nos exemplos recém apresentados que não se deve usar certos nomes para variáveis ou funções para não se perder a possibilidade de usar os manipuladores intrínsecos à biblioteca iostream.

As operações de entrada são manejadas de uma maneira semelhante às operações de saída, usando-se cin para fluxo de entrada e >> como operador de extração. Por exemplo, o comando

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int i,j,k;

cin >> i >> j >> k;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]O fluxo de entrada cin automaticamente divide o string de entrada em palavras e termina quando recebe EOF.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]2.3.2 - I/O em arquivos[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Input e output para arquivos texto são manejados pela inclusão do arquivo fstream.h, e pela declaração de variáveis do tipo ifstream e ofstream respectivamente. Por exemplo, o seguinte programa lê um arquivo denominado xxx e escreve em um arquivo denominado yyy:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]#include <iostream.h>

#include <fstream.h>

 

void main()

{

char c;

ifstream infile("xxx");

ofstream outfile("yyy");

 

if (outfile && infile) // They will be 0 on err.

while (infile >> c)

outfile << c;

}[/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]As variáveis infile e outfile recebem o nome do arquivo na inicialização e são usadas exatamente como cin e cout. O código desse exemplo não funciona como seria de esperar porque brancos, tabs e caracteres \0 ao final de cada linha são ignorados, do mesmo modo que espaço em branco quando se usa <<. Nesse caso, melhor usar a função get, como mostrado a seguir:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]while (infile.get©)

outfile << c;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]ou[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]while (infile.get©)

outfile.put©;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]É ainda possível ler linhas inteiras usando-se a função getline, da mesma maneira que se usa a função get. Para abrir um arquivo para acrescentar dados, use o seguinte:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]ofstream("xxx", iosapp);[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

Essa linha, bem como a notação da função .get, fará mais sentido na medida em que você conheça mais sobre o C++. O fato de ofstream algumas vezes receber um, outras vezes dois parâmetros é uma característica intrínseca ao C++.

Note que não há necessidade de uma função close para os arquivos de entrada ou de saída. O arquivo é fechado automaticamente quando se encera o escopo da variável que denomina o arquivo. Se você precisar fechar explicitamente um arquivo, use

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]outfile.close();[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]2.3.3 - I/O em string[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Entradas podem ser lidas a partir de string na memória, e saídas pode ser enviadas para strings na memória, duplicando-se assim as ações de sscanf e sprintf. Para tanto você deve incluir o arquivo strstream.h e declarar a entrada e a saída em string. Uma saída em string e mostrada a seguir:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]char s[100];

ostrstream outstring(s,100);

 

outstring << 3.14 << " is pi" << ends;

cout << s;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]O string s é preenchido com o texto

 

3.14 is pi.

 

Se o tamanho de s for atingido, outstring vai automaticamente interromper a colocação de valores em s.

 

Se um string s já existe e você deseja ler dados a partir dele, você pode usar um fluxo de entrada string como mostrado a seguir:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]char *s = "3.14 12 cat";

istrstream instring(s, strlen(s));

float f;

int i;

char t[100];

 

instring >> f >> i >> t;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]A biblioteca iostream tem muitas e muitas outras capacidades não discutidas aqui. Para maiores informações, veja a documentação fornecida junto com o seu compilador. Ela geralmente contém uma referência completa para a biblioteca de I/O[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.4 - Declarações de variáveis

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]Variáveis são declaradas em C++ assim como o são em C. As variáveis podem ser declaradas em qualquer ponto do código em C++, estabelecendo um nível de flexibilidade quase como o que existe em FORTRAN. A variável torna-se existente quando é declarada, e deixa de existir quando o } do bloco corrente é encontrado. Por exemplo, na seguinte codificação:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]{

int i;

... code ...

int j;

... code ...

int k=func(i,j);

... code ...

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]todas as três variáveis passam a existir no ponto em que são declaradas, e desaparecem ao }.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.5 - Constantes

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]Em C você cria uma constante usando uma macro do pré-processador, como no seguinte exemplo:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]#define MAX 100[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

Quando o programa é compilado, o pré-processador encontra cada ocorrência da palavra MAX e a substitui pelo string 100.

Em C++ usa-se a palavra const, que é aplicável normalmente às declarações de variáveis:

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]const int MAX=100;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

A codificação int MAX=100; é formatada exatamente da mesma maneira que uma declaração normal. O termo const que precede a declaração simplesmente define que a variável MAX não pode ser modificada. O uso de letras maiúsculas para nomes de variáveis constantes é uma tradição em C, que você pode preservar ou abandonar.

O modificador const pode também ser usado em listas de parâmetros para especificar o uso correto do parâmetro. As três funções a seguir exemplificam diferentes usos de const.

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]void func1(const int i)

{

i=5; // cannot modify a constant

}

 

void func2(char * const s)

{

s="hello"; // cannot modify the pointer

}

 

void func3(const char * s)

{

s="hello"; // this is OK

*s='A'; // cannot modify what is pointed to

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]A notação mostrada em func2 deve, sempre que possível, ser usada quando um parâmetro char* é passado para a função.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.6 - Sobrecarregando funções

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]Um dos mais poderosos novos recursos do C++ é denominado sobrecarga de função. Uma função sobrecarregada possui várias e diferentes listas de parâmetros. A linguagem distingue qual das opções de chamada da função deve ser usada, com base no padrão das listas de parâmetros. Aqui está uma demonstração extremamente simples desse processo:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]#include <iostream.h>

 

void func(int i)

{

cout << "function 1 called" << endl;

cout << "parameter = " << i << endl;

}

 

void func(char c)

{

cout << "function 2 called" << endl;

cout << "parameter = " << c << endl;

}

 

void func(char *s)

{

cout << "function 3 called" << endl;

cout << "parameter = " << s << endl;

}

 

void func(char *s, int i)

{

cout << "function 4 called" << endl;

cout << "parameter = " << s;

cout << ", parameter = " << i << endl;

}

 

main()

{

func(10);

func('B');

func("hello");

func("string", 4);

return 0;

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Quando esse código é executado, cada versão da função func é escolhida, e chamada, de acordo com as correspondências entre as listas de parâmetros. Você vai conseguir usar essa capacidade, que é uma grande característica do C++, uma vez que você encare sobrecarga de função como uma solução para muitos dos problemas de programação. Por exemplo, se você cria uma função para inicializar um módulo, você pode ter uma chamada diferente, para a mesma função, dependendo da característica do parâmetro que é passado; um string, um inteiro, um ponto flutuante, e assim por diante.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.7 - Argumentos default

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center]C++ também permite que você determine valores default para os parâmetros. Se o parâmetro não for passado, o valor default é usado. Essa capacidade é demonstrada no seguinte código:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]#include <iostream.h>

 

void sample(char *s, int i=5)

{

cout << "parameter 1 = " << s << endl;

cout << "parameter 2 = " << i << endl;

}

 

main()

{

sample("test1");

sample("test1",10);

return 0;

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

A primeira chamada da função vai apresentar o valor default 5 para o parâmetro i, enquanto a segunda chamada vai apresentar o valor 10.

Quando criando parâmetros default, você precisa evitar ambigüidade entre as listas de parâmetros default e as demais listas de parâmetros. Examinando a função de nosso último exemplo, não é possível criar uma versão sobrecarregada que aceite um parâmetro char* isolado, porque o compilador não conseguiria escolher que versão chamar no caso de se passar um string.

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.8 - Alocação de memória

[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD]

C++ substitui as funções C de alocação e de desalocação de memória , malloc e free, pelas novas funções new e delete, respectivamente e torna esse processo muito mais fácil para o programador. new e delete permitem que tipos de dados definidos pelo programador sejam alocados e desalocados tão facilmente quanto os tipos já existentes em C++.

O código seguinte exemplifica o modo mais simples de uso de new e delete. Um pointer para um inteiro aponta para um bloco de memória criado por new:

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int *p;

p = new int;

*p = 12;

cout << *p;

delete p;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]É ainda possível alocar blocos compostos de matrizes de tamanhos variados, usando uma técnica similar. Repare o uso de [] para excluir a matriz:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]int *p;

p = new int[100];

p[10] = 12;

cout << p[10];

delete [] p;[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

O valor 100 poderia ser uma variável, se desejável.

Quando aplicada a tipos de dados definidos pelo programador, new funciona do mesmo modo. Por exemplo:

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]typedef node

{

int data;

node *next;

} node;

 

main()

{

node *p;

p=new node;

p->date = 10;

delete p;

} [/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Conforme veremos mais adiante nessa série de tutoriais, o operador delete é bastante sofisticado quando opera com classes definidas pelo programador.[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

[TABLE=width: 100%]

[TR]

[TD=width: 25%, align: center]

[/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[TD=width: 25%, align: center][/TD]

[/TR]

[/TABLE]

[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]2.9 - Declarações de referência[/TD]

[/TR]

[TR]

[TD]


[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Em C, pointers são freqüentemente usados para passar parâmetros para funções. Por exemplo, a seguinte função swap inverte dois valores que lhe são passados:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]void swap(int *i, int *j)

{

int t = *i;

*i = *j;

*j = t;

}

 

main()

{

int a=10, b=5;

 

swap(& a, & b);

cout << a << b << endl;

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]C++ provê um novo operador de referência que simplifica muito essa sintaxe. O seguinte código funciona em C++[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]void swap(int& i, int& j)

{

int t = i;

i = j;

j = t;

}

 

main()

{

int a=10, b=5;

 

swap(a, b);

cout << a << b << endl;

}[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]Os parâmetros i e j declarados como tipo int& atuam como referências para os inteiros passados (leia int& como uma referência para um inteiro). Quando uma variável é atribuída à referência de uma outra variável, a referência toma o endereço da variável e opera a atribuição para a localização real para a variável . Por exemplo:[/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD]

int a; int & b=a; a=0; b=5; cout << a << endl; [/TD]

[/TR]

[TR]

[TD=align: center][/TD]

[/TR]

[TR]

[TD=align: center]O código acima produz 5 como saída porque b referencia a. É o mesmo que usar pointer e operadores de endereços em C, mas a sintaxe aqui é muito mais simples. Note que b deve ser inicializado quando de sua criação, como mostrado no exemplo.[/TD]

[/TR]

[/TABLE]

Entendendo C++ Funções virtuais

 

[TABLE=width: 500]

[TR]

[TD=align: center]

[/TD]

[/TR]

[TR]

[TD]

Ao longo desses tutoriais nós vimos vários exemplos de herança, porque herança é muito importante para programação orientada a objeto. Vimos que herança permite que dados membro e que funções membro sejam acrescentadas às funções derivadas. Vimos também vários exemplos em que o mecanismo de herança foi usado para modificar o comportamento de uma função. Por exemplo, no Tutorial 3 nós vimos um exemplo onde a função Insert da classe base List foi sobreposta por uma outra função Insert que continha totalização.

Uma hierarquia similar é mostrada a seguir, usando uma classe base denominada List e uma classe derivada denominada TotalingList:

[/TD]

[/TR]

[TR]

[TD=align: center]

[/TD]

[/TR]

[TR]

[TD=align: center]#include <iostream.h>

 

class List

{

int array[100];

int count;

public:

List(): count(0) {}

void Insert(int n) { array[count++]=n; }

int Get(int i) { return array; }

int Size() { return count; }

};

 

void ManipList(List list)

{

// do things to the list

list.Insert(100);

list.Insert(200);

// do things to the list

}

 

class TotalingList: public List

{

int total;

public:

TotalingList(): List(), total(0) {}

void Insert(int n)

{

total += n;

List::Insert(n);

}

int GetTotal() { return total; }

};

 

void main()

{

TotalingList list;

int x;

 

list.Insert(10);

list.Insert(5);

cout << list.GetTotal() << endl;

ManipList(list);

cout << list.GetTotal() << endl;

for (x=0; x < list.Size(); x++)

cout << list.Get(x) << ' ';

cout << endl;

} [/TD]

[/TR]

[TR]

[TD=align: center]

[/TD]

[/TR]

[TR]

[TD]

Nesse código, a classe List implementa a lista mais simples possível com as três funções membro Insert, Get e Size e o construtor. A função ManipList é um exemplo de uma função qualquer que usa a classe List, e que chama a função Insert duas vezes apenas como ilustração do que pretendemos demonstrar.

A classe TotalingList herda a classe List e acrescenta um dado membro denominado total. Esse dado membro armazena o total corrente de todos os números contidos na lista. A função Insert é sobreposta para que total seja atualizado a cada inserção.

A função main declara uma instância da classe TotalingList. Insere os valores 10 e 5 e imprime o total. Em seguida chama ManipList. Pode ser uma surpresa para você que essa construção não resulte em erro de compilação: se você olhar o protótipo de ManipList vai ver que ela espera uma parâmetro do tipo List, e não do tipo TotalingList. Acontece que C++ entende certas peculiaridades sobre herança, uma delas é que um parâmetro do tipo classe base deve aceitar também qualquer tipo classe derivada daquela classe base. Portanto, desde que TotalingList é derivada da classe List, ManipList vai aceitar um parâmetro do tipo TotalingList. Esse é um dos recursos do C++ que torna o mecanismo de herança tão poderoso: você pode criar classes derivadas e passá-las para funções existentes que conhecem apenas a classe base.

Quando o código mostrado for executado, ele não vai produzir o resultado correto. Ele vai produzir a seguinte saída:

15

15

10 5

Essa saída indica que não apenas a totalização não funcionou, mas que os valores 100 e 200 não foram inseridos na lista quando da chamada a ManipList. Uma parte desse erro se deve a um erro no código: o parâmetro aceito por ManipList deve ser um pointer ou uma referência, do contrário nenhum valor será retornado. Modificando o protótipo de ManipList para corrigir parcialmente o problema:

void ManipList(List& list)

Agora teremos a seguinte saída:

15

15

10 5 100 200

É didático seguir a execução de ManipList passo-a-passo e observar o que acontece. Quando ocorre a chamada para a função Insert, a função invocada é List:Insert ao invés de TotalingList:Insert.

Esse problema também pode ser resolvido. É possível, em C++, criar uma função com um prefixo virtual, e isso leva o C++ a sempre chamar a função na classe derivada. Ou seja, quando uma função é declarada como virtual, o compilador pode chamar versões da função que sequer existiam quando o código da função chamada foi escrito. Para verificar isso, acrescente a palavra virtual a frente das funções Insert tanto na classe List quanto na classe TotalingList, como mostrado a seguir:

[/TD]

[/TR]

[TR]

[TD=align: center]

[/TD]

[/TR]

[TR]

[TD]

 

class List { int array[100]; int count; public: List(): count(0) {} virtual void Insert(int n) { array[count++]=n; } int Get(int i) { return array; } int Size() { return count; } }; void ManipList(List& list)

{ // do things to the list list.Insert(100); list.Insert(200); // do things to the list } class TotalingList: public List { int total; public: TotalingList(): List(), total(0) {} virtual void Insert(int n) { total += n; List::Insert(n); } int GetTotal() { return total; } };

[/TD]

[/TR]

[TR]

[TD=align: center]

[/TD]

[/TR]

[TR]

[TD]

De fato é necessário colocar a palavra virtual apenas a frente do nome da função na classe base, mas é um bom hábito repeti-la nas funções das classes derivadas, como uma indicação explícita do que está ocorrendo.

Agora você pode executar o programa e obter as saídas corretas:

15

315

10 5 100 200

O que está acontecendo? A palavra virtual a frente da função determina para o C++ que você planeja criar novas versões dessa mesma função em classes derivadas. Ou seja, virtual permite que você declare suas futuras intenções em relação à classe em definição. Quando uma função virtual é chamada, o C++ examina a classe que chamou a função, e busca a versão da função para esta classe, mesmo que a classe derivada ainda não existisse quando a chamada da função foi escrita.

Isso significa que em certos casos você tem que pensar no futuro quando escreve o código. Você deve refletir eu, ou qualquer outro programador, poderia no futuro precisar modificar o comportamento dessa função? Se a resposta for sim então a função deve ser declarada como virtual.

Deve-se ter alguns cuidados para que uma função virtual funcione corretamente. Por exemplo, você deve realmente prever a necessidade de se modificar o comportamento da função e lembrar-se de declará-la como virtual na classe base. Um outro ponto importante pode ser visto no exemplo acima: experimente remover o & da lista de parâmetros em ManipList e siga o código passo-a-passo. Mesmo a função Insert estando marcada como virtual na classe base, a função List::Insert é chamada, ao invés da função TotalingList::Insert.

Isso acontece porque, quando o & não está presente, a lista de tipos de parâmetros em List está atuando como se fosse uma definição de conversão de tipos. Qualquer classe passada é convertida para a classe base List. Quando o & está presente na lista de parâmetros, tal conversão não acontece.

Pode-se ver funções virtuais em praticamente toda hierarquia de classes em C++. Um hierarquia de classes típica espera que, no futuro, se altere o comportamento das funções para adaptar a biblioteca de classes às necessidades específicas de uma ou de outra aplicação.

Funções virtuais são usadas com freqüência quando o projetista da classe não pode realmente saber o que será feito com a classe no futuro. Suponha que você está usando a interface de uma classe que implementa buttons na tela. Quando você cria uma instância do button, ele se desenha na tela e se comporta de modo padrão, ou seja, ilumina-se quando é clicado pelo usuário. Entretanto, o programador que projetou essa classe não tinha idéia do que os programadores usuários da classe poderiam querer fazer quando o button fosse clicado. Para esses casos, o projetista da classe poderia ter definido uma função virtual denominada, por exemplo, handleEvent que é chamada sempre que o button é clicado. Feito isso, você pode sobrescrever a função virtual com uma função própria que maneja a situação do modo mais adequado à sua aplicação.

[/TD]

[/TR]

[TR]

[TD=align: center]

[/TD]

[/TR]

[/TABLE]

[TABLE=width: 500]

[TR]

[TD=align: center]Conclusão[/TD]

[/TR]

[TR]

[TD] [/TD]

[/TR]

[TR]

[TD]

Cobrimos uma grande quantidade de temas nessa série de tutoriais, mas você talvez tenha a sensação de que ainda há muito o que se aprofundar em C++. Isso é verdade em certo sentido: C++ é uma linguagem muito profunda, com algumas sutilezas e artifícios que somente a experiência pode lhe ajudar a dominar. C também é assim, só que em escala menor.

A única maneira de se compreender completamente uma linguagem de programação é escrever, e ler, muito código. Você pode aprender muito usando, e estudando, bibliotecas criadas por outros. Todos os benefícios do C++ se tornarão mais evidentes para você na medida em que você compreenda mais e mais essa linguagem. Então... vamos à codificação!

[/TD]

[/TR]

[/TABLE]

Créditos: Totalmente a arnaut.eti.br

geek.png

Always.

Link para o comentário
Compartilhar em outros sites

  • 4 semanas atrás...

Muito legal seu tópico, bem explicativo e fácil de entender!

Faço um curso de programação e tenho vários colegas que ainda não entendem algumas lógicas, vou mostrar isso pra eles.

Sobre o tópico, você também pode usar conversão de dados para fazer operações como:

 

É necessário se cadastrar para acessar o conteúdo.

Se você não fizer cast pode dar erro, né?

Link para o comentário
Compartilhar em outros sites

  • 2 semanas atrás...
Este tópico está impedido de receber novos posts.
 Compartilhar

×
×
  • Criar Novo...

Informação Importante

Nós fazemos uso de cookies no seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies , caso contrário, vamos supor que você está bem para continuar.