Introduçao a POO

81
Curso de Programação – III Introdução a POO Linguagem de Programação C++ Autores: Renato Borges André Clínio

description

Introduçao a POO

Transcript of Introduçao a POO

  • Curso de Programao III

    Introduo a POO

    Linguagem de Programao C++

    Autores: Renato Borges Andr Clnio

  • Captulo 1

    Qualidade do software O principal objetivo da Engenharia de Software contribuir para a produo de programas de

    qualidade. Esta qualidade, porm, no uma idia simples; mas sim como um conjunto de noes e fatores. desejvel que os programas produzidos sejam rpidos, confiveis, modulares, estruturados,

    modularizados, etc. Esses qualificadores descrevem dois tipos de qualidade: De um lado, considera-se aspectos como eficincia, facilidade de uso, extensibilidade, etc. Estes

    elementos podem ser detectados pelos usurios do sistema. A esses fatores atribui-se a qualidade externa do sofware.

    Por outro lado, existe um conjunto de fatores do programa que s profissionais de computao podem detectar. Por exemplo, legibilidade, modularidade, etc. A esses fatores atribui-se a qualidade interna do sofware.

    Na realidade, qualidade externa do programa em questo que importa quando de sua utilizao. No entanto, os elementos de qualidade interna so a chave para a conquista da qualidade externa. Alguns fatores de qualidade externa so apresentados na prxima seo. A seo posterior trata da qualidade interna, analisando como a qualidade externa pode ser atingida a partir desta.

    Fatores de qualidade externa

    Corretude

    a condio do programa de produzir respostas adequadas e corretas cumprindo rigorosamente suas tarefas de acordo com sua especificao.

    Obviamente, uma qualidade primordial. Se um sistema no faz o que ele foi proposto a fazer, ento qualquer outra questo torna-se irrelevante.

    Robustez

    a capacidade do programa de funcionar mesmo sob condies anormais.

    A robustez do programa diz respeito ao que acontece quando do aparecimento de situaes anmalas. Isto diferente de corretude, que define como o programa se comporta dentro de sua especificao. Na robustez, o programa deve saber encarar situaes que no foram previstas sem efeitos colaterais catastrficos. Neste sentido, o termo confiabilidade muito utilizado para robustez; porm denota um conceito mais amplo que melhor interpretado como que englobando os conceitos de corretude e robustez.

    Extensibilidade

    definida como a facilidade com que o programa pode ser adaptado para mudanas em sua especificao.

    Neste aspecto, dois princpios so essenciais: Simplicidade de design: uma arquitetura simples sempre mais simples de ser adaptada ou

    modificada Descentralizao: quanto mais autnomos so os mdulos de uma arquitetura, maior ser a

    probabilidade de que uma alterao implicar na manuteno de um ou poucos mdulos.

    Capacidade de reuso

    a capacidade do programa de ser reutilizado, totalmente ou em partes, para novas aplicaes.

  • A necessidade de reutilizao vem da observao de que muitos elementos dos sistemas seguem padres especficos. Neste sentido, possvel explorar este aspecto e evitar que a cada sistema produzido os programadores fiquem reinventando solues de problemas j resolvidos anteriormente.

    Compatibilidade

    a facilidade com que o programa pode ser combinado com outros.

    Esta uma qualidade importante pois os programas no so desenvolvidos stand-alone. Normalmente, existe uma interao entre diversos programas onde o resultado de um determinado sistema utilizado como entrada para outro.

    Outros aspectos Os aspectos resumidos acima so aqueles que podem ser beneficiados com a boa utilizao das

    tcnicas de orientao por objetos. No entanto, existem outros aspectos que no podem ser esquecidos: Eficincia: o bom aproveitamento dos recursos computacionais como processadores, memria,

    dispositivos de comunicao, etc. Apesar de no explicitamente citado anteriormente, este um requisito essencial para qualquer produto. A linguagem C++ em particular, por ser to eficiente quanto C, permite o desenvolvimento de sistemas eficientes.

    Portabilidade: a facilidade com que um programa pode ser transferido de uma plataforma (hardware, sistema operacional, etc.). Este fator depende muito da base sobre a qual o sistema desenvolvido. A nvel de linguagem, C++ satisfaz este fator por ser uma linguagem padronizada com implementaes nas mais diversas plataformas.

    Facildade de uso: a facilidade de aprendizagem de como o programa funciona, sua operao, etc.

    Modularidade - qualidade interna Alguns fatores apresentados na seo anterior podem ser beneficiados com o uso de orientao por

    objetos. So eles: corretude, robustez, extensibilidade, reuso e compatibilidade. Estes refletem as mais srias questes para o desenvolvimento de programas. A medida que se olha para estes cinco aspectos, dois subgrupos surgem:

    Extensibilidade, reuso e compatibilidade demandam arquiteturas que sejam flexveis, design descentralizado e a construo de mdulos coerentes conectados por interfaces bem definidas.

    Corretude e robustez so favorecidos por tcnicas que suportam o desenvolvimento de sistemas baseados em uma especificao precisa de requisitos e limitaes.

    Da necessidade da construo de arquiteturas de programas que sejam flexveis, surge a questo de tornar os programas mais modulares.

    Uma definio precisa para modularidade dificilmente encontrada. No entanto, esta tcnica no traz benefcios se os mdulos no forem autonmos, coerentes e organizados em uma estrutura robusta. Ser utilizado no decorrer deste texto um conjunto de cinco critrios e cinco princpios.

    Critrios para modularidade Seguem abaixo cinco critrios que ajudam a avaliar/construir a modularidade do design:

    Decomposio O critrio de decomposio modular alcanado quando o modelo de design ajuda a decomposio do

    problema em diversos outros subproblemas cujas solues podem ser atingidas separadamente. O mtodo deve ajudar a reduzir a aparente complexidade de problema inicial pela sua decomposio

    em um conjunto de problemas menores conectados por uma estrutura simples. De modo geral, o processo repetitivo: os subproblemas so tambm divididos em problemas menores e assim sucessivamente.

    Uma exemplificao deste tipo de design o chamado mtodo top-down. Este mtodo dirige os desenvolvedores a comear com uma viso mais abstrata do funcionamento do sistema. Esta viso abstrata vai sendo refinada como um conjunto de passos sucessivos menores e assim por diante at que seus elementos estejam em um nvel que permita sua implementao. Este processo pode ser modelado como uma rvore.

  • Composio O mtodo que satisfaz o critrio de composio favorece a produo de elementos de programas que

    podem ser livremente combinados com os outros de forma a produzir novos sistemas, possivelmente em um ambiente bem diferente daquele em que cada um destes elementos foi criado.

    Enquanto que a decomposio se concentra na diviso do software em elementos menores a partir da especificao, a composio se estabelece no sentido oposto: agregando elementos de programas que podem ser aplicados para construo de novos sistemas.

    A composio diretamente relacionada com a questo da reutilizao: o objetivo achar maneiras de desenvolver pedaos de programas que executam tarefas bem definidas e utilizveis em outros contextos. Este contexto reflete um sonho antigo: transformar o processo de design de programas como elementos independentes onde programas so construdos pela combinao de elementos existentes.

    Um exemplo deste tipo de abordagem a construo de bibliotecas como conjuntos de elementos que podem ser utilizados em diversos programas (pacotes grficos, bibliotecas numricas, etc.).

    Entendimento Um mtodo que satisfaz o critrio de entendimento ajuda a produo de mdulos que podem ser

    separadamente compreeendidos pelos desenvolvedores; no pior caso, o leitor deve ter ateno sobre poucos mdulos vizinhos. Este critrio especialmente relevante quando se tem em vista o aspecto da manuteno.

    Um contra-exemplo deste tipo de mtodo quando h dependncia sequencial onde um conjunto de mdulos elaborado de modo que a execuo dos mesmos seja feita em uma ordem determinada. Desta forma, os mdulos no so entendidos de forma individual mas em conjunto com seus vizinhos.

    Continuidade Um mtodo de design satisfaz a continuidade se uma pequena mudana na especificao do problema

    resulta em alteraes em um nico ou poucos mdulos. Tal alterao no tem reflexos na arquitetura geral do sistema; isto , no relacionamento inter-modular.

    Este critrio reflete o problema de extensibilidade do sistema. A continuidade significa que eventuais mudanas devem afetar os mdulos individualmente da estrutura do sistema e no a estrutura em si.

    Um exemplo simples deste tipo de critrio a utilizao de constantes representadas por nomes simblicos definidos em um nico local. Se o valor deve ser alterado, apenas a definio deve ser alterada.

    Proteo Um mtodo de design satisfaz a proteo se este prov a arquitetura de isolamento quando da

    ocorrncia de condies anmalas em tempo de execuo. Ao aparecimento de situaes anormais, seus efeitos ficam restritos quele mdulo ou pelo menos se propagar a poucos mdulos vizinhos.

    Os erros considerados neste critrio so somente aqueles ocorridos em tempo de execuo como falta de espao em disco, falhas de hardware, etc. No se considera, neste caso, a correo de erros, mas um aspecto importante para a modularidade: sua propagao.

    Princpios de modularidade Estabelecidos os critrios de modularidade, alguns princpios surgem e devem ser observados

    cuidadosamente para se obter modularidade. O primeiro princpio se relaciona com a notao e os outros se baseiam no modo de comunicao entre os mdulos.

    Seguem abaixo estes princpios:

    Lingustica modular Este princpio expressa que o formalismo utilizado para expressar o design, programas, etc. deve

    suportar uma viso modular; isto :

    Mdulos devem correspoder s unidades sintticas da linguagem utilizada.

    Onde a linguagem utilizada pode ser qualquer linguagem de programao, de design de sistemas, de especificao, etc.

    Este princpio segue de diversos critrios mencionados anteriormente:

  • Decomposio: Se pretende-se dividir o desenvolvimento do sistema em tarefas separadas, cada uma destas deve resultar em uma unidade sinttica bem delimitada. (compilveis separadamente no caso de linguagens de programao)

    Composio: S pode-se combinar coisas como unidades bem delimitadas. Proteo: Somente pode haver controle do efeito de erros se os mdulos esto bem delimitados.

    Poucas interfaces Este princpio restringe o nmero de canais de comunicao entre os mdulos na arquitetura do

    sistema. Isto quer dizer que:

    Cada mdulo deve se comunicar o mnimo possvel com outros.

    A comunicao pode ocorrer das mais diversas formas. Mdulos podem chamar funes de outros, compartilhar estruturas de dados, etc. Este princpios limita o nmero destes tipos de conexo.

    Pequenas interfaces Este princpio relaciona o tamanho das interfaces e no suas quantidades. Isto quer dizer que:

    Se dois mdulos possuem canal de comunicao, estes devem trocar o mnimo de informao possvel; isto , os canais da comunicao inter-modular devem ser limitados.

    Interfaces explcitas Este um princpio que vai mais adiante do que poucas interfaces e pequenas interfaces. Alm de

    impor limitaes no nmero de mdulos que se comunicam e na quantidade de informaes trocadas, h a imposio de que se explicite claramente esta comunicao. Isto :

    Quando da comunicao de dois mdulos A e B, isto deve ser explcito no texto de A, B ou ambos.

    Este princpio segue de diversos critrios mencionados anteriormente: Decomposio e composio: Se um elemento formado pela composio ou decomposio de

    outros, as conexes devem ser bem claras entre eles. Entendimento: Como entender o funcionamento de um mdulo A se seu comportamento

    influenciado por outro mdulo B de maneira no clara?

    Ocultao de informao (information hiding) A aplicao deste princpio assume que todo mdulo conhecido atravs de uma descrio oficial e

    explcita; ou melhor sua interface. Pela definio, isto requer que haja uma viso restrita do mdulo.

    Toda informao sobre um mdulo deve ser oculta (privada) para outro a no ser que seja especificamente declarada pblica.

    A razo fundamental para este princpio o critrio de continuidade. Se um mdulo precisa ser alterado, mas de maneira que s haja manuteno de sua parte no pblica e no a interface; ento os mdulos que utilizam seus recursos (clientes) no precisam ser alterados. Alm, quanto menor a interface, maiores as chances de que isto ocorra.

    Apesar de no haver uma regra absoluta sobre o que deve ser mantido pblico ou no, a idia geral simples: a interface deve ser uma descrio do funcionamento do mdulo. Tudo que for relacionado com sua implementao no deve ser pblico.

    importante ressaltar que, neste caso, este princpio no implica em proteo no sentido de restries de segurana. Embora seja invisvel, pode haver acesso s informaes internas do mdulo.

    Calculadora RPN em C Os primeiros exemplos sero feitos a partir de um programa escrito em C. Vrias modificaes sero

    feitas at que este se torne um programa C++. O programa uma calculadora RPN (notao polonesa reversa), apresentada nesta seo.

  • Este tipo de calculadora utiliza uma pilha para armazenar os seus dados. Esta pilha est implementada em um mdulo parte. O header file et listado abaixo:

    #ifndef stack_h#define stack_h

    #define MAX 50

    struct Stack {int top;int elems[MAX];

    };

    void push(struct Stack* s, int i);int pop(struct Stack* s);int empty(struct Stack* s);struct Stack* createStack(void);

    #endif

    A implementao destas funes est no arquivo stack-c.c:

    #include #include "stack-c.h"

    void push(struct Stack*s, int i) { s->elems[s->top++] = i; }int pop(struct Stack*s) { return s->elems[--(s->top)]; }int empty(struct Stack*s) { return s->top == 0; }

    struct Stack* createStack(void){

    struct Stack* s = (struct Stack*)malloc(sizeof(struct Stack));s->top = 0;return s;

    }A calculadora propriamente dita utiliza estes arquivos:

    #include #include #include "stack-c.h"

    /* dada uma pilha, esta funo pe nosparmetros n1 e n2 os valores do topoda pilha. Caso a pilha tenha menos de doisvalores na pilha, um erro retornado */

    int getop(struct Stack* s, int* n1, int* n2){

    if (empty(s)){

    printf("empty stack!\n");return 0;

    }*n2 = pop(s);if (empty(s)){

    push(s, *n2);printf("two operands needed!\n");return 0;

    }*n1 = pop(s);return 1;

    }

  • /* a funo main fica em um looplendo do teclado os comandos da calculadora.Se for um nmero, apenas empilha.Se for um operador, faz o calculo.Se for o caracter 'q', termina a execuo.Aps cada passo a pilha mostrada. */

    int main(void){

    struct Stack* s = createStack();while (1){

    char str[31];int i;printf("> ");gets(str);if (sscanf(str, "%d", &i)==1) push(s, i);else{

    int n1, n2;char c;sscanf(str, "%c", &c);switch(c){

    case '+':

    if (getop(s, &n1, &n2)) push(s, n1+n2);break;

    case '-':

    if (getop(s, &n1, &n2)) push(s, n1-n2);break;

    case '/':if (getop(s, &n1, &n2)) push(s, n1/n2);break;

    case '*':if (getop(s, &n1, &n2)) push(s, n1*n2);break;

    case 'q':return 0;

    default:printf("error\n");

    }}{

    int i;for (i=0; itop; i++)

    printf("%d:%6d\n", i, s->elems[i]);}

    }}

    Tipos abstratos de dados O conceito de tipo foi um passo importante no sentido de atingir uma linguagem capaz de suportar

    programao estruturada. Porm, at agora, no possvel usar uma metodologia na qual os programas sejam desenvolvidos por meio de decomposies de problemas baseadas no reconhecimento de abstraes. Para a abstrao de dados adequada a este propsito no basta meramente classificar os objetos quanto a suas estruturas de representao; em vez disso, os objetos devem ser classificados de acordo com o seu comportamento esperado. Esse comportamento expressado em termos das operaes que fazem sentido sobre esses dados; estas operaes so o nico meio de criar, modificar e se ter acesso aos objetos.

  • Classes em C++ Uma classe em C++ o elemento bsico sobre o qual toda orientao por objetos est apoiada. Em

    primeira instncia, uma classe uma extenso de uma estrutura, que passa a ter no apenas dados, mas tambm funes. A idia que tipos abstratos de dados no so definidos pela sua representao interna, e sim pelas operaes sobre o tipo. Ento no h nada mais natural do que incorporar estas operaes no prprio tipo.Estas operaes s fazem sentido quando associadas s suas representaes.

    No exemplo da calculadora, um candidato natural a se tornar uma classe a pilha. Esta uma estrutura bem definida; no seu header file esto tanto a sua representao (struct Stack) quanto as funes para a manipulao desta representao. Seguindo a idia de classe, estas funes no deveriam ser globais, mas sim pertencerem estrutura Stack. A funo empty seria uma das que passariam para a estrutura. A implementao de empty pode ficar dentro da prpria estrutura:

    struct Stack {// ...int empty() { return top == 0; }

    };ou fora:

    struct Stack {// ...int empty();

    };

    int Stack::empty(void) { return top == 0; }

    No segundo caso a declarao fica no header file e a implementao no .c. A diferena entre as duas opes ser explicada na seo sobre funes inline.

    Com esta declarao, as funes so chamadas diretamente sobre a varivel que contm a representao:

    void main(void){

    struct Stack s;s.empty();

    }Repare que a funo deixou de ter como parmetro uma pilha. Isto era necessrio porque a funo

    estava isolada da representao. Agora no, a funo faz parte da estrutura. Automaticamente todos os campos da estrutura passam a ser visveis dentro da implementao da funo, sem necessidade se especificar de qual pilha o campo top deve ser testado (caso da funo empty). Isto j foi dito na chamada da funo.

    O paradigma da orientao a objetos Esta seo apresenta cinco componentes chave do paradigma de orientao por objetos. As

    componentes so objeto, mensagem, classe, instncia e mtodo. Eis as definies: Objeto: uma abstrao encapsulada que tem um estado interno dado por uma lista de atributos

    cujos valores so nicos para o objeto. O objeto tambm conhece uma lista de mensagens que ele pode responder e sabe como responder cada uma. Encapsulamento e abstrao so definidos mais frente.

    Mensagem: representada por um identificador que implica em uma ao a ser tomada pelo objeto que a recebe. Mensagens podem ser simples ou podem incluir parmetros que afetam como o objeto vai responder mensagem. A resposta tambm influenciada pelo estado interno do objeto.

    Classe: um modelo para a criao de um objeto. Inclui em sua descrio um nome para o tipo de objeto, uma lista de atributos (e seus tipos) e uma lista de mensagens com os mtodos correspondentes que o objeto desta classe sabe responder.

    Instncia: um objeto que tem suas propriedades definidas na descrio da classe. As propriedades que so nicas para as instncias so os valores dos atributos.

    Mtodo: uma lista de instrues que define como um objeto responde a uma mensagem em particular. Um mtodo tipicamente consiste de expresses que enviam mais mensagens para objetos. Toda mensagem em uma classe tem um mtodo correspondente.

  • Traduzindo estas definies para o C++, classes so estruturas, objetos so variveis do tipo de alguma classe (instncia de alguma classe), mtodos so funes de classes e enviar uma mensagem para um objeto chamar um mtodo de um objeto.

    Resumindo:

    Objetos so instncias de classes que respondem a mensagens de acordo com os mtodos e atributos, descritos na classe.

    Exerccio 1 - Calculadora RPN com classes Transformar a pilha utilizada pela calculadora em uma classe.

  • Captulo 2a Descrio de recursos de C++ no relacionados s classes

    Comentrios O primeiro recurso apresentado simplesmente uma forma alternativa de comentar o cdigo. Em

    C++, os caracteres // iniciam um comentrio que termina no fim da linha na qual esto estes caracteres. Por exemplo:

    int main(void){

    return -1; // retorna o valor -1 para o sistema operacional}

    Declarao de variveis Em C as variveis s podem ser declaradas no incio de um bloco. Se for necessrio usar uma varivel

    no meio de uma funo existem duas solues: voltar ao incio da funo para declarar a varivel ou abrir um novo bloco, simplesmente para possibilitar uma nova declarao de varivel.

    C++ no tem esta limitao, e as variveis podem ser declaradas em qualquer ponto do cdigo:

    void main(void){

    int a;a = 1;printf("%d\n", a);// ...char b[] = "teste";printf("%s\n", b);

    }O objetivo deste recurso minimizar declaraes de variveis no inicializadas. Se a varivel pode

    ser declarada em qualquer ponto, ela pode ser sempre inicializada na prpria declarao. Um exemplo comum o de variveis usadas apenas para controle de loops:

    for (int i=0; i

  • struct a a1; // em C o tipo usado como "struct a"a a2; // em C++ a palavra struct

    // no mais necessria}

    Declarao de unies Em C++ as unies podem ser annimas. Uma unio annima uma declarao da forma:

    union { lista dos campos };Neste caso, os campos da unio so usados como se fossem declarados no escopo da prpria unio:

    void f(void){

    union { int a; char *str; };a = 1;// ...str = malloc( 10 * sizeof(char) );

    }a e str so campos de uma unio annima, e so usados como se tivessem sido declarados como

    variveis da funo. Mas na realidade as duas tem o mesmo endereo. Um uso mais comum para unies annimas dentro de estruturas:

    struct A {int tipo;union {

    int inteiro;float real;void *ponteiro;

    };};

    Os trs campos da unio podem ser acessados diretamente, da mesma maneira que o campo tipo.

    Prottipos de funes Em C++ uma funo s pode ser usada se esta j foi declarada. Em C, o uso de uma funo no

    declarada geralmente causava uma warning do compilador, mas no um erro. Em C++ isto um erro. Para usar uma funo que no tenha sido definida antes da chamada tipicamente chamada de

    funes entre mdulos , necessrio usar prottipos. Os prottipos de C++ incluem no s o tipo de retorno da funo, mas tambm os tipos dos parmetros:

    void f(int a, float b); // prottipo da funo f

    void main(void){

    f(1, 4.5); // o prottipo possibilita a utilizao de f aqui}

    void f(int a, float b){

    printf("%.2f\n", b+a+0.5);}

    Uma tentativa de utilizar uma funo no declarada gera um erro de smbolo desconhecido.

    Funes que no recebem parmetros Em C puro, um prottipo pode especificar apenas o tipo de retorno de uma funo, sem dizer nada

    sobre seus parmetros. Por exemplo,

    float f(); // em C, no diz nada sobre os parmetros de f um prottipo incompleto da funo f. De acordo com a seo anterior, um prottipo incompleto no

    faz muito sentido. Na realidade, esta uma das diferenas entre C e C++. Um compilador de C++

    fabricioRealce

    fabricioRealce

  • interpretar a linha acima como o prottipo de uma funo que retorna um float e no recebe nenhum parmetro. Ou seja, exatamente equivalente a uma funo (void):

    float f(); // em C++ o mesmo que float f(void);

    Funes inline Funes inline so comuns em C++, tanto em funes globais como em mtodos. Estas funes tem

    como objetivo tornar mais eficiente, em relao velocidade, o cdigo que chama estas funes. Elas so tratadas pelo compilador quase como uma macro: a chamada da funo substituda pelo corpo da funo. Para funes pequenas, isto extremamente eficiente, j que evita gerao de cdigo para a chamada e o retorno da funo.

    O corpo de funes inline pode ser to complexo quanto uma funo normal, no h limitao alguma. Na realidade o fato de uma funo ser inline ou no apenas uma otimizao; a semntica de uma funo e sua chamada a mesma seja ela inline ou no.

    Este tipo de otimizao normalmente s utilizado em funes pequenas, pois funes inline grandes podem aumentar muito o tamanho do cdigo gerado. Do ponto de vista de quem chama a funo, no faz nenhuma diferena se a funo inline ou no.

    Nem todas as funes declaradas inline so expandidas como tal. Se o compilador julgar que a funo muito grande ou complexa, ela tratada como uma funo normal. No caso de o programador especificar uma funo como inline e o compilador decidir que ela ser uma funo normal, ser sinalizada uma warning. O critrio de quando expandir ou no funes inline no definido pela linguagem C++, e pode variar de compilador para compilador.

    Para a declarao de funes inline foi criada mais uma palavra reservada, inline. Esta deve preceder a declarao de uma funo inline:

    inline double quadrado(double x){

    return x * x;}

    Neste caso a funo quadrado foi declarada como inline. A chamada desta funo feita normalmente:

    {double c = quadrado( 7 );double d = quadrado( c );

    }Assim como a chamada feita da mesma maneira, o significado tambm o mesmo com ou sem

    inline. A diferena fica por conta do cdigo gerado. O trecho de cdigo acima ser compilado como se fosse:

    {double c = 7 * 7;double d = c * c;

    }Alm de ser mais eficiente, possibilita, por parte do compilador, otimizaes extras. Por exemplo,

    calcular automaticamente, durante a compilao, o resultado de 7*7, gerando cdigo para uma simples atribuio de 49 a c.

    Fazendo uma comparao com as macros, que so usadas em C para produzir este efeito, nota-se que funes inline as substituem com vantagens. A primeira que uma macro uma simples substituio de texto, enquanto que funes inline so elementos da linguagem, e so verificadas quanto a erros. Alm disso, macros no podem ser usadas exatamente como funes, como mostra o exemplo a seguir:

    #define quadrado(x) ((x)*(x))void main(void){

    double a = 4;double b = quadrado(a++);

    }Antes da compilao, a macro ser expandida para:

    double b = ((a++)*(a++));

    fabricioRealce

  • A execuo desta linha resultar na atribuio de 4*5 = 20 a b, alm de incrementar duas vezes a varivel a. Com certeza no era este o efeito desejado.

    Assim como funes, mtodos tambm podem ser declarados como inline. No caso de mtodos no necessrio usar a palavra reservada inline. A regra a seguinte: funes com o corpo declarado dentro da prpria classe so tratadas como inline; funes declaradas na classe mas definidas fora no so inline.

    Supondo uma classe Pilha, o mtodo vazia simples o suficiente para justificar uma funo inline:

    struct Pilha {elemPilha* topo;int vazia() { return topo==NULL; }void push(int v);int pop();

    };Alguns detalhes devem ser levados em considerao durante a utilizao de funes inline. O que o

    compilador faz quando se depara com uma chamada de funo inline? O corpo da funo deve ser expandido no lugar da chamada. Isto significa que o corpo da funo deve estar disponvel para o compilador antes da chamada.

    Um mdulo C++ composto por dois arquivos, o arquivo com as declaraes exportadas (.h) e outro com as implementaes (.c, .cc ou .cpp). Tipicamente, uma funo exportada por um mdulo tem o seu prottipo no .h e sua implementao no .c. Isso porque um mdulo no precisa conhecer a implementao de uma funo para us-la. Mas isso s verdade para funes no inline. Por causa disso, funes inline exportadas precisam ser implementadas no .h e no mais no .c.

    Referncias Referncia um novo modificador que permite a criao de novos tipos derivados. Assim como pode-

    se criar um tipo ponteiro para um inteiro, pode-se criar uma referncia para um inteiro. A declarao de uma referncia anloga de um ponteiro, usando o caracter & no lugar de *.

    Uma referncia para um objeto qualquer , internamente, um ponteiro para o objeto. Mas, diferentemente de ponteiros, uma varivel que uma referncia utilizada como se fosse o prprio objeto. Os exemplos deixaro estas idias mais claras. Vamos analisar referncias em trs utilizaes: como variveis locais, como tipos de parmetros e como tipo de retorno de funes.

    Uma varivel local que seja uma referncia deve ser sempre inicializada; a no inicializao causa um erro de compilao. Como as referncias se referenciam a objetos, a inicializao no pode ser feita com valores constantes:

    {int a; // ok, varivel normalint& b = a; // ok, b uma referencia para aint& c; // erro! no foi inicializadaint& d = 12; // erro! inicializao invlida

    }A varivel b utilizada como se fosse realmente um inteiro, no h diferena pelo fato de ela ser uma

    referncia. S que b no um novo inteiro, e sim uma referncia para o inteiro guardado em a. Qualquer alterao em a se reflete em b e vice versa. como se b fosse um novo nome para a mesma varivel a:

    {int a = 10;int& b = a;printf("a=%d, b=%d\n", a, b); // produz a=10, b=10a = 3;printf("a=%d, b=%d\n", a, b); // produz a=3, b=3b = 7;printf("a=%d, b=%d\n", a, b); // produz a=7, b=7

    }No caso de a referncia ser um tipo de algum argumento de funo, o parmetro ser passado por

    referncia, algo que no existia em C e era simulado passando-se ponteiros:

    void f(int a1, int &a2, int *a3){

    a1 = 1; // altera cpia locala2 = 2; // altera a varivel passada (b2 de main)

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

  • *a3 = 3; // altera o contedo do endereo de b3}

    void main(){

    int b1 = 10, b2 = 20, b3 = 30;f(b1, b2, &b3);printf("b1=%d, b2=%d, b3=%d\n", b1, b2, b3);// imprime b1=10, b2=2, b3=3

    }O efeito o mesmo para b2 e b3, mas repare que no caso de b3 passado o endereo explicitamente, e

    a funo tem que tratar o parmetro como tal. Falta ainda analisar um outro uso de referncias, quando esta aparece como tipo de retorno de uma

    funo. Por exemplo:

    int& f(){

    static int global;return global; // retorna uma referncia para a varivel

    }

    void main(){

    f() = 12; // altera a varivel global}

    importante notar que este exemplo vlido porque global uma varivel static de f, ou seja, uma varivel global com escopo limitado a f. Se global fosse uma varivel local comum, o valor de retorno seria invlido, pois quando a funo f terminasse a varivel global no existiria mais, e portanto a referncia seria invlida. Como um ponteiro perdido. Outras sees apresentam exemplos prticos desta utilizao.

    Alocao de memria A alocao dinmica de memria, que em C era tratada com as funes malloc e free, diferente em

    C++. Programas C++ no precisam mais usar estas funes. Para o gerenciamento da memria, foram criados dois novos operadores, new e delete, que so duas palavras reservadas. O operador new aloca memria e anlogo ao malloc; delete desaloca memria e anlogo ao free. A motivao desta modificao ficar clara no estudo de classes, mais especificamente na parte de construtores e destrutores.

    Por ser um operador da linguagem, no mais necessrio, como no malloc, calcular o tamanho da rea a ser alocada. Outra preocupao no mais necessria a converso do ponteiro resultado para o tipo correto. O operador new faz isso automaticamente:

    int * i1 = (int*)malloc(sizeof(int)); // Cint * i2 = new int; // C++

    A alocao de um vetor tambm bem simples:

    int * i3 = (int*)malloc(sizeof(int) * 10); // Cint * i4 = new int[10]; // C++

    A liberao da memria alocada feita pelo operador delete. Este operador pode ser usado em duas formas; uma para desalocar um objeto e outra para desalocar um vetor de objetos:

    free(i1); // alocado com malloc (C )delete i2; // alocado com new (C++)free(i3); // alocado com malloc (C )delete [] i4; // alocado com new[] (C++)

    A utilizao de delete para desalocar um vetor, assim como a utilizao de delete[] para desalocar um nico objeto tem consequncias indefinidas. A necessidade de diferenciao se explica pela existncia de destrutores, apresentados mais frente.

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

  • Valores default para parmetros de funes Em C++ existe a possibilidade de definir valores default para parmetros de uma funo. Por

    exemplo, uma funo que imprime uma string na tela em alguma posio especificada. Se nenhuma posio for especificada, a string deve ser impressa na posio do cursor:

    void impr( char* str, int x = -1, int y = -1){

    if (x == -1) x = wherex();if (y == -1) y = wherey();gotoxy( x, y );cputs( str );

    }Esta definio indica que a funo impr tem trs parmetros, sendo que os dois ltimos tem valores

    default. Quando uma funo usa valores default, os parmetros com default devem ser os ltimos. A funo acima pode ser utilizada das seguintes maneiras:

    impr( "especificando a posio", 10, 10 ); // x=10, y=10impr( "s x", 20 ); // x=20, y=-1impr( "nem x nem y" ); // x=-1, y=-1

    O uso deste recurso tambm envolve alguns detalhes. A declarao do valor default s pode aparecer uma vez. Isto deve ser levado em considerao ao definir funes com prottipos. A maneira correta especifica o valor default apenas no prottipo:

    void impr( char* str, int x = -1, int y = -1 );...

    void impr( char* str, int x, int y ){

    ...

    }Mas a regra diz apenas que os valores s aparecem em um lugar. Nada impede a seguinte construo:

    void impr( char* str, int x, int y );...

    void impr( char* str, int x = -1, int y = -1 ){

    ...

    }Nesse caso, antes da definio do corpo de impr ela ser tratada como uma funo sem valores

    default.

    Sobrecarga de nomes de funes Este novo recurso permite que um nome de funo possa ter mais de um significado, dependendo do

    contexto. Ter mais de um significado quer dizer que um nome de funo pode estar associado a vrias implementaes diferentes. Apesar disso soar estranho para pessoas familiarizadas com C, no dia a dia aparecem situaes idnticas no uso da nossa linguagem. Consideremos o verbo tocar; podemos us-lo em diversas situaes. Podemos tocar violo, tocar um disco etc. Em C++, diz-se que o verbo tocar est sobrecarregado. Cada contexto est associado a um significado diferente, mas todos esto conceitualmente relacionados.

    Um exemplo simples uma funo que imprime um argumento na tela. Seria interessante se pudssemos usar esta funo para vrios tipos de argumentos, mas cada tipo exige uma implementao diferente. Com sobrecarga isto possvel; o compilador escolhe que implementao usar dependendo do contexto. No caso de funes o contexto o tipo dos parmetros (s os parmetros, no os resultados):

    display( "string" );display( 123 );display( 3.14159 );

    Na realidade, a maioria das linguagens usa sobrecarga internamente, mas no deixa este recurso disponvel para o programador. C uma destas linguagens. Por exemplo:

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

  • a = a + 125;b = 3.14159 + 1.17;

    O operador + est sobrecarregado. Na primeira linha, ele se refere a uma soma de inteiros; na segunda a uma soma de reais. Estas somas exigem implementaes diferentes, e como se existissem duas funes, a primeira sendo

    int +(int, int);e a outra como

    float +(float, float);Voltando funo display, preciso definir as vrias implementaes requeridas:

    void display( char *v ) { printf("%s", v); }void display( int v ) { printf("%d", v); }void display( float v ) { printf("%f", v); }

    A simples declarao destas funes j tem todas as informaes suficientes para o compilador fazer a escolha correta. Isto significa que as funes no so mais distinguidas apenas pelo seu nome, mas pelo nome e pelo tipo dos parmetros.

    A tentativa de declarar funes com mesmo nome que no possam ser diferenciadas pelo tipo dos parmetros causa um erro:

    void f(int a);int f(int b); // erro! redeclarao de f!!!

    O uso misturado de sobrecarga e valores default para parmetros tambm pode causar erros:

    void f();void f(int a = 0);

    void main(){

    f( 12 ); // ok, chamando f(int)f(); // erro!! chamada ambgua: f() ou f(int = 0)???

    }Repare que nesse caso o erro ocorre no uso da funo, e no na declarao.

    Parmetros de funes no utilizados A maioria dos compiladores gera uma warning quando um dos parmetros de uma funo no

    utilizado no seu corpo. Por exemplo:

    void f(int a){

    return 1;}

    Este um caso tpico. O aviso do compilador faz sentido, j que a no utilizao do parmetro a pode ser uma erro na lgica da funo. Mas e se for exatamente esta a implementao da funo f? primeira vista pode parecer estranho uma funo que no utilize algum de seus parmetros, mas em programao com callbacks (isto ficar mais claro a partir da introduo de programao orientada a eventos) comum as funes ignorarem alguns parmetros. Se for este o caso, no necessrio conviver com as warnings do compilador. Basta omitir o nome do parmetro que no ser usado:

    void f(int){

    return 1;}

    Esta uma funo que recebe um inteiro como parmetro, mas este inteiro no utilizado na implementao da funo.

    fabricioRealce

  • Operador de escopo C++ possui um novo operador que permite o acesso a nomes declarados em escopos que no sejam o

    corrente. Por exemplo, considerando o seguinte programa C:

    char *a;

    void main(void){

    int a;a = 23;/* como acessar a varivel global a??? */

    }A declarao da varivel local a esconde a global. Apesar de a varivel a global existir, no h como

    referenciar o seu nome, pois no escopo da funo main, o nome a est associado local a. O operador de escopo possibilita o uso de nomes que no esto no escopo corrente, o que pode ser

    usado neste caso:

    char *a;

    void main(void){

    int a;a = 23;::a = "abc";

    }A sintaxe deste operador a seguinte:

    escopo::nome,

    onde escopo o nome da classe onde est declarado no nome nome. Se escopo no for especificado, como no exemplo acima, o nome procurado no espao global.

    Outros usos deste operador so apresentados nas sees sobre classes aninhadas, campos de estruturas static e mtodos static.

    Incompatibilidades entre C e C++ Teoricamente, um programa escrito em C compatvel com um compilador C++. Na realidade esta

    compatibilidade no de 100%, pois s o fato de C++ ter mais palavras reservadas j inviabiliza esta compatibilidade total. Outros detalhes contribuem para que um programa C nem sempre seja tambm um programa C++ vlido.

    Sero discutidos os pontos principais destas incompatibilidades; tanto as diferenas na linguagem como a utilizao de cdigos C e C++ juntos em um s projeto. No entanto a discusso no ser extensiva, pois algumas construes suspeitas podem levar a vrias pequenas incompatibilidades, como por exemplo:

    a = b //* comentrioetc. */ 2;

    Extrados os comentrios, este cdigo em C fica sendo:

    a = b / 2;

    Enquanto que em C++, o resultado diferente:

    a = betc. */ 2;

    Casos como estes so incomuns e no vale a pena analis-los.

    fabricioRealce

  • Palavras reservadas Talvez a maior incompatibilidade seja causada pelo simples fato que C++ tem vrias outras palavras

    reservadas. Isto significa que qualquer programa C que declare um identificador usando uma destas palavras no um programa C++ correto. Aqui est uma lista com as novas palavras reservadas:

    catch new templateclass operator thisdelete private throwfriend protected tryinline public virtual

    A nica soluo para traduzir programas que fazem uso destas palavras trocar os nomes dos identificadores, o que pode ser feito usando diretivas #define.

    Exigncia de prottipos Um dos problemas que pode aparecer durante a compilao de um programa C est relacionado ao

    fato de que prottipos no so obrigatrios em C. Como C++ exige prottipos, o que era uma warning C pode se transformar em um erro C++.

    Estes erros simplesmente foram o programador a fazer uma coisa que j devia ter sido feita mesmo com o compilador C: usar prottipos para as funes.

    void main(void){

    printf("teste"); // C: warning! printf undeclared// C++: error! printf undeclared

    }

    Funes que no recebem parmetros Programas que utilizam prottipos incompletos, ou seja, prottipos que s declaram o tipo de retorno

    de uma funo, podem causar erros em C++. Este tipo de prottipo considerado obsoleto em C, mas ainda vlido. Em C++, estes prottipos so interpretados como declaraes de funes que no recebem parmetros, o que certamente causar erros se a funo receber algum:

    float square(); // C: prottipo incompleto, no se sabe nada// sobre os parmetros// C++: prottipo completo,// no recebe parmetros

    void main(void){

    float a = square(3); // C: ok// C++: error! too many arguments

    }

    Estruturas aninhadas Outra incompatibilidade pode aparecer em programas C que declaram estruturas aninhadas. Em C, s

    h um escopo para os tipos: o global. Por esse motivo, mesmo que uma estrutura tenha sido declarada dentro de outra, ou seja, aninhada, ela pode ser usada como se fosse global. Por exemplo:

    struct S1 {struct S2 {

    int a;} b;

    };

    void main(void){

    struct S1 var1;

    Esta lista no definitiva, j que constantemente novos recursos vo sendo adicionados ao padro.

    fabricioRealce

  • struct S2 var2; // ok em C, errado em C++}

    Este um programa correto em C. Em C++, uma estrutura s vlida no escopo em que foi declarada. No exemplo acima, por ter sido declarada dentro de S1, a estrutura S2 s pode ser usada dentro de S1. Nesse caso, a tentativa de declarar a varivel var2 causaria um erro, pois S2 no um nome global.

    Em C++, S2 pode ser referenciada utilizando-se o operador de escopo:

    struct S1::S2 var2; // soluo em C++

    Uso de bibliotecas C em programas C++ Esta seo discute um problema comum ao se tentar utilizar bibliotecas C em programas C++. A

    origem do problema o fato de C++ permitir sobrecarga de nomes de funes. Vamos analisar o trecho de cdigo abaixo:

    void f(int);

    void main(void){

    f(12);}

    O programa acima declara uma funo f que ser chamada com o argumento 12. O cdigo final gerado para a chamada da funo ter uma linha assim:

    call nome-da-funo-escolhida

    Este nome deve bater com o nome gerado durante a compilao da funo f, seja qual for o mdulo a que ela pertence. Caso isto no acontea, a linkedio do programa sinalizar erros de smbolos indefinidos. Isto significa que existem regras para a gerao dos nomes dos identificadores no cdigo gerado. Em C, esta regra simples: os smbolos tm o mesmo nome do cdigo fonte com o prefixo _. No caso acima, o cdigo gerado seria:

    call _f

    Entretanto, C++ no pode usar esta mesma regra simples, porque um mesmo nome pode se referir a vrias funes diferentes pelo mecanismo de sobrecarga. Basta imaginar que poderamos ter:

    void f(int);void f(char*);

    Nesse caso a regra de C no suficiente para diferenciar as duas funes. O resultado que C++ tem uma regra complexa para gerao dos nomes, que envolve codificao dos tipos tambm. O compilador Borland C++ 4.0 codifica as funes acima, respectivamente, como:

    @f$qi@f$qpc

    Isto deve ser levado em considerao na compilao da chamada da funo. A chamada de f com o argumento 12 pode gerar dois cdigos diferentes. Se a funo f for na realidade uma funo compilada em C, o seu nome ser _f, enquanto que se esta for uma funo C++, o seu nome ser @f$qi. Qual deve ser ento o cdigo gerado? Qual nome deve ser chamado?

    A nica maneira de resolver este problema indicar, no prottipo da funo, se esta uma funo C ou C++. No exemplo acima, o compilador assume que a funo C++. Se f for uma funo C, o seu prottipo deve ser:

    extern "C" void f(int);A construo extern C pode ser usada de outra maneira, para se aplicar a vrias funes de uma vez:

    extern "C"{void f(int);void f(char);// ...

    }

  • Dentro de um bloco extern pode aparecer qualquer tipo de declarao, no apenas funes. A soluo ento alterar os header files das bibliotecas C, explicitando que as funes no so C++. Para isso, basta envolver todas as declaraes em um bloco destes.

    Como uma declarao extern C s faz sentido em C++, ela causa um erro de sintaxe em C. Para que os mesmos header files sejam usados em programas C e C++, basta usar a macro pr-definida __cplusplus, definida em todo compilador C++. Com estas alteraes, um header file de uma biblioteca C ter a seguinte estrutura:

    #ifdef __cplusplusextern "C" {#endif

    // todas as declaraes// ...

    #ifdef __cplusplus}#endif

    Exerccio 2 - Calculadora RPN com novos recursos de C++ Analisar a calculadora alterando-a de acordo com os novos recursos de C++

    fabricioRealce

  • Captulo 2b Recursos de C++ relacionados s classes

    Classes aninhadas Declaraes de classes podem ser aninhadas. Uma classe aninhada no reside no espao de nomes

    global, como era em C. Para referenciar uma classe aninhada fora do seu escopo, preciso usar o operador de escopo.

    Este aninhamento usado para encapsular classes auxiliares que s fazem sentido dentro de um escopo reduzido.

    struct A {struct B {

    int b;};int a;

    };

    void main(){

    A a;A::B b;

    }O cdigo acima simplesmente declara a estrutura B dentro de A; no existe nenhum campo de A que

    seja do tipo B.

    Declarao incompleta Em C, a declarao de estruturas mutuamente dependentes feita naturalmente:

    struct A { struct B *next; };struct B { struct A *next; };

    Em C++ a construo acima pode ser usada. No entanto, como a declarao de uma estrutura em C++ j define o nome como um tipo sem necessidade de typedefs, normal em C++ o uso de tipos sem o uso da palavra reservada struct:

    struct A { B *next; }; // Erro! B indefinidostruct B { A *next; };

    No caso de tipos mutuamente dependentes, isto s possvel usando uma declarao incompleta:

    struct B; // declarao incompleta de Bstruct A { B *next; };struct B { A *next; };

    Assim como em C, antes da declarao completa da estrutura s possvel usar o nome para declarar ponteiros e referncias deste tipo. Como as informaes esto incompletas, no possvel declarar variveis deste tipo nem utilizar campos da estrutura.

    Mtodos const Mtodos const so mtodos que no alteram o estado interno de um objeto. Assim como era possvel

    declarar variveis e parmetros const em C, possvel declarar um mtodo const em C++. A especificao const faz parte da declarao do mtodo.

    A motivao para esta nova declarao pode ser esclarecida com o exemplo abaixo:

    struct A {int value;int get() { return value; }void put (int v) { value = v; }

    };

  • void f(const A a){

    // parmetro a no pode ser modificado// quais mtodos de a podem ser chamados???int b = a.get(); // Erro! mtodo no const

    }A funo f no pode alterar o seu parmetro por causa da declarao const. Isto significa que os

    campos de a podem ser lidos mas no podem ser modificados. E quanto aos mtodos? O que define quais mtodos podem ser chamados a sua implementao. Esta verificao no pode ser feita pelo compilador, j que na maioria das vezes o cdigo dos mtodos no est disponvel. responsabilidade do programador declarar quais mtodos no modificam o estado interno do objeto. Estes mtodos podem ser aplicados a objetos declarados const.

    No exemplo acima, o mtodo get pode ser declarado const, o que possibilita a sua chamada na funo f. Se o mtodo put for declarado const, o compilador acusa um erro de compilao pois o campo value modificado em sua implementao:

    struct A {int value;int get() const { return value; }void put (int v) { value = v; }

    };

    void f(const A a){

    // parmetro a no pode ser modificadoint b = a.get(); // ok, mtodo const

    }

    this Em todo mtodo no static (ver seo sobre mtodos static), a palavra reservada this um ponteiro

    para o objeto sobre o qual o mtodo est executando. Todos os mtodos de uma classe so sempre chamados associados a um objeto. Durante a execuo

    de um mtodo, os campos do objeto so manipulados normalmente, sem necessidade de referncia ao objeto. E se um mtodo precisar acessar no os campos de um objeto, mas o prprio objeto? O exemplo abaixo ilustra este caso:

    struct A {int i;A& inc();

    };

    // Este mtodo incrementa o valor interno// e retorna o prprio objetoA& A::inc(){

    // estamos executando este cdigo para obj1 ou obj2?// como retornar o prprio objeto?i++;return *this; // this aponta para o prprio objeto

    }

    void main(){

    A obj1, obj2;obj1.i = 0;obj2.i = 100;for (j=0; j

  • printf("%d\n", (obj1.inc()).i);}

    O tipo de this dentro de um mtodo de uma classe X

    X* const

    a no ser que o mtodo seja declarado const. Nesse caso, o tipo de this :

    const X* const

    Campos de estrutura static Em C++, membros de uma classe podem ser static. Quando uma varivel declarada static dentro de

    uma classe, todas as instncias de objetos desta classe compartilham a mesma varivel. Uma varivel static uma varivel global, s que com escopo limitado classe.

    A declarao de um campo static aparece na declarao da classe, junto com todos os outros campos. Como a declarao de uma classe normalmente includa em vrios mdulos de uma mesma aplicao via header files (arquivos .h), a declarao dentro da classe equivalente a uma declarao de uma varivel global extern. Ou seja, a declarao apenas diz que a varivel existe; algum mdulo precisa defini-la. O exemplo abaixo ilustra a utilizao de campos static:

    struct A {int a;static int b; // declara a varivel,

    // equivalente ao uso de extern para// variveis globais

    };

    int A::b = 0; // define a varivel criando o seu espao// esta a hora de inicializar

    void main(void){

    A a1, a2;a1.a = 0; // modifica o campo a de a1a1.b = 1; // modifica o campo b compartilhado por a1 e a2a2.a = 2; // modifica o campo a de a1a2.b = 3; // modifica o campo b compartilhado por a1 e a2printf("%d %d %d %d", a1.a, a1.b, a2.a, a2.b);// imprime 0 3 2 3

    }Se a definio

    int A::b = 0;

    for omitida, o arquivo compilado mas o linker acusa um erro de smbolo no definido. Como uma varivel esttica nica para todos os objetos da classe, no necessrio um objeto para

    referenciar este campo. Isto pode ser feito com o operador de escopo:

    A::b = 4;

    Mtodos static Assim como campos static so como variveis globais com escopo reduzido classe, mtodos static

    so como funes globais com escopo reduzido classe. Isto significa que mtodos static no tem o parmetro implcito que indica o objeto sobre o qual o mtodo est sendo executado (this), e portanto apenas os campos static podem ser acessados:

    struct A {int a;static int b;static void f();

    };

    fabricioRealce

  • int A::b = 10;

    void A::f(){

    a = 10; // errado, a s faz sentido com um objetob = 10; // ok, b declarado static

    }

    Ponteiros para mtodos possvel obter o ponteiro para um mtodo. Isto pode ser feito aplicando o operador & a um nome de

    mtodo totalmente qualificado, ou seja:

    & classe::mtodo

    Uma varivel do tipo ponteiro para um membro da classe X obtida com o declarador X::*:

    struct A {void f(int);

    };

    void main(){

    void (A::*p) (int); // declara a varivel pp = &A::f; // obtm o ponteiro para f

    A a1;A* a2 = new A;

    (a1.*p)(10); // aplica p a a1(a2->*p)(20); // aplica p a a2

    }A aplicao dos mtodos feita utilizando-se os novos operadores .* e ->*.

  • Captulo 3

    Encapsulamento Como foi visto anteriormente, um TAD definido pela sua interface, ou seja, como ele manipulado.

    Um TAD pode ter diversas implementaes possveis, e, independentemente desta ou daquela implementao, objetos deste tipo so usados sempre da mesma forma. Os atributos alm da interface, ou seja, os atributos dependentes da implementao, no precisam e no devem estar disponveis para o usurio de um objeto, pois este deve ser acessado exclusivamente atravs da interface definida. O ato de esconder estas informaes chamado de encapsulamento.

    Os mecanismos apresentados at aqui permitem a definio da interface em um tipo, permitindo cdigos bem mais modulares e organizados. No entanto, no permitem o encapsulamento de dados e/ou cdigo.

    Controle de acesso - public e private Parte do objetivo de uma classe esconder o mximo de informao possvel. Ento necessrio

    impor certas restries na maneira como uma classe pode ser manipulada, e que dados e cdigo podem ser usados. Podem ser estabelecidos trs nveis de permisso de acordo com o contexto de uso dos atributos:

    nos mtodos da classe a partir de objetos da classe mtodos de classes derivadas (este ponto ser visto posteriormente) Cada um destes trs contextos tm privilgios de acesso diferenciados; cada um tem uma palavra

    reservada associada, private, public e protected respectivamente. O exemplo abaixo ilustra o uso destas novas palavras reservadas:

    struct controle {private:

    int a;int f1( char* b );

    protected:int b;int f2( float );

    public:int c;float d;void f3( controle* );

    };As sees podem ser declaradas em qualquer ordem, inclusive podem aparecer mais de uma vez. O

    exemplo abaixo equivalente ao apresentado acima:

    struct controle {int c;int d;

    private:int a;

    protected:int b;

    protected:int f2( float );

    public:void f3( controle* );

    private:int f1( char* b );

    };Atributos private so os mais restritos. Somente a prpria classe pode acessar os atributos privados.

    Ou seja, somente os mtodos da prpria classe tem acesso a estes atributos.

    struct SemUso {private:

  • int valor;void f1() { valor = 0; }int f2() { return valor; }

    };

    int main(){

    SemUso p; // cria um objeto do tipo SemUsop.f1(); // erro, f1 private!printf("%d", p.f2() ); // erro, f2 privatereturn 0;

    }No exemplo anterior, todos os membros so privados, o que impossibilita o uso de um objeto da classe

    SemUso. Para que as funes f1 e f2 pudessem ser usadas, elas precisariam ser pblicas. O cdigo a seguir uma modificao da declarao da classe SemUso para tornar as funes f1 e f2 pblicas e portanto passveis de serem chamadas em main.

    struct SemUso {private:

    int valor;public:

    void f1() { valor = 0; }int f2() { return valor; }

    };Membros protected sero explicados quando forem introduzidas as classes derivadas. Para exemplificar melhor o uso do controle de acesso, vamos considerar uma implementao de um

    conjunto de inteiros. As operaes necessrias so, por exemplo, inserir e retirar um elemento, verificar se um elemento pertence ao conjunto e a cardinalidade do conjunto. Ento o nosso conjunto ter pelo menos o seguinte:

    struct Conjunto {void insere(int n);void retira(int n);int pertence(int n);int cardinalidade();

    };Se a implementao deste conjunto usar listas encadeadas, preciso usar uma estrutura auxiliar

    elemento que seriam os ns da lista. A nova classe teria ainda uma varivel private que seria o ponteiro para o primeiro elemento da lista. Outra varivel private seria um contador de elementos. Vamos acrescentar ainda um mtodo para limpar o conjunto, para ser usado antes das funes do conjunto propriamente ditas. Eis a definio da classe:

    struct Conjunto {private:

    struct listElem {listElem *prox;int valor;

    };listElem* lista;int nElems;

    public:void limpa() { nElems=0; lista=NULL; }void insere(int n);void retira(int n);int pertence(int n);int cardinalidade();

    };Como somente os mtodos desta classe tem acesso aos campos privados, a estrutura interna no pode

    ser alterada por quem usa o conjunto, o que garante a consistncia do conjunto.

    fabricioRealce

  • Declarao de classes com a palavra reservada class As classes podem ser declaradas usando-se a palavra reservada class no lugar de struct. A diferena

    entre as duas opes o nvel de proteo caso nenhum dos especificadores de acesso seja usado. Os membros de uma struct so pblicos, enquanto que em uma class, os membros so privados:

    struct A {int a; // a pblico

    };

    class B {int a; // a privado

    };Como as interfaces das classes devem ser as menores possveis e devem ser tambm explcitas, as

    declaraes com a palavra class so mais usadas. Com class, somente os nomes explicitamente declarados como public so exportados.

    A partir de agora os exemplos vo ser feitos usando-se a palavra class.

    Classes e funes friend Algumas vezes duas classes so to prximas conceitualmente que seria desejvel que uma delas

    tivesse acesso irrestrito aos membros da outra. No exemplo anterior, no h meio de percorrer o conjunto para, por exemplo, imprimir todos os

    elementos. Para fazer isto, seria preciso ter acesso ao dado lista e estrutura listElem, ambos private. Uma maneira de resolver este problema seria aumentar a interface do conjunto oferecendo funes para percorrer os elementos. Esta soluo seria artificial, pois estas operaes no fazem parte do TAD conjunto.

    A soluo normalmente usada a idia de iteradores. Um iterador atua sobre alguma coleo de elementos e a cada chamada retorna um elemento diferente da coleo, at que j tenha retornado todos. Um iterador para o nosso conjunto seria:

    class IteradorConj {Conjunto::listElem *corrente;

    public:void inicia( Conjunto* c ) { corrente = c->lista; }int terminou() { return corrent==NULL; }int proxElem(){ int n=corrente->valor; corrent=corrente->prox; return n; }

    };

    void main(){

    Conjunto conj; // cria conjuntoconj.limpa(); // inicializa para operaesconj.insere(10); //insere o elemento 10// ...IteradorConj it; // cria um iteradorit.inicia( &conj ); // inicializa o iterador com conjwhile (!it.terminou()) // percorre todos os elementos

    printf("%d\n", it.proxElem() ); // imprimindo-os}

    O problema aqui que o iterador usa dados privados de Conjunto, o que gera erros durante a compilao. Esse um caso em que as duas classes esto intimamente ligadas, e ento seria conveniente, na declarao de Conjunto, dar acesso irrestrito classe IteradorConj.

    Para isso existe a palavra reservada friend. Ela serve para oferecer acesso especial algumas classes ou funes. Na declarao da classe Conjunto possvel dar permisso irrestrita aos seus atributos. A classe Conjunto chega sua forma final:

    class Conjunto {friend class IteradorConj;

    struct listElem {

    fabricioRealce

  • ...

    ...

    Tambm possvel declarar funes friend. Nesse caso, a funo ter acesso irrestrito aos componentes da classe que a declarou como friend. Exemplo de funo friend:

    class No {friend int leValor( No* ); // d acesso privilegiado

    // funo leValorint valor;

    public:void setaValor( int v ) { valor=v; }

    };

    int leValor( No* n ){

    return n->valor; // acessa dado private de No}

    O recurso de classes e funes friend devem ser usados com cuidado, pois isto um furo no encapsulamento dos dados de uma classe. Projetos bem elaborados raramente precisam lanar mo de classes ou funes friend.

    Exerccio 3 - Calculadora RPN com controle de acesso Modificar a calculadora de acordo com o controle de acesso

    Construtores e destrutores Como o nome j indica, um construtor uma funo usada para construir um objeto de uma dada

    classe. Ele chamado automaticamente assim que um objeto criado. Analogamente, os destrutores so chamados assim que os objetos so destrudos.

    Assim como o controle de acesso desempenha um papel importante para manter a consistncia interna dos objetos, os construtores so fundamentais para garantir que um objeto recm criado esteja tambm consistente.

    No exemplo onde foi implementada a classe Conjunto, foi necessria a introduo de uma funo, limpa, que precisava ser chamada para inicializar o estado do objeto. Se esta funo no for chamada antes da manipulao de cada objeto, os resultados so imprevisveis (os campos nElems e lista contm lixo). Deixar esta chamada a cargo do programador aumentar o potencial de erro do programa. Na realidade, a funo limpa deveria ser um construtor de Conjunto. O compilador garante que o construtor a primeira funo a ser executada sobre um objeto. A mesma coisa acontecia em IteradorConj. A funo inicia deveria ser um construtor, pois antes desta chamada o estado do objeto imprevisvel.

    Destrutores so normalmente utilizados para liberar recursos alocados pelo objeto, como memria, arquivos etc.

    Declarao de construtores e destrutores Os construtores e destrutores so mtodos especiais. Nenhum dos dois tem um tipo de retorno, e o

    destrutor no pode receber parmetros, ao contrrio do construtor. A declarao de um construtor feita definindo-se um mtodo com o mesmo nome da classe. O nome

    do destrutor o nome da classe precedido de ~ (til). Ou seja, um construtor de uma classe X declarado como um mtodo de nome X, o nome do destrutor ~X; ambos sem tipo de retorno. O destrutor no pode ter parmetros; o construtor pode. Pelo mecanismo de sobrecarga (funes so diferenciadas no apenas pelo nome, mas pelos parmetros tambm), classes podem ter mais de um construtor.

    Com o uso de construtores, as classe Conjunto e IteradorConj passa a ser:

    class Conjunto {friend class IteradorConj;struct listElem {

    listElem *prox;int valor;

    };listElem* lista;int nElems;

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

  • public:Conjunto() { nElems=0; lista=NULL; }~Conjunto(); // desaloca os ns da listavoid insere(int n);void retira(int n);int pertence(int n);int cardinalidade();

    };

    class IteradorConj {Conjunto::listElem *corrente;

    public:IteradorConj( Conjunto* c ) { corrente = c->lista; }int terminou() { return corrent==NULL; }int proxElem(){ int n=corrente->valor; corrent=corrente->prox; return n; }

    };

    Chamada de construtores e destrutores Os construtores so chamados automaticamente sempre que um objeto da classe for criado. Ou seja,

    quando a varivel declarada (objetos alocados na pilha) ou quando o objeto alocado com new (objetos dinmicos alocados no heap).

    Os destrutores de objetos alocados na pilha so chamados quando o objeto sai do seu escopo. O destrutor de objetos alocados com new s chamado quando estes so desalocados com delete:

    struct A {A() { printf("construtor\n"); }~A() { printf("destrutor\n"); }

    };

    void main(){

    A a1; // chama construtor de a1{

    A a2; // chama construtor de a2A *a3 = new A; // chama construtor de a3

    } // chama destrutor de a2delete a3; // chama destrutor de a3

    } // chama destrutor de a1 interessante observar que os objetos globais j esto inicializados quando a funo main comea a

    ser executada. Isto significa que os construtores de objetos globais so chamados antes de main:

    A global;

    void main(){

    printf("main\n");}

    Este cdigo produz a seguinte sada:

    construtormaindestrutor

    Construtores com parmetros No exemplo sobre conjuntos, a classe IteradorConj possui um construtor que recebe um parmetro. Se

    os construtores existem para garantir a consistncia inicial dos objetos, o cdigo est indicando que, para que um iterador esteja consistente, necessrio fornecer um conjunto sobre o qual ser feita a iterao.

  • Se for possvel criar um IteradorConj sem fornecer este conjunto, ento o construtor no est garantindo nada. Mas no isto que acontece. A declarao de um construtor impe que os objetos s sejam criados atravs deles. Sendo assim, no mais possvel criar um IteradorConj sem fornecer um Conjunto. O exemplo abaixo utiliza estas classes e mostra como os construtores so chamados:

    void main(){

    Conjunto conj;conj.insere(10);// ...IteradorConj i; // erro! obrigatrio fornecer o parmetroIteradorConj it( &conj ); // cria um iterador passando conjwhile (!it.terminou()) // percorre os elementos

    printf("%d ", it.proxElem() ); // imprimindo-os}

    Caso o iterador seja alocado dinamicamente (com new), a sintaxe a seguinte:

    IteradorConj *i = new IteradorConj(&conj)No possvel alocar um vetor de objetos passando parmetros para o construtor. Por exemplo, no

    possvel criar um vetor de iteradores:

    Conjunto c;IteradorConj it(&c)[10]; // erro!!!IteradorConj *pit;pit = new(&c)[20]; // erro!!!

    Construtores gerados automaticamente O fato de algumas classes no declararem construtores no significa que elas no tenham construtores.

    Na realidade, o compilador gera alguns construtores automaticamente. Um dos construtores s gerado se a classe no declarar nenhum. Este o construtor vazio, que

    permite que os objetos sejam criados. Por exemplo, como a classe

    class X {int a;

    };no declara nenhum construtor, o compilador automaticamente gera um construtor vazio pblico para

    esta classe. A declarao abaixo equivalente:

    class X {int a;public: X() {} // construtor vazio

    };Outro construtor gerado o construtor de cpia. Este gerado mesmo que a classe declare algum

    outro construtor. O construtor de cpia de uma classe recebe como parmetro uma referncia para um objeto da prpria classe. A classe X acima possui este construtor:

    class X {int a;// public: X() {} construtor vazio// public: X(const X&); construtor de cpia

    };O construtor de cpia constri um objeto a partir de outro do mesmo tipo. O novo objeto uma cpia

    byte a byte do objeto passado como parmetro. Repare que a existncia deste construtor no um furo na consistncia dos objetos, j que ele s pode ser usado a partir de um objeto existente, e portanto, consistente. Este construtor pode ser chamado de duas formas:

    void main(){

    X a1; // usa construtor vazioX a2(a1); // usa construtor de cpia

  • X a3 = a2; // usa construtor de cpia}

    A atribuio s chama o construtor de cpia quando usada junto com a declarao do objeto. importante notar que este construtor pode ser redefinido, basta declarar um construtor com a mesma assinatura (ou seja, recebendo como parmetro uma referncia para um objeto da prpria classe).

    Objetos temporrios Assim como no preciso declarar uma varivel dos tipos primitivos sempre que se quer usar um

    valor temporariamente, possvel criar objetos temporrios em C++. Uma utilizao tpica a seguinte: quando uma funo aceita como parmetro um inteiro, e voc quer cham-la passando o valor 10, no necessrio atribuir 10 a uma varivel apenas para chamar a funo. O mesmo deve se aplicar a tipos definidos pelo usurio (classes):

    class A {public:

    A(int);~A();

    };

    void f(A);

    void main(){

    A a1(1);f(a1);f(A(10)); // cria um objeto temporrio do tipo A

    // passando 10 para o construtor// aps a chamada a f o objeto destrudo

    }

    Converso por construtores Um construtor com apenas um parmetro pode ser encarado como uma funo de converso do tipo

    do parmetro para o tipo da classe. Por exemplo,

    class A {public:

    A(int);A(char*, int = 0);

    };

    void f(A);

    void main(){

    A a1 = 1; // a1 = A(1)A a2 = "abc"; // a2 = A("abc", 0)a1 = 2; // a1 = A(2)f(3); // f(A(3))

    }Esta converso s feita em um nvel. Ou seja, se o construtor da classe A no aceita um determinado

    tipo, no feita uma tentativa de converter via outros construtores o tipo dado para o tipo aceito pelo construtor:

    class A {public: A(int);

    };

    class B {public: B(A);

    };

  • B a = 1; // erro: B(A(1)) no tentado

    No exemplo acima, pelo menos uma converso deve ser feita explicitamente:

    B a = A(1)

    Construtores privados Os construtores, assim como qualquer mtodo, podem ser privados. Como o construtor chamado na

    criao, os objetos s podero ser criados com este construtor dentro de mtodos da prpria classe ou em classes e funes friend.

    Destrutores privados Destrutores tambm podem ser privados. Isto significa que objetos desta classe s podem ser

    destrudos onde os destrutores podem ser chamados (mtodos da prpria classe, classes e funes friend). Usando este recurso, possvel projetar classes cujos objetos no so nunca destrudos. Outra possibilidade o projeto de objetos que no podem ser alocados na pilha, apenas dinamicamente. Exemplo:

    class A {~A() {}

    public:int a;

    };

    void main(){

    A a1; // erro! destrutor privado,// no pode ser chamado// quando o objeto sair do escopo

    A* a2 = new A; // ok, s no pode usar delete depois}

    No exemplo acima, o destrutor privado impe duas restries: objetos no podem ser alocados na pilha e, mesmo que sejam criados dinamicamente, no podem nunca ser destrudos. Para permitir a destruio dos objetos basta criar um mtodo que faa isso:

    class A {~A() {}

    public:int a;void destroy() { delete this; }

    };

    Inicializao de campos de classes com construtores Quando um objeto no tem um construtor sem parmetros, preciso passar obrigatoriamente valores

    como os parmetros. No caso do objeto ser uma varivel, basta definir os parmetros na hora da declarao:

    class A {public: A(int);

    };

    A a(123);

    Mas e se o objeto for um campo de uma outra classe? Nesse caso ele estar sendo criado quando um objeto desta outra classe for criado, e nessa hora os parmetros precisaro estar disponveis:

    class A {public: A(int);

    };

  • class B {A a;

    };Ao se criar um objeto do tipo B, que inteiro deve ser passado ao campo a? Nesse caso, o construtor de

    B tem que especificar este inteiro, e o compilador no gera um construtor vazio. Ou seja, a classe B tem que declarar um construtor para que seja possvel criar objetos deste tipo. A sintaxe a seguinte:

    class B {A a;

    public:B() : a(3) {}

    };

    Exerccio 4 - Calculadora RPN com construtores Utilizar construtores na calculadora

  • Captulo 4

    Sobrecarga de operadores O uso de funes sobrecarregadas no s uniformiza chamadas de funes para diferentes objetos

    como tambm permite que os nomes sejam mais intuitivos. Se um dos objetivos da sobrecarga permitir que as funes sejam chamadas pelo nome mais natural possvel, no importa se o nome j foi usado, porque no deixar o programador sobrecarregar tambm os operadores?

    Na realidade, um operador executa algum cdigo com alguns parmetros, assim como qualquer funo. A aplicao de um operador equivalente chamada de uma funo. Em C++ permitido sobrecarregar um operador, com o objetivo de simplificar a notao e uniformizar a expresso.

    Existem duas maneiras de implementar operadores para classes de objetos: como funes membro e como funes globais. Por exemplo, dado um objeto w e um operador unrio !, a expresso

    !w

    equivalente s chamadas de funes

    w.operator!(); // usando uma funo membrooperator!(w); // usando uma funo global

    Vejamos agora como seria com um operador binrio, por exemplo, &. A expresso

    x & y

    equivalente s chamadas

    x.operator&(y); // usando uma funo membrooperator&(x,y); // usando uma funo global

    Um detalhe importante que uma funo y.operator&(x) nunca ser considerada pelo compilador para resolver a expresso x&y, j que isto implicaria que o operador comutativo.

    Antes do primeiro exemplo, precisamos ter em mente que C++ no permite a criao de novos operadores; s podem ser redefinidos os operadores que j existem na linguagem. Isto implica que, por exemplo, o operador / ser sempre binrio. Outra caracterstica que a prioridade tambm no pode ser alterada, preservada a original do C.

    Um nmero complexo pode ser modelado com uma classe que permita que as operaes matemticas sobre ele sejam feitas da mesma maneira que os tipos primitivos, ou seja, com os operadores +, - etc. Uma possibilidade seria:

    class Complex {public:

    Complex operator+ (const Complex&);Complex operator- (const Complex&);

    };Com estes operadores, possvel fazer:

    void main(){

    Complex c1, c2, c3;c1 = c2 + c3;

    }

    Exerccio 5 - Classe Complex Implementar uma classe que represente um nmero complexo.

    Operadores como funes globais Suponhamos que a classe que modela complexos possui um construtor da forma:

    Complex(float re = 0.0, float im = 0.0);Este construtor permite criar um complexo especificando suas partes real e imaginria, s

    especificando a parte real ou ainda sem dizer nada. Em particular, este construtor define como converter

    fabricioRealce

  • um float em um complexo, o que equivalente a chamar o construtor com apenas um parmetro. Esta converso permite expresses do tipo:

    c1 = c2 + 3.0; // equivalente a c1 = c2 + Complex(3.0, 0.0)Considerando que o operador uma funo, esta expresso poderia ser vista como:

    c1 = c2.operator+(Complex(3.0,0.0));A converso foi feita porque a funo operator+ espera um Complex, e o valor era um float. Nesse

    caso o compilador converte automaticamente o valor. Mas e se a expresso for a seguinte:

    c1 = 3.0 + c2;

    Nesse caso 3.0 no parmetro de funo nenhuma, ento a converso no feita. Para possibilitar esta expresso, seria preciso converter o valor explicitamente:

    c1 = Complex(3.0) + c2;No entanto, se 3.0 fosse o parmetro para alguma funo, o compilador saberia fazer a converso.

    Lembrando que os operadores podem ser definidos como mtodos ou como funes globais, possvel tornar 3.0 um parmetro. o caso de definir o operador como uma funo global:

    Complex operator+ (const Complex&, const Complex&);Com esta funo definida, os dois operandos passam a ser parmetros, e ambos podem ser convertidos

    automaticamente.

    Operadores que podem ser redefinidos A maior parte dos operadores podem ser sobrecarregados. So eles:

    new delete+ - * / % ^& | ~! = < > += -= *= /= %=^= &= |= > >>= () []

    Tanto as formas unrias como as binrias de

    + - * &

    podem ser sobrecarregadas, assim como as formas pr-fixadas ou ps fixadas de

    ++ --

    Os seguintes operadores no podem ser sobrecarregados:

    . .* :: sizeof ?:

    j que estes operadores j tem um significado predefinido (exceto ?:) para objetos de qualquer classe. A funo de atribuio operator=() definida por default para todos os objetos como a atribuio byte

    a byte dos campos do objeto.

    Exemplo de redefinio do operador [] - classe Vector As estratgias usadas para redefinies variam muito de acordo com o operador. Esta seo apresenta

    um exemplo que traz vrios detalhes que devem ser levados em considerao dependendo do operador. O exemplo encapsula um vetor como uma classe com o objetivo de checar as indexaes, evitando que posies aleatrias da memria sejam acessadas. Um vetor normal de C++ permite a indexao com qualquer inteiro; mesmo se o ndice estiver alm da rea alocada o acesso permitido, com conseqncias imprevisveis.

    O operador [ ] ser redefinido para permitir o uso dos objetos desta classe da mesma maneira que um vetor C++. Alm do operador, a classe ter um construtor que aloca os elementos do vetor. O funcionamento ser o seguinte: o construtor, alm de alocar os elementos, guarda o tamanho do vetor. O operador, que retorna o elemento correspondente do vetor alocado, checa se o ndice vlido (o construtor guarda o tamanho) antes de fazer o acesso. Alguma coisa da forma:

    fabricioRealce

  • float Vector::operator[](int i){

    if (i>=0 && i=0 && i

  • class Arquivo {FILE *file;

    public:Arquivo( char* nome ) { file=fopen(nome, "r"); }~Arquivo() { fclose(file); }char read() { return file?fgetc(file):EOF; }int aberto() { return file!=NULL; }operator FILE*() { return file; }

    }

    void main(){

    int i;Arquivo arq("teste.c");fscanf( (FILE*)arq, "%d", &i );

    }

    Exerccio 6 - Classe Complex com operadores globais Alterar a classe Complex para permitir expresses do tipo 1+c, onde c complexo.

    Exerccio 7 - Calculadora RPN para complexos Modificar a calculadora para operar com nmeros complexos.

    Exerccio 8 - Classe String Implementar uma classe String. A classe deve permitir comparaes, concatenaes, atribuies e

    acesso a cada caracter, alm de poder ser utilizada em funes como printf. O usurio desta classe no precisa se preocupar com tamanho de memria alocado. Ou seja, se for necessrio realocar memria para uma atribuio ou uma concatenao, esta realocao deve ser feita automaticamente dentro da classe. O acesso aos caracteres deve ser seguro. Por exemplo, no caso de a string ter 10 caracteres e o usurio tentar escrever no vigsimo, isto no pode alterar uma rea de memria aleatria.

  • Captulo 5

    Aspectos de reutilizao Por que software no como hardware? Por que todo desenvolvimento comea do nada?

    Deviam existir catlogos de mdulos de software, assim como existem catlogos de chips: quando ns construmos um novo sistema, ns deveramos estar usando os componentes destes catlogos e combinando-os, em vez de sempre reinventar a roda. Ns escreveramos menos software, a talvez faramos um desenvolvimento melhor . Ser que alguns problemas dos quais todo mundo reclama - custos altos, prazos insuficientes, pouca confiabilidade - no desapareceriam? Por que no assim?

    Talvez voc j tenha ouvido esta argumentao antes; talvez voc prprio j tenha pensado nisso. Em 1968, no famoso workshop da OTAN sobre a crise de software, D. McIlroy j estava pregando a produo de componentes de software em massa. A reutilizao, como um sonho, no nova.

    Qualquer pessoa que lide com o desenvolvimento de software se impressiona com seu carter repetitivo. Diversas vezes, os programadores constroem funes e programas com o mesmo padro: ordenao, busca de um elemento, comparao, etc. Uma maneira de interessante de avaliar esta situao responder a seguinte pergunta: Quantas vezes, nos ltimos seis meses, voc escreveu uma rotina de busca de um elemento x em uma tabela t ?

    As dificuldades tcnicas de reutilizao se tornam mais visveis quando se observa a natureza das repeties no desenvolvimento de sistemas. Esta anlise revela que apesar dos programadores tenderem a escrever os mesmos tipos de rotinas diversas vezes, estas no so exatamente iguais. Se fosse, a soluo mais simples teoricamente; na prtica, porm, muitos detalhes mudam de implementao para implementao (tipos dos elementos, estrutura de dados associada, etc.).

    Apesar das dificuldades, algumas solues foram propostas com relativos sucessos: Reutilizao de cdigo-fonte: Muito comum no meio cientfico. Muito da cultura UNIX foi difundida

    pelos laboratrios e universidades do mundo graas disponibilidade de cdigo-fonte ajudando estudadentes a estudarem, imitarem e estenderem o sistema. No entanto, esta no a forma mais utilizada nos meios industrial e comercial alm de que esta tcnica no suporta ocultao de informao (information hiding).

    Reutilizao de pessoal: uma forma muito comum na indstria. Consiste na transferncia de engenheiros de software de projetos a projetos fazendo a permanncia de know-how na compania e assegurando a aplicao de experincias passadas em novos projetos. Obviamente, esta uma maneira no-tcnica e limitada.

    Reutilizao de design: A idia por trs desta tcnica que as companhias devem acumular repositrios de idias descrevendo designs utilizados para os tipos de aplicao mais comuns.

    Requisitos para reuso As idias apresentadas anteriormente, apesar de limitadas, mostram aspectos importantes para a

    reutilizao de cdigo: A noo de reuso de cdigo-fonte lembra que software definido pelo seu cdigo. Uma poltica

    satisfatria de reutilizao deve produzir programas (cdigos) reutilizveis. A reutilizao de pessoal fundamental pois os componentes de software so inteis sem

    profissionais bem treinados e com experincia para reconhecer as situaes com possibilidade de reuso. A reutilizao de design enfatiza a necessidade de componentes reutilizveis que estejam em um nvel

    conceitual e de generalidade alto no somente com solues para problemas especficos. Neste aspecto, poder ser visto como o conceito de classes nas linguagens orientadas por objetos pode ser visto como mdulos de design e de implementao.

    A maneira de produzir mdulos que permitem boa possibilidade de reuso descrita abaixo. Neste caso, o nosso exemplo de procurar um elemento x em uma tabela t bem ilustrativo.

    Variao no tipo Um mdulo que implemente uma determinada funcionalidade deve ser capaz de faz-lo sobre

    qualquer tipo a ele atribudo. Por exemplo, no caso da busca de um elemento x, o mdulo deve ser aplicvel a diferentes instncias de tipo para x. interessante a utilizao do mesmo mdulo para procurar um inteiro numa tabela de inteiros ou o registro de um empregado na sua tabela correspondente, etc.

  • Variao nas estruturas de dados e algoritmos No caso da busca de um elemento x, o modo de busca pode ser adaptado para diversos tipos de

    estruturas de dados e algoritmos de busca associados: tabelas seqenciais (ordenadas ou no), vetores, listas, rvores binrias, B-trees, diferentes estruturas de arquivos, etc. Neste sentido, o mdulo deve suportar variaes nas estruturas a ele associado.

    Existncia de rotinas relacionadas De forma a fazer uma pesquisa em uma tabela, deve-se saber como esta criada, como os elementos

    podem ser inseridos, retirados, etc. Deste modo, uma rotina de busca no por si s suficiente; h a necessidade do acoplamento de diversas rotinas primitivas e relacionadas entre si.

    Independncia de representao Uma estrutura modular verdadeiramente flexvel habilita seus clientes uma operao sem o

    conhecimento de modo pelo qual o mdulo foi implementado. Por exemplo, deve ser possvel ao cliente escrever a seguinte chamada para a busca de x:

    esta_presente = BUSCA( x, T );sem saber qual o tipo da tabela T no momento da chamada. Se vrios algoritmos de busca foram

    implementados, os mecanismos internos do mdulo so responsveis de saber qual o apropriado sem a interveno do cliente. De maneira simplificada, isto uma extenso do princpio de ocultao de informao pois havendo a necessidade de mudana na implementao, os clientes esto protegidos.

    No entanto, a idia vai mais alm. O princpio da independncia de representao no significa somente que mudanas na representao devem ser invisveis para os clientes durante o ciclo de desenvolvimento do sistema: os clientes devem ser imunes tambm a mudanas durante a execuo. No exemplo acima, desejvel que a rotina BUSCA se adapte automaticamente para a forma de T em tempo de execuo mesmo que esta forma tenha sido alterada do instante da ltima chamada.

    Este requisito importante no somente pela questo do reuso mas tambm pela extensibilidade. Se T pode mudar de forma em tempo de execuo, ento uma deciso no sistema deve ser tomada para a utilizao da verso de BUSCA correta. Em outras palavras, se no houver esse mecanismo automtico o cdigo, em algum local, deve conter um controle do tipo:

    se T do tipo A ento "mecanismo A"se T do tipo B ento "mecanismo B"se T do tipo C ento "mecanismo C"

    A estrutura de deciso deve estar ou no mdulo ou no cliente. Ambos os casos no so satisfatrios. Se a deciso estiver no mdulo, este mdulo deve saber sobre todos as possibilidades existentes. Tal poltica pode levar a construo de mdulos difceis de gerenciar e sujeitos a constantes manutenes. Deixar a deciso para o cliente no melhor. Desta forma, o cliente obrigado a especificar que T uma tabela do tipo A, B, etc. mas no requerido a dizer mais nada: esta informao suficiente para determinar que variante de BUSCA deve ser utilizada.

    A soluo para o problema introduzida pelas linguagens orientadas por objetos com o mecanismo de herana em que o desenvolvimento feito atravs da descentralizao da arquitetura modular. Esta construda por sucessivos incrementos e modificaes conectadas por relaes bem definidas que definem as verses corretas de BUSCA. Este mecanismo chamado de Amarrao Dinmica (Late-Binding ou Dynamic binding ).

    Semelhanas nos subcasos Este ltimo item em reuso afeta o design e a construo dos mdulos e no seus clientes. Este

    fundamental pois determina a possibilidade da construo de mdulos bem construdos sem repeties indesejveis. O aparecimento de repeties excessivas nos mdulos compromete suas consistncias internas e torna-os difceis de fazer manuteno.

    O problema surge como os programadores podem se aproveitar e tomar vantagem de semelhanas em subcasos. Para tal, deve existir no conjunto de possibilidades de implementao subgrupos de solues com a mesma estrutura. No exemplo de busca na tabela, um exemplo tpico o aparecimento de implementaes relacionadas com tabelas seqenciais. Neste caso, o algoritmo pode ser descrito da mesma forma para todos os subcasos diferindo apenas em um conjunto reduzido de rotinas primitivas. A figura abaixo ilustra o algoritmo seqencial genrico e algumas implementaes de operaes primitivas.

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

  • int BUSCA( Elemento x; TabelaSequencial T ){

    int PosCOMEA();while not FINAL() && not ACHOU( pos, x, T ) do

    MOVE_PROXIMO();return not FINAL();

    }

    Vetor Lista Encadeada Arquivo Seq.

    COMEA() i := 1 l :=ponta_lista

    rewind()

    MOVE_PROXIMO()

    i := i +1

    l := l.next read()

    FINAL() i >tamanho

    l == NULL eof()

    Neste caso, evita-se a repetio do mtodo de BUSCA nas implementaes de busca seqencial. Tem-se uma nica funo de pesquisa que se difere em quais funoes especficas de COMEA, MOVE_PRXIMO e FINAL sero chamadas.

    Os mecanismos decritos acima so compreendidos nas linguagens orientadas por objetos pelo mecanismo de herana descrito a seguir.

    Herana Provavelmente herana o recurso que torna o conceito de classe mais poderoso. Em C++, o termo

    herana se aplica apenas s classes. Variveis no podem herdar de outras variveis e funes no podem herdar de outras funes.

    Herana permite que se construa e estenda continuamente classes desenvolvidas por voc mesmo ou por outras pessoas, sem nenhum limite. Comeando da classe mais simples, pode-se derivar classes cada vez mais complexas que no so apenas mais fceis de debuggar, mas elas prprias so mais simples.

    O objetivo de um projeto em C++ desenvolver classes que resolvam um determinado problema. Estas classes so geralmente construdas incrementalmente comeando de uma classe bsica simples, atravs de herana. Cada vez que se deriva uma nova classe comeando de uma j existente, pode-se herdar algumas ou todas as caractersticas da classe pai, adicionando novas quando for necessrio. Um projeto completo pode ter centenas de classes, mas normalmente estas classes so derivadas de algumas poucas classes bsicas. C++ permite no apenas herana simples, mas tambm mltipla, permitindo que uma classe incorpore comportamentos de todas as suas classes bases.

    Reutilizao em C++ se d atravs do uso de uma classe j existente ou da construo de uma nova classe a partir de uma j existente.

    Classes derivadas A descrio anterior pode ser interessante, mas um exemplo a melhor forma de mostrar o que

    herana e como ela funciona. Aqui est um exemplo de duas classes, a segunda herdando as propriedades da primeira:

    class Caixa {public:

    int altura, largura;void Altura(int a) { altura=a; }void Largura(int l) { largura=l; }

    };

    class CaixaColorida : public Caixa {public:

    int cor;void Cor(int c) { cor=c; }

    };

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

    fabricioRealce

  • Usando a terminologia de C++, a classe Caixa chamada classe base para a classe CaixaColorida, que chamada classe derivada. Classes base so tambm chamadas de classes pai. A classe CaixaColorida foi declarada com apenas uma funo, mas ela herda duas funes e duas variveis da classe base. Sendo assim, o seguinte cdigo possvel:

    void main(){

    CaixaColorida cc;cc.Cor(5);cc.Largura(3); // herdadacc.Altura(50); // herdada

    }Note que as funes herdadas so udadas exatamente como as no herdadas. A classe Colorida no

    precisou sequer mencionar o fato de que as funes Caixa::Altura() e Caixa::Largura() foram herdadas. Esta uniformidade de expresso um grande recurso de C++. Usar um recurso de uma classe no requer que se saiba se este recurso foi herdado ou no, j que a notao invariante. Em muitas classes pode existir uma cadeia de classes base derivadas de outras classes