Ir para conteúdo

Entendendo C++


Posts Recomendados

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:

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:

Porque C++ existe e quais são suas vantagens sobre o C?

Que recursos estão disponíveis no C++ para materializar idéias orientadas a objeto?

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:

"Tudo o que leio tem um vocabulário maluco: encapsulamento, herança, funções virtuais, classes, sobrecarga, amigos... De onde vem tudo isso?

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 memory leak, 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.

#honux

Completei o Tópico ( ele não copiou inteiro ) e coloquei a fonte.

Fonte : http://www.arnaut.eti.br/op/CPPAI01.htm

Editado por honux
Link para o comentário
Compartilhar em outros sites

aew cara..

antes eu nem fazia idéia do que era C++.. :confused:

em programação sou completamente noob =X

agora tenho um pouco de idéia e quem sabe usando seu tutorial que ficou realmente muito bom eu não consigo aprender esquemas :icon1:

vlws

flws ae

fuiz!!

Link para o comentário
Compartilhar em outros sites

  • 1 month later...
  • 1 month later...
  • 2 months later...
  • 1 month later...
  • 6 months later...
  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...