Programar Em C++ Wikibooks

  • Published on
    25-Nov-2015

  • View
    23

  • Download
    4

Transcript

  • PDF gerado usando o pacote de ferramentas em código aberto mwlib. Veja http://code.pediapress.com/ para mais informações. PDF generated at: Sun, 30 Mar 2014 20:34:50 UTC Programar em C++
  • Conteúdo Páginas Objetivo 1 Por que C++? 1 Diferenças entre C e C++ 2 Introdução 3 Alô, Mundo! 6 Variáveis e constantes 12 Ponteiros 18 Vetores 20 Estruturas 29 Operadores 34 Decisão e controle de fluxo 37 Estruturas de repetição 42 Funções 49 Referências de dados 51 Entrada e saída de dados 56 Entrada e saída de dados 2 65 Manipulando strings 80 Classes 88 Encapsulamento 103 Herança 106 Polimorfismo 119 Friend 124 Classes internas 126 Sobrecarga de operadores 127 Alocação dinâmica de memória 129 Exceções 136 Namespace 136 Templates 136 Containers 138 Compilação 138 Lista de Palavras Reservadas do C++ 140 Lista de Sequências de Escape 141 Tabela ASCII 142 C++11 143
  • Referências Fontes e Editores da Página 144 Fontes, Licenças e Editores da Imagem 145 Licenças das páginas Licença 146
  • Objetivo 1 Objetivo O livro Programar em C++ tem por objetivo apresentar os fundamentos desta linguagem, de modo que o estudante possa desenvolver diferentes tipos de softwares em alto e baixo nível para os diversos ambientes existentes, desde aplicações para GNU/Linux ou Windows até programas para microcontroladores, além de fornecer a base para os estudos avançados de C++. Por ser um livro específico sobre a linguagem C++, é altamente recomendável que o leitor tenha conhecimentos prévios sobre a linguagem C. Espera-se que este livro aborde: •• Aspectos teóricos •• Aspectos práticos •• Os erros comuns Para tanto cada tópico deverá ter uma explicação teórica, citar os erros mais comuns e exercícios. Por que C++? Imagine que você deve fazer um programa para fazer a máquina de um pequeno relógio de pulso funcionar, então você pensa: •• Bom, isso pode ser feito com Assembly... •• Porém, pensando melhor, você decide mudar de linguagem quando você pondera. •• O problema maior é que se eu tiver que mudar o processador do relógio, vou ter que refazer o programa. É melhor usar linguagem "C". •• Depois você termina por avaliar outra possibilidade: •• Bem, se eu já estou pensando em "C" é melhor usar "C++", depois vai ser mais fácil de entender o código, reaproveitar em outras coisas e ainda vai ser mais fácil de expandir para outros modelos de relógio. E assim é o que podemos perceber como C++ é poderosa, flexível e abrangente. Ela pode ser usada para programar qualquer tipo de hardware, desde os mais simples até os mais complexos. Além disso, C++ é uma linguagem que gera programas em código de máquina, que funcionam com ou sem a participação de sistemas operacionais no dispositivo. Alguns profissionais afirmam que C++ é a linguagem mais poderosa que existe, veja algumas características dela: • É um superconjunto da linguagem C, e contém vários melhoramentos; • Deu origem a grandes linguagens como Java e D; • É a porta para a programação orientada a objetos; • C++ pode virtualmente ser efetivamente aplicado a qualquer tarefa de programação; •• Há vários compiladores para diversas plataformas tornando a linguagem uma opção para programas multiplataforma. A linguagem C++ é utilizada em projetos como: • Compiladores; • Editores; • Ferramentas de programação; • Jogos; • Programas de redes. Até ao momento foram realizadas 3 grandes revisões à linguagem:
  • Por que C++? 2 •• 1ª em 1985; •• 2ª em 1990; • 3ª em 1998 a que deu origem ao ANSI \ ISO standard a que ficou comummente denominada de Standard C++. Esta versão é suportada por todos os compiladores C++ famosos incluindo Microsoft’s Visual C++, Borland’s C++ Builder e GCC. Esta foi revista em 2003. C++ é considerada uma linguagem que está entre linguagem de alto nível (em inglês, high level language) e linguagem de baixo nível (em inglês, low level language). Dito de outra forma, é uma linguagem que está próxima da linguagem humana (linguagem de alto nível), mas ao mesmo tempo permite estar próximo da maneira como o computador processa, próximo do Assembly (uma linguagem de baixo nível). Diferenças entre C e C++ Quem sabe programar em C++, é capaz de programar C, devido à semelhança entre as linguagens e o fato do C++ ser uma extensão do C. Contudo o C não é completamente um subconjunto do C++. Grande parte de código C pode ser perfeitamente compilado em C++, mas existem algumas pequenas diferenças sintáticas e semânticas entre as linguagens que tornam alguns trechos de código C válidos em código C++ inválido, ou códigos que exibem comportamentos diferentes em cada linguagem. Algumas diferenças básicas: • O C permite a conversão implícita entre o tipo de dado void* para ponteiros para outros tipos, algo que o C++ não permite. • O C permite que constantes de caracteres sejam inseridas em chamadas de funções com parâmetros tipo char*, em C++ é preciso declarar o parâmetro como const char *; Além destas pequenas diferenças, C++ tem um conjunto de características que a torna fundamentalmente diferente de "C". Esse conjunto, torna possível programar em C++ de um modo totalmente diferente do modo de programar da linguagem "C". O que traz a diferença é o modo da orientação na montagem do código. Chamamos o modo de programar em "C" de orientado a procedimentos e chamamos o modo do "C++" de orientado a objetos. Muitas pessoas confundem as coisas quando começam a programar usando um compilador C++, pois esta linguagem permite programar nos dois modos. Essa é uma das características que a torna mais flexível. Apesar de C++ permitir programar em modo orientado a procedimentos, podemos dizer que nestes casos estamos programando em "C", usando um compilador C++. Quando usamos C++ programamos em modo orientado a objetos. Devido a estas características, o C++ permite programar em modo misto, ou seja, escrevendo partes do código orientadas a procedimentos e outras orientadas a objetos. As diferenças entre os dois modos de programar serão esclarecidas nos capítulos subsequentes. Por hora nos basta deixar claro que os dois modos são diferentes. Usar estes dois modos de programar ao mesmo tempo é uma das facilidades que o C++ permite, enquanto que outras linguagens orientadas a objetos como Java, Eifel, etc, não permitem. Esta página é um esboço de informática. Ampliando-a você ajudará a melhorar o Wikilivros.
  • Introdução 3 Introdução Pensando no código Considerando o conjunto de operações e eventos que nosso programa deve executar temos diversas maneiras de criar o código, porém o mais difícil é criar um código eficiente, rápido e compacto. Na verdade, diversos fatores podem interferir nestes aspectos da programação, entre eles, a escolha do compilador, o método de estruturar o código, a orientação do mesmo, etc... Em termos gerais, um código torna-se mais próximo do ideal a medida que suas partes tornam-se mais simples de se analisar e quando todos os processos estão bem definidos e especializados. Quando temos um código que contém muito mais exceções do que regras, este precisa de uma reestruturação. Podemos definir C++ como um "superconjunto" da linguagem C, ou seja, uma linguagem com mais funcionalidades que a linguagem C. Embora este seja o ponto de vista de quem já tem um conhecimento da linguagem C, ela é muito mais que isto. Podemos mudar completamente a forma de criar o programa quando usamos os recursos avançados da linguagem, as estruturas de decisão (por exemplo, if-else ou switch) podem ser simplificadas e a organização do código pode ser bem mais globalizada e genérica, possibilitando a reutilização do código em diversas situações diferentes. Vejamos como as funcionalidades da linguagem C++ podem ajudar a redefinir os meios de programação que aprendemos com o bom e velho estilo C. Dois modos de programar Observando o modo de programar que as linguagens oferecem desde os primórdios da computação, podemos notar vários métodos que foram sendo superados e outros que se estabelecem por um momento. O modo de programar mais usual e bem mais difundido é conhecido como modelo estruturado sequencial. Em síntese, refere-se a forma de programar onde uma instrução segue a outra numa sequência que inicia e termina em um fluxo parcialmente "previsível". A programação estruturada ainda pode ser classificada em dois modos: um orientado a procedimentos e outro orientado a objetos. Os dois podem ser confundidos por quem não tem muita experiência, mas o uso de um ou do outro implica em características próprias para cada caso. Logo, é preciso entender bem os conceitos antes de definir se um código é procedural ou orientado a objetos. O modelo sequenciado procedural é bem simples de implementar, porém, aumenta a complexidade para tarefas mais bem trabalhadas e sofisticadas. Isto ocorre devido a estrutura do modelo, que exige rotinas cada vez mais extensas. Não raramente é possível encontrar rotinas que, em alguns casos, tornam-se comparáveis a programas completos, usando como referência o número de linhas de instruções. O uso deste modelo, muitas vezes dificulta a manutenção e expansão do código, caso isto não seja feito de maneira muito bem organizada. O segundo modelo é o orientado a objetos. O que significa isso e o que muda no modo de programar, caso o adotemos em vez do modelo anterior? A orientação define o modo de abordar o problema para tentar solucioná-lo: •• O modelo de orientação a procedimentos se preocupa em fornecer meios para resolver o problema sem contabilizar, a princípio, os dados que serão usados durante o processo. •• O modelo de orientação a objetos se preocupa com os elementos que são necessários para a solução de um problema. Sob este ângulo, os dados são os elementos principais na análise do problema. Este livro traz uma visão dos problemas sob a óptica da orientação a objetos, enquanto que o livro "Programar em C" traz a análise sob a óptica da orientação a procedimentos. Isto não quer dizer que devamos escolher a orientação a objetos como o modo mandatório de programação, mas que podemos contar com seus recursos sempre que esta escolha facilite a resolução do problema. Portanto, cabe sempre uma análise para definir se o problema é melhor
  • Introdução 4 tratado por uma abordagem orientada a objetos ou a procedimentos. Um pouco sobre orientação a objetos A programação orientada a objetos é um paradigma de programação que visa organização, produtividade e sustentabilidade. A apresentação dos conceitos de orientação a objetos é bastante abrangente, o que implica na abordagem de diversos aspectos, como modelagem, estudo de performance de modelos, aplicabilidade de técnicas, estruturação de objetos, otimização, manutenção do código, entre outros. Por este motivo, nosso objetivo aqui não é apresentar a orientação a objetos em sua totalidade. Para um estudo mais detalhado do tema sugerimos o livro POO, que trata especificamente deste tema. O objetivo aqui é apresentar como a orientação a objetos se aplica na linguagem C++, porém os conceitos aqui apresentados devem ser suficientes para a estruturação de programas de bom nível. A ideia principal por traz do modelo de programação orientado a objetos está em transformar entidades do mundo real em identificadores dentro do programa (objetos), trabalhando-os como entidades da linguagem que possuem características e operações próprias. Esta abordagem transforma o programa em um meio de simulação de situações virtuais por meio de entidades de código que tem comportamento predefinido. Esta abstração é uma aliada do programador por permitir idealizar sistemas mais sofisticados de uma maneira bastante intuitiva. Todas as linguagens orientadas a objetos contêm os princípios de: •• Encapsulamento É um mecanismo para esconder os detalhes envolvidos no processamento de uma ação. Por exemplo, quando usamos um telefone, não precisamos lidar diretamente com o circuito interno; a interface do telefone cuida desse problema. •• Polimorfismo Isso permite o uso de uma única interface ― uma única forma de uso ― para objetos de tipos diferentes; em particular, a mesma interface para objetos de uma classe e objetos de classes derivadas dessa. •• Herança Como o nome sugere, isso permite que uma classe herde de outra suas características, podendo também introduzir suas próprias ou alterar as características herdadas. O uso de herança acaba poupando trabalho e melhorando a organização do código. Paradigmas da Programação: Desde o início da programação, já passamos pelos seguintes paradigmas: • Não estruturada - exemplos: COBOL, FORTRAN, BASIC (anos 50-60) • Procedimental ou Procedural - exemplos: C, Pascal (anos 70) • Modular - exemplo: Modula II (anos 80) • Abstração de tipos de dados - exemplo: Ada (anos 80) • Programação Orientada a Objetos - exemplos: C++, Java, Delphi (Object Pascal) entre outras. (décadas 80-90-2000)
  • Introdução 5 Objetos Objeto é, genericamente, uma entidade de armazenamento e manipulação de dados. O mesmo deve ser criado para processar os dados que armazena e recebe, sendo sensível a entradas do programa principal para fornecer as saídas esperadas pelo mesmo. Por estes motivos o objeto deve ser pensado como uma entidade de dados autônoma, encarregada de processar todos os dados que mantém. Da mesma forma que podemos usar tipos de dados nativos da linguagem podemos criar nossos tipos de dados. Na linguagem C podemos criar tipos de dados compostos que chamamos de estruturas, estes são criados com a palavra chave struct. C++ possibilita o uso de estruturas de dados e introduz um novo tipo chamado de classe. Como o nome sugere, uma classe refere-se a um conjunto de características dadas a um grupo de "indivíduos", ou seja, grupo de objetos. Por este motivo, classe é a definição de tipo de objeto. Em C++ as classes de objetos são criadas através da palavra chave class. Esta nomenclatura é usada por muitas outras linguagens de programação mais caracteristicamente restritas a orientação a objetos. Estes aspectos facilitam um pouco o aprendizado por programadores já familiarizados com estas linguagens quando iniciam a programação em C++. O processo de criação de um objeto segue a sequência: •• Definir os dados e procedimentos que a classe deve conter; •• Criar a classe de objetos; •• Declarar (instanciar) o objeto. A definição de uma classe de objetos deve ser feita de forma a tornar, preferencialmente, todos os dados protegidos de interferências de códigos externos ao objeto. Por este motivo um objeto deve ser uma parte do código do programa com uma certa autonomia. Este deve ter controle sobre seus dados e ser capaz de provê-los e lidar com eventos a eles relacionados. Dentro de seu escopo de responsabilidades, a entidade deve essencialmente "ter vida própria".
  • Alô, Mundo! 6 Alô, Mundo! Olá mundo! É comum, no aprendizado de uma linguagem de programação, que seu primeiro programa faça com que o computador exiba "Olá mundo!". Na linguagem C++ este primeiro programa já introduz muitos conceitos sobre a linguagem. Veja o código do nosso primeiro programa: #include using namespace std; int main () { cout
  • Alô, Mundo! 7 #endif Em sistemas parecidos com UNIX, como GNU/Linux ou FreeBSD, pode-se usar um terminal de linha de comando facilmente, pois os mesmos possuem o recurso facilmente acessível, mesmo quando o usuário está usando a interface gráfica. Por isso, para esses sistemas um comando para solicitar pausa ao sistema não é necessário. Entrada de dados e comentários no código Comentário é um recurso muito útil em programação. Ele permite que o programador adicione texto ao programa para facilitar o entendimento do programa por parte de outros programadores, ou até dele mesmo. Os comentários são usados para explicar trechos de código, adicionar cláusulas e qualquer texto em geral. Vamos agora para um programa mais completo com entrada de dados e comentários dentro do código: // Este é um comentário de uma linha /* Este é um comentário de várias linhas */ #include using namespace std; int main () { int x; cout > x; cout
  • Alô, Mundo! 8 Ao analisar o código, o preprocessador encontra a sequência // e vai eliminar o texto que está a seguir até ao fim da linha. Mais uma forma de adicionar comentários: /* Este é um comentário de várias linhas */ A linguagem C++ permite também fazer comentários por mais do que uma linha. Chama-se comentário por bloco e o que faz é eliminar tudo o que encontrar entre a sequência inicial /* e o final */. A vantagem de termos esta possibilidade é poder comentar o nosso código. Existem algumas regras de boa conduta para comentários que foram criadas por pessoas que já têm muito tempo nisto: •• Uma é criar logo no topo um comentário a dizer o que é o nosso programa, e o que faz numa forma geral; •• Outra é fazer comentários a cada função que aparece no código a explicar; •• Outra é comentar uma linha mais obscura, mais difícil de entender, que não é óbvia; •• A outra é não comentar tudo. O comentar deve ser para sobressair. Esta última regra pode ser esquecida quando o programa é didático, neste caso pode-se usar o programa como texto comentado. Incluindo cabeçalhos #include O símbolo # é uma chamada de atenção ao compilador a dizer que aquela linha é para o preprocessador, depois temos o "include" (que basicamente diz para incluir código). Incluir o quê? Deve incluir o ficheiro/arquivo iostream. (in+out+stream, "fluxo de entrada e saída", padrão) (na maioria das vezes, como entrada padrão temos o teclado e como saída temos o monitor) (este ficheiro/arquivo contém declarações das funções e definições que o nosso código fonte irá necessitar) Este código que será incluído é chamado de cabeçalho devido a uma característica evidente, o fato de ser código de declaração inicial do programa, que deve estar no topo do arquivo/ficheiro. Existem outros arquivos (ficheiros cabeçalho), o iostream é para fluxos de entrada e saída, mas temos muitos mais para matemática, manipulação de tempo, tratamento de caracteres, etc... Na maioria das vezes, os arquivos de cabeçalho fazem parte de uma biblioteca. Podemos ver na parte dos anexos, algumas bibliotecas que existem juntamente com as funções de cada uma. Nós próprios podemos criar uma biblioteca com código e nosso próprio cabeçalho. E até podemos comprar bibliotecas existentes comercialmente, através de empresas especializadas em desenvolvimento, que também terão seus arquivos/ficheiros de cabeçalhos. Mas, o que são bibliotecas? São arquivos com um conjunto de códigos que alguém fez antes. As que enunciamos antes são as "standard", são aquelas que têm as funcionalidades básicas, pertencentes aos padrões da linguagem. Repare-se que precisamos da biblioteca até para escrever (no ecrã)/(na tela) (stream + out) que nos permite utilizar o cout. O ficheiro/arquivo iostream está envolvido em < >, isto significa que o preprocessador deve procurar o ficheiro/arquivo no sítio/diretório usual (que é onde o compilador usa como padrão para os "includes"). Se tivéssemos o ficheiro/arquivo iostream envolvido em "" significaria que o preprocessador deveria procurá-lo dentro de uma lista de diretórios de inclusão, "includes", iniciando pelo diretório atual.
  • Alô, Mundo! 9 As bibliotecas são compostas por 2 partes: um índice de todas as funções e definições e declarações, o cabeçalho, e depois a definição de todas as funções existentes no índice, arquivos de código. As diretivas de preprocessamento não terminam com o ponto e vírgula como nas instruções. Namespace using namespace std; Observando esta linha, alguns tradicionais programadores em linguagem C, têm uma novidade: namespaces são espaços de nomes dentro do código, eles funcionam, entre outras coisas, como um meio de evitar duplicação de nomes dentro de um projeto extenso, que geralmente contam com inúmeros arquivos. O C++ usa os namespaces para organizar os diferentes nomes usados nos programas. Cada nome usado no ficheiro/arquivo biblioteca "standard iostream" faz parte do "namespace" chamado de std. O objeto de saída padrão, cout, está definido dentro do "namespace" std, ele é um objeto da classe "ostream" "output stream", para acessá-lo temos que referenciá-lo como "std::cout". Para evitar que tenhamos que informar "std::" todas as vezes que precisarmos usar os recursos deste "namespace", podemos informar que estamos usando-o dentro do arquivo atual, conforme vemos na linha declarada no início deste tópico. O "namespace" permite que as variáveis sejam localizadas em certas regiões do código. Declarar o "namespace std" permite que todos os objetos e funções da biblioteca "standard input-output" possam ser usados sem qualquer qualificações específicas, desta maneira, não é mais necessário o uso de "std::". Este é um conceito avançado que podemos explorar mais, vamos deixá-lo para depois. Função "main" int main(){} Como na linguagem C, a função principal de entrada do programa a partir do sistema operacional é a função main. Por isso mesmo ela é obrigatória em qualquer programa. Se não existisse uma "main function", não haveria entrada para que o sistema iniciasse o programa. Todas as funções são declaradas e usadas com o operador ( ), assim é que o compilador reconhece que estas são funções. A ideia de ter funções é permitir o encapsulamento de uma ideia ou operação, dar um nome a isso e depois chamar essa operação de várias partes do programa simplesmente usando o seu nome. As funções declaradas como membros de uma classe de objetos podem ser chamadas de métodos. Do ponto de vista funcional, um código dentro de uma função executa operações em outra parte do programa, que não é aquela de onde foi chamada, por este motivo as mesmas contam com um mecanismo de passagem de dados, ao declarar uma função indicamos quais os dados que entram e o que ela deve fornecer a quem lhe chamou. Pode-se dizer que, tal qual uma função matemática, a função em C/C++ poderá ser substituída, depois de sua execução, pelo valor que ela retorna, este valor será especificado antes do nome da função na declaração da mesma, conforme vemos no início deste tópico. O int significa que a função vai retornar um inteiro. Existem outros tipos de dados como, por exemplo, os seguintes: • int que é a abreviatura de inteiro; • char que é a abreviatura de caratere; • float que é a abreviatura de "floating point number", ou seja, uma representação para número real. Vejamos um exemplo: Quando criamos uma função soma, obviamente só para ilustração pois isso não é necessário, podemos fazer: int soma(int a, int b) { return a + b; }
  • Alô, Mundo! 10 Agora imagine que tenhamos que somar 2 e 3, colocando o resultado em outra variável chamada valor, para isto faremos: valor = soma(2, 3); Primeiro analisamos qual é o resultado e depois substituímos a função pelo valor que ela retorna: valor = 5; Simples, não? ; - Final de sequência de instruções O ponto e vírgula funciona como ponto final, separa as instruções e contextos. Repare que apenas as funções, ou melhor, as definições de funções e as diretivas de preprocessamento é que não têm o ";" É importante notar que o código poderia ser todo escrito quase numa linha tipo: int main (){int a; cout > a;cout
  • Alô, Mundo! 11 O que esta linha faz é colocar o valor que foi digitado numa área de memória que foi chamada de "a". Da mesma forma que o cout existe para saída de dados, temos outro objeto para entrada através do teclado, este objeto é chamado de Console IN - cin, seguindo a mesma analogia. Observe que o operador >> é usado para dar ideia de que os dados estão vindo do cin para a variável "a". cout
  • Variáveis e constantes 12 Variáveis e constantes Constantes Compatível com a linguagem C, o C++ mantém as constantes básicas e introduz algumas novas funcionalidades possibilitadas pelo modificador const. O uso do modificador const tem duas funções principais: 1.1. Resguarda da inviolabilidade de valores apontados por ponteiros; 2.2. Auxílio na compreensão das características de funções, durante a implementação. Simbólicas Constantes simbólicas podem ser criadas com as diretivas do preprocessador #define. Neste modo os valores, de fato, não são interpretados imediatamente pelo compilador, antes são identificados e substituidos pelo preprocessador no estágio anterior à compilação. Por exemplo: #define BUFFER_LENGTH 2048 ... ... ... char data[BUFFER_LENGTH]; Observe que o valor 2048 será usado logo abaixo no código, depois que o preprocessador substituir a constante simbólica BUFFER_LENGTH pelo valor que lhe foi atribuído. Note que as constantes são escritas com todas as letras maiúsculas, isso não é uma regra, mas ajuda a identificar o que é constante simbólica dentro do programa, sendo adotado pela maioria dos desenvolvedores como uma boa prática de programação. Neste caso, podemos definir valores simbólicos compostos, por exemplo: #define BUFFER_LENGTH 2048 #define N_BUFFERS 100 #define MASTER_LENGTH ( BUFFER_LENGTH * N_BUFFERS ) ... ... ... char screen[MASTER_LENGTH]; Os valores podem ser simbólicos em formato de código, o que permite criar programas com melhor legibilidade. Para isso podemos colocar expressões com funcionalidades bem definidas substituídas por nomes que as identifiquem. Por exemplo: float a[3]; #define PRINT_VECTOR cout
  • Variáveis e constantes 13 PRINT_VECTOR; Desta forma, todas as vezes que quisermos mostrar o valor do vetor de três coordenadas podemos usar a constante PRINT_VECTOR. Literais Constantes literais podem ser declaradas da mesma forma que na linguagem "C", ou seja, podemos definir valores fixos em qualquer parte do programa, expressando-os diretamente no código através de seu valor significativo. Por exemplo, podemos definir números: 256 //decimal 0400 //octal 0x100 //hexadecimal Também podemos definir valores para caracteres ou cadeias de caracteres, como segue: 'a' // um caractere "abc" // uma cadeia de caracteres "\xF3\x23\x12" // uma cadeia de caracteres representada por seus valores em hexadecimal Temos ainda a possibilidade de declarar constantes compostas por valores e operadores: (4.23e14 * (12.75 + 12976.18/36)) // constante composta Enumerações Valores enumerados são muito recorrentes nos ambientes de programação, por isso podemos contar com a declaração de enum em C++ também, o que segue a mesma sintaxe que temos em "C": enum seq {A,B,C,D}; seq x; ou ainda: enum nomes {LANY=100,SANDRA=200,MARCIA=300,RODRIGO=400}; nomes x; Porém, observamos uma diferença: a palavra enum pode ser dispensada na declaração da variável, enquanto que em C é obrigatório,apesar desta pequena diferença a funcionalidade do recurso é a mesma, ou seja, pode-se definir variáveis que assumem estritamente os valores presentes na declaração de enumeração. Este recurso torna-se útil na padronização de valores a serem usados como entrada de funções, por exemplo. Pode ser considerada como uma funcionalidade mnemônica, seu uso não altera o código final caso modifiquemos o programa para que use variáveis inteiras ou strings de mesmo valor do enum. A seguinte sintaxe: seq x = 3; Não é permitida, mesmo que o valor presente no enum seja avaliado como 3 pelo compilador em tempo de compilação. Isso pode parecer confuso, mas lembre-se de que os valores serão atribuidos pelo compilador, logo isso
  • Variáveis e constantes 14 evita que o mesmo programa seja compilado em ambientes diferentes e tenha comportamento diferente. Variáveis As variáveis no C++ podem ser usadas da mesma forma que na linguagem "C", porém algumas poucas diferenças podem ser destacadas, principalmente aquelas que trazem à linguagem C++ características próprias da orientação a objetos. Tipos Como na linguagem "C", os tipos nativos do compilador em uso são referenciados por: char int float double Que correspondem a números com tamanho relativos, com os significados respectivos: caractere, inteiro, ponto flutuante e ponto flutuante de dupla precisão. De qualquer forma a extensão dos mesmos depende da máquina que se pretende programar. Considerando que nem sempre teremos que programar apenas computadores, poderemos ter extensões bem distintas dependendo do hardware a ser programado, por exemplo, computadores domésticos tipicamente tem processadores de 32 ou 64 bits hoje em dia, enquanto que dispositivos embarcados podem ter processadores de 8, 16 ou 32 bits. Portanto, o compilador para cada caso atribui faixas diferentes para cada tipo em cada situação. A linguagem C++ introduz o tipo bool, que representa o valor booleano, falso ou verdadeiro, o que não existe na linguagem "C", porém seu tamanho na memória depende da capacidade de otimização do compilador usado. Tipicamente os compiladores para computadores usam uma variável do tamanho de char para representar o valor, o que poderia ser considerado um desperdício, mas devido à abundância de memória não chega a ser inadequado. Porém em sistemas pequenos há compiladores que armazenam o valor booleano em apenas um bit. Obviamente, se o processador possuir recursos de manipulação de bits isso é muito útil e pode ser usado como um fator de melhoria da qualidade do software desenvolvido. Em outros ambientes, onde a manipulação de bits traga prejuízo para o desempenho usa-se a estratégia padrão de desperdiçar um pouco de espaço em favor de uma agilidade maior nas operações. Portanto, embora as variações de utilização do espaço sejam muitas, o compilador sempre fará a mais apropriada para cada ambiente de utilização da linguagem. Modificadores O C++ conta com os modificadores de amplitude (short,long) presentes na linguagem "C" e modificadores de acesso, alguns exclusivos do C++, que estão diretamente ligados a características da POO (programação orientada a objetos). Desta forma descreveremos apenas os tipos relevantes exclusivamente para a programação na linguagem escopo do livro presente sem nos aprofundarmos na teoria por trás dos mesmos. A prática do uso dos mesmos é melhor indicada como meio de aprofundamento do tema. Assim contamos com os modificadores da linguagem "C": static short long unsigned signed
  • Variáveis e constantes 15 const A linguagem C++ introduz um novo modificador chamado const, que tem comportamento variado dependendo do local onde está sendo declarado. Sua função, basicamente, é estabelecer um vínculo entre declaração e obrigatoriedade da coerência no uso do símbolo declarado. A princípio, quando declaramos uma constante com este modificador fazemos com que seja obrigatório o uso do símbolo de forma que o mesmo não possa ter seu valor alterado. Assim, se fizermos: const int x = 4; O inteiro x não poderá deixar de ter valor igual a 4. Qualquer tentativa de modificar o valor da constante ao longo do programa será reportada como erro pelo compilador. Porém podemos considerar esta funcionalidade como óbvia e trivial, ainda temos o uso do modificador de uma forma mais proveitosa, na passagem de parâmetros para funções, por exemplo: void inhibitX(const int *x) { ... ... BASEADDRESS = z*((*x) - 23p*71); ... ... } Neste caso, a função acima recebe um valor inteiro através de um ponteiro, que não obrigatoriamente precisa ser constante no escopo fora da função, porém dentro da mesma a variável será constante. Fazendo este simples procedimento teremos como fazer com que um símbolo seja variável fora da função e constante dentro da mesma, de forma que dentro do escopo da mesma só façamos leituras do seu valor. O artifício cria duas consequëncias importantes: a primeira é a melhor legibilidade do código, visto que ao usarmos uma função teremos certeza de que os valores não serão alterados dentro da função; a segunda é que poderemos evitar erros inadvertidos de atribuição de valores à variável quando da construção da função. volatile Uma variável "volátil", como a própria expressão sugere, é uma variável que pode ser modificada sem o conhecimento do programa principal, mesmo que esta ainda esteja declarada dentro do escopo onde o programa está sendo executado. Isso está relacionado, principalmente a processos concorrentes e "threads", estes podem alterar o conteúdo da variável em eventos fora da previsibilidade do tempo de compilação. Em outras palavras, o compilador não pode prever com segurança se pode otimizar trechos de programa onde esta variável se encontra. A palavra reservada volatile é destinada as situações onde uma variável pode ter seu valor alterado por fatores diversos, e portanto, não pode ser otimizada. Usando-a o programador informa ao compilador que não deve interferir na forma com que o programa foi escrito para acesso a esta variável. Desta forma impede que erros inseridos por otimização estejam presentes na versão final do executável. O uso desta palavra implica em mudança no comportamento do compilador durante a interpretação do código. As classes de objetos do tipo voláteis só poderão ser acessadas por rotinas que declarem aceitar como entrada dados voláteis, da mesma forma que apenas objetos voláteis podem acessar variáveis voláteis. Esta "amarração" faz com que o uso de tais variáveis se torne mais seguro. Podemos declarar variáveis voláteis da seguinte forma: volatile int x;
  • Variáveis e constantes 16 Enquanto que para funções que acessam tais variáveis teremos consequências visíveis na montagem do código, por exemplo, se tivermos o seguinte trecho de programa: int x = 1265; void main_loop() { while( x == 1265) { // fazer alguma coisa } } Poderemos ter uma otimização gerada pelo compilador como segue: int x = 1265; void main_loop_optimized() { while( true ) { // fazer alguma coisa } } Considerando que em um programa que foi desenhado para ambiente multitarefa isso não pode ser considerado verdadeiro, pois o programa pode estar esperando que uma das tarefas modifique o estado da variável para prosseguir seu curso, a otimização acima será um desastre, uma vez que a função acima jamais será encerrada. Para evitar isso fazemos: volatile int x = 1265; void main_loop() { while( x == 1265) { // fazer alguma coisa } } E o compilador não poderá mais avaliar que o valor de x pode ser otimizado para o valor corrente, pois informamos na declaração que o valor da variável pode ser alterado sem seu conhecimento. Desta forma o mesmo não alterará o algorítmo e fará o teste da variável dentro do while.
  • Variáveis e constantes 17 Nomeando tipos A linguagem "C" possui recursos de nomeação de tipos simples e compostos através das palavras chaves typedef e struct. Adicionada a estas o C++ acrescenta a palavra chave class. Vejamos como devemos definir um novo tipo através desta palavra chave. A palavra class atribui a um conjunto de tipos de dados o estado de modelo de objeto. Este conceito é fundamental para o modo avançado de programar usando o C++. Com este identificador declaramos objetos, da mesma forma que declaramos estruturas. Uma classe pode ser definida em um cabeçalho "header", da seguinte forma: class nome_da_classe { variavel_1; variavel_2; . . . variavel_n; ----- nome_funcao ( variavel_1, variavel_2, variavel_3 ...); }; O mais interessante de observar é a presença de uma função dentro da declaração acima. Então poderíamos perguntar: Por que colocar uma função dentro de um tipo? A resposta é simples: Para manipular os dados dentro do tipo! Não apenas por esta característica, mas por várias outras que aboradaremos nos capítulos seguintes, o tipo class é extremamente flexível e poderoso. É importante ressaltar que em C++ a declaração do identificador: enum, struct, class, etc... é dispensado quando se declara uma variável ou objeto para o referido tipo. Desta forma podemos ter também as seguintes declarações como válidas, além do uso padrão da linguagem "C": struct data{ int a; int b; int c; }; class object{ int a; char b; long w; float p; void getData(); }; ... ... ... ... void func() { data x; object y;
  • Variáveis e constantes 18 ... ... y.getData(); } Como podemos ver na função acima se a variável x for declarada para uma estrutura data o uso da palavra struct não é obrigatório, assim como também não o é para outros tipos de dados compostos. Ponteiros Ponteiros Em linguagem "C", podemos definir variáveis ponteiro, ou seja, variáveis que armazenam o endereço de outras variáveis. Este recurso é largamente explorado pela linguagem, embora que deva ser usado com cautela por iniciantes devido ao seu poder destrutivo. Como linguagem "irmã mais nova" o C++ também permite o uso de ponteiros, o que a distingue de muitas outras linguagens orientadas a objeto. Embora seja muito difundida a idéia da criação de linguagens que não suportem acesso a ponteiros, basicamente pressupondo que todos os programadores são inexperientes, a falta deste recurso limita as capacidades de interação de programas com o hardware. Em outras palavras, a falta de um meio de manipular ponteiros faz a linguagem limitada ou dependente de fabricantes de bibliotecas que acessem o hardware. A disponibilidade do uso de ponteiros em C++ agrega um poder a mais ao conjunto da linguagem, porém implica em necessidade de cautela na elaboração de programas que usam deste recurso. Certamente, nem todos os programadores precisam ser considerados inaptos, a priori, através da supressão ou inserção de complicadores de recursos criados explicitamente para forçá-los a não usar dos recursos. Por isso, a linguagem C++ disponibiliza o recurso para quem deseja utilizá-lo e também apresenta diversos outros recursos que são alternativas ao uso de ponteiros quando eles não são imprescindíveis. O operador * O operador *, chamado de apontador, funciona em C++ da mesma forma que em C. Considerando que tenhamos uma variável ponteiro p: • Em p armazena-se o endereço de memória que queiramos manipular. Na maioria das vezes obtemos o endereço de outra variável e colocamos em p; • Se p é um ponteiro, *p é o valor apontado por p, ou seja, o valor que está armazenado no endereço de memória que queremos ler ou alterar. Na declaração de variáveis, uma variável declarada com * é um ponteiro. Exemplo: int *px; Muitas vezes, iniciantes podem se sentir confusos porque quando declaramos um ponteiro usamos o * e quando atribuímos endereços a ele não usamos o *. A conceituação básica é a seguinte: • Declaramos o ponteiro com *, para que o compilador identifique que a variável é um ponteiro; • Usamos o ponteiro sem *, para acessar o endereço que ele aponta na memória; • Usamos o ponteiro com *, para acessar o valor do dado armazenado na posição de memória;
  • Ponteiros 19 O operador & Na linguagem "C", o operador & tem duas funções básicas, funciona como operador da função lógica AND e como operador de leitura de endereços. Para operações com vetores, isso é usado da seguinte forma: int a = 12; int *pa; ... ... pa = &a; ... ... *pa = 100; Ou seja, declaramos a variável a, depois declaramos um ponteiro pa, através do operador & obtemos o endereço de a e atribuímos o valor 100 à variável usando o ponteiro ao invés da variável a. Desta forma alteramos o valor de a indiretamente. Um outro uso de & (que não tem similar em "C") pode ser visto mais adiante, em ../Referências de dados/, mas, para isto, é necessário estudar o que são ../Funções/. O ponteiro "this" Imagine que tenhamos criado um objeto qualquer de uma classe X, se quisermos ter acesso ao ponteiro que contém a posição de memória onde está armazenado este objeto basta chamar o ponteiro "this". O ponteiro "this" é uma das características dos objetos em C++ e algumas outras linguagens que suportam orientação a objetos. Ele é um membro inerente a todos os objetos que instanciamos em programas escritos em C++. Faremos uma breve explanação a respeito de objetos para esclarecer este tópico. Objeto são parecidos com estruturas, uma diferença básica é que estes possuem "habilidades específicas" representadas por funções que estão dentro do seu escopo. Vejamos um exemplo: struct Data { int x,y; int get_x(){ return x;} int get_y(){ return y;} int set_x(int a){ return x=a;} int set_y(int b){ return y=b;} }; Observe que a estrutura acima, apresenta dois inteiros e duas funções para cada um deles, uma que atribui o valor e outra que lê o valor de uma das mesmas. Detalhes das implicações a respeito desse modo de operar os valores serão dados nos capítulos seguintes que tratam de objetos. Por ora vamos nos ater a um conceito fundamental importante para a noção de ponteiros em C++, a identidade de um objeto. Veja, temos uma estrutura de dados que está na memória, os dados estão lá (variáveis x e y), porém as funções não estarão lá, pois se tivéssemos que copiar uma função para cada estrutura que criássemos o programa tomaria um tamanho monstruoso. O que se faz é apenas guardar o endereço da estrutura em um ponteiro especial, o ponteiro this. Assim, o compilador poderá criar uma única cópia de função para todas as estruturas que criarmos e depois quando a função quiser manipular os dados de uma estrutura em particular, o fará através do ponteiro this. Examinemos os detalhes mais de perto... Digamos que instanciemos um objeto "A" da classe Data:
  • Ponteiros 20 Data A; A.set_x(2); A.set_y(7); Para acessar estas funções o compilador fará: Data A; A.set_x(2); // { Data *this = &A; // return this->x = 2; // } A.set_y(7); // { Data *this = &A; // return this->y = 7; // } Desta forma podemos perceber como diferentes conjuntos de dados podem ser manipulados pela mesma função. Quando declaramos uma função dentro de uma estrutura de dados esta rotina recebe um ponteiro com o endereço do conjunto de dados que deve tratar toda vez que for invocada pelo programa. Assim, sempre acessará os dados através deste ponteiro, o this. Como todos os objetos precisam ser identificados por esse ponteiro, ele é definido para qualquer objeto com o mesmo nome: this. Vetores Vetores e Matrizes Façamos uma pequena revisão de conceitos: •• Vetores e matrizes são variáveis compostas homogêneas, ou seja, são agrupamentos de dados que individualmente ocupam o mesmo tamanho na memória e são referenciados pelo mesmo nome, geralmente são individualizadas usando-se índices. •• Vetores distinguem-se das matrizes apenas pela característica de ter dimensão (1 x n) ou (n x 1), essencialmente vetores são matrizes linha ou matrizes coluna. Em linguagem "C" vetores e matrizes são usados abundantemente para compor estruturas de dados necessárias para composição de diversos recursos. Esta usa, mais explicitamente, vetores de caracteres para definir cadeias de texto, o que é conhecido como o mais trivial uso de vetores. Além deste recurso, o "C" também define meio de criação de matrizes tipo (n x m), provendo, desta forma os recursos necessários para criação destes conjuntos de dados. A linguagem "C++" suporta os mesmos recursos e permite a criação de matrizes de objetos. Uma vez que um objeto é essencialmente um tipo de dado criado pelo programador, todas as características básicas legadas aos "tipos" em geral são observados nos tipos criados (classes de objetos).
  • Vetores 21 Vetores Os vetores em C++ seguem a mesma notação da linguagem "C", via de regra declara-se o tipo seguido de um asterisco. Para acessar o valor apontado pela variável usa-se um asterisco de forma semelhante, como pode ser visto no trecho de código abaixo: int *x; int a = 3; x = &a; cout
  • Vetores 22 equivalente a soma de suas variáveis internas para ser alocado na memória. Declarando arranjo Os arrays permitem fazer o seguinte: int a1, a2, a3,….a100; é equivalente a ter int a[100]; Ou seja permite declarar muitas variáveis de uma forma bem simples, poupa escrita e é bastante compreensível. • O número que está dentro de brackets [] é o size declarator. Ele é que vai permitir ao computador dizer quantas variáveis a serem geradas e logo quanta memória deverá ser reservada. A memória reservada para as variáveis vão ser todas seguidas, um int a seguir ao outro •• Há uma forma para não dizer o valor deste size declarator, mas isso apenas acontece antes da compilação, ou seja o compilador é que vai fazer esse preenchimento por nós, visualizando o nosso código e contanto os membros que colocámos. Isto é um automatismo dos compiladores recentes. chama-se a isto iniciação implícita que vamos ver nesta secção. •• As variáveis geradas pelos arrays vão ser todos do mesmo tipo. •• Reparem que o valor do size declarator é um número. É literal, ele não vai mudar quando o programa estiver a correr. Por isso quando não soubermos o número de elementos o que fazemos? Veja uma tentativa: #include using namespace std; int main () { int numTests; cout > numTests; int testScore[numTests]; return 0; } Isto vai dar um erro de compilação, porque o array está a espera de uma constante e não uma variável. Há uma maneira de contornar isto que é através da memória dinâmica que vamos dar mais tarde, num capitulo próprio, pois isto vai envolver muitos conceitos. Constantes Reparem que há uma diferença entre literais e constantes, apesar de em ambos os casos o valor não é alterado durante a execução do programa, a constant é um nome que representa o valor, o literal é o valor. Declarar constantes É exatamente como declarar uma variável com duas diferenças: 1.1. A declaração começa com a palavra const. Isto vai dizer ao compilador que é uma constante e não uma variável 2.2. Teremos de atribuir logo o valor na declaração, ou seja, é fazer a iniciação Exemplo: const int numTests = 3; Portanto se tentarmos colocar um outro valor ao numTest, isso vai dar um erro de compilação
  • Vetores 23 Array index a[100] é composto por a[0], a[1],… a[99] ( De a[0], a[1],… a[99] existe 100 posições) Pergunta: Porque é que o índex começa em zero e não um? Ou seja temos as 100 posições que pedimos mas o índex começa no zero e não no 1. A razão disto tem a ver com offset – que refere ao valor adicionado para o endereço base para produzir a segunda address. Bem não entendi bem! Eu explico de novo: O endereço (address) do primeiro elemento do array, é o mesmo do que o endereço base do próprio array. ah espera aí, o que estão a dizer é que o endereço do array é igual ao do primeiro elemento do array. Assim o valor que teria de ser adicionado, ao endereço base do array, para conseguirmos o endereço do primeiro elemento seria zero. Agora sim, percebi! Erro: Um erro comum é esquecer que o index começa no zero, e portanto quando se querem referir ao último elemento, esquecem-se que têm de subtrair uma unidade. O que advém desse esquecimento é que podem estar a alterar memória pertencente a uma variável, instrução,..de um outro programa. – Ou seja vai existir violação de dados. Se o array for declarado globalmente em vez de ser localmente, então cada elemento é inicializado ao valor defaut que é zero. Iniciação Iniciação, se bem se recordam é atribuir um valor a uma variável ao mesmo tempo que declaramos a variável. Podemos fazer a iniciação de um array de 2 maneiras: 1) explicit array sizing int testScore[3] = { 74, 87, 91 }; float milesPerGallon[4] = { 44.4, 22.3, 11.6, 33.3}; char grades[5] = {'A', 'B', 'C', 'D', 'F' }; string days[7] = {"Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"}; Pergunta: O que é que acontece se dermos mais valores de atribuição do que elementos do array? int a[3] = { 74, 87, 91, 45 }; Isto vai dar um erro de compilação “too many initializers” Pergunta: O que é que acontece se tivermos menos valores do que os elementos int a[3] = { 74 }; Não acontece nada simplesmente não temos valores para a[1] e a[2]. Porém em alguns compiladores os elementos não inicializados ficam com os valores defaut, que no caso dos ints é 0 no caso dos floats é 0.0 e nos caracteres é o caractere nulo ("\0"). No entanto se não inicializarmos um dos elementos, os restantes elementos terão de ser não inicializados pois caso contrário teremos um erro de compilação 2) implicit array sizing int testScore[ ] = { 74, 87, 91 }; float milesPerGallon[ ] = { 44.4, 22.3, 11.6, 33.3}; char grades[ ] = {'A', 'B', 'C', 'D', 'F' }; Aqui o compilador faz o trabalho por nós, conta os elementos e preenche o número de elementos
  • Vetores 24 Caracter array char name[ ] = {'J', 'e', 'f', 'f', '\0' }; char name[ ] = "Jeff"; Ambas as inicializações são permitidas. Porém tomar atenção á ultima iniciação! Quando colocámos as aspas duplas o compilador acrescenta o "\0" na array que cria! Não tem []!! Esta até costuma ser a preferida pelos programadores, é ao estilo de strings (Na verdade as strings são arrays de char mas vamos falar disso num capitulo próprio) O char "\0" é o escape sequence para caracterer null. Este escape sequence sinaliza ao cout o fim do character array. É o último elemento do array preenchido! Se não tivéssemos este carácter apareceriam estranhos caracteres a seguir ao "jeff", chamados "caracteres-lixo". (porquê?) Isto não significa que o último elemento deva ser sempre o null carácter Arrays de várias dimensões Podemos ter duas dimensões tipo_da_variável nome_da_variável [altura][largura]; como também poderíamos ter infinitas tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN]; Iniciando float vect [6] = { 1.3, 4.5, 2.7, 4.1, 0.0, 100.1 }; int matrx [3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; char str [10] = { 'J', 'o', 'a', 'o', '\0' }; char str [10] = "Joao"; char str_vect [3][10] = { "Joao", "Maria", "Jose" }; Peguemos no exemplo: int a [2][3]={1,2,3,4,5,6,} Na memória teríamos as coisas assim. ou seja os elementos são seguidos e do mesmo tipo a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] 1 2 3 4 5 6 Portanto ter int a [2][3] é equivalente a ter int a [6] o nome que se dá é que é diferente. Pergunta: será pedido espaço par 6 ints ou antes um espaço com o tamanho de 6 ints? Como nós sabemos que os arrays os elementos têm endereços de memoria consecutivos, por isso, não podem ser pedidos 6 ints, pois se fosse esse o caso, poderia acontecer que eles não ficassem juntos.
  • Vetores 25 Const Constant arrays const int daysInMonth [] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; Recordar que temos de inicializar quando queremos fazer uma constante array Atribuir valores ao array #include using namespace std; int main () { int testScore[3]; cout > testScore[0]; cout > testScore[1]; cout > testScore[2]; cout
  • Vetores 26 { int testScore[MAX]; for (int i = 0; i < MAX; i++) { cout
  • Vetores 27 } Vamos torná-lo mais modular, escrevendo uma função para atribuir valores ao array e outa função para mostrar os valores do array #include using namespace std; void assignValues(int[], int); void displayValues(int[], int); const int MAX = 3; int main () { int testScore[MAX]; assignValues(testScore, MAX); displayValues(testScore, MAX); return 0; } void assignValues(int tests[], int num) { for (int i = 0; i < num; i++) { cout
  • Vetores 28 } Este exemplo por acaso está muito curioso Pergunta: mas agora deveríamos perguntar se neste caso tínhamos uma passagem por valor ou referência. Quando um array é passado para uma função, a função recebe não a cópia do array mas invés disso o endereço, address do primeiro elemento do array, que é igual ao valor do array (base). Assim todas as modificações que se efectuarem na função que foi chamada irão repercutir-se no array passado. Vamos confirmar: #include using namespace std; void doubleThem(int a[], int size); int main() { int a; int myInts[10] = {1,2,3,4,5,6,7,8,9,10}; doubleThem(myInts, 10); //passei o array base for (a=0; a
  • Estruturas 29 Estruturas Breve revisão Conceito Da linguagem "C" também temos o conceito de estrutura, do qual faremos uma pequena revisão agora. Como todos sabemos, nem todos os dados que precisamos usar podem ser agrupados em matrizes. Frequentemente usamos dados de diversos tipos diferentes, com tamanhos diferentes. Para tipos de dados de diferentes tamanhos existem estruturas de armazenamento de dados heterogêneos. O especificador struct é usado para esta finalidade. Com ele podemos criar tipos que armazenam dados compostos por agrupamentos de outros tipos primitivos da linguagem. Geralmente, os dados são armazenados de forma a facilitar a identificação de cada campo de dados que pretende-se manter, para isso usamos nomes para cada campo dentro da estrutura de dados, de forma a ter um meio de acessá-la depois. Estruturas são blocos básicos de informação e são manipulados de maneira primitiva. Basicamente o compilador instrui a montagem de um código que manipula-as de forma a copiar, referenciar e obter posição na memória. Todas as outras formas de tratar os dados devem ser providas pelo código do programa. Implementação Para criar um tipo de dados composto heterogêneo, basicamente, cria-se uma lista de tipos e nomes de variáveis separadas por ponto e vírgula. Podemos imaginar esta lista como um bloco de código, em linguagem C, onde estão presentes apenas as declarações de variáveis. Para isso temos a seguinte sintaxe: struct Estrutura { NomeVariavelA; NomeVariavelA2; NomeVariavelA3; NomeVariavelB; NomeVariavelC; NomeVariavelD; ... ... NomeVariavelZ; } []; O nome da variável composta pode ser omitido na declaração da estrutura e depois definido onde for mais apropriado, geralmente, dentro de algum bloco de código onde venha a ser usado. Podemos ver logo abaixo, o exemplo de uma declaração de estrutura: struct Estrutura { int Inteiro; double PontoFlutuante; char Caracteres[10]; };
  • Estruturas 30 int main() { Estrutura MinhaVariavelComposta; ... ... ... return 0; } Acessando dados internos O modo de acesso a variáveis internas de uma estrutura é feito através do operador ponto ".", porém, quando usamos ponteiros para guardar o endereço de uma estrutura usamos o operador seta "->". Vejamos um pequeno trecho de código: Estrutura st; Estrutura *pst; st.Inteiro = 200; pst = &st; pst->PontoFlutuante = 23.976; Estruturas em C++ As estruturas em C++ funcionam de modo análogo ao apresentado em linguagem "C". A diferença, a princípio, notável entre elas nas duas linguagens é que em "C++" o especificador struct não precisa ser escrito quando criamos a estrutura: Em C, para criar uma estrutura de dados chamada st, declaramos: struct Estrutura st; Para fazer o mesmo em C++, declaramos: Estrutura st; Este simples detalhe revela uma característica importante das estruturas em C++: Nesta linguagem as estruturas são tratadas como tipos primitivos de objetos. Elas têm características semelhantes às classes, que veremos nos capítulos subsequentes. As estruturas em C++ também podem conter funções além de dados. Este fato vem da ideia de modelo de objeto que as estruturas mantém nesta linguagem. Objetos devem ter propriedades (variáveis) e métodos (funções membro), por isso temos a possibilidade de criar funções dentro do corpo das estruturas, com a finalidade de lidar com os dados que elas mantém.
  • Estruturas 31 Construtores Os construtores são funções que são criadas automaticamente sempre que tentamos criar um objeto. Eles funcionam da mesma maneira que construtores de classe. A esses que são criados automaticamente são os chamados de defaut. Se escrevermos o código: #include #include using namespace std; const int MAX = 3; struct Person { string name; int height; }; int main () { Person p1; cout
  • Estruturas 32 #include #include using namespace std; const int MAX = 3; struct Person { string name; int height; Person() { name = "No name assigned"; height = -1; } }; int main () { Person p1; cout
  • Estruturas 33 }; int main () { int metro; string strName; cout metro; cin.ignore(); Person p1(strName,metro); cout
  • Estruturas 34 int metro; string strName; cout metro; cin.ignore(); Person p1(strName, inches); cout
  • Operadores 35 operações e alterá-las quando nos seja conveniente. Entendendo o operador Basicamente, temos dois tipos de implementação para operadores, o tipo global e o tipo membro de classe. Os dois tipos são usados regularmente nas implementações mais comuns. Analisaremos o tipo global, uma vez que ainda não temos uma noção de classes suficiente para abordar o tipo membro de classe. Digamos que temos uma estrutura ponto, como definida abaixo: struct Ponto { int x; int y; }; Uma vez que tenhamos definido um ponto, nada mais natural que queiramos somar, subtrair, enfim operar, pontos diferentes de acordo com nossas necessidades. Para isso podemos criar operadores para fazer isso, da seguinte forma: •• Observamos a quantidade de parâmetros, o retorno e a forma de chamar o operador que queiramos definir e criamos uma função que execute a operação desejada; • Inserimos o código da referida função dentro de uma chamada de operador, usando a palavra reservada operator seguida do operador que desejamos definir: Ponto operator+ ( Ponto a, Ponto b ) { Ponto soma; soma.x = a.x + b.x; soma.y = a.y + b.y; return soma; } E assim, o operador é entendido como uma função, sendo a sobrecarga um processo de definição da operação a ser executada. Recebe esse nome porque todo operador já existe e a definição de uma nova funcionalidade apenas adiciona (sobrecarrega) as habilidades anteriores do operador. Embora isto seja comum, é bom lembrar que operações de tipos primitivos não poderão ser modificadas, restando apenas a funcionalidade de criar operadores para nossos tipos (classes). Os argumentos Agora vejamos como os argumentos são vistos pelo compilador durante a chamada ao operador. Essa sintaxe, muitas vezes, confunde iniciantes, mas é bastante intuitiva. Veja: c = a + b; Consideremos que a,b,c são pontos. Em termos gerais, qualquer operador binário (com dois argumentos) definido em escopo global, receberá a e b como primeiro e segundo argumento da função que define o operador. Podemos ver a chamada da seguinte forma: c = operator+( a, b );
  • Operadores 36 Operadores aritméticos Operadores aritméticos são utilizados para efetuar operações matemáticas entre dados. São 5 operadores aritméticos em C++: #include using namespace std; int main() { int soma = 5 + 5; // o operador '+' realiza somas. double subtracao = 5 - 5; // o operador '-' efetua subtração. float multiplicacao = 5.1 * 0.5; // o operador '*' efetua multiplicação. char divisao = 100 / 2; // o operador '/' efetua divisão. int modulo = 51 % 5; // retorna o resto da divisão inteira. cout
  • Operadores 37 Divisão real A divisão real é aquela efetuada entre tipos ponto flutuante ou entre ponto flutuante e inteiros/caracteres. Isso efetuará a divisão até que o resto seja zero, ou quando o resto repetir-se indefinidamente (no caso de dízimas periódicas como, por exemplo, 10/3). Se quisermos que a divisão entre inteiros retorne a divisão real, deveremos efetuar uma conversão explícita, conforme o exemplo: int num = 3; int num2 = 2; cout
  • Decisão e controle de fluxo 38 if-else if Se você quer que o software execute um determinado comando somente em certas situações, utilize if para determinar isto. O programa vai, então, executar a primeira linha de código após o if, se a declaração entre parênteses for verdadeira. Exemplo: #include using namespace std; int main(void) { int variavel; cout > variavel; if(variavel == 5) cout
  • Decisão e controle de fluxo 39 booleano falso. Podemos ainda encadear vários else, e obter ainda mais possibilidades: if(deposito < 20) { cout
  • Decisão e controle de fluxo 40 • Cada um dos casos tem de ser uma constante (não pode alterar durante a vida do programa), neste exemplo A, B, … •• O defaut serve para a condição de todas as avaliações dos casos anteriores der falsa. (é tipo o else) •• O break serve para terminar o switch, caso contrário se num dado case fosse verdadeiro, iria executar todos os statementes mesmo de outros cases até terminar o switch. •• Aqui para cada caso não necessitamos de {} se tivermos mais do que 2 statements. •• o if-else é mais forte do que o switch por que permite fazer coisas como: if (apples == oranges) do this; else if (sales >= 5000) do that; •• Para além do ponto já dito de os casos serem obrigatoriamente constantes, no switch •• Também posso utilizar operadores lógicos no switch switch (age >= 18 && citizen == true) { case true: cout
  • Decisão e controle de fluxo 41 • 5>3 ? a : b // returns the value of a, since 5 is greater than 3. • a>b ? a : b // returns whichever is greater, a or b. O Comando goto O goto realiza um salto para um local especificado. Este local é determinado por um rótulo. Portanto pode ser em qualquer parte do programa. nome_do_rótulo: .... goto nome_do_rótulo; .... // goto loop example #include using namespace std; int main () { int n=10; loop: cout
  • Estruturas de repetição 42 Estruturas de repetição Laços (loops em inglês), ou estruturas de repetição, são comandos existentes nas linguagens de programação destinados a executar uma ou mais instruções quantas vezes forem necessárias. Cada ciclo de um loop é chamado de iteração. Podemos ter também loops dentro de outro loop. While O while, "enquanto" em inglês, é um laço que ordena o computador a executar determinadas instruções enquanto uma condição for verdadeira. Isso faz com que um comando seja executado uma vez a cada verificação da condição. De modo geral o comando sempre deve ser elaborado de forma que se leve a condição de execução a ser falsa em algum momento, de forma a interromper o laço para que o resto do programa entre em execução. Sintaxe while (condição) comando; Onde condição é a condição de execução do laço while. O código abaixo mostra o uso do laço while para imprimir na tela do número 1 ao número 10. Perceba o uso de uma variável inteira intitulada contador. Esta variável é utilizada para armazenar um valor a ser impresso bem como participar da condição de execução do laço. Assim que a variável atingir o valor 11 o programa segue para o comando logo após o laço. #include using namespace std; int main() { int contador; // Declara a variável contador. contador=1; // contador recebe o valor 1. while (contador
  • Estruturas de repetição 43 Do-While O laço do-while é um while invertido, onde você coloca as instruções a serem repetidas antes da verificação da condição de execução. Isso significa que os comandos do laço serão executados ao menos uma vez. Sintaxe do { comando; } while (condição); Onde condição é a condição de execução do laço do-while. Os comandos pertencentes ao laço somente deixarão de se repetir quando a condição for falsa. O algoritmo abaixo mostra como seria o algoritmo exemplo usado na seção do laço while convertido para o uso do laço do-while. #include using namespace std; int main() { int contador; // Declara a variável contador. contador=1; // contador recebe o valor 1. do { cout
  • Estruturas de repetição 44 fixo, dez no algoritmo em questão. #include using namespace std; int main() { int contador; // Declara a variável contador. for (contador=1; contador
  • Estruturas de repetição 45 Dicas Bloco de Comandos Em muitos casos, os laços devem repetir dois ou mais comandos. Para isso, necessitamos criar um bloco de comandos contendo todas as instruções a serem repetidas. A criação de um bloco de comandos é simples, basta colocar todos os comandos entre chaves { }. Os algoritmos de exemplo dos laços while e do-while fazem uso de um bloco de comandos. Caso o laço não encontre a abertura de um bloco logo em seguida, ele assumirá que o comando imediatamente abaixo é o único que deve ser repetido. Exemplo 1: while (condição) comando1; // Este comando faz parte do laço. comando2; // Este comando não faz parte do laço. Exemplo 2: while (condição) { comando1; // Este comando faz parte do laço. comando2; // Este comando faz parte do laço. } O Comando break O que o break faz é quebrar a execução para fora do bloco de código onde ela está presente #include using namespace std; int main(void) { int num; char choice; bool quit = false; while (true) { cout > num; if (num > 0) break; else { cout > choice; if (choice != 'Y') { quit = true; break; }
  • Estruturas de repetição 46 } } if (quit == false) cout
  • Estruturas de repetição 47 int num = 2; cout
  • Estruturas de repetição 48 cout
  • Funções 49 Funções Função, do latim functio, onis, representa na computação, um pequeno algoritmo com uma função simples e bem definida. É como se cada função fosse um micro programa, ou um tijolo na construção do programa principal. O uso de funções facilita muito o desenvolvimento, pois, divide o problema principal em pequenos problemas mais simples. Essa técnica se chama, Dividir para conquistar. A experiência mostra que o uso de funções facilita e acelera a criação e manutenção de sistemas. Todo programa em C++ tem pelo menos uma função, o main. Veja o exemplo do programa em C++: #include //Biblioteca com funções de entrada e saída de dados using namespace std; int main (void) //Função principal do programa { cout
  • Funções 50 int i_f,i_c; //if is a reserved word df = 75.0; ff = 75.0; i_f = 75; // The compiler resolves the correct // version of ConvertFToC based on // the arguments in each call cout
  • Funções 51 Parâmetros default (padrão) Pode acontecer que tenhamos que declara varia vezes o mesmo valor como parâmetro de uma função. Para simplificar a chamada a funções que variam pouco podemos definir uma parâmetro default. #include #include /*-----------------------------Cabeçalho--------------------------------*/ /*Definimos uma funçao*/ void function(int a,int b, int c = 100 ) { printf("Meu Primeiro argumento :%d\n",a ); printf("Meu Segundo argumento :%d\n",b ); printf("Meu terceiro argumento :%d\n",c ); getchar(); } int main (void) { function( 10, 30); /* Agora use a função assim e veja o que acontece */ // function( 10,30,999); } Os parâmetros por default devem ser os últimos da lista, ou seja, mais à direita. O parâmetro padrão deve ser especificado no protótipo e não na declaração da função. Referências de dados Variáveis de referência Em C++ podemos criar variáveis que podem ser uma alternativa para os ponteiros em algumas situações. A vantagem de não usar diretamente o endereço (valor de ponteiro) em situações onde não precisamos lidar diretamente com valores de memória torna a programação mais segura e simplificada. Podemos deixar as operações com ponteiros apenas para quando for estritamente necessário. Variáveis de referência podem ser criadas para dar um nome diferente para as variáveis que já existem no programa, ou para passar a variável para dentro do corpo de uma função. Observemos, inicialmente, um caso simples: int a = 10; int &b = a; b = 20; Neste trecho de programa, criamos uma variável de referência b para a variável a, o que significa que criamos outro nome para a variável a. De fato, b é a própria variável a com outro nome, apenas isso. Desta forma, podemos alterar o valor de a usando b.
  • Referências de dados 52 Passagem de parâmetros Na linguagem "C", durante a chamada de uma função, os argumentos (parâmetros) têm seus valores copiados para a área de processamento da função. Depois que os mesmos foram usados dentro do bloco de processamento da função, eles são descartados. A função retorna o processamento para o bloco que a chamou trazendo apenas o valor de retorno. A única maneira de fazer com que a função modifique o valor de alguma variável definida no bloco de programa que a chamou é passá-la por um ponteiro com o seu endereço. Vejamos o fragmento de código seguinte: int f( int x ) { x--; return x; } int main() { int a = 10; int b; b = f(a); ... ... Em "C", a menos que o programador seja bem malicioso e faça manipulações de memória arriscadas, a função f jamais alterará o valor do seu argumento x. Diferentemente da linguagem "C", a chamada a uma função em C++ pode alterar o valor de uma variável definida antes da chamada da função, mesmo sem esta variável ser explicitamente passada como um ponteiro. Este modo é chamado de passagem por referência. Em termos mais gerais, significa a passagem da variável propriamente dita, para o corpo interno da função com outro nome, aquele definido na lista de parâmetros da função. Em C++, uma função pode ser chamada na forma acima e alterar o valor das suas variáveis. Para isso basta declará-la como: int function(int & x) { x--; return x; } Temos em C++ o operador & que se comporta diferentemente de C, tendo uma função a mais, a de criar variáveis de referência: • &x quando usado no código retorna o pointero para o endereço de x; • &x quando usado na declaração de variável, cria uma referência; • &x quando usado como parâmetro na declaração de uma função faz com que suas chamadas transfira o argumento/parâmetro passando-o de forma similar a passagem de seu pointero. (Passagem por referência). Em termos semânticos, ao passar a variável para uma função onde o parâmetro é uma referência, o endereço da variável é atribuído ao endereço do parâmetro. Desta forma, o parâmetro é a mesma variável passada, no trecho de código onde a função foi invocada, assumindo um nome diferente dentro da função. Vejamos um exemplo usando a função anterior:
  • Referências de dados 53 int m = 4; function(m); cout
  • Referências de dados 54 int &ref = val1; cout
  • Referências de dados 55 Comparação entre passagem por referência e ponteiros Para exercitar vamos criar um novo problema: Criar um função que duplique qualquer valor colocado pelo utilizador: 1º PROGRAMA-via referência 2º PROGRAMA – via ponteiros - endereços #include using namespace std; void doubleIt(int&);//prototype com endereço de variavel int main () { int num; cout > num; doubleIt(num); //chamo função, passando parametro num cout
  • Entrada e saída de dados 56 Entrada e saída de dados Entrada e saída Aqui vamos dar início ao estudo de recursos que possibilitarão inserir dados e fazer reporte da falta deles. No C++ a entrada e saída podem ser feitas através da biblioteca iostream. Para podermos usá-la deveremos colocar a linha de código: #include A estrutura de comunicação com o meio externo em modo texto é composta por um conjunto de objetos. Estas, em conjunto com operadores e funções de formatação possibilitam uma forma de comunicação mais intuitiva. Devido à abstração de elementos do mundo real por recursos da orientação a objetos, a forma de entender o código torna-se mais natural. Na biblioteca iosteam, temos os seguintes objetos: • cin - Este objeto fornece entrada de dados "bufferizada" através do "standard input device", o dispositivo de entrada padrão; • cout - Este objeto fornece saída de dados "bufferizada" através do "standard output device", o dispositivo de saída padrão; • cerr - Este objeto fornece saída de dados não "bufferizada" para o standard error device, o dispositivo de erro padrão, que é inicialmente definido para a tela. • clog - Este objeto fornece saída "bufferizada" através do "standard error device", o dispositivo de erro padrão que é inicialmente definido para a tela. O foco de orientação a objetos que a biblioteca iostream confere aos dispositivos de entrada e saída é uma das características da linguagem C++. Ele está presente na maneira na qual o código foi idealizado e está formatado, modificando a maneira como as partes do sistema de entrada/saída interagem. Desta forma, as operações de interação entre o usuário e o software tornam-se mais intuitivas para o programador. O sistema de entrada e saída é um exemplo deste modelo de programação, onde cada entidade física ou lógica de entrada e saída é representada por objetos cujas operações podem ser acessadas diretamente nos programas. Buffer Para entendermos um pouco mais sobre Buffer, se faz necessário recordar um pouco sobre o funcionamento da memória e suas operações relacionadas a Buffer. Bufferização é um meio de sincronização entre dispositivos de velocidades diferentes, tais quais memória e dispositivos de armazenamento mecânicos, como discos magnéticos. Para evitar que as operações do dispositivo mais lento interfiram no desempenho do programa pode-se fazer com que os dados sejam colocados em uma memória mais rápida e depois sejam enviadas ao dispositivo mais lento a medida que ele tenha disponibilidade para recebê-los, desta forma temos os seguintes modos de escrita em dispositivos de saída: • unbuffered – significa que qualquer mensagem ou dados serão escritos imediatamente. É o caso da escrita no dispositivo cerr; • buffered - significa que os dados serão mantidos num buffer de memória até que o dispositivo de destino solicite, ou que um comando de descarregamento seja executado, ou quando o buffer estiver cheio. O problema é que se o programa é interrompido antes do buffer ser escrito esses dados são perdidos.
  • Entrada e saída de dados 57 cout cout
  • Entrada e saída de dados 58 • \\ barra oposta (contrabarra) (\) cin O objeto cin obtém informação do "standard input" (que usualmente é o teclado). Este objeto está tal como o cout declarado no cabeçalho da biblioteca A sintaxe mais comum da instrução para obter dados do cin é: • cin >> [variable name]; Aqui temos o operador de extração ">>" que diz que tudo o que o teclado escrever, coloque esses dados na variável que me segue. Este operador consegue até traduzir o conceito de dados de fora para dentro. #include using namespace std; int main(void) { int testScore; cin >> testScore; cout
  • Entrada e saída de dados 59 Aqui já não temos a questão de dar a volta ao intervalo permitido, aqui temos o caso em que vai ser colocado um número estranho. Isto não está perfeitamente explicado. Chama-se prompt quando é dito ao utilizador o que deve fazer, o que não deixar como no exemplo anterior o cursor a piscar sem o utilizador saber o que fazer. Além de que temos o problema de overflow em execução, portanto é bom que o utilizador cumpra os requerimentos. De uma maneira muito conveniente, os dados recebidos pelo cin são tratados de forma a tornar o processo polimórfico, da mesma forma que no caso de cout, assim temos como receber os dados da maneira que precisamos, ou seja, quando declaramos uma variável int e a usamos para receber um dado do cin, o mesmo é convertido na entrada para inteiro, quando usamos uma variável de outro tipo, a entrada é convertida para o tipo da variável. Lendo um caractere Ler um caractere até é simples, basta utilizar o objeto cin e será guardado o valor digitado na variável. char nivel; cout > nivel; Porém teremos de pressionar a tecla ENTER depois de digitar o caractere. Isto leva a várias questões. O problema “pressione uma tecla para continuar...” #include using namespace std; int main(void) { char ch; do { cout > ch; if (ch != 'S' && ch != 's') cout
  • Entrada e saída de dados 60 A função cin.get() Já tivemos oportunidade para discutir a função getline (função membro) do objeto cin. cin.getline(name,80); Aqui vamos utilizar uma outra função, a cin.get(). Esta função pode ser chamada, tal como a getline(), através de 3 argumentos, onde o primeiro é o array de caracteres, mas também o pode ser sem argumentos ou ainda apenas um argumento. No caso de não conter argumentos apenas irá ler um caractere, em vez de uma cadeia de caracteres. No caso de ter um argumento, ela aceita qualquer tecla incluindo o enter. (o que não se passa com o cin e o operador de extração). Aqui um exemplo #include using namespace std; int main(void) { char ch; do { cout
  • Entrada e saída de dados 61 Ou seja, na segunda iteração, é retirado o caractere nova linha – que ficou da 1ª iteração - e é colocado na variável ch. Agora o "input buffer" está vazio. cin.ignore() Uma solução é limpar o caractere nova linha do "input buffer" antes da chamada da função getline(). E fazemos isso usando a função ignore() do objeto cin. Esta função membro tal com a get() e a getline() são sobrecarregadas, podem ser chamadas sem argumentos, com um ou dois argumentos. Utilizar a função ignore() sem argumentos, permite que o próximo caractere no "input buffer" seja lido e depois descartado,- e isto é exatamente aquilo que queríamos. A função com 1 ou 2 argumentos é usada para cadeias de caracteres. •• Com um argumento, o argumento é o número máximo de caracteres a ser removido do "input buffer". Exemplo: cin.ignore(80); // Remove até 80caracteres do input buffer •• Com dois argumentos, o segundo argumento é o delimitador, um caractere que se encontrado, antes do número de caracteres especificado no primeiro paramento, faz com que a remoção pare. Exemplo: cin.ignore (80, '\n'); // Remove 80 caracteres se até lá não encontrar o nova linha. Reescrevendo o código anterior utilizando o cin.ignore() #include using namespace std; int main(void) { char ch; do { cout
  • Entrada e saída de dados 62 Se pressionarmos a tecla Enter para continuar, teremos de fazer isso duas vezes, pois a primeira vez é ignorada. A razão: é que não existe nada no "input buffer" quando a função ignore é chamada, por isso é que a tecla enter necessita de ser pressionada 2 vezes, colocando um caractere nova linha a mais no "buffer" para a função ignore() remover. Se tentarmos modificar isto através do if? #include using namespace std; int main(void) { char ch; do { cout
  • Entrada e saída de dados 63 cout
  • Entrada e saída de dados 64 • Razão: A função get() com um argumento deixa o caractere nova linha no "input buffer" se pressionarmos um caractere e o enter. mas não deixará, se apenas pressionarmos o enter. portanto é necessário confirmar. Entrada de valores para variáveis múltiplas Podemos fazer com que o programa receba vários valores ao mesmo tempo cin >> [first variable] >> [second variable] >> [third variable]; Neste caso o utilizador separa os dados por espaços (o enter também dá) e como anteriormente o utilizador fecha utilizando o enter #include #include using namespace std; int main(void) { int peso, altura; string nome; cout > nome >> peso >> altura; cout
  • Entrada e saída de dados 2 65 Entrada e saída de dados 2 Entrada/Saída em ficheiros (arquivos) Nota introdutória: Este capitulo geralmente é colocado uns capítulos mais para o fim, mas acho por bem que se torne as coisas mais interativas, o que é possível introduzindo agora operações em arquivos, dá muito mais entusiasmo. Encontramos aqui conceitos avançados mas poderão ser deixados para depois se o leitor não quiser observar o tema neste momento. Gravar (Salvar) os dados para um ficheiro(arquivo) Os dados que mantemos nos programas estão guardados na memória RAM, que é limpa quando o programa ou computador para de funcionar. Isso implicaria que perderíamos toda a informação! Porém existe uma maneira para tornar os dados persistentes que é gravar os dados num ficheiro (arquivo) no "hard drive" (disco rígido) ou no outro meio persistente. Nas formas mais diretas de escrita podemos passar os dados em formato binário para o ficheiro(arquivo). Outros meios avançados para guardar dados podem envolver bases de dados relacionais ou XML. O que é um ficheiro(arquivo)? Um arquivo é uma coleção de dados que estão localizados numa memória persistente tipo hard drive, cd-rom, etc. Para identificarmos o arquivo podemos atribuir-lhe um nome (filename). Os "filenames" têm usualmente uma extensão, que determina o tipo de arquivo em sistemas operacionais semelhantes aos da Microsoft®, mas que podem ser dispensados em sistemas operacionais que guardam as características dos arquivos no meio de armazenamento, tais quais sistemas UNIX® e seus similares GNU/Linux, FreeBSD, etc... A extensão é representada por 3 ou 4 letras que seguem após o nome do arquivo e um ponto ".". Por exemplo: "joao.doc" ou "joao.odt". Isto diz-me que temos um ficheiro(arquivo) que se chama "joao", e que tem a extensão .doc que refere usualmente a documentos do WORD no primeiro caso e com extensão ".odt" do OpenOffice no segundo. Outros tipos de extensões podem ser ".xls" para documentos EXCEL, ".ods" para planilhas do OpenOffice. ou ainda ".cpp" para ficheiros(arquivos) de códigos de c++. Ficheiros(Arquivos) binários e tipo texto Existem na verdade dois tipos de ficheiros(arquivos): os do tipo texto e os do tipo binário. •• Os arquivos tipo texto apenas armazenam texto obedecendo uma codificação de caracteres, a mais comum é a ASCII, isto implica no uso do código para armazenamento, ou seja, pode ser que a codificação seja interpretada antes de ser efetivada no meio de armazenamento. • Os arquivos binários podem guardar mais informação, como imagens, base de dados, programas…Por exemplo, editores de texto com formatação, como o OpenOffice e o Word, guardam os seus arquivos em formatos binários, porque eles possuem além do texto, informação acerca da formatação do texto, para as tabelas, as listas numeradas, tipo de fonte, etc... daí aparecerem os caracteres de formatação tipo ã6, ÌL, h5… Os arquivos binários poderão ser mais bem explorados em um tópico avançado, vamos trabalhar inicialmente com arquivos tipo texto, que poderemos operar de maneira mais simplificada.
  • Entrada e saída de dados 2 66 biblioteca padrão fstream Até agora temos usado a biblioteca iostream (i de input + o de output + stream), que suporta, entre várias funcionalidades, o objeto cin para ler da "standard input" (que é usualmente o teclado) e o objeto cout para "standard output" (que usualmente é o monitor) Ora, agora queremos é ler e escrever para ficheiros(arquivos) e isso requer a biblioteca fstream (f de file + stream). Esta biblioteca define 3 novos tipos de classe: • ofstream (apenas para saída – "out to a file". serve para criar, manipular ficheiros (arquivos) e escrever, não serve para ler). • ifstream (apenas para entrada – "in from a file" . serve para ler ficheiros (arquivos), receber dados dos mesmos, não serve para criar nem escrever). •• fstream (este conjuga os dois tipos anteriores, "input and output to file". cria ficheiros (arquivos), escreve e lê informação dos mesmos. Abrir um ficheiro(arquivo) Um ficheiro(arquivo) deve ser aberto pelo programa para que o mesmo possa ser manipulado, a abertura do arquivo implica, entre outras coisas, em atribuir um identificador que nos permita ter acesso aos seus dados. É necessário criar uma linha de comunicação entre o arquivo e o objeto stream. Podemos recorrer a dois métodos para abrir um ficheiro (arquivo): 1.1. Usando um construtor; 2.2. Usando a função membro chamada de "open". Usando o Construtor O construtor é uma função que é automaticamente chamada quando tentamos criar uma instância de um objeto. fstream afile; //é criado uma instância do fstream chamada de afile Os construtores de objetos podem ser sobrecarregados, ou seja, para a mesma classe podemos ter um construtor sem argumentos, com um argumento, dois argumentos, etc. No exemplo anterior criamos um sem argumentos. Os construtores não retornam valores, geralmente o compilador reporta erro quando se declara funções que retornam valor e estas têm o mesmo nome da classe, pois este nome é reservado para os construtores. Vamos dar um exemplo com dois argumento: ofstream outfile ("joao.doc", ios::out); Chama o construtor com dois argumentos, criando uma instância de ofstream e abrindo o ficheiro(arquivo) "joao.doc" para operações de saída. Usando a função membro "open" Esta função tem como primeiro argumento o nome e localização do ficheiro/(arquivo) a ser aberto, o segundo argumento especifica o modo de abertura. Sobre a questão da localização existem 2 tipos, o "path" relativo e o "path" absoluto. Para este último indicamos o caminho todo: "c:\\....\\joao.doc" em sistemas Microsoft® ou "/home/joao/joao.odt" para sistemas UNIX® e similares. O "path" relativo dispensa essa descrição se o ficheiro/(arquivo) estiver (na mesma directoria)/(no mesmo diretório) que o programa. Sobre a questão do modo de abertura temos as seguintes modalidades:
  • Entrada e saída de dados 2 67 Modo do abertura sinalizador (Flag) Descrição ios::app "Append mode" Todos os dados do arquivo são preservados e qualquer saída é escrita a partir do fim do arquivo. ios::ate Se o arquivo já existe,o programa vai diretamente ao seu fim.O modo de escrita é então feito de forma aleatória.Usado normalmente com arquivos do modo binário(binary mode). ios::binary "Binary mode" Informações são escritas na forma binária e não na forma textual(text mode). ios::in "Input mode" Leitura de informações de arquivo(não irá criar um arquivo novo) ios::out "Output mode" Informações serão escritas no arquivo. ios::trunc Se o arquivo já existe,suas informações serão truncadas, outra forma de se dizer: deletadas e reescritas. Os sinalizadores (flags) são números em potências da base binária, portanto podemos ter vários flags ao mesmo tempo se usarmos o operador unário para a operação "OU", como no exemplo abaixo: ofstream outfile; //crio o objeto outfile outfile.open("students.dat", ios::binary | ios::app); /*chamo a função membro open do objeto, com o 1º parâmetro que é o nome do arquivo e o 2º o modo de abertura. */ Observe que estamos abrindo o arquivo "students.dat" em modo binário e ao mesmo tempo com o modo "append", isto significa que abriremos o arquivo e poderemos preservar o seu conteúdo anterior inserindo os novos dados no fim do arquivo. Comparando os dois métodos (pela função membro e pelo construtor) O primeiro método é similar a ter int age; age=39; O segundo método é similar a int age=39; A escolha do melhor método em cada situação depende do contexto em que estamos criando o código, geralmente quando já temos o objeto criado e ele está fechado podemos abrir um novo arquivo com ele e depois fechá-lo novamente, isto nos sugere que usemos a função open quando o objeto deve abrir arquivos diferentes em cada trecho de código, embora que possam surgir outras funcionalidades, dependendo de como o projeto foi idealizado.
  • Entrada e saída de dados 2 68 Abrir um arquivo para leitura A história aqui é a mesma só tem uma diferença: é que no caso de leitura, não será criado nenhum ficheiro (arquivo) caso ele não exista. ifstream arq; //cria objeto "arq" arq.open (“joão.doc”); //chama função membro open ao objeto "arq", com o //parâmetro do nome do ficheiro Poderíamos fazer o mesmo com o construtor: ifstream arq (“joão.doc”); Ou ainda fstream bloco; bloco.open("joao.doc", ios::in) ou ainda fstream b(“joao.doc”, ios::in) Há mais uma nota a fazer, se quisermos ler e escrever, não podemos usar o ofstream e o ifstream ao mesmo tempo, teremos de usar o fstream. Teremos de fazer: fstream a (“joão.doc”, ios::in | ios::out); Neste caso, o comportamento padrão é preservar o conteúdo do ficheiro (arquivo) ou criá-lo caso ele não exista. Verificar se o ficheiro (arquivo) foi aberto. Vamos verificar o que acontece quando tentamos abrir um arquivo que não existe, a primeira versão do nosso exemplo observa o comportamento básico do fstream: #include #include using namespace std; int main () { ifstream arq; //crio objeto "arq" da classe ifstream - leitura arq.open("joao.doc"); //chamo função membro open cout
  • Entrada e saída de dados 2 69 } No caso do ficheiro (arquivo) “joao.doc” não existir: (arq) = 00000000 (arq.fail()) = 1 No caso do ficheiro (arquivo) “joao.doc” existir no mesmo diretório que o programa: (a) = 0012FE40 (a.fail()) = 0 Repare que o resultado é a impressão do endereço, do objeto a de ifstream. dá um ponteiro!! Agora, vajamos um exemplo mais completo: #include #include using namespace std; int main () { ifstream arq; //crio objeto "arq" da classe ifstream - leitura string str; arq.open("joao.doc"); //chamo função membro open if (arq.is_open() && arq.good()) { arq >> str; cout
  • Entrada e saída de dados 2 70 Fechar um ficheiro (arquivo) Devemos fechar depois de ler e/ou escrever. Mas por que, se o objeto do ficheiro irá ser fechado assim que o programa acabar? Porque estamos a utilizar recursos com um ficheiro (arquivo) aberto, porque alguns sistemas operativos (operacionais) limitam o nº de ficheiros (arquivos) abertos, e estando este aberto impede que outros se possam abrir e por fim porque se não fecharmos, outros programas não poderão abri-lo até que o fechemos. Este comportamento faz parte de um esquema de controle de acesso usado pelo sistema para assegurar que os arquivos não serão usados por processos diferentes ao mesmo tempo. ofstream outfile; outfile.open("students.dat"); // .... outfile.close(); Vamos criar um exemplo mais real. Queremos criar um programa que escreva informação inserida pelo utilizador num ficheiro por nós escolhido #include #include using namespace std; int main () { char data[80]; //criamos um array de 80 caracteres ofstream outfile; //criamos objeto da classe ofstream outfile.open("joao.doc"); //chamamos a função membro da classe para o objeto criado. // Esta função membro cria o arquivo "joao.doc" if (outfile.is_open() && outfile.good()) //verificamos se está tudo bem { cout
  • Entrada e saída de dados 2 71 } Podemos ir ver o novo ficheiro/arquivo com o nome joao.doc e tem lá escrito aquilo que digitamos. Agora vamos tentar ler o que escrevemos no documento criado. #include #include using namespace std; int main () { char data[80]; ifstream infile; infile.open("joao.doc"); if (infile.is_open() && infile.good()) //verificamos se está tudo bem { infile >> data; //colocamos os dados abertos no array cout data; //colocamos os dados abertos no array cout data; //colocamos os dados abertos no array cout
  • Entrada e saída de dados 2 72 return 0; } Agora já obtemos 2 palavras e são apresentadas em linhas diferentes. Mas temos de arranjar um método para não estar a repetir constantemente, podemos fazer isso com infile.getline(data, 80); Então ficamos com: #include #include #include using namespace std; int main () { string data; ofstream outfile; outfile.open("joao.doc"); if (outfile.is_open() && outfile.good()) //verificamos se está tudo bem { cout
  • Entrada e saída de dados 2 73 getline(infile, data); cout data; while(!infile.fail()) { infile >> data; cout
  • Entrada e saída de dados 2 74 { cout
  • Entrada e saída de dados 2 75 bool writeFile (ofstream&, char*); bool readFile (ifstream&, char*); int main () { string data; bool status; ofstream outfile; status = writeFile(outfile, "students.dat"); if (!status) { cout
  • Entrada e saída de dados 2 76 getline(infile, data); } infile.close(); } #ifdef WIN32 system ("pause"); #endif return 0; } bool writeFile (ofstream& file, char* strFile) { file.open(strFile); return !(file.fail()||!file.is_open()||!file.good()); } bool readFile (ifstream& ifile, char* strFile) { ifile.open(strFile); return !(ifile.fail()||!ifile.is_open()||!ifile.good()); } Manipuladores Os objetos das classes "stream" podem ser configurados para fornecer e reportar os dados de maneira pré-formatada. Da mesma maneira que temos a formatação quando usamos funções de formatação, como printf() e scanf(), na linguagem C, podemos usar os manipuladores na linguagem C++ para informar os objetos streams em que formato desejamos receber os dados deles ou fornecer para eles. Abaixo temos uma série de manipuladores úteis: Manipulator Uso boolalpha Faz com que variáveis tipo bool sejam reportadas como "true" ou "false". noboolalhpa (padrão) Faz com que variáveis tipo bool sejam reportadas omo 0 ou 1. dec (padrão) Determina que variáveis tipo inteiras (int) sejam reportadas na base 10. hex Determina que variáveis tipo inteiras (int) sejam reportadas em hexadecimal. oct Determina que variáveis tipo inteiras (int) sejam reportadas em octal. left Faz com que textos sejam justificados a esquerda no campo de saída. right Faz com que textos sejam justificados a direita no campo de saída. internal Faz com que o sinal de um número seja justificado a esquerda e o número seja justificado a direita. noshowbase (padrão) Desativa a exibição do prefixo que indica a base do número. showbase Ativa a exibição do prefixo que indica a base do número. noshowpoint (padrão) Mostra o ponto decimal apenas se uma parte fracionária existe. showpoint Mostra o ponto decimal sempre.
  • Entrada e saída de dados 2 77 noshowpos (padrão) Nenhum sinal "+" prefixado em números positivos. showpos Mostra um sinal "+" prefixado em números positivos. skipws (padrão) Faz com que espaços em branco, tabulações, novas linhas "\n" sejam descartados pelo operador de entrada >>. noskipws Faz com que espaços em branco, tabulações, novas linhas "\n" não sejam descartados pelo operador de entrada >> fixed (padrão) Faz com que números com ponto flutuante sejam mostrados em notação fixa. Scientific Faz com que números com ponto flutuante sejam mostrados em notação científica. nouppercase (padrão) 0x é mostrado para números em hexadecimal e para notação científica. uppercase 0X é mostrado para números em hexadecimal e para notação científica. Ajustando a largura da entrada/saída •• setw(w) - Ajusta a largura da saída e entrada para w; precisa ser incluído. •• width(w) - Uma função membro das classes iostream. Preenchimento de espaços em branco •• setfill(ch) - Preenche os espaços em branco em campos de saída com ch; precisa ser incluído. •• fill(ch) - Uma função membro das classes iostream. Ajustando a precisão •• setprecision(n) - Ajusta a precisão de casas decimais em números com ponto flutuante, para n dígitos. Este ajuste é apenas visual, de forma que o manipulador não afeta o modo de cálculo do número pelo programa. Exemplificando o uso de manipuladores: #include #include #include using namespace std; int main() { int intValue = 15; cout
  • Entrada e saída de dados 2 78 cout
  • Entrada e saída de dados 2 79 int char_count = 0; int sentence_count = 0; char ch; ifstream object("jo.txt"); if (! object) { cout
  • Manipulando strings 80 Manipulando strings "Char strings" e Strings Os caracteres são entendidos como sendo números que geralmente têm oito bits, esses números são traduzidos na tabela ASCII de 128 caracteres, como existem inúmeras regiões no mundo com características linguísticas próprias, a tabela ASCII é estendida por um bloco de caracteres acima dos 128 mais baixos que varia de acordo com as necessidades de cada língua. A parte superior da tabela ASCII é conhecida como parte estendida e é referenciada por páginas de códigos para cada propósito linguístico, isso quer dizer que podemos ter os mesmos números significando caracteres diferentes para cada região do mundo. No estilo da linguagem C quando queremos representar um conjunto de caracteres colocamos todos eles em uma matriz sequenciada na memória: Endereço relativo 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A Dado U m a f r a s e . Por exemplo, para declarar um espaço na memória que contenha 20 caracteres fazemos: char dados[20]; Este é o estilo de strings usado pela linguagem C pura. Para manipular este tipo de string é preciso ter certo cuidado, pois a matriz sempre tem um tamanho definido e caso façamos um acesso a um endereço fora da matriz invadiremos outras áreas de memória que não temos como definir o que são, e portanto poderemos fazer o programa parar de funcionar, em muitos sistemas pode também haver danos aos outros programas e até mesmo ao próprio sistema operacional, porém em sistemas operacionais mais sofisticados como o GNU/Linux, que possuem gerenciamento de memória com proteção de memória, apenas o programa que causou a falha irá parar de funcionar. Para manipular este tipo de string a biblioteca padrão da linguagem C dispõe de diversas funções, para mais detalhes consulte o livro Programar em C. No estilo C++, como era de se esperar, as strings são objetos, eles podem ser criados facilmente através da biblioteca padrão referenciada pelo arquivo de cabeçalho . As strings são objetos com recursos que permitem manipular os seus caracteres com as funcionalidades das funções da linguagem C e mais algumas características próprias possibilitadas pela orientação a objetos. // listandoCodigoASCII.cpp #include using std::cout; using std::cin; using std::endl; #include using std::string; using std::getline; int main(){ string anyWord;
  • Manipulando strings 81 cout
  • Manipulando strings 82 Função Descrição isalpha Retorna verdadeiro se o argumento é uma letra do alfabeto; falso em caso contrário. isalnum Retorna verdadeiro se o argumento é uma letra do alfabeto ou um dígito; falso em caso contrário. isdigit Retorna verdadeiro se o argumento é um dígito; falso em caso contrário. islower Retorna verdadeiro se o argumento é uma letra minúscula, falso em caso contrário. isprint Retorna verdadeiro se o argumento é um caractere imprimível (incluíndo espaços); falso em caso contrário. ispunct Retorna verdadeiro se o argumento é um sinal de pontuação (caracteres imprimíveis que não sejam letras, dígitos ou espaço); falso em caso contrário. isupper Retorna verdadeiro se o argumento é uma letra maiúscula; falso em caso contrário. isspace Retorna verdadeiro se o argumento é um espaço, tabulação ou nova linha; falso em caso contrário. Strings em C++ As cadeias de caracteres da linguagem C podem formatar um novo tipo de dados, porém criar tipos de dados mais sofisticados não é possível nesta linguagem, as strings em C++ são objetos da classe string, o que isso traz de novo para o tratamento de textos em programas? A primeira coisa a notar quando criamos strings em C++ é a maneira de criá-las, a classe disponibiliza uma série de construtores: 1 string ( ); 2 string ( const string& st ); 3 string ( const string& st, size_t position, size_t n = npositions ); 4 string ( const char * ps, size_t n ); 5 string ( const char * ps ); 6 string ( size_t n, char ch ); Isto torna possível, basicamente, criar string de seis maneiras diferentes: 1.1. Podemos definir um objeto string vazio, para futuramente usarmos de acordo com a necessidade; 2.2. Podemos criar um objeto string com uma cópia de outro; 3.3. Podemos criar um objeto string com uma cópia de uma porção de outra string; 4.4. Podemos criar um objeto string com uma cópia de uma parte de uma "char string"; 5.5. Podemos criar um objeto string com uma cópia de uma "char string"; 6.6. Podemos criar um objeto string preenchida com uma quantidade definida de um determinado caractere; Quando manipulamos strings, podemos fazê-lo com operadores, como por exemplo "+", "+=", "
  • Manipulando strings 83 5.5. Enviar uma string a um output stream; 6.6. Receber uma string do input stream. Apenas com estas poucas informações já é possível operar strings com bastante flexibilidade e de uma maneira muito intuitiva, vejamos alguns exemplos: string a = "Alice e Beto gostam de ", b("chocolate."), c = string("doce de leite."), d = "pipoca.", e(c); cout
  • Manipulando strings 84 Comparando formas de operar strings em C e C++ Em C, temos diversas funções que são usadas para manipular strings, para mais detalhes veja o livro Programar em C, aqui faremos uma comparação dos modos de operar strings em C e C++, algumas particularidades da linguagem C++ permitem uma operação mais intuitiva das strings e algumas novas formas de tratá-las. Vejamos como manipular estes dados tão comuns em qualquer programa. Funções uteis para o uso de strings strlen() – (str=string + len=length)- aceita um argumento que pode ser um array (uma cadeia) de caracteres, um ponteiro (que aponta para um array de caracteres) ou uma string literal. retorna um número inteiro que representa o número de caracteres, não incluindo o caractere "null": int len; len = strlen("Jeff") // a extensão é 4 char* stinkydog = "Dante"; len = strlen(stinkydog); // a extensão é 5 char name[80] = "Devvie"; len = strlen(name); // a extensão é 6 No c++ temos duas funções similares na classe string que são o lenght() e size(). Estas funções não tem argumentos pois reportam as informações sobre o objeto a quem pertencem, ambas retornam um inteiro que representa o tamanho das strings: string s = "Jeff Kent"; cout
  • Manipulando strings 85 Agora, reflitamos no que significa estas operações: Em primeiro lugar "string" não é um tipo primitivo de dado, é uma classe, portanto é um tipo de dado mais "inteligente", uma das características dos objetos string é que eles são redimensionáveis, ou seja, quando atribuímos a uma string um dado maior que seu espaço interno de armazenamento ela aumenta o seu espaço interno para comportar o novo dado. Outra característica é que a operação "=" para a string é uma operação de atribuição de conteúdo, de forma que a string copia a outra quando usamos este operador e não apenas o ponteiro que referência o endereço da string. Unir strings strcat() – (string+concatenate) – une duas frases. Recebe 2 argumentos, a frase primária – o ponteiro para esse array. char target[80] = "Jeff"; char* source= " Kent"; strcat(target, source); cout
  • Manipulando strings 86 char str1[80] = "Devvie Kent"; char str2[80] = "Devvie Kent"; if (!strcmp(str1, str2)) cout
  • Manipulando strings 87 using namespace std; string str1 = "check"; string str2 = "chess"; if (str1 == str2) cout
  • Manipulando strings 88 #ifdef WIN32 system ("pause"); #endif return 0; } Neste exemplo temos a vantagem de o usuário inserir um dígito para o array de caracteres em vez de um inteiro, para evitar um "run-time error" ou "garbage data" que aconteceria se a entrada fosse não numérica. Depois o array é verificado para ver se representa um número. Se o numero for negativo tem o caractere "–". Em C++ usamos objetos da classe stringstream (biblioteca sstream.h) para armazenar temporariamente os caracteres, depois usamos o operador ">>" para converter os caracteres em número, bastando para isto criar a variável no formato que desejamos receber o número. Mais uma vez temos o uso do poliformismo para resolução de um problema comum de programação, a operação do ">>" é diferente para cada tipo de dado, selecionada automaticamente pelo compilador de acordo com o tipo de dado da variável destino. string name = "123"; stringstream sst; int i; sst i; Os passos acima armazenam o valor 123 na variável "i", todo processo de conversão é feito pelo operador ">>". Classes Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. Classes Existem duas categorias de tipos de dados usuais em C++, são classificados como tipos básicos e tipos definidos pelo programador. Assim como na linguagem C, podemos definir dados compostos por associações dos tipos básicos, estes tipos são chamados de estruturas (structs). C++ traz uma nova representação de dados, muito semelhante na forma às estruturas, porém diferentes na forma conceitual: a palavra chave class, que é usada para criar uma classe de objetos mais rica que as structs. Ao declararmos um identificador, tal qual fazemos ao declarar uma variável e no lugar do tipo especifiquemos uma classe criaremos um objeto. Antes de prosseguirmos, vejamos um pouco sobre o conceito por trás do uso de objetos. Um objeto é entendido como uma entidade de dados dentro da memória que, basicamente, deve ser responsável por seu conteúdo, ou seja, um objeto deve ser capaz de gerenciar seu conteúdo autonomamente, ou prover meios de outras entidades de código fazê-lo de forma segura.
  • Classes 89 Origem (atributos) Observemos, por exemplo, o código abaixo: struct MyData { int n; char data[10]; float nReal; }; Esta declaração, bem conhecida de quem já está familiarizado com a linguagem C, cria um tipo de dado composto heterogêneo, que neste exemplo chamamos de MyData, o que acontece aqui é que os dados estão agrupados dentro desta estrutura, isto promove a possibilidade de manipulá-los em conjunto. Um dos problemas com esta estrutura é a presença de uma matriz de caracteres chamada "data", observe que a mesma tem um tamanho definido de 10 caracteres, imagine que em algum momento da execução do programa tentamos colocar um caractere na posição 11, ou qualquer posição fora da matriz, neste caso estamos colocando o referido dado em endereços inválidos para a operação que pretendemos realizar, ou seja, não há controle nenhum que assegure que o código não fará um acesso fora da área que pertença a matriz. Um acesso de memória a qualquer elemento da matriz acima da posição 9, fará com que invadamos dados na área onde a variável nReal está definida. Funções membro (Métodos) Agora suponha que tenhamos como definir um modo para entrar e outro para ler dados da matriz: struct MyData { int n; char data[10]; float nReal; bool write_data(int pos, char c) { if (pos >= 0 && pos < 10) { data[pos]=c; return true; } return false; } char read_data(int pos) { if (pos >= 0 && pos < 10) { return data[pos]; } return '\0'; } }; Agora temos assegurados métodos de inclusão e acesso a dados da matriz de caracteres, porém ainda existe um pequeno problema: Quem quiser o antigo método de acesso direto conseguirá facilmente, pois os elementos da estrutura estão acessíveis publicamente por padrão.
  • Classes 90 Conceituação O problema da visibilidade pública dos dados em uma estrutura pode ser resolvido com um dos conceitos de objetos, o encapsulamento. Encapsular os dados, significa reservar o acesso a funções que estejam dentro de um grupo restrito, especializado para tais operações de manipulação destes dados. Uma das vantagens deste procedimento é que o código adquire um formato mais organizado, onde os processos tornam-se claramente distintos, caso tenhamos que analisar o código, cada procedimento estará restrito a partes definidas para cada operação. Declarando classes As estruturas são bem parecidas com as classes, com uma pequena diferença, peguemos o caso da passagem de estruturas como argumentos de funções: #include #include using namespace std; class Person { string name; int height; }; void setValues(Person&); void getValues(const Person&); int main () { Person p1; setValues(p1); cout
  • Classes 91 cout
  • Classes 92 else name = s; } int Person::getHeight() const { return height; } void Person::setHeight(int h) { if (h < 0) height = 0; else height = h; } void setValues(Person&); void getValues(const Person&); int main () { Person p1; setValues(p1); cout
  • Classes 93 Mas perguntam: Por que é que nos demos ao trabalho de recorrer a membros privados em vez de fazer todos públicos? Quando tínhamos uma estrutura no lugar de uma classe, não havia nada que impedisse a colocação de valores inválidos, por isso poderíamos ter valores vazios para a string e valores negativos para a variável "height". Agora que "Person" é uma classe, as funções membro podem realizar a validação dos dados antes da atribuição de valores nas variáveis. Poderíamos fazer com que a função setName verificasse se a entrada na string seria vazia e caso fosse, colocaria um valor padrão como: “sem nome”. similarmente poderíamos ter "setHeight" para verificar se seriam colocados valores de entrada negativos e caso fossem, colocaria zero, ou não tomaria nenhuma ação. Todas estas características demonstram o conceito de encapsulamento. A sua finalidade é de tornar o código mais modularizado, restringindo o escopo de análise a partes bem delimitadas dos programas. Devido a este conceito podemos contar com códigos mais fáceis de analisar e fazer manutenção. Instanciando objetos Instanciação de objetos é o processo de criar a estrutura lógica dos mesmos na memória. Isto ocorre quando declaramos os objetos, pois neste momento todo o processo de construção dos mesmos é efetivado. Assim, toda vez que declaramos um objeto estamos instanciando-o, ou seja, estamos criando uma instância da classe. Podemos declarar os objetos logo após definir a classe conforme podemos ver no 1º caso logo abaixo. Neste caso teremos a variável rect criada como um objeto conforme estabelecido pelo modelo definido pela palavra chave class. Este tipo de declaração é mais usual para objetos criados globalmente, pois a inclusão desta declaração no cabeçalho pode fazer com que vários objetos sejam criados com o mesmo nome quando o cabeçalho é invocado de vários arquivos. Portanto, é mais prudente usar esta opção quando a declaração está no arquivo fonte e não no cabeçalho. 1º caso: class CRectangle { int x, y; public: void set_values (int,int); int area (void); } rect; No 2º caso, apresentado logo abaixo, podemos declarar objetos apenas quando precisarmos. Esta opção de declarar o objeto depois é a mais usada, pois na maioria das vezes temos o modelo dos objetos, a classe, declarada em um arquivo de cabeçalho enquanto que os objetos serão criados no resto do código fonte. Desta forma é mais usual criar as classes em cabeçalhos e depois declarar os objetos na parte do programa que for mais conveniente. 2º caso: class CRectangle { int x, y; public: void set_values (int,int); int area (void); }; int main() { CRectangle rect;
  • Classes 94 } Em ambos os casos temos CRectangle Private public int x int y void set_values (int,int); int area (void); rect Podemos, então, entender os objetos como blocos de dados que têm propriedades (variáveis) e que podem fazer algo (métodos). Então, criamos todas as funcionalidades que precisamos que a classe forneça aos programas, fazendo os testes necessários para assegurar sua consistência e estabilidade. Sempre que precisemos utilizar os objetos só temos que instanciá-los (declará-los), e não precisamos nos preocupar como eles funcionam internamente, uma vez que os desenhamos adequadamente. Para entendermos melhor este conceito podemos fazer uma analogia. Consideremos um objeto resistência: sabemos que temos de usá-lo e que ela deve ter certas características, então teremos o seu valor em Ohms, sua potência máxima, tolerância, entre outras, e teremos uma função que nos dará a corrente que passa por ela quando lhe aplicamos uma tensão elétrica. Não precisamos saber de que é que ela é feita, ou como estas características internas a faz funcionar, basta-nos receber os resultados. Vejamos o exemplo: Agora vamos mostrar que podemos ter funções membro apenas como protótipos e defini-las fora da classe. Para isso usamos o operador de definição de escopo :: que permite definir o local do código onde um identificador existe, no formato: ESCOPO::função ou ESCOPO::dado. De maneira geral, quando declaramos identificadores dentro da classe podemos defini-los no escopo global referenciando estes pelo operador de escopo. // classes example #include using namespace std; class CRectangle { int x, y; public: void set_values (int,int); int area () {return (x*y);} }; void CRectangle::set_values (int a, int b) { x = a; y = b; }
  • Classes 95 //repare no “::” que pemite-nos definir a função membro da classe CRectangle fora da classe int main () { CRectangle rect; //definimos objeto de classe rect.set_values (3,4); //objeto-membro cout
  • Classes 96 int getAge(); void setWeight(int weight); int getWeight(); void speak(); private: int age; int weight; }; void Dog::setAge(int age) { this->age = age; } int Dog::getAge() { return age; } void Dog::setWeight(int weight) { this->weight = weight; } int Dog::getWeight() { return weight; } void Dog::speak() { cout
  • Classes 97 estas informações Agora planejemos os métodos. Vamos, primeiramente, assumir que temos a restrição de
  • Classes 98 um vetor com 4 characteres, ou a variação de qualquer um dos anteriores sem sinal, mas estes detalhes não precisam estar expostos. 2.2. Se a representação interna dos dados for modificada, desde que os tipos de retorno e de parâmetros das funções públicas mantenham-se inalteradas, não necessitemos de alterar código que utilizem objetos da classe. Ou seja, o encapsulamento simplifica a programação escondendo as particulariadades da classe e elimina o retrabalho do código por alterações da mesma. Geralmente as funções (métodos) privadas, são auxiliares a outras funções da classe. Se nenhum especificador de acesso for usado, todos os membros e metodos são declarados como privados por padrão. Há dois métodos para definir as funções membro: •• Eles podem ser definidos dentro da classe, o que é apropriado para funções pequenas; •• E funções grandes podem ser definidas fora da classe. Neste caso terão de ser identificadas como pertencentes à classe e para isso utilizamos o operador de resolução de escopo “::”. Construtores Conceito Os construtores "constructors" são funções membro (métodos) especiais de uma classe. Permitem a inicialização das variáveis membro de um objeto. Ou melhor, permitem a construção e a inicialização de objetos das classes. Se não os declararmos o compilador faz isso por nós. Os construtores têm sempre o mesmo nome que a classe. Os objetos são construídos através destas funções especiais chamadas de construtores. Até aqui não os declaramos, eram criados automaticamente. Estas funções tem certas características que as fazem distintas das normais, que permitem que as mesmas construam a estrutura lógica inicial do objeto. Desta forma estas funções são características da orientação a objetos e servem para criação dos mesmos. Construtores não podem ser chamados explicitamente como fazemos no caso de funções membro regulares. Eles são apenas executados quando um novo objeto da classe é criado. Portanto, existe apenas um evento capaz de executar um construtor, a instanciação do objeto. As principais características dos construtores são: •• Não têm qualquer valor de retorno; •• Não podem ser executados por chamada explícita no código; •• São executados logo depois que os tipos básicos do objeto foram criados; •• Inicializam os dados com os valores que o objeto precisa para começar a funcionar corretamente. Declaração Podemos criar construtores facilmente, através das características que os distinguem das funções membro convencionais. Ou seja, definimos uma função membro que possua o mesmo nome da classe, não tenha tipo de retorno e a declaramos como pública para que possa ser acessada por quem queira instanciar objetos. Vejamos como definir um construtor: class Caneta { string cor; int volume; /////////////// public:
  • Classes 99 Caneta( string c, int v ); }; Caneta::Caneta( string c, int v ) { cor = c; volume = v; } Construtores podem iniciar os membros da classe de uma forma simplificada. Este formato é usado sempre que o construtor tem dados básicos que podem ser iniciados antes do resto da construção da estrutura do objeto. Podemos iniciar os dados do objeto declarando-o desta forma: class Caneta { string cor; int volume; /////////////// public: Caneta( string c, int v ) : cor(c), volume(v) { } }; Para fazê-lo, como vemos no código, basta listar as variáveis membro em uma sequência depois da declaração do nome do construtor e de um sinal de dois pontos ":". Iniciamos uma lista de membros, com o valor a ser atribuído entre parênteses depois de cada um, separando-os por vírgulas. Destrutores Conceito Além do construtor a linguagem C++, assim como outras linguagens orientadas a objeto, possuem outro tipo de função especialmente criada e gerenciada pela linguagem, os destrutores. Estas são destinadas a desmontar a estrutura do objeto quando o mesmo está sendo encerrado. O destrutor terá o mesmo nome da classe, mas precedido pelo sinal til “~” e também não retorna valor. O destrutor tem as seguintes características: •• O destrutor é chamado quando o objeto está sendo finalizado; •• É usado para liberar qualquer memória que tenha sido alocada; Declaração Façamos a classe Dog com o construtor e o destrutor. class Dog { public: Dog(); //Constructor ~Dog(); //Destructor void setAge(int age); int getAge(); void setWeight(int weight);
  • Classes 100 int getWeight(); void speak(); private: int age; int weight; }; Dog::Dog() { age = 0; weight = 0; cout
  • Classes 101 }; Employee::Employee(char *name, int id) { _id = id; _name = new char[strlen(name) + 1]; //Allocates an character array object strcpy(_name, name); } Employee::~Employee() { delete _name; } int main() { Employee programmer("John",22); cout
  • Classes 102 liberar duas vezes para o mesmo endereço, provocando um erro no sistema de alocação dinâmica de memória, o que forçará o sistema operacional a eliminar o programa da memória. Para resolver esse problema podemos definir um construtor de cópia ("copy constructor") na classe, substituindo a implementação padrão do compilador. Este recurso é automaticamente identificado e faz com que o compilador não crie a sua versão do construtor. Assim, definir um construtor de cópia próprio é a maneira mais eficiente quando nossos objetos detêm características que os fazem diferentes do padrão. Para criar o construtor basta definí-lo na classe: class Employee { public: Employee( const Employee & e); Employee(char *name, int id); ~Employee(); char *getName(){return _name;} private://Other Accessor methods int _id; char *_name; }; Employee::Employee( const Employee & e ) { _id = e._id; _name = new char[strlen(e._name) + 1]; //Allocates an character array object strcpy(_name, e._name); } Agora temos um construtor que pode fazer a cópia da forma correta. Neste novo construtor alocamos uma cadeia de caracteres para copiar o conteúdo da original no objeto a ser copiado e copiamos o conteúdo para o novo objeto. Assim, teremos objetos distintos, cada um com seu próprio conteúdo.
  • Encapsulamento 103 Encapsulamento Conceito Encapsulamento, em linguagens orientadas a objeto, é a capacidade de ocultação de detalhes de implementação por parte de entidades de manipulação de dados. Esta característica visa prover um meio de manter cada classe responsável por operações a elas atribuídas sem interferências externas. A vantagem dessa característica é de manter os indivíduos de cada classe com funções bem delimitadas e criar meios de criar módulos onde cada classe faça bem aquilo de que está encarregada, tendo total controle sobre tais operações. Atributos de restrição Classes podem proteger sua estrutura de acessos de outras entidades de dados que não pertencem a seu corpo. Para isto, em C++ temos atributos de acesso para blocos de dados ou funções membros de classes. Em algumas linguagens os atributos são definidos para cada membro individualmente. Os atributos de restrição de acesso em C++ são três: private, public e protected. Cada atributo oferece um nível de ocultação para membros de classes. Eles são usados para assegurar que apenas grupos identificados de código tenham acesso a partes presselecionadas da classe. Os níveis de proteção estão ligados ao parentesco do código que pretende acesso com a classe em que os mesmos estão definidos. Mais especificamente, classes que não são filhas da que pretendem acessar só poderão ter acesso a membros públicos, classes filhas terão acesso a membros protegidos (protected) ou públicos (public) e finalmente, nenhum código que não pertença a própria classe poderá acessar membros privados (private). Classes derivadas (pequena introdução) Precisamos dos conceitos básicos de herança para entender alguns conceitos de encapsulamento. Para não criarmos dependências circulares entre os tópicos, ou seja, para não dependermos de conceitos de herança que também precisa de tópicos de encapsulamento, faremos uma pequena introdução dos conceitos de classes derivadas antes de prosseguirmos com o nosso estudo de encapsulamento. Uma classe pode ser estendida a partir de outra, ou seja, podemos reaproveitar um código já existente em uma determinada classe que já temos e criar uma nova classe com tudo que já existia na primeira, mais o que definirmos para a nova. Vejamos um exemplo básico: class veículo { string cor; string combustivel; ... ... }; class carro : public veiculo { int nrodas; ... ... int mover( int nkilometros );
  • Encapsulamento 104 }; A segunda classe declarada possui a extensão ": public veiculo" a mais, esta parte refere-se a uma declaração de parentesco. De fato, ao declarar a classe desta forma estamos informando ao compilador que a classe veiculo é mãe da classe carro. Semanticamente, isto significa que a classe carro possui toda a estrutura da classe veiculo além de seus próprios membros. Definindo acessos Considerando o exemplo anterior, podemos observar que os atributos "cor", "combustivel", "nrodas" poderiam ser alterados em qualquer ponto do programa se tivéssemos usado a palavra struct, porém usando a palavra class algo de diferente ocorre, pois não podemos ter acesso a estes atributos, a menos que estejamos acessando-os através de funções definidas dentro das classes. Em classes, o atributo private é definido por padrão, ou seja, os membros que não tenham definidos os seus atributos de acesso explicitamente, serão definidos como privados. Este comportamento revela a necessidade de resguardar os membros de uma classe através de atributos de restrições. Em C++, ao definir membros em uma classe antes da definição de qualquer atributo de restrição estamos definindo-os como privados (private). Ainda levando em consideração o exemplo anterior, podemos definir atributos de restrições para grupos de membros e modificar o comportamento padrão da classe. Se definirmos public, os dados estarão acessíveis a qualquer parte do programa, o que é equivalente a coloca-los em uma estrutura com a palavra struct ao invés de dentro de uma classe. Se definirmos protected temos uma situação peculiar, apenas funções membro da classe ou de suas "filhas" poderão acessar dados da classe mãe. Vejamos o exemplo anterior com algumas alterações: class veiculo { string cor; protected: string combustivel; ... ... public: bool altCor( string c ) { if ( c == "vermelho" ) { cor = c; return true; } if ( c == "azul" ) { cor = c; return true; } if ( c == "prata" ) { cor = c; return true;
  • Encapsulamento 105 } return false; } }; class carro : public veiculo { int nrodas; ... ... int mover( int nkilometros ); }; Neste exemplo definimos que o atributo cor não pode ser modificado, a não ser pela função altCor(), onde restringimos as cores a um conjunto que desejamos. Observe que ao tentar atribuir qualquer cor diferente de "vermelho", "azul" e "prata", receberemos um retorno "false". Assim, temos a possibilidade de controlar o comportamento do objeto criado através da restrição imposta. O atributo "combustível" será manipulado livremente pela classe "veículo" e pela classe "carro", visto que o atributo protected dá visibilidade às classes derivadas de "veiculo". O mesmo mecanismo de controle visto no parágrafo anterior poderá ser implementado para acessos fora do escopo das duas classes, ou seja, para funções no escopo global ou em outras classes. Escopos globais Até aqui vimos os atributos de restrição sendo usados em classes para encapsular partes internas a elas, porém, existe outro mecanismo de encapsulamento muito útil em C++, os namespaces. Estes "espaços de nomes" são meios de delimitar áreas onde símbolos são usados, o que permite evitar que erros ocorram por coincidência de nomes. A sintaxe para criação de um namespace é bem simples. Vejamos um exemplo de código, para observarmos os detalhes: namespace MeuEspaco { void print() { cout
  • Herança 106 Herança Conceito Herança é um dos pontos chave de programação orientada a objetos (POO). Ela fornece meios de promover a extensibilidade do código, a reutilização e uma maior coerência lógica no modelo de implementação. Estas características nos possibilitam diversas vantagens, principalmente quando o mantemos bibliotecas para uso futuro de determinados recursos que usamos com muita frequência. Uma classe de objetos "veiculo", por exemplo, contém todas as características inerentes aos veículos, como: combustível, autonomia, velocidade máxima, etc. Agora podemos dizer que "carro" é uma classe que têm as características básicas da classe "veículo" mais as suas características particulares. Analisando esse fato, podemos concluir que poderíamos apenas definir em "carro" suas características e usar "veículo" de alguma forma que pudéssemos lidar com as características básicas. Este meio chama-se herança. Agora podemos definir outros tipos de veículos como: moto, caminhão, trator, helicóptero, etc, sem ter que reescrever a parte que está na classe "veículo". Para isso define-se a classe "veículo" com suas características e depois cria-se classes específicas para cada veículo em particular, declarando-se o parentesco neste instante. Outro exemplo: Imagine que já exista uma classe que defina o comportamento de um dado objeto da vida real, por exemplo, animal. Uma vez que eu sei que o leão é um animal, o que se deve fazer é aproveitar a classe animal e fazer com que a classe leão derive (herde) da classe animal as características e comportamentos que a mesma deve apresentar, que são próprios dos indivíduos classificados como animais. Ou seja, herança acontece quando duas classes são próximas, têm características mútuas mas não são iguais e existe uma especificação de uma delas. Portanto, em vez de escrever todo o código novamente é possível poupar algum tempo e dizer que uma classe herda da outra e depois basta escrever o código para a especificação dos pontos necessários da classe derivada (classe que herdou). Sintaxe Para declarar uma classe derivada de outra já existente, procedemos de forma a declarar o parentesco e o grau de visibilidade (acesso) que a classe derivada terá dos membros de sua classe base. Para isso seguimos o seguinte código sintático: class classe_derivada : [] classe_base { //corpo da classe derivada } Repare que temos o operador ":" ( dois pontos ) como elo entre as duas classes. Este operador promove o "parentesco" entre as duas classes quando é usado na declaração de uma classe derivada. O termo [] é opcional, mas se estiver presente deve ser public, private ou protected. Ele define o grau de visibilidade dos membros da classe base quando a classe derivada precisar acessá-los. Exemplo de implementação: // Demonstra herança. #include using namespace std; class veiculo_rodoviario // Define uma classe base veículos. { int rodas; int passageiros;
  • Herança 107 public: void set_rodas(int num) { rodas = num; } int get_rodas() { return rodas; } void set_pass(int num) { passageiros = num; } int get_pass() { return passageiros; } }; class caminhao : public veiculo_rodoviario // Define um caminhao. { int carga; public: void set_carga(int size) { carga = size; } int get_carga() { return carga; } void mostrar(); }; enum tipo {car, van, vagao}; class automovel : public veiculo_rodoviario // Define um automovel. { enum tipo car_tipo; public: void set_tipo(tipo t) { car_tipo = t; } enum tipo get_tipo() { return car_tipo; } void mostrar(); }; void caminhao::mostrar() { cout
  • Herança 108 t1.set_rodas(18); t1.set_pass(2); t1.set_carga(3200); t2.set_rodas(6); t2.set_pass(3); t2.set_carga(1200); t1.mostrar(); cout
  • Herança 109 • Classe base herdada como public: •• Membros públicos (public) da classe base: •• É como se copiássemos os membros da classe base e os colocássemos como "public" na classe derivada. No final, eles permanecem como públicos. •• Membros privados (private) da classe base: •• Os membros estão presentes na classe, porém ocultos como privados. Desta forma as informações estão presentes, mas só podem ser acessadas através de funções publicas ou protegidas da classe base. •• Membros protegidos (protected) da classe base: •• Se tivermos membros protegidos (protected) na classe derivada, eles se comportam como se tivessem sido copiados para a classe derivada como protegidos (protected). • Classe base herdada como private: •• Membros públicos (public) da classe base: •• Os membros se comportam como se tivessem sido copiados como privados (private) na classe derivada. •• Membros privados (private) da classe base: •• Os membros estão presentes na classe, porém ocultos como privados. Desta forma as informações estão presentes, mas não poderão ser acessadas, a não ser por funções da classe base que se utilizem delas. •• Membros protegidos (protected) da classe base: •• Os membros se comportam como se tivessem sido copiados como privados (private) na classe derivada. • Classe base herdada como Protected: •• Membros públicos (public) da clase base: •• Se comportam como se tivéssemos copiado-os como protegidos (protected) na classe derivada •• Membros privados (private) da classe base: •• Os membros estão presentes na classe, porém ocultos como privados. Desta forma as informações estão presentes, mas não poderão ser acessadas, a não ser por funções da classe base que se utilizem delas. •• Membros protegidos (protected) da classe base: •• Se comportam como se estivéssemos copiado-os como protegidos (protected) na classe derivada. Em suma, estas regras podem ser sintetizadas em uma regra muito simples: Prevalece o atributo mais restritivo. Para isto basta-nos listar os atributos por ordem de restrições decrescente: 1.1. private 2.2. protected 3.3. public Assim, temos todas as combinações definidas quando colocamos um atributo combinado com o outro, bastando para isto escolher sempre o mais restritivo na combinação. Por exemplo: Quando temos uma variável pública na base e a herança é privada, a combinação resulta em uma variável privada na classe derivada. Aqui está um exemplo muito simples:
  • Herança 110 #include using namespace std; class base { int i, j; public: void set(int a, int b) { i = a; j = b; } void show() { cout
  • Herança 111 { int k; public: derived(int x) { k = x; } void showk() { cout
  • Herança 112 classes. { public: void set(int i, int j) { x = i; y = j; } }; int main() { derived ob; ob.set(10, 20); // Disponível pela classe "derived" ob.showx(); // Pela classe base1 ob.showy(); // Pela classe base2 #ifndef WIN32 system ("pause"); #endif return 0; } Repare que utilizamos o operador vírgula para dizer ao compilador que a classe derivada herda mais de uma classe. Com efeito, temos uma lista de classes separadas por vírgulas depois do operador ":" (dois pontos). Quando queremos que a classe derivada herde uma classe como pública e outra como privada ou protegida basta preceder a classe com o seu especificador de acesso. Da mesma forma, a omissão do especificador leva o compilador a usar o padrão que é privado (private). Construtores e destrutores Temos uma série de classes que mantém relações de parentesco conforme mostramos nas seções anteriores. Em termos genéricos, classes que herdam características de uma base precisam de regras claras quando forem criadas e destruídas. Precisamos definir a sequência em que os construtores e destrutores serão chamados, uma vez que cada classe tem pelo menos um construtor e um destrutor. Agora temos a questão: Quando é que os construtores são chamados quando eles são herdados? •• Quando um objeto da classe derivada é instanciado, o construtor da classe base é chamado primeiro seguido do construtor das classes derivadas, em sequência da base até a última classe derivada. •• Quando o objeto da classe derivada é destruído, o seu destrutor é chamado primeiro seguido dos destrutores das outras classes derivadas logo abaixo, em sequência até a base. Vamos testar e ver como isto funciona. No caso em que termos herança sequencial A-B-C, teremos: #include using namespace std; class base { public: base() { cout
  • Herança 113 class derivada1 : public base { public: derivada1() { cout
  • Herança 114 ~derivada() { cout
  • Herança 115 protected: int i; public: base(int x) { i = x; cout
  • Herança 116 public: base2(int x) { k = x; cout
  • Herança 117 { public: void comer(); void mover(); void dormir() { cout
  • Herança 118 Acessando funções superpostas da classe base O mecanismo para obter acesso às classes base a partir da classe derivada é intuitivo. Para isto usamos o operador de resolução de escopo, composto por um par de dois pontos "::", usando a seguinte sintaxe: ::(lista_de_parâmetros); Ou seja, basta invocar a função informando qual é a versão específica que se deseja utilizar. Se tivermos uma classe "A" e outra "B" com uma função "Print()", por exemplo, e quisermos usar a função "Print()" da classe "B" fazemos: B::Print(); Talvez seja melhor visualizar um exemplo no código mais completo: class B { public: void Print() { cout
  • Polimorfismo 119 Polimorfismo Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. Conceito Polimorfismo em linguagens orientadas a objeto, é a capacidade de objetos se comportarem de forma diferenciada em face de suas características ou do ambiente ao qual estejam submetidos, mesmo quando executando ação que detenha, semanticamente, a mesma designação. O polimorfismo em C++ se apresenta sob diversas formas diferentes, desde as mais simples, como funções com mesmo nome e lista de parâmetros diferentes, até as mais complexas como funções virtuais, cujas formas de execução são dependentes da classe a qual o objeto pertence e são identificadas em tempo de execução. Funções virtuais #include using std::cout; using std::endl; class Base { public: // declaração da função virtual virtual void Quem_VIRTUAL() { cout
  • Polimorfismo 120 } }; int main () { Base *ptr_base; Derivada derivada; ptr_base = &derivada; // conversão implícita permissível ptr_base->Quem_VIRTUAL(); // chamada polimórfica (mostra: "Derivada") ptr_base->Quem_NAO_VIRTUAL(); // chamada comum, não-polimórfica (mostra: "Base") cout
  • Polimorfismo 121 public: int sum; }; int main() { derived3 ob; ob.i = 10; // Isto se torna ambíguo; A qual "i" estamos nos referindo??? ob.j = 20; ob.k = 30; ob.sum = ob.i + ob.j + ob.k;// "i" ambíguo aqui, também cout
  • Polimorfismo 122 class derived3 : public derived1, public derived2 /* "derived3" herda as bases "derived1" e "derived2". Isto significa que há duas cópias de bases em "derived3"! */ { public: int sum; }; int main() { derived3 ob; ob.derived1::i = 10; // escopo resolvido, usa o "i" em "derived1". ob.j = 20; ob.k = 30; ob.sum = ob.derived1::i + ob.j + ob.k; // escopo resolvido. cout
  • Polimorfismo 123 int k; }; class derived3 : public derived1, public derived2 /* derived3 inherits both derived1 and derived2. This time, there is only one copy of base class. */ { public: int sum; }; int main() { derived3 ob; ob.i = 10; // now unambiguous ob.j = 20; ob.k = 30; ob.sum = ob.i + ob.j + ob.k;// unambiguous cout
  • Friend 124 Friend Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. O que é Friend é um atributo especial de acesso a classes. Com ele declaramos que uma função fora de uma classe é amiga da mesma. Declarando que uma função (externa á classe) "friend" dentro de uma classe, permite que a função (a amiga) possa ler e manipular membros (variáveis e funções membro) "private" - privados e "protected" - protegidos (e claro "public" - publicas, mas isso já era permitido antes de serem "friends"). Considerando que o acesso a membros pode ser restrito para determinadas partes do código, podemos adotar uma postura mais flexível para funções que conhecemos como confiáveis e evitar os inconvenientes da restrição a membros por códigos que não provoquem problemas maiores a nossa estrutura da aplicação. Declarar funções "friend" O processo para fazer com que funções fora do escopo da classe tenham acesso a membros sem nenhuma restrição é muito simples. Para isto apenas temos de colocar o protótipo da função externa dentro da classe precedido da palavra "friend". Desta forma o compilador passa a ignorar os atributos de restrição de acesso da classe quando a função acessa membros da mesma. Vamos ao exemplo: // friend functions #include using namespace std; class CRectangle { int width, height; public: void set_values (int, int); int area () {return (width * height);} friend CRectangle duplicate (CRectangle); }; void CRectangle::set_values (int a, int b) { width = a; height = b; } CRectangle duplicate (CRectangle rectparam) { CRectangle rectres; rectres.width = rectparam.width*2; rectres.height = rectparam.height*2; return (rectres); }
  • Friend 125 int main () { CRectangle rect, rectb; rect.set_values (2,3); rectb = duplicate (rect); cout
  • Friend 126 friend class CRectangle; //declaro friend class }; void CRectangle::convert (CSquare a) { width = a.side; height = a.side; } int main () { CSquare sqr; CRectangle rect; sqr.set_side(4); rect.convert(sqr); cout
  • Classes internas 127 void setn(int inn); }; Uma vez que criamos o protótipo de objeto dentro de outra classe podemos usá-lo através do operador de resolução de escopo. Deveremos nos referir ao operador para acessar o corpo da primeira classe e depois o invocamos novamente para alcançar a outra. Desta forma, poderíamos usá-la como exemplificado abaixo: data::idata ni; É importante notar que objetos diferentes terão dados diferentes na região da classe interna, isso impede que usemos dados da classe mais externa dentro das classes internas. Devido a esta característica as funções dentro da classe interna não podem acessar dados da classe mais externa, a menos que sejam estáticos, o que não exige definição do objeto a qual eles pertencem. Portanto, o uso de classes internas permite um isolamento de parte dos dados e prover um tratamento diferenciado para os mesmos. Sobrecarga de operadores Modificando operadores A linguagem C++ possui os mesmos operadores presentes na linguagem C. Estes têm funções padronizadas e comportamentos semelhantes a seus parentes diretos em C. Esta característica a traz compatibilidade que é um requisito fundamental e adiciona uma nova funcionalidade chamada sobrecarga de operadores. Quando operamos tipos nativos da linguagem, fazemos com funções específicas predefinidas e padronizadas. Como poderemos operar os nossos objetos que definimos com nossas classes? Simples: criamos as funcionalidades e as atribuimos a operadores já conhecidos, de forma a manter a idéia básica da operação embutida na simbologia. Ao definir novas funções para os operadores padrão, na verdade não substituimos a sua função, apenas adicionamos mais uma função ao mesmo operador. Esta operação é chamada de sobrecarga de operador. O nome parece um pouco fora do comum, mas apenas reflete o comportamento da linguagem quando esta lida com a definição de vários tratamentos para o mesmo identificador, que, neste caso, é o símbolo do operador. Portanto, sobrecarga de operador é a definição de novas tarefas para o mesmo operador. Definindo novas operações Digamos que temos uma classe chamada ponto, que define dois inteiros para um plano hipoteticamente definido. Este par de inteiros poderá representar uma coordenada neste plano formado por pontos espaçados um do outro. Sob estas condições, cada objeto desta classe será uma coordenada neste plano: class ponto { int x,y; public: ponto(int a, int b) { x = a; y = b; } }; Se quisermos operar estes objetos não teremos como fazê-lo, pois não há meios de operar os objetos do tipo ponto. Nenhuma operação é possivel, pois a linguagem não define como operá-los. Cabe ao programador dizer ao compilador como ele deve efetuar a operação do novo tipo criado.
  • Sobrecarga de operadores 128 ponto p1(1,5),p2(3,4), Soma; Soma = p1 + p2; Ao tentar compilar este trecho de código o compilador retornará um erro por não conhecer a maneira de como operar este tipo de dado. Como criamos o tipo de dado, precisamos definir como fazer a soma do mesmo. Podemos fazer a seguinte definição: class ponto { int x,y; public: ponto(int a, int b) { x = a; y = b; } ponto operator+(ponto p); }; ponto ponto::operator+(ponto p) { int a,b; a = x + p.x; b = y + p.y; return ponto(a,b); } A sintaxe desta definição, muitas vezes causa confusão, mas poderá ser facilmente entendida depois que tenhamos assimilado as idéias básicas por traz dela. Ela opera, aparentemente, apenas um dado de entrada, porém o operador deve somar dois. Como isto é possível? Observando mais atentamente o código poderemos entender: Verificamos que, no código, nos referimos a x e y sem definir a qual objeto pertence. Acontece que a operação está ocorrendo dentro de um dos objetos, aquele imediatamente antes do operador. Esta é a primeira coisa a ter em mente: O operador "pertence" a um dos objetos que está sendo operado, sendo sempre aquele que o antecede. Com isso, só precisamos declarar o segundo dado a operar. Podemos visualizar isto melhor, da seguinte forma: P3 = P1 + P2; Que pode ser entendido como a invocação da função: P3 = P1.operator+( P2); Agora podemos entender como acontece a invocação da função que define o operador. Observe que P1 contém o operador que recebe P2, fazendo o cálculo e devolvendo uma cópia do objeto resultante para P3. A sintaxe esconde o mecanismo para tornar o código mais simples de ser entendido quando tiver que ser lido.
  • Alocação dinâmica de memória 129 Alocação dinâmica de memória Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. Alocação dinâmica de memória O compilador reserva espaço na memória para todos os dados declarados explicitamente, mas se usarmos ponteiros precisamos reservar o espaço necessário e colocar o endereço inicial nos mesmos. Para isto, podemos usar o endereço de uma variável definida previamente ou reservar o espaço necessário no momento que precisemos. Este espaço que precisamos reservar em tempo de execução é chamada de memória alocada dinamicamente. Refere-se à possibilidade de termos o nosso programa a correr e o utilizador ter de inserir dados e como tal não sabemos exatamente a quantidade de dados que o utilizador vai colocar, portanto temos de arranjar uma memória que nos permita lidar com esta indeterminação quanto à quantidade de dados inseridos. Este é o caso em que não sabemos no momento da programação a quantidade de dados que deverão ser inseridos mas o programa já está a correr. É tentar responder a perguntas: quantas pessoas existem na tua turma? Quantas letras vamos escrever, etc. Em vez de estarmos a prever um limite superior para abarcar todas as situações, temos esta possibilidade do dinâmico. Além de que colocar no momento da programação cria reserva de memória por isso, estaríamos a reservar memória para um limite que possivelmente não iríamos ter necessidade. O exemplo típico disto é os processadores de texto. em que não sabemos a quantidade de letras que o utilizador vai escrever. Vamos voltar a uma ponta solta num dos capítulos anteriores, onde queríamos fazer com que o utilizador dissesse quantos elementos do array é que se deveria utilizar. Já dissemos antes que o declarador do nº de elementos do array tem de ser ou uma constante ou um literal, mas não pode ser uma variável. Isso dá erro. Aqui vai o exemplo desse erro: #include using namespace std; int main () { int numTests; cout > numTests; int testScore[numTests]; return 0; } A razão da exigência de ter uma constante (ou literal) é que vamos alocar memória para o array na altura da compilação, e o compilador necessita de saber exatamente a quantidade de memória que deve reservar… porém se a variável é o size declarator, o compilador não sabe quanta memória deve reservar para alocar a variável, pois o seu valor pode mudar.
  • Alocação dinâmica de memória 130 Operador new Reformulando o exemplo anterior agora com dados dinâmicos. #include using namespace std; int main () { int numTests; cout > numTests; int * iPtr = new int[numTests]; //colocamos um ponteiro no inicio da memória dinâmica for (int i = 0; i < numTests; i++) { cout
  • Alocação dinâmica de memória 131 Operador Delete - Memory Leak O tempo de vida de uma variável criada dinamicamente é o tempo de execução do programa. Se um ponteiro aponta para uma variável dinâmica e fica out of scope, já não conseguiremos acessar essa memória criada dinamicamente. Fica indisponível. A isso se chama Memory Leak Explicando: se alocamos memória dinamicamente dentro de uma função usando um ponteiro local, quando a função termina, o ponteiro será destruído, mas a memória mantém-se. Assim já não teríamos maneira de chamar essa memória porque ela não tem nome! Apenas tínhamos o endereço que estava no ponteiro. Portanto, se realmente não necessitamos mais dos dados que estão nessa memória dinâmica, em vez de eles estarem a ocupar espaço vamos apagá-los! Necessitamos de libertar essa memória através do operador delete – este operador entrega ao sistema operacional a memória reservada dinamicamente. A sintaxe é delete [] iPtr; Este delete operator não apaga o ponteiro mas sim a memória onde o ponteiro aponta. dynamic memory allocation funciona porque a memória não é reservada no momento da compilação, mas antes na execução. Em vez de ser no STACK (compilação) a memória é reservada no HEAP (execução). O heap é uma parte da memória que é usada como memória temporária. Pergunta: onde é que fica situado o Heap? Vamos explicar melhor todo este processo: pois isto tem de entrar na cabeça! void myfunction() { int *pt; int av; pt = new int(1024); .... .... //No delete } int main() { while (some condition exists) // Pseudo-code { myfunction(); } exit 0; } quando a função “myfunction” é chamada a variável “av” é criada no stack e quando a função acaba a variável é retirada do stack. O mesmo acontece com o ponteiro pt, ele é uma variável local. ou seja quando a função acaba o ponteiro também termina e é retirado do stack. Porém o objeto alocado dinamicamente ainda existe. E agora não conseguimos apagar esse objeto por que ele não tem nome e a única maneira que tínhamos para saber onde ele estava era através do ponteiro que terminou quando termina a função pois ele é local. Então à medida que o programa continua a operar mais e mais memória será perdida do Heap (free store). se o programa continuar o tempo suficiente, deixaremos de ter memória disponível e o programa deixará de operar.
  • Alocação dinâmica de memória 132 Retornando um ponteiro para uma variável local #include using namespace std; char * setName(); int main (void) { char* str = setName(); //ponteiros para a função cout
  • Alocação dinâmica de memória 133 Returning a Pointer to a Dynamically Created Variable Outra alternative, talvez melhor #include using namespace std; char * setName(); int main (void) { char* str= setName(); cout
  • Alocação dinâmica de memória 134 ou se quisermos desta maneira int *buff = new int[1024]; for (i = 0; i < 1024; i++) { buff[i] = 52; //Assigns 52 to each element; } para utilizar o delete em arrays delete[] pt; delete[] myBills; Dangling Pointers int *myPointer; myPointer = new int(10); cout
  • Alocação dinâmica de memória 135 Vamos ver um exemplo com o caso de nothrow // rememb-o-matic #include using namespace std; int main () { int i,n,* p; cout > i; p= new (nothrow) int[i]; //criámos I variaveis na execução if (p == 0) cout
  • Exceções 136 Exceções Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. Uma exception é um erro que ocorre em tempo de execução. Podemos lidar com estes erros e criar rotinas para muitos deles, o que nos permite automatizar muitos erros que antes teriam de ser emendados à mão. Namespace Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. O propósito dos namespace é localizar os identifiers (os nomes) por forma a evitar que haja apenas um, para evitar colisões. Por exemplo eu poderia criar uma função com um determinado nome e depois vir a saber que esse mesmo nome existia na biblioteca. E isto pode ocorrer bem freqüentemente quando temos vários programadores contribuindo para o mesmo projeto e ainda mais quando se recorre a bibliotecas para usar código criado por outros. O que o namespace permite é continuar a termos o mesmo nome mas irá fazer a diferenciação pela detecção do contexto de aplicação para cada nome. Templates Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. Os templates permitem a criação de código reusado, usando templates é possível criar funções e classes genéricas. Assim o tipo de dados usadas pelas funções são parâmetros. Podemos criar um template para soma, e depois enviamos que tipo de dados queremos somar, podemos até utilizar a sobrecarga de operadores para tal fim. Funções genéricas template ret-type func-name(parameter list) { // body of function } vamos dar o exemplo // Function template example. #include using namespace std; template void swapargs(X &a, X &b)// This is a function template. { X temp; temp = a; a = b;
  • Templates 137 b = temp; } int main() { int i=10, j=20; double x=10.1, y=23.3; char a='x', b='z'; cout
  • Templates 138 } int main() { myfunc(10, "hi"); myfunc(0.23, 10L); system ("pause"); return 0; } Repare que temos dois tipos de dados diferentes na mesma função. Sobrecarregando explicitamente uma função genérica apesar de uma função genérica poder ser overload automaticamente se necessário, nós podemos explicitar. a isso chamamos deexplicit specialization Containers •• list •• set •• unordered_set •• map •• unordered_map Compilação Esta página precisa ser reciclada (discuta). Ao melhorá-la, você estará ajudando o Wikilivros. Compilação é o processo de "tradução" do programa escrito em uma linguagem de programação para um formato no qual o computador entenda. A compilação gera um ficheiro - arquivo em português brasileiro - binário (executável) a partir do código fonte. A tradução do código para o computador Em C e C++ são 3 os programas usados para fazer a tradução do código fonte (que são as linhas de código que o programador escreve) num ficheiro executável, que o computador pode executar: 1.1. Preprocessor - pré-processador 2.2. Compiler - compilador 3.3. Linker
  • Compilação 139 Pré-processador Este é um programa que busca no código fonte – no código que escrevemos – por diretivas que foram dirigidas a ele, ou seja linhas iniciadas com “#”, assim o pré-processador sabe que aquela instrução é dirigida para ele. No exemplo tínhamos: #include Então o pré-processador inclui ficheiros localizados no ficheiro iostream. Então tínhamos o código fonte que depois é transformado num outro código fonte de acordo com as diretivas pré-processadas. Essencialmente, o pré-processador é um processador de macros para uma linguagem de alto nível.[1] Compilador Compilador é um programa que pega código fonte preprocessado e o traduz em instruções de linguagem de máquina, linguagem que o computador entende. Estas são guardadas num arquivo a parte, chamado de "object file" e tem a extensão .o ou .obj dependendo do compilador. Existem diferentes compiladores para diferentes linguagens de programação. Essa tradução é feita se o código estiver na linguagem que o compilador compilar. Existem regras de escrita e de gramática. No caso de existir um erro de sintaxe, então dará um erro de compilação. Linker Apesar do nosso "object file" ter as instruções em linguagem máquina, o computador ainda não poder correr como um programa. A razão é que é necessário outro código da biblioteca, que é o código do run-time library, que é para as operações comuns tipo a tradução o input do teclado ou a capacidade para interagir com hardware externo tipo o monitor para apresentar uma mensagem. Estas bibliotecas run-time costumam já estar instaladas com o sistema operacional, caso isso não aconteça teremos de fazer o download delas. Então o resultado da combinação do "object file" com as partes necessárias da biblioteca run-time fazem finalmente a criação de um ficheiro executável com a extensão .exe Processo de compilação Em primeiro lugar, vamos escrever um código que está na linguagem C++ e vamos gravar esse código todo num ficheiro, que é uma quantidade de memória no computador. Esse ficheiro fica com a terminação “.CPP” ou .CP ou C.. Chama-se a este conjunto de linhas que escrevemos de, código fonte ou source code (pode ter a terminação “.c”, “.cpp”, e “.cc” - estas extensões dependem do compilador que se utiliza). Esse "source code" é de alguma forma críptica e para alguém que não saiba de c++ no entanto é aquilo que neste momento chamamos de humam readable form. Para tornar o nosso source code num programa usamos um compilador que irá produzir um "object file". Este ficheiro tem normalmente a extensão. OBJ porém ainda não temos um programa executável. Para isso vamos utilizar o linker. Uma das vantagens do c++ é que usufrui de uma estrutura multi-file. A linguagem permite compilação separada, onde partes do programa total podem estar numa ou mais source files e estes podem ser compilados independentemente de cada um. A ideia é que o processo de compilação produz files que depois podem ser linked together usando um editor de link ou loads que o sistema provem. Os programas feitos em c++ são tipicamente ficheiros .OBJ ligados uns aos outros com uma ou mais libraries. Estas são uma coleção de linkable files que nós criámos ou que foram fornecidas (vêm com o compilador ou compramos). Depois de se fazer este linking é que obtemos o arquivo executável. No Windows, ele tem terminação “.exe”. Assim, o sistema operacional reconhece o programa como independente.
  • Compilação 140 Os compiladores atuais incluem também pré-compiladores, (ou pré-processadores) (antes eram software independente, extra), estes pré-compiladores vão fazer alterações ao código fonte, que basicamente consistem em eliminar pedaços de código que escrevemos, e/ou substituir pedaços de código que escrevemos por outro (copy-paste), enfim, é alterar o código fonte por outro código fonte. Depois é que se compila. Referências [1] http:/ / www. dca. fee. unicamp. br/ cursos/ EA876/ apostila/ HTML/ node150. html O pré-processador C Lista de Palavras Reservadas do C++ Estas são as palavras reservadas do c++: alignas (since C++11) alignof (since C++11) and and_eq asm auto bitand bitor bool break case catch char char16_t (since C++11) char32_t (since C++11) class compl const constexpr (since C++11) const_cast continue decltype (since C++11) default delete do double dynamic_cast else enum explicit export extern false float for friend goto if inline int long mutable namespace new noexcept (since C++11) not not_eq nullptr (since C++11) operator or or_eq private protected public register reinterpret_cast return short signed sizeof static static_assert (since C++11) static_cast struct switch template this thread_local (since C++11) throw true try typedef typeid typename union unsigned using virtual void volatile wchar_t while xor xor_eq
  • Lista de Palavras Reservadas do C++ 141 • Maiores detalhes podem ser encontrados em: http:/ / en. cppreference. com/ w/ cpp/ keyword Lista de Sequências de Escape Estes são caracteres que são difíceis de serem expressos de outra forma em um código fonte. Todos eles são precedidos de uma barra invertida. A Tabela 1 apresenta a lista de sequências de escape. Controle/Caracter Sequência de escape Valor ASCII Nulo (null) \0 00 Campainha (bell) \a 07 Retrocesso (backspace) \b 08 Tabulação horizontal \t 09 Nova linha (new line) \n 10 Tabulação vertical \v 11 Alimentação de folha (form feed) \f 12 Retorno de carro (carriage return) \r 13 Aspas (") \" 34 Apóstrofo (') \' 39 Interrogação (?) \? 63 Barra invertida (\) \\ 92 Tabela 1 - Lista de Sequências de Escape em C++
  • Tabela ASCII 142 Tabela ASCII Programa que gera a tabela ASCII do C++ (programa escrito em c++). Nenhum dos 2 programas mostra a tabela inteira. O programa mostra alguns caracteres que o programa 2 não mostra. De modo geral, faltam alguns caracteres. Esta página é um esboço de informática. Ampliando-a você ajudará a melhorar o Wikilivros. Programa 1 #include #include using namespace std; int main() { //CARACTER " " (ENTER) = DECIMAL 10 HEXADECIMAL A; int s = -127; cout
  • Tabela ASCII 143 printf("%c %3i\t%2X\t",x,x,x); printf("%c %3i\t%2X\t",x+32,x+32,x+32); printf("%c %3i\t%2X\t",x+64,x+64,x+64); printf("%c %3i\t%2X\n",x+96,x+96,x+96); } system ("PAUSE"); return 0; } C++11 Origem: Wikipédia, a enciclopédia livre. C++11, anteriormente conhecido por C++0x é o novo padrão para a linguagem de programação C++. Ele substitui o antigo padrão do C++, o ISO/IEC 14882, que foi publicado em 1998 e atualizado em 2003. Estes predecessores foram informalmente chamados C++98 e C++03. O novo padrão incluirá muitas adições ao núcleo da linguagem (sua implementação principal), e estenderá a biblioteca padrão do C++, incluindo a maior parte da biblioteca do chamado C++ Technical Report 1 — um documento que propõe mudanças ao C++ — com exceção das funções matemáticas específicas. Esse nome é uma referência ao ano no qual o padrão será lançado. O comitê pretendia introduzir o novo padrão em 2009,1 a partir do que o então chamado "C++0x" passaria a se chamar "C++09", o que significa que o documento deveria estar pronto para a ratificação dos membros do comitê até o final de 2008. Para cumprir o prazo, o comitê decidiu focar seus esforços nas soluções introduzidas até 2006 e ignorar novas propostas.2 porém ele ficará pronto apenas em 2010. Linguagens de programação como o C++ utilizam um processo evolucionário para desenvolverem suas definições. Tal processo inevitavelmente culmina em problemas de compatibilidade com código pré-existente, o que ocasionalmente aconteceu durante o processo de desenvolvimento do C++. Entretanto, de acordo com o anúncio feito por Bjarne Stroustrup — inventor da linguagem C++ e membro do comitê — o novo padrão será quase completamente compatível com o padrão atual.3 Novas classes: •• std::array •• std::tuple •• std::foward_list •• std::unordered_set •• std::unordered_multiset •• std::unordered_map •• std::unordered_multimap • Regular expressions library: http:/ / en. cppreference. com/ w/ cpp/ regex • Atomic operations library: http:/ / en. cppreference. com/ w/ cpp/ atomic • Thread support library: http:/ / en. cppreference. com/ w/ cpp/ thread
  • Fontes e Editores da Página 144 Fontes e Editores da Página Objetivo  Fonte: https://pt.wikibooks.org/w/index.php?oldid=212683  Contribuidores: Contribuidor, Helder.wiki, Marcos Antônio Nunes de Moura, SallesNeto BR, Wbrito Por que C++?  Fonte: https://pt.wikibooks.org/w/index.php?oldid=244119  Contribuidores: Abacaxi, Helder.wiki, Jorge Morais, Marcos Antônio Nunes de Moura, SallesNeto BR, Wbrito, 2 edições anónimas Diferenças entre C e C++  Fonte: https://pt.wikibooks.org/w/index.php?oldid=265805  Contribuidores: Abacaxi, Fabio Basso, Helder.wiki, Marcos Antônio Nunes de Moura, Master, Torneira, 1 edições anónimas Introdução  Fonte: https://pt.wikibooks.org/w/index.php?oldid=267436  Contribuidores: Abacaxi, Helder.wiki, Mappim, Marcos Antônio Nunes de Moura, Rautopia, 4 edições anónimas Alô, Mundo!  Fonte: https://pt.wikibooks.org/w/index.php?oldid=267384  Contribuidores: Abacaxi, Albmont, Carlosmacapuna, Daveiro, Edudobay, Focli, Helder.wiki, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Petrusz1, Rautopia, Renatofq, Sygmn, Wbrito, 20 edições anónimas Variáveis e constantes  Fonte: https://pt.wikibooks.org/w/index.php?oldid=238714  Contribuidores: Jorge Morais, Marcos Antônio Nunes de Moura, Rautopia Ponteiros  Fonte: https://pt.wikibooks.org/w/index.php?oldid=269171  Contribuidores: Abacaxi, Albmont, Jonathan Queiroz, Jorge Morais, Marcos Antônio Nunes de Moura, 5 edições anónimas Vetores  Fonte: https://pt.wikibooks.org/w/index.php?oldid=267854  Contribuidores: Abacaxi, Daveiro, Edudobay, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Master, Raylton P. Sousa, Wbrito, 6 edições anónimas Estruturas  Fonte: https://pt.wikibooks.org/w/index.php?oldid=256735  Contribuidores: Abacaxi, Albmont, Marcos Antônio Nunes de Moura, 5 edições anónimas Operadores  Fonte: https://pt.wikibooks.org/w/index.php?oldid=244121  Contribuidores: Abacaxi, Marcos Antônio Nunes de Moura Decisão e controle de fluxo  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266793  Contribuidores: Abacaxi, Marcos Antônio Nunes de Moura, 11 edições anónimas Estruturas de repetição  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266523  Contribuidores: Abacaxi, Diegobza, Helder.wiki, LlamaAl, Lucas Daltro, Marcos Antônio Nunes de Moura, 1 edições anónimas Funções  Fonte: https://pt.wikibooks.org/w/index.php?oldid=249232  Contribuidores: Abacaxi, Jonas AGX, 2 edições anónimas Referências de dados  Fonte: https://pt.wikibooks.org/w/index.php?oldid=239660  Contribuidores: Albmont, Marcos Antônio Nunes de Moura, Reder, 5 edições anónimas Entrada e saída de dados  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266602  Contribuidores: Abacaxi, Albmont, Daveiro, Edudobay, Jeferson90, Jonathan Queiroz, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Torneira, Wbrito, 16 edições anónimas Entrada e saída de dados 2  Fonte: https://pt.wikibooks.org/w/index.php?oldid=256748  Contribuidores: Abacaxi, Daveiro, Diego.g.a, Edudobay, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Raylton P. Sousa, Wbrito, 19 edições anónimas Manipulando strings  Fonte: https://pt.wikibooks.org/w/index.php?oldid=264914  Contribuidores: Abacaxi, Albmont, Daveiro, Fabiobasso, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Wbrito, 20 edições anónimas Classes  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266994  Contribuidores: Abacaxi, Albmont, Daveiro, Hudsonkem, Jeferson90, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Wbrito, 18 edições anónimas Encapsulamento  Fonte: https://pt.wikibooks.org/w/index.php?oldid=214125  Contribuidores: MGFE Júnior, Marcos Antônio Nunes de Moura, 2 edições anónimas Herança  Fonte: https://pt.wikibooks.org/w/index.php?oldid=214299  Contribuidores: Albmont, Daveiro, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Wbrito, 14 edições anónimas Polimorfismo  Fonte: https://pt.wikibooks.org/w/index.php?oldid=265821  Contribuidores: Abacaxi, Daveiro, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Torneira, Wbrito, 5 edições anónimas Friend  Fonte: https://pt.wikibooks.org/w/index.php?oldid=255619  Contribuidores: Abacaxi, Daveiro, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Wbrito, 8 edições anónimas Classes internas  Fonte: https://pt.wikibooks.org/w/index.php?oldid=212708  Contribuidores: Marcos Antônio Nunes de Moura Sobrecarga de operadores  Fonte: https://pt.wikibooks.org/w/index.php?oldid=231778  Contribuidores: Marcos Antônio Nunes de Moura, Raylton P. Sousa, 5 edições anónimas Alocação dinâmica de memória  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266992  Contribuidores: Abacaxi, Daveiro, Helder.wiki, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Petrusz1, Wbrito, 14 edições anónimas Exceções  Fonte: https://pt.wikibooks.org/w/index.php?oldid=203880  Contribuidores: Daveiro, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, SallesNeto BR, Wbrito, 5 edições anónimas Namespace  Fonte: https://pt.wikibooks.org/w/index.php?oldid=203888  Contribuidores: Daveiro, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Wbrito, 4 edições anónimas Templates  Fonte: https://pt.wikibooks.org/w/index.php?oldid=267395  Contribuidores: Abacaxi, Daveiro, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Wbrito, 8 edições anónimas Containers  Fonte: https://pt.wikibooks.org/w/index.php?oldid=267423  Contribuidores: Abacaxi, 1 edições anónimas Compilação  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266790  Contribuidores: Abacaxi, Daveiro, Edudobay, Jonas AGX, Jorge Morais, Marcelo-Silva, Marcos Antônio Nunes de Moura, Master, Renatofq, SallesNeto BR, Wbrito, 5 edições anónimas Lista de Palavras Reservadas do C++  Fonte: https://pt.wikibooks.org/w/index.php?oldid=266289  Contribuidores: Abacaxi, Lucas Daltro, 1 edições anónimas Lista de Sequências de Escape  Fonte: https://pt.wikibooks.org/w/index.php?oldid=253634  Contribuidores: Abacaxi, Yoroi Tabela ASCII  Fonte: https://pt.wikibooks.org/w/index.php?oldid=265804  Contribuidores: Abacaxi, Helder.wiki, Torneira, 2 edições anónimas C++11  Fonte: https://pt.wikibooks.org/w/index.php?oldid=267422  Contribuidores: Abacaxi, 1 edições anónimas
  • Fontes, Licenças e Editores da Imagem 145 Fontes, Licenças e Editores da Imagem Image:Nuvola apps konsole.png  Fonte: https://pt.wikibooks.org/w/index.php?title=Ficheiro:Nuvola_apps_konsole.png  Licença: GNU Lesser General Public License  Contribuidores: Alno, Alphax Image:Recycle001.svg  Fonte: https://pt.wikibooks.org/w/index.php?title=Ficheiro:Recycle001.svg  Licença: desconhecido  Contribuidores: Users Cbuckley, Jpowell on en.wikipedia
  • Licença 146 Licença Creative Commons Attribution-Share Alike 3.0 //creativecommons.org/licenses/by-sa/3.0/