3.0 - Introdução
O último tutorial enfocou elementos da linguagem C++ que extendem as capacidades originais, ou corrigem problemas inerentes à linguagem C. Essas extensões são bastante simples de se entender.
O outro conjunto de recursos do C++ são dirigidas à programação orientada a objeto, e talvez não sejam de entendimento tão imediato. Enquanto as capacidades de cout são apenas uma outra forma de manejar operações de saída de dados - as quais você já conhecia previamente - algumas extensões orientadas a objeto talvez não lhe sejam tão familiares. O objetivo desse capítulo é lhe dar uma primeira exposição de algumas dessas extensões. Assim, vamos examinar a sintaxe C++ que suporta os conceitos de orientação a objeto e posteriormente rever os conceitos.


3.1 - Vocabulário C++
Olhe o mundo a sua volta. Você pode entender uma grande parte da estrutura, do vocabulário e da organização do C++ apenas olhando a estrutura e a organização do mundo real, e refletindo sobre o vocabulário que usamos para falar sobre o mundo real. Muitos dos elementos do C++ - em da orientação a objeto em geral - tentam emular o modo como interagimos com o mundo real.
Por exemplo, sempre que você olha em torno você vê uma grande quantidade de objetos. Nós organizamos esses objetos em nossas mentes arranjando-os em categorias, ou classes. Se você tem um livro em suas mãos, um livro é uma classe genérica de objetos. Você poderia dizer "esse objeto que eu tenho nas mãos é classificado como um livro." Uma hierarquia de classes de objetos envolve a classe livro e a estende em duas direções. Livros são membros da classe mais geral publicações. Há ainda tipos específicos de livros, tais como livros de computação, livros de ficção, biografias, e assim por diante. A organização hierárquica se estende em ambos os sentidos: do mais geral para o mais específico. Em nosso exemplo, você tem nas mãos um determinado livro, um livro específico. No idioma OOP, você tem nas mãos uma instância da classe livro. Livros tem certos atributos que são comuns e portanto são compartilhados por todos os livros: uma capa, vários capítulos, não tem anúncios, etc. Livros tem também atributos comuns a publicações em geral: título, data de publicação, editora, etc. Tem ainda atributos comuns a objetos físicos: localização, tamanho, forma e peso. Essa idéia de atributos comuns é muito importante em C++. C++ modela o conceito de atributos comuns usando herança.

Há certas coisas que você faz com e para certos objetos, e essas ações são diferentes de objeto para objeto. Por exemplo, você pode ler um livro, folhear suas páginas. Você pode olhar um título, procurar um capítulo específico, pesquisar o índice, contar o número de páginas, etc. Essas ações são aplicáveis unicamente a publicações. Você não poderia folhear as páginas de um martelo, por exemplo. Entretanto, há ações que são genéricas e aplicáveis a todos os objetos físicos, como pegá-los. C++ também leva em conta esse fato e modela esses casos usando herança.

A natureza hierárquica das categorias de objetos, bem como nossa organização hierárquica de atributos de objetos e de ações, estão contidas na sintaxe e no vocabulário do C++. Por exemplo, quando projetando um programa você vai subdividi-lo em objetos, cada um dos quais tem uma classe. Você vai herdar atributos de uma classe base quando você criar uma classe derivada. Ou seja, você vai criar classes mais gerais de objetos e então fazer classes mais específicas, a partir das classes gerais, derivando o particular a partir do geral. Você vai encapsular o dados em um objeto com funções membro funções membro funções membro funções membro funções membro funções membro, e para ampliar classes você vai sobrecarregar e sobrescrever funções da classe base. Confuso? Vamos examinar um exemplo simples para ver o que esses termos significam na realidade.

O exemplo clássico de programação orientada a objeto é um programa gráfico que lhe permite desenhar objetos - linhas, retângulos, círculos, etc. - na tela do terminal. O que todos esses objetos tem em comum? Que atributos todos esses objetos compartilham? Todos tem uma localização na tela. Podem ter uma cor. Esses atributos - localização e cor - são comuns a todas as formas exibidas na tela. Portanto, como projetista do programa você poderia criar uma classe base - ou em outras palavras, uma classe genérica de objetos - para conter os atributos comuns a todos os objetos apresentados na tela. Essa classe base poderia ser denominada Forma, para melhor identificá-la como classe genérica. Você poderia então derivar diferentes objetos - círculos, quadrados, linhas - a partir dessa classe base, adicionando os novos atributos que são próprios de cada forma em particular. Um círculo específico desenhado na tela é uma instância da classe Círculo, que herdou uma parte de seus atributos de uma classe mais genérica denominada Forma. É possível criar tal conjunto de hierarquia em C, mas nem de longe com tanta facilidade quanto em C++. C++ contém sintaxe para tratar herança. Por exemplo, em C você poderia criar uma estrutura básica para conter os atributos localização e cor dos objetos. As estruturas específicas de cada objeto poderiam incluir essa estrutura básica e ampliá-la. C++ torna esse processo mais simples. Em C++, as funções são agrupadas, reunidas dentro de uma estrutura, e essa estrutura é denominada classe. Assim, a classe base pode ter funções, denominadas em C++ como funções membro, que permitam que os objetos sejam movidos ou re-coloridos. As classes derivadas podem usar essas funções membro da classe base tal como são, criar novas funções membro, ou ainda sobrescrever funções membro da classe base.

O mais importante recurso que diferencia C++ do C é a idéia de classe, tanto em nível sintático quanto em nível conceptual. Classes permitem que você use todas as facilidades de programação orientada a objeto - encapsulamento, herança e polimorfismo - em seus programas em C++. Classes são ainda a estrutura básica sobre a qual outros recursos são implementados, como sobrecarga de operador para novos tipos de dados definidos pelo programador. Tudo isso pode lhe parecer confuso ou desarticulado nesse momento, mas na medida em você de torne familiarizado com os conceitos e com esse vocabulário vai perceber todo o poder dessas técnicas


3.2 - A evolução das classes

Entendidos os conceitos poderosos agregados ao conceito de classe, a compreensão da sintaxe torna-se quase automática. Uma classe é simplesmente uma melhoria das estruturas em C. Basicamente, uma classe possibilita que você crie uma estrutura que contenha também todas as funções para lidar com os dados da estrutura. Esse processo é denominado encapsulamento. É um conceito muito simples, mas é o ponto central da orientação a objeto: dados + funções = objetos. Classes podem também ser construídas sobre outras classes, usando herança. Com herança, uma nova classe amplia as capacidades da classe base. Finalmente, novas classes podem modificar o comportamento de suas classes base, uma capacidade denominada polimorfismo.
Essa é uma nova maneira de pensar sobre o código: uma abordagem tridimensional. Você pode considerar um código linear , um que não contenha e nem invoque qualquer função, como um código unidimensional. Um código que começa no início e termina no fim (sic). Nada mais. Agora você acrescenta funções a esse código linear, para remover redundância de codificação, e dá nomes a essas porções de código, identificando assim as funções. Isso é código bidimensional. Agora vamos acrescentar uma terceira dimensão a tudo isso agrupando as funções e os dados em classes para que o código fique ainda mais organizado. A hierarquia de classes criada pela herança de classes estabelece a terceira dimensão. Da mesma forma que pilotar um avião é mais difícil que dirigir um carro, porque voar acrescenta uma terceira dimensão ao problema de guiar, programação orientada a objeto pode requerer um certo tempo para ser completamente compreendida.

Uma das melhores maneiras para se entender classes e sua importância para você como programador é aprender como e porque o conceito de classe evoluiu. As raízes do conceito de classe nos levam a um tópico denominado abstração de dados.

Imagine que você está olhando uma típica sala de estudantes de computação cheia de alunos escrevendo programas. Imagine que alguns desses estudantes são alunos do primeiro semestre do curso de Pascal. Eles já sabem como criar comandos if, loops e matrizes e portanto estão quase prontos a escrever código, mas não sabem ainda como organizar o pensamento. Se você pedir a um deles que crie um programa, ele vai criar um código que funciona de qualquer maneira. Não será uma boa solução, mas provavelmente vai funcionar. Imagine agora que você peça a esses estudantes que criem um programa para executar o jogo cannon. Um jogo em que os jogadores vêm uma bola e um alvo e obstáculos no terreno. A localização do alvo, o terreno e obstáculos mudam de jogo para jogo. O objetivo é estabelecer um ângulo de trajetória e uma força a ser aplicada a bola para que esta atinja o alvo sem tocar em qualquer dos obstáculos do terreno.

[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]

Assuma que os dados do terreno existem em um arquivo texto contendo pares de coordenadas. As coordenadas são pontos finais dos segmentos de linhas que definem o terreno. Os estudantes imaginam que precisam ler esse arquivo para poder desenhar o terreno, e ainda manter o arquivo em memória para poder verificar as interseções da trajetória da bola com as coordenadas do terreno, e assim determinar o ponto do terreno onde a bola para. Então o que eles fazem? Declaram uma matriz global para conter as coordenadas, lêem o arquivo e armazenam os dados na matriz, e usam a matriz onde for necessário, em qualquer ponto do programa.
O problema com essa abordagem é que a matriz está como que embutida em todo o código. Se uma alteração se fizer necessária, por exemplo em lugar da matriz usar-se uma lista ligada, o programa terá que ser rescrito porque contém referências explícitas para a matriz. De um ponto de vista de produção de programas profissionais essa é uma péssima abordagem, porque as estruturas de dados freqüentemente são alteradas em sistemas de informação reais.

Uma maneira melhor de projetar o programa é usar um tipo de dado abstrato. Nessa abordagem, o programador primeiramente tem que decidir como os dados serão usados. Em nosso exemplo do terreno, o programador poderia pensar "Bem, eu preciso poder carregar coordenadas do terreno, independentemente de onde venham, para desenhar o terreno na tela e para verificar as interseções da trajetória da bola com os obstáculos do terreno". Repare que esta última abordagem abstrai-se da forma como os dados estarão armazenados, não fazendo qualquer menção a matriz ou lista ligada. Então o programador cria uma função para implementar as capacidades de que precisa. As funções poderiam ser denominadas

Código:

carrega_terreno 
desenha_terreno
verifica_intersecoes.

Essas funções são usadas ao longo de todo o programa. As funções atuam como uma barreira. Elas ocultam a estrutura dos dados, separando-a do programa. Se mais tarde a estrutura dos dados precisar ser alterada, por exemplo de matriz para uma lista ligada, a maior parte do programa não será afetada. Apenas as funções precisarão ser modificadas. Dessa forma o programador criou um tipo de dado abstrato.

Algumas linguagens formalizam esse conceito. Em Pascal você pode usar um unit, em C você pode usar uma biblioteca para criar um arquivo de compilação em separado que contém a estrutura de dados e as funções que os processam. Você pode determinar que a estrutura de dados seja oculta, de tal maneira que a matriz seja acessada exclusivamente pelas funções internas à unidade.

Mais ainda, a unidade pode ser compilada para ocultar o próprio código. Assim, outros programadores podem chamar as funções através de uma interface pública, mas não podem modificar o código original.

Units em pascal e bibliotecas em C representam um passo nessa cadeia evolucionária. Começam a enfrentar o problema de abstração de dados mas não vão longe o bastante. Funciona, mas com alguns problemas:

O mais importante deles é que não é fácil modificar ou estender as capacidades de uma unit após a compilação.

Esses tipos abstratos não se encaixam muito bem na linguagem original. Sintaticamente são uma confusão, e não aceitam os operadores normais da linguagem. Por exemplo, se você cria um novo tipo de dado para o qual a operação de adição seria natural, não há meios de você usar o sinal + para representar a operação, ao invés disso você tem que criar uma função de soma.

Se você ocultar uma matriz em uma unit você poderá ter apenas uma matriz. Você não pode criar múltiplas instâncias de tipos de dados.

Classes em C++ eliminam essas deficiências


3.3 - C++ e abstração de dados
Em resposta a esses problemas, linguagens orientadas a objeto como C++ oferece modos fáceis e extensíveis de se implementar abstração de dados. Tudo o que você tem que fazer é mudar o seu enfoque, e passar a pensar em solução de problemas com uma abordagem abstrata. Essa mudança de atitude mental será mais fácil quando você tiver examinado alguns exemplos.
Primeiramente você vai tentar pensar em termos de tipos de dados. Quando você criar um novo tipo de dado, você precisa pensar em todas as coisas que pretende fazer ele, e então agrupar todas as funções criadas para lidar especificamente com o tipo de dado. Por exemplo, digamos que você está criando um programa que requer um tipo de dado retângulo, contendo dois pares de coordenadas. Você deveria pensar "o que eu vou precisar fazer com esse tipo de dado?". Você poderia iniciar com as seguintes ações: estabelecer um valor para as coordenadas, verificar sua igualdade com outro retângulo, verificar interseção com outro retângulo e verificar se um determinado ponto está dentro do retângulo. Se você precisa de um dado terreno, você segue o mesmo processo e inicia com funções para carregar os dados do terreno, desenhar o terreno, e assim por diante. Você então agrupa essas funções junto com os dados. Fazer isso para cada tipo de dado que você precisa no programa é a essência de programação orientada a objeto.

A outra técnica usada na abordagem orientada a objeto envolve treinar sua mente para pensar em hierarquia, do mais geral para o mais específico. Por exemplo, quando pensando sobre um objeto terreno, você deve reparar as semelhanças entre essa estrutura de dados e uma lista. Afinal a descrição do terreno é uma lista de coordenadas carregada a partir de um arquivo. Uma lista é um objeto genérico que pode ser usado em vários pontos de vários programas. Assim, você poderia criar uma classe genérica Lista e construir o objeto terreno a partir dela. Nós vamos examinar esse processo mais detalhadamente a medida em que vejamos outros exemplos nos próximos tutoriais dessa série.