Mudanças entre as edições de "Introdução C++"

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar
 
(43 revisões intermediárias por 2 usuários não estão sendo mostradas)
Linha 3: Linha 3:
 
= Bibliografia =  
 
= Bibliografia =  
  
 +
* [http://www.cplusplus.com/doc/tutorial/ Tutorial sobre linguagem C++]
 
* [http://glu.fcfrp.usp.br/tulio/materiais/c++.pdf Introdução a C++ (USP)]
 
* [http://glu.fcfrp.usp.br/tulio/materiais/c++.pdf Introdução a C++ (USP)]
 
* [http://www.inf.ufrgs.br/~johann/cpp2004/ Introdução a C++ (UFRGS)]
 
* [http://www.inf.ufrgs.br/~johann/cpp2004/ Introdução a C++ (UFRGS)]
 
* [http://orion.lcg.ufrj.br/C++/curso/ Introdução a C++ (UFRJ)]
 
* [http://orion.lcg.ufrj.br/C++/curso/ Introdução a C++ (UFRJ)]
 +
* [http://www.cplusplus.com/ Cplusplus.com: um sítio com muita informação sobre C++ (incluindo um guia de referência da biblioteca C++ padrão)]
 +
* [http://www.icce.rug.nl/documents/cplusplus/ C++ Annotations (muito bom e aprofundado)]
 +
* [https://isocpp.org/ News, Status & Discussion about Standard C++]
 
* [https://pihisall.wordpress.com/2007/03/15/aprenda-a-programar-em-dez-anos/ Aprenda a programar em 10 anos !]
 
* [https://pihisall.wordpress.com/2007/03/15/aprenda-a-programar-em-dez-anos/ Aprenda a programar em 10 anos !]
 
* [http://www.johndcook.com/blog/2011/06/14/why-do-c-folks-make-things-so-complicated/ Por que os caras de C++ fazem tudo tão complicado ?]
 
* [http://www.johndcook.com/blog/2011/06/14/why-do-c-folks-make-things-so-complicated/ Por que os caras de C++ fazem tudo tão complicado ?]
Linha 128: Linha 132:
 
== Organização de código-fonte ==
 
== Organização de código-fonte ==
  
= Classes e objetos =
+
* [http://tele.sj.ifsc.edu.br/~msobral/prg2/source_code_organization.pdf Capítulo 15 do livro ''C: A Modern Approach, 2n ed., de K. N. King'']
  
A linguagem C foi projetada segundo um paradigma de programação chamado de [https://en.wikipedia.org/wiki/Structured_programming programação estruturada]. Essa forma de expressar programas se baseia em subrotinas (funções no caso da linguagem C), blocos de instruções e laços de repetição, os quais atuam sobre variáveis que contêm os dados a serem acessados e transformados. O que se deve notar é a forma com que se costuma modelar um programa nesse caso, dando-se ênfase aos algoritmos que modificam os dados. Não é à toa que uma ferramenta de modelagem tradicional e ainda muito usada seja o fluxograma. Porém existem outras formas de pensar os problemas a serem resolvidos e expressar programas, dentre elas o [https://en.wikipedia.org/wiki/Object-oriented_programming paradigma de orientação a objetos] seguido por diversas linguagens tais como C++, Java e Python.
 
  
 +
Programas em linguagens C e C++ são usualmente organizados em um conjunto de arquivos de código-fonte de dois tipos:
 +
* '''Arquivos de cabeçalho (extensão .h):''' nesse tipo de arquivo se escrevem declarações necessárias em outros arquivos de código-fonte. Essas declarações incluem tipos de dados, [https://en.wikipedia.org/wiki/Function_prototype protótipos de funções], [[Introdução_C%2B%2B#Declara.C3.A7.C3.A3o_de_classes|classes]] e [[Introdução_C%2B%2B#Templates|templates]], variáveis globais e [http://www.cplusplus.com/doc/tutorial/preprocessor/ macros]. Com exceção de templates e macros, nesses arquivos não se escrevem implementações (corpos de funções e métodos).
 +
* '''Arquivos de implementação (extensão .cpp, .cc ou .c):''' nesses arquivos se escrevem as implementações de funções e métodos de classes. Arquivos de cabeçalhos podem ser incluídos com a diretiva [http://www.cplusplus.com/doc/tutorial/preprocessor/#include #include] para obter declarações necessárias à implementação.
  
Um programa orientado a objetos é composto por objetos que interagem. Um objeto é uma abstração que representa uma ''coisa'' que faz parte do problema a ser resolvido. Um objeto contém dados que representam seu estado (chamados de ''atributos''), e procedimentos que representam seu comportamento (chamados de ''métodos''). No caso do exemplo dos horários, um horário pode ser representado por um objeto. O estado desse objeto ''Horario'' é definido pelos valores de ''hora'', ''minuto'' e ''segundo'', e seu comportamento é dado pelos métodos ''mostreSe'' e ''acrescenta''. Isso deve ser entendido da seguinte forma: um ''objeto 'Horario'' pode ser mostrado, ao se chamar seu método 'mostreSe'', e pode ser somado a outro objeto ''Horario'', ao se chamar seu método ''acrescenta''. Essas são as únicas operações definidas para esse objeto, e assim nada mais se consegue fazer com ele ... a não ser que se definam novas operações.
+
== Argumentos de linha de comando ==
  
 +
* [[Argparse|Argparse: um exemplo de uma biblioteca para facilitar o processamento de argumentos de linha de comando]]
  
Muitos objetos similares podem existir. Por exemplo, pode haver muitos diferentes objetos ''Horario'', que se diferenciam por seus estados (valores de ''hora'', ''minuto'' e ''segundo''). Todos os objetos cujos estados são definidos pelo mesmo conjunto de atributos, e que possuem o mesmo comportamento (conjunto de métodos), pertencem à mesma '''classe'''. O conceito de classe é semelhante ao de ''tipo de dados'', porém tem uma implicação mais profunda. Um ''tipo de dados'' define como o conteúdo de uma variável deve ser interpretado (ex: um número inteiro, ponto flutuante, uma string, ...), porém não define os procedimentos que podem manipular essas variáveis. Desta forma, uma ''classe'' vincula a seus objetos os algoritmos que atuam sobre eles. Isso tem uma importante implicação no [http://effectivesoftwaredesign.com/2012/02/05/separation-of-concerns/ acoplamento e coesão de um programa], que são métricas fundamentais de qualidade de um projeto de software modular.
 
  
 +
Argumentos de linha de comando são dados passados a um programa no momento de sua execução. Na época em que a linguagem C foi inventada, e quando então os sistemas Unix se popularizaram, a interface com usuários era em modo texto. Usuários interagiam com o sistema operacional por meio de um programa chamado de '''interpretador de comandos''', ou simplesmente [https://en.wikipedia.org/wiki/Shell_(computing) shell]. Para um usuário executar um programa, ele deveria digitar o nome desse programa no prompt do ''shell'' e pressionar a tecla ENTER (ou RETURN). Cabia então ao ''shell'' ordenar ao sistema operacional que o arquivo de programa solicitado fosse executado. Além do nome do programa, um usuário poderia opcionalmente especificar um ou mais argumentos, que são strings a serem passadas ao programa no momento de sua execução. Os dois exemplos a seguir mostram a execução do programa [http://manpages.ubuntu.com/manpages/bionic/man1/ls.1.html ls] por meio de um ''shell''.
  
  
== Declaração de classes ==
+
<syntaxhighlight lang=bash>
 +
aluno@M1:$ ls
 +
Área de trabalho  Downloads  Modelos  Público  Vídeos
 +
Documentos        Imagens    Música  teste.sh
 +
aluno@M1:$
 +
</syntaxhighlight>
 +
Execução do programa ''ls'' sem argumentos de linha de comando. O prompt do shell é a string ''aluno@M1:$''.
  
Uma classe é declarada usando-se a palavra-chave ''class'' seguida do nome da classe:
 
  
<syntaxhighlight lang=c>
+
<syntaxhighlight lang=bash>
class MinhaClasse {
+
aluno@M1:$ ls -l
  // declarações internas da classe
+
total 48
};
+
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Área de trabalho
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Documentos
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Downloads
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Imagens
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Modelos
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Música
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Público
 +
-rw-r--r-- 1 aluno aluno 14076 abr 12  2018 teste.sh
 +
drwxr-xr-x 2 aluno aluno  4096 abr 12 2018 Vídeos
 +
aluno@M1:$
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
Execução do programa ''ls'' com um argumento de linha de comando (a string ''-l''). Cabe ao programa ''ls'' interpretar esse argumento, e usá-lo para desempenhar sua funcionalidade.
  
De certa forma, isso se assemelha à declaração de ''struct''. No entanto, as declarações internas da classe têm um significado um pouco diferente. Por exemplo, suponha-se que a classe ''MinhaClasse'' represente um número de telefone, incluindo seu código de área. A declaração seria estendida desta forma:
 
  
 +
Do ponto de vista do programador, os argumentos de linha de comando são acessíveis por meio de dois parâmetros da função ''main'', os quais são tradicionalmente denominados:
 +
* '''argc''': um número inteiro que informa quantos argumentos de linha de comando foram passados ao programa. Esse parâmetro tem no mínimo o valor 1 (um), pois o próprio nome do programa é considerado seu primeiro argumento.
 +
* '''argv''': um vetor de strings que contém os argumentos. A primeira string desse vetor é sempre o próprio nome do programa. A posição no vetor em seguida ao último argumento contém o ponteiro NULL.
  
<syntaxhighlight lang=c>
 
class MinhaClasse {
 
private:
 
  string numero;
 
  string ddd;
 
public:
 
// declarações dos procedimentos da classe
 
};
 
</syntaxhighlight>
 
  
A classe agora possui dois atributos:
+
O programa C a seguir mostra como esses parâmetros são especificados na função ''main'', e como podem ser usados. Neste exemplo, apresentam-se na tela todos os argumentos de linha de comando (um por linha), na ordem em que foram passados ao programa.
* ''string numero'': armazena o número de telefone
 
* ''string ddd'': armazena o código DDD
 
  
Note-se que as declarações desses atributos na classe são precedidas pela cláusula ''private:''. Isso significa que esses atributos não podem ser acessados diretamente de fora da classe (por procedimentos implementados fora da classe). Logo após a declaração desses atributos, aparece a cláusula ''public:''. Ela define que o que for declarado em seguida pode ser acessado diretamente de fora da classe. Por exemplo, os métodos da classe podem ser declarados logo após ''public:''.
+
<syntaxhighlight lang=c>
 +
#include <stdio.h>
  
 +
int main(int argc, char * argv[]) {
 +
  int n;
  
<syntaxhighlight lang=c>
+
  for (n=0; n < argc; n++) {
class MinhaClasse {
+
    printf("argv[%d]=%s\n", n, argv[n]);
private:
+
  }
  string numero;
 
  string ddd;
 
public:
 
  // Construtor: executado quando se cria um objeto desta classe
 
  MinhaClasse(string umNumero, string umDDD);
 
  
   // Destrutor: executado quando se destrói um objeto desta classe
+
   return 0;
  ~MinhaClasse();
+
}
 +
</syntaxhighlight>
  
  // métodos para acessar os valores dos atributos
 
  string obtemNumero();
 
  string obtemDDD();
 
  string obtemNumeroDDD();
 
};
 
</syntaxhighlight>
 
  
Um ''método'' é uma função que pertence a uma classe. A idéia é que um objeto de uma classe possui um estado dado pelos valores de seus atributos, e somente os métodos daquela classe podem acessar ou modificar esse estado. Quer dizer, somente métodos da classe podem acessar diretamente esses atributos. Dois métodos são especiais:
+
A versão em C++ desse exemplo é praticamente idêntica, mudando somente a forma com que os dados são mostrados na tela.
*''construtor'': este método é executado sempre que se cria um objeto de uma classe. Ele tem por finalidade iniciar o estado daquele objeto. Isso envolve definir os valores iniciais dos atributos, e conferir se os parâmetros que porventura tenham sido passados são válidos (ex: se o número tem somente caracteres numéricos).
 
* ''destrutor'': este método é executado quando um objeto é destruído. Ele é responsável por limpar o estado desse objeto, desfazendo qualquer ação que ele tenha iniciado. Por exemplo, se o objeto alocou memória dinamicamente em algum momento, o destrutor deve liberá-la.
 
  
 +
<syntaxhighlight lang=c>
 +
#include <iostream>
  
A declaração da classe apresentada apenas informa quais métodos ela implementa, mas não mostra como eles são implementados. Essa implementação pode ser feita de duas formas:
+
using namespace std;
* ''inline'': a implementação de um método é feita dentro da própria declaração da classe. Por exemplo, o método ''obtemNumeroDDD'' poderia estar implementado assim: <syntaxhighlight lang=c>
 
class MinhaClasse {
 
private:
 
  string numero;
 
  string ddd;
 
public:
 
  // Construtor: executado quando se cria um objeto desta classe
 
  MinhaClasse(string umNumero, string umDDD);
 
  
  // Destrutor: executado quando se destrói um objeto desta classe
+
int main(int argc, char * argv[]) {
   ~MinhaClasse();
+
   int n;
  
   // métodos para acessar os valores dos atributos
+
   for (n=0; n < argc; n++) {
  string obtemNumero();
+
    cout << "argv[" << n << "]=" << argv[n] << endl;
   string obtemDDD();
+
   }
  
   // implementação inline
+
   return 0;
  string obtemNumeroDDD() {
 
    string num = ddd + '-' + numero;
 
    return num;
 
  }
 
};
 
</syntaxhighlight>
 
* ''fora da classe'': a implementação é feita externamente, possivelmente em outro arquivo. No caso de ''obtemNumeroDDD'', isso poderia ser feito assim:<syntaxhighlight lang=c>
 
// deve-se prefixar o nome do método com o nome da classe, para indicar que é um método dessa classe
 
string MinhaClasse::obtemNumeroDDD() {
 
  string num = ddd + '-' + numero;
 
  return num;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Criação de objetos ===
 
  
Uma vez tendo declarado e implementado uma classe, podem-se criar objetos. A isso se chama ''instanciação'', e os objetos também podem ser chamados de ''instâncias''. Objetos podem ser criados diretamente, como se fossem variáveis usuais, como mostrado a seguir:
+
Apesar de hoje em dia as interfaces de usuário em modo texto serem menos usadas, o modelo de execução de programas continua o mesmo. Mesmo programas gráficos se valem de argumentos de linha de comando para modularem a forma com que devem funcionar (ver, por exemplo, o [http://manpages.ubuntu.com/manpages/bionic/man1/firefox.1.html manual do firefox]). Independente do tipo de interface de usuário, argumentos de linha de comando são de grande utilidade para passar informação a programas quando forem executados.
  
<syntaxhighlight lang=c>
+
= Classes e objetos =
#include "minhaclasse.h"
+
 
#include <iostream>
+
A linguagem C foi projetada segundo um paradigma de programação chamado de [https://en.wikipedia.org/wiki/Structured_programming programação estruturada]. Essa forma de expressar programas se baseia em subrotinas (funções no caso da linguagem C), blocos de instruções e laços de repetição, os quais atuam sobre variáveis que contêm os dados a serem acessados e transformados. O que se deve notar é a forma com que se costuma modelar um programa nesse caso, dando-se ênfase aos algoritmos que modificam os dados. Não é à toa que uma ferramenta de modelagem tradicional e ainda muito usada seja o fluxograma. Porém existem outras formas de pensar os problemas a serem resolvidos e expressar programas, dentre elas o [https://en.wikipedia.org/wiki/Object-oriented_programming paradigma de orientação a objetos] seguido por diversas linguagens tais como C++, Java e Python.
  
using namespace std;
 
  
int main() {
+
Um programa orientado a objetos é composto por objetos que interagem. Um objeto é uma abstração que representa uma ''coisa'' que faz parte do problema a ser resolvido. Um objeto contém dados que representam seu estado (chamados de ''atributos''), e procedimentos que representam seu comportamento (chamados de ''métodos''). No caso do exemplo dos horários, um horário pode ser representado por um objeto. O estado desse objeto ''Horario'' é definido pelos valores de ''hora'', ''minuto'' e ''segundo'', e seu comportamento é dado pelos métodos ''mostreSe'' e ''acrescenta''. Isso deve ser entendido da seguinte forma: um ''objeto 'Horario'' pode ser mostrado, ao se chamar seu método 'mostreSe'', e pode ser somado a outro objeto ''Horario'', ao se chamar seu método ''acrescenta''. Essas são as únicas operações definidas para esse objeto, e assim nada mais se consegue fazer com ele ... a não ser que se definam novas operações.
  string num, ddd;
 
  
  cout << "Numero: ";
 
  cin >> num;
 
  cout << "DDD: ";
 
  cin >> ddd;
 
  
  // o construtor da classe é chamado quando se cria um objeto
+
Muitos objetos similares podem existir. Por exemplo, pode haver muitos diferentes objetos ''Horario'', que se diferenciam por seus estados (valores de ''hora'', ''minuto'' e ''segundo''). Todos os objetos cujos estados são definidos pelo mesmo conjunto de atributos, e que possuem o mesmo comportamento (conjunto de métodos), pertencem à mesma '''classe'''. O conceito de classe é semelhante ao de ''tipo de dados'', porém tem uma implicação mais profunda. Um ''tipo de dados'' define como o conteúdo de uma variável deve ser interpretado (ex: um número inteiro, ponto flutuante, uma string, ...), porém não define os procedimentos que podem manipular essas variáveis. Desta forma, uma ''classe'' vincula a seus objetos os algoritmos que atuam sobre eles. Isso tem uma importante implicação no [http://effectivesoftwaredesign.com/2012/02/05/separation-of-concerns/ acoplamento e coesão de um programa], que são métricas fundamentais de qualidade de um projeto de software modular.
  // Aqui, o objeto é dado pela variável "fone"
 
  MinhaClasse fone(num, ddd);
 
  
  cout << "Numero com DDD é " << fone.obtemNumeroDDD() << endl;
 
  
  // objetos são destruídos automaticamente ao final do escopo em que foram criados
 
  // o destrutor é então executado
 
}
 
</syntaxhighlight>
 
  
 +
== Declaração de classes ==
  
Objetos podem também ser criados dinamicamente, em que a memória necessária é alocada. Para isso usa-se o operador [http://www.cplusplus.com/reference/new/operator%20new/ new] (e não a função ''malloc'' !). Objetos criados dessa forma são referenciados por ponteiros, e devem ser explicitamente destruídos usando-se o operador [http://www.cplusplus.com/reference/new/operator%20delete/ delete] (e não a função ''free'' !). O exemplo a seguir mostra a criação e destruição de objetos com ''new'' e ''delete'':
+
Uma classe é declarada usando-se a palavra-chave ''class'' seguida do nome da classe:
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
#include "minhaclasse.h"
+
class MinhaClasse {
#include <iostream>
+
// declarações internas da classe
 +
};
 +
</syntaxhighlight>
  
using namespace std;
+
De certa forma, isso se assemelha à declaração de ''struct''. No entanto, as declarações internas da classe têm um significado um pouco diferente. Por exemplo, suponha-se que a classe ''MinhaClasse'' represente um número de telefone, incluindo seu código de área. A declaração seria estendida desta forma:
  
int main() {
 
  string num, ddd;
 
  // o ponteiro fone é capaz de apontar objetos de MinhaClasse
 
  MinhaClasse * fone;
 
  
  cout << "Numero: ";
+
<syntaxhighlight lang=c>
   cin >> num;
+
class MinhaClasse {
  cout << "DDD: ";
+
private:
  cin >> ddd;
+
  string numero;
 +
   string ddd;
 +
public:
 +
// declarações dos procedimentos da classe
 +
};
 +
</syntaxhighlight>
  
  // o construtor da classe é chamado quando se cria um objeto com o operador "new"
+
A classe agora possui dois atributos:
  // Aqui, o objeto é dado pelo ponteiro "fone"
+
* ''string numero'': armazena o número de telefone
  fone = new MinhaClasse(num, ddd);
+
* ''string ddd'': armazena o código DDD
  
  cout << "Numero com DDD é " << fone->obtemNumeroDDD() << endl;
+
No exemplo, as declarações desses atributos na classe são precedidas pela cláusula ''private:''. Isso significa que esses atributos não podem ser acessados diretamente de fora da classe (por procedimentos implementados fora da classe). Logo após a declaração desses atributos, aparece a cláusula ''public:''. Ela define que o que for declarado em seguida pode ser acessado diretamente de fora da classe. Por exemplo, os métodos da classe podem ser declarados logo após ''public:''.
  
  // objetos dinâmico devem ser destruídos explicitamente usando o operador "delete"
 
  // o destrutor é então executado
 
  delete fone;
 
}
 
</syntaxhighlight>
 
  
=== Destruição de objetos ===
+
<syntaxhighlight lang=c>
 +
class MinhaClasse {
 +
private:
 +
  string numero;
 +
  string ddd;
 +
public:
 +
  // Construtor: executado quando se cria um objeto desta classe
 +
  MinhaClasse(string umNumero, string umDDD);
  
A destruição de um objeto ocorre em duas situações:
+
  // Destrutor: executado quando se destrói um objeto desta classe
# '''Quando se chega ao fim o escopo dentro do qual o objeto foi criado:''' isso vale para objetos declarados e instanciados diretamente. Ex: <syntaxhighlight lang=c>
+
  ~MinhaClasse();
void ecoa() {
 
  string palavra; // aqui se cria um objeto string
 
  
   cout << "Digite uma palavra: ";
+
   // métodos para acessar os valores dos atributos
   cin >> palavra;
+
  string obtemNumero();
   cout << "Você digitou: " << palavra << endl;
+
   string obtemDDD();
} // fim do escopo ... o objeto string "palavra" é destruido automaticamente
+
   string obtemNumeroDDD();
 +
};
 
</syntaxhighlight>
 
</syntaxhighlight>
# '''Quando se destroi o objeto com o operador [http://www.cplusplus.com/reference/new/operator%20delete/ delete]:''' isso vale somente para objetos criados dinamicamente com o operador [http://www.cplusplus.com/reference/new/operator%20new/ new]. Ex: <syntaxhighlight lang=c>
 
void ecoa() {
 
  string * palavra; // aqui se declara um ponteiro para string
 
  
  palavra = new string; // aqui se cria dinamicamente um objeto string
+
Um ''método'' é uma função que pertence a uma classe. A idéia é que um objeto de uma classe possui um estado dado pelos valores de seus atributos, e somente os métodos daquela classe podem acessar ou modificar esse estado. Quer dizer, somente métodos da classe podem acessar diretamente esses atributos. Dois métodos são especiais:
  cout << "Digite uma palavra: ";
+
*''construtor'': este método é executado sempre que se cria um objeto de uma classe. Ele tem por finalidade iniciar o estado daquele objeto. Isso envolve definir os valores iniciais dos atributos, e conferir se os parâmetros que porventura tenham sido passados são válidos (ex: se o número tem somente caracteres numéricos).
  cin >> *palavra;
+
* ''destrutor'': este método é executado quando um objeto é destruído. Ele é responsável por limpar o estado desse objeto, desfazendo qualquer ação que ele tenha iniciado. Por exemplo, se o objeto alocou memória dinamicamente em algum momento, o destrutor deve liberá-la.
  cout << "Você digitou: " << *palavra << endl;
 
  
  delete palavra; // aqui se destroi o objeto string
 
}
 
</syntaxhighlight>
 
  
 +
A declaração da classe apresentada apenas informa quais métodos ela implementa, mas não mostra como eles são implementados. Essa implementação pode ser feita de duas formas:
 +
* ''inline'': a implementação de um método é feita dentro da própria declaração da classe. Por exemplo, o método ''obtemNumeroDDD'' poderia estar implementado assim: <syntaxhighlight lang=c>
 +
class MinhaClasse {
 +
private:
 +
  string numero;
 +
  string ddd;
 +
public:
 +
  // Construtor: executado quando se cria um objeto desta classe
 +
  MinhaClasse(string umNumero, string umDDD);
  
A destruição de um objeto implica liberar as áreas de memória a ele alocadas dinamicamente em sua criação, ou durante seu tempo de vida. Essa faxina deve ser feita pelo método destrutor da classe (chamado simplesmente de [http://www.cplusplus.com/doc/tutorial/classes2/#destructor ''destructor'']). Tal método é declarado da seguinte forma em uma classe:
+
  // Destrutor: executado quando se destrói um objeto desta classe
 +
  ~MinhaClasse();
  
<syntaxhighlight lang=c>
+
  // métodos para acessar os valores dos atributos
classe Demo {
+
   string obtemNumero();
public:
+
   string obtemDDD();
   Demo(); // constructor da classe
 
   ~Demo(); // destructor da classe
 
  
   // demais métodos da classe
+
   // implementação inline
 +
  string obtemNumeroDDD() {
 +
    string num = ddd + '-' + numero;
 +
    return num;
 +
  }
 
};
 
};
 
+
</syntaxhighlight>
// implementação do destructor
+
* ''fora da classe'': a implementação é feita externamente, possivelmente em outro arquivo. Deve-se declarar a classe a que pertence o método, prefixando seu nome com ''Nome_da_classe::''. No caso de ''obtemNumeroDDD'', isso poderia ser feito assim:<syntaxhighlight lang=c>
Demo::~Demo() {  
+
// deve-se prefixar o nome do método com o nome da classe, para indicar que é um método dessa classe
   // limpeza do objeto
+
string MinhaClasse::obtemNumeroDDD() {
 +
   string num = ddd + '-' + numero;
 +
  return num;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
A implementação desse método deve ser responsável pela limpeza do objeto. Apenas áreas de memória alocadas dinamicamente precisam ser liberadas. O exemplo a seguir mostra uma classe com memória alocada dinamicamente:
+
== Criação de objetos ==
  
<syntaxhighlight lang=c>
+
Uma vez tendo declarado e implementado uma classe, podem-se criar objetos. A isso se chama ''instanciação'', e os objetos também podem ser chamados de ''instâncias''. Objetos podem ser criados diretamente, como se fossem variáveis usuais, como mostrado a seguir:
classe Demo {
 
public:
 
  Demo(const string & name); // constructor da classe
 
  ~Demo(); // destructor da classe
 
  
  // demais métodos da classe
+
<syntaxhighlight lang=c n>
private:
+
#include "minhaclasse.h"
  // atributos privativos da classe
+
#include <iostream>
  string nome;
 
  char * buffer;
 
};
 
  
// implementação do constructor
+
using namespace std;
Demo::Demo(const string & name) {
 
  nome = name;
 
  buffer = new char[1024]; // aloca dinamicamente 1kB
 
}
 
  
// implementação do destructor
+
int main() {
Demo::~Demo() {  
+
   string num, ddd;
   // libera a área de memória alocada no constructor
 
  delete buffer;
 
}
 
</syntaxhighlight>
 
  
=== Um exemplo ===
+
  cout << "Numero: ";
 +
  cin >> num;
 +
  cout << "DDD: ";
 +
  cin >> ddd;
  
O exemplo abaixo pode esclarecer melhor os conceitos de classe e objeto. Ele declara uma classe ''Vetor'', que representa vetores bidimensionais. As operações sobre tais objetos são: módulo, produto escalar, subtração e escrita em um arquivo. Veja como pode ser escrita essa classe.
+
  // o construtor da classe é chamado quando se cria um objeto
 +
  // Aqui, o objeto é dado pela variável "fone"
 +
  MinhaClasse fone(num, ddd);
 +
 
 +
  cout << "Numero com DDD é " << fone.obtemNumeroDDD() << endl;
  
<syntaxhighlight lang=c>
+
  // objetos são destruídos automaticamente ao final do escopo em que foram criados
// a classe Vetor define objetos que representam vetores bidimensionais
+
  // o destrutor é então executado
class Vetor {
+
}
private: // as declarações abaixo são privativas (podem ser acessadas somente dentro da classe)
+
</syntaxhighlight>
  
  double x, y;
 
  
public: // as declarações abaixo são públicas, podendo ser acessadas de fora da classe
+
Objetos podem também ser criados dinamicamente, em que a memória necessária é alocada. Para isso usa-se o operador [http://www.cplusplus.com/reference/new/operator%20new/ new] (e não a função ''malloc'' !). Objetos criados dessa forma são referenciados por ponteiros, e devem ser explicitamente destruídos usando-se o operador [http://www.cplusplus.com/reference/new/operator%20delete/ delete] (e não a função ''free'' !). O exemplo a seguir mostra a criação e destruição de objetos com ''new'' e ''delete'':
  
  // construtor da classe: um método especial que cria objetos desta classe
+
<syntaxhighlight lang=c n>
  Vetor(double px, double py) {
+
#include "minhaclasse.h"
    // inicia o estado de um objeto
+
#include <iostream>
    x = px;
 
    y = py;
 
  }
 
  
  // destrutor da classe: um método especial que destrói objetos desta classe
+
using namespace std;
  ~Vetor() {} // nada demais precisa ser feito
 
  
   // este método retorna o módulo do vetor
+
int main() {
   double modulo() {
+
  string num, ddd;
    return sqrt(x*x + y*y);
+
   // o ponteiro fone é capaz de apontar objetos de MinhaClasse
  }
+
   MinhaClasse * fone;
  
   // este método retorna o produto escalar deste vetor com outro vetor
+
   cout << "Numero: ";
   double produto_escalar(const Vetor & outro) {
+
   cin >> num;
    return x*outro.x + y*outro.y;
+
  cout << "DDD: ";
   }
+
   cin >> ddd;
  
   // este método calcula a soma entre este e outro vetor (este + outro),
+
   // o construtor da classe é chamado quando se cria um objeto com o operador "new"
   // retornando um novo vetor como resultado
+
   // Aqui, o objeto é dado pelo ponteiro "fone"
   Vetor adicione(const Vetor & outro) {
+
   fone = new MinhaClasse(num, ddd);
    Vetor novo(x+outro.x, y+outro.y);
 
  
    return novo;
+
  cout << "Numero com DDD é " << fone->obtemNumeroDDD() << endl;
  }
 
  
   // este método calcula a diferença entre este e outro vetor (este - outro),
+
   // objetos dinâmico devem ser destruídos explicitamente usando o operador "delete"
   // retornando um novo vetor como resultado
+
   // o destrutor é então executado
   Vetor subtraia(const Vetor & outro) {
+
   delete fone;
    Vetor novo(x-outro.x, y-outro.y);
+
}
 +
</syntaxhighlight>
 +
 
 +
=== Invocação do construtor ===
  
    return novo;
+
* [http://www.cplusplus.com/doc/tutorial/classes#member_initialization Inicialização dos atributos de um objeto]
  }
 
  
  // este método escreve uma representação do objeto no arquivo "out"
+
A criação de um objeto causa a execução do método ''construtor'' da classe. Esse método é responsável por iniciar os valores dos atributos do objeto (chamados de ''variáveis de instância''), tornando-o pronto para ser utilizado. O ''construtor'' recebe os parâmetros passados na criação do objeto (ver linhas 16 do primeiro exemplo e 18 do segundo). No exemplo em questão, o método construtor poderia fazer algo assim:
  // (que é um objeto da classe ostream)
 
  void mostreSe(ostream & out) {
 
    out << "(";
 
    out << x;
 
    out << ", ";
 
    out << y;
 
    out << ")";
 
  }
 
  
};
+
<syntaxhighlight lang=c>
 +
MinhaClasse::MinhaClasse(string umNumero, string umDDD) {
 +
  numero = umNumero;
 +
  ddd = umDDD;
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Uma vez existindo a classe ''Vetor'', pode-se escrever um programa que use objetos dessa classe. No exemplo abaixo, calcula-se o vetor resultante de dois outros vetores:
+
... quer dizer, apenas copiar os valores dos parâmetros para os respectivos atributos. Outra forma de implementar o construtor e iniciar os valores dos atributos é esta:
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
int main() {
+
MinhaClasse::MinhaClasse(string umNumero, string umDDD) : numero(umNumero), ddd(umDDD) {
  int x, y;
+
}
 +
</syntaxhighlight>
 +
 
  
  cout << "Coordenada X do vetor 1: ";
+
Deve-se observar que os atributos são iniciados usando uma sintaxe especial. Logo após o nome do método construtor declara-se uma lista de inicialização de atributos. Essa lista é iniciada com ''':''', seguida dos nomes dos atributos invocados como se fosse outros objetos sendo criados. Na verdade é exatamente isso que acontece nessa forma de iniciar os atributos: eles são criados como objetos que fazem parte do objeto criado. Essa forma de iniciar atributos é imprescindível quando uma classe possui um ou mais atributos que são de fato objetos de alguma classe, e que precisam receber parâmetros ao serem criados.
  cin >> x;
 
  cout << "Coordenada Y do vetor 1: ";
 
  cin >> y;
 
  cout << endl;
 
  
  // Aqui se cria o primeiro objeto Vetor
+
== Destruição de objetos ==
  Vetor v1(x, y);
 
  
  cout << "Coordenada X do vetor 2: ";
+
A destruição de um objeto ocorre em duas situações:
  // Aqui se lê pela entrada padrão a coordenada X
+
# '''Quando se chega ao fim o escopo dentro do qual o objeto foi criado:''' isso vale para objetos declarados e instanciados diretamente. Ex: <syntaxhighlight lang=c>
  cin >> x;
+
void ecoa() {
   cout << "Coordenada Y do vetor 2: ";
+
   string palavra; // aqui se cria um objeto string
  // Aqui se lê pela entrada padrão a coordenada Y
 
  cin >> y;
 
  cout << endl;
 
  
   // Aqui se cria o segundo objeto Vetor
+
   cout << "Digite uma palavra: ";
  Vetor v2(x, y);
+
  cin >> palavra;
 +
  cout << "Você digitou: " << palavra << endl;
 +
} // fim do escopo ... o objeto string "palavra" é destruido automaticamente
 +
</syntaxhighlight>
 +
# '''Quando se destroi o objeto com o operador [http://www.cplusplus.com/reference/new/operator%20delete/ delete]:''' isso vale somente para objetos criados dinamicamente com o operador [http://www.cplusplus.com/reference/new/operator%20new/ new]. Ex: <syntaxhighlight lang=c>
 +
void ecoa() {
 +
  string * palavra; // aqui se declara um ponteiro para string
  
   // Aqui se obtém a resultante dos vetores v1 e v2. Note como a operação "adicione"
+
   palavra = new string; // aqui se cria dinamicamente um objeto string
   // do objeto "v1" é chamada.
+
  cout << "Digite uma palavra: ";
  Vetor v3 = v1.adicione(v2);
+
  cin >> *palavra;
 +
   cout << "Você digitou: " << *palavra << endl;
  
   cout << "Vetor resultante: ";
+
   delete palavra; // aqui se destroi o objeto string
  // Aqui se escreve uma representação de "v3" na tela. "cout" é um objeto da biblioteca C++ padrão
 
  // que representa a saída padrão de um processo. A saída padrão funciona como um arquivo. O objeto
 
  // "cout" é similar ao "stdout" da linguagem C.
 
  v3.mostreSe(cout);
 
  cout << endl;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
O programa em si se apresenta como um pequeno algoritmo que lê pela entrada padrão valores de coordenadas e cria os respectivos vetores (''v1'' e ''v2''), calculando sua resultante a apresentando na saída padrão o novo vetor (''v3'').
 
  
  
Além das regras de escrita da linguagem, o que se denomina ''sintaxe'', ressalta-se o uso dos objetos feito no programa. O pequeno programa criado trata de calcular a resultante de dois vetores bidimensionais. As ''coisas'' a serem diretamente manipuladas pelo programa são os vetores, sendo estes representado por objetos. Outros objetos usados são os arquivos que representam a entrada e saída padrão do programa (respectivamente ''cin'' e ''cout''). O programa '''usa''' os objetos da classe ''Vetor'', explorando as operações que eles oferecem. Assim, a declaração da classe ''Vetor'' se preocupa em definir o que é um objeto Vetor (seus atributos) e o que ele é capaz de fazer (seu comportamento). Já o programa apenas utiliza esses objetos.
+
A destruição de um objeto implica liberar as áreas de memória a ele alocadas dinamicamente em sua criação, ou durante seu tempo de vida. Essa faxina deve ser feita pelo método destrutor da classe (chamado simplesmente de [http://www.cplusplus.com/doc/tutorial/classes2/#destructor ''destructor'']). Tal método é declarado da seguinte forma em uma classe:
  
=== O ponteiro predefinido ''this'' ===
+
<syntaxhighlight lang=c>
 +
classe Demo {
 +
public:
 +
  Demo(); // constructor da classe
 +
  ~Demo(); // destructor da classe
  
* [http://www.cplusplus.com/doc/tutorial/templates/ Ver seção ''the keyword this'' neste documento]
+
  // demais métodos da classe
 +
};
  
 +
// implementação do destructor
 +
Demo::~Demo() {
 +
  // limpeza do objeto
 +
}
 +
</syntaxhighlight>
 +
 +
A implementação desse método deve ser responsável pela limpeza do objeto. Apenas áreas de memória alocadas dinamicamente precisam ser liberadas. O exemplo a seguir mostra uma classe com memória alocada dinamicamente:
  
Ao se implementarem métodos de uma classe pode-se usar uma variável predefinida chamada ''this'', que é um ponteiro para o objeto no escopo do qual se executa um método. Sempre que dentro de um método for necessário eliminar uma ambiguidade quanto ao uso de um atributo do objeto, ou por clareza, pode-se usar esse ponteiro ''this''. O exemplo a seguir ilustra o uso de ''this''.
 
 
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
class Coisa {
+
classe Demo {
 
  public:
 
  public:
   Coisa(int x, int y);
+
   Demo(const string & name); // constructor da classe
   ~Coisa() {}
+
   ~Demo(); // destructor da classe
   // outros métodos da classe ...
+
 
 +
   // demais métodos da classe
 
  private:
 
  private:
   // os atributos dos objetos desta classe
+
   // atributos privativos da classe
   int x,y;
+
   string nome;
 +
  char * buffer;
 
};
 
};
  
// construtor da classe Coisa: os parâmetros "x" e "y" são usados para iniciar os
+
// implementação do constructor
// atributos "x" e "y". Para resolver a ambiguidade entre parâmetros e atributos, dentro do construtor
+
Demo::Demo(const string & name) {
// se usa o ponteiro "this"
+
  nome = name;
Coisa::Coisa(int x, int y) {
+
  buffer = new char[1024]; // aloca dinamicamente 1kB
   this->x = x;
+
}
   this->y = y;
+
 
 +
// implementação do destructor
 +
Demo::~Demo() {  
 +
   // libera a área de memória alocada no constructor
 +
   delete buffer;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
  
Existem outros casos em que o ponteiro ''this'' tem utilidade. Um exemplo pode ser visto na [[Introdu%C3%A7%C3%A3o_C%2B%2B#Construtor_de_c.C3.B3pia|seção sobre construtor de cópia]].
+
== Um exemplo ==
  
=== Operador de atribuição ===
+
O exemplo abaixo pode esclarecer melhor os conceitos de classe e objeto. Ele declara uma classe ''Vetor'', que representa vetores bidimensionais. As operações sobre tais objetos são: módulo, produto escalar, subtração e escrita em um arquivo. Veja como pode ser escrita essa classe.
  
Em C++ pode-se definir como um operador específico atua sobre um objeto. Operadores típicos são: ''='' (atribuição), ''+'' (adição), ''-'' (subtração), e [http://www.cplusplus.com/doc/tutorial/operators/ outros]. Um operador é definido por uma função ou método que implementa sua operação. No caso da Fila, um operador útil é o de atribuição, pois pode ser usado para que uma fila existente seja modificada para se tornar idêntica a outra fila. Supondo que esse operador tenha sido implementado, o exemplo a seguir mostra a fila ''f1'' sendo atribuída a ''f2'' (linha 19), a qual já existe e tem inclusive dados armazenados, e demonstra em seguida como ficou o conteúdo de ''f2''.
+
<syntaxhighlight lang=c>
 +
// a classe Vetor define objetos que representam vetores bidimensionais
 +
class Vetor {
 +
private: // as declarações abaixo são privativas (podem ser acessadas somente dentro da classe)
  
 +
  double x, y;
  
<syntaxhighlight lang=c n>
+
public: // as declarações abaixo são públicas, podendo ser acessadas de fora da classe
#include <iostream>
 
#include "fila.h"
 
  
using namespace std;
+
  // construtor da classe: um método especial que cria objetos desta classe
 +
  Vetor(double px, double py) {
 +
    // inicia o estado de um objeto
 +
    x = px;
 +
    y = py;
 +
  }
  
int main() {
+
   // destrutor da classe: um método especial que destrói objetos desta classe
   Fila f1(5);
+
   ~Vetor() {} // nada demais precisa ser feito
   Fila f2(7);
 
  
   f1.enfileira(2);
+
   // este método retorna o módulo do vetor
  f1.enfileira(7);
+
  double modulo() {
   f1.enfileira(4);
+
    return sqrt(x*x + y*y);
 +
   }
  
   f2.enfileira(3);
+
   // este método retorna o produto escalar deste vetor com outro vetor
 +
  double produto_escalar(const Vetor & outro) {
 +
    return x*outro.x + y*outro.y;
 +
  }
  
   cout << "Comprimento de f1: " << f1.comprimento() << endl;
+
   // este método calcula a soma entre este e outro vetor (este + outro),
   cout << "Comprimento de f2: " << f2.comprimento() << endl;
+
   // retornando um novo vetor como resultado
 +
  Vetor adicione(const Vetor & outro) {
 +
    Vetor novo(x+outro.x, y+outro.y);
  
   f2 = f1; // atribui f1 a f2 ... f2 torna-se uma cópia de f1
+
    return novo;
 +
   }
  
   cout << "Comprimento de f1: " << f1.comprimento() << endl;
+
   // este método calcula a diferença entre este e outro vetor (este - outro),
   cout << "Comprimento de f2: " << f2.comprimento() << endl;
+
  // retornando um novo vetor como resultado
 +
   Vetor subtraia(const Vetor & outro) {
 +
    Vetor novo(x-outro.x, y-outro.y);
 +
 
 +
    return novo;
 +
  }
  
   while (not f1.vazia()) cout << "Desenfileirou de f1: " << f1.desenfileira() << endl;
+
   // este método escreve uma representação do objeto no arquivo "out"
 
+
  // (que é um objeto da classe ostream)
  cout << "Comprimento de f1: " << f1.comprimento() << endl;
+
  void mostreSe(ostream & out) {
  cout << "Comprimento de f2: " << f2.comprimento() << endl;
+
    out << "(";
 +
    out << x;
 +
    out << ", ";
 +
    out << y;
 +
    out << ")";
 +
  }
  
  while (not f2.vazia()) cout << "Desenfileirou de f2: " << f2.desenfileira() << endl;
+
};
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
+
Uma vez existindo a classe ''Vetor'', pode-se escrever um programa que use objetos dessa classe. No exemplo abaixo, calcula-se o vetor resultante de dois outros vetores:
O operador de atribuição da fila pode ser declarado como mostrado abaixo:
 
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
class Fila {
+
int main() {
public:
+
   int x, y;
  Fila(int umaCapacidade); // cria uma fila capaz de guardar "capacidade" elementos
 
  Fila(const Fila & outra);
 
   ~Fila(); // destroi a fila
 
  Fila& operator=( const Fila& outra ); // OPERADOR DE ATRIBUIÇÃO
 
  // demais declarações da Fila ...
 
};
 
</syntaxhighlight>
 
  
 +
  cout << "Coordenada X do vetor 1: ";
 +
  cin >> x;
 +
  cout << "Coordenada Y do vetor 1: ";
 +
  cin >> y;
 +
  cout << endl;
  
Como se pode notar, sua declaração é muito parecida com a de um método qualquer da classe. A palavra-chave ''operator'' indica tratar-se de um método de operador, e o símbolo que aparece logo em seguida indica o operador a ser redefinido (no caso, ''=''). O único parâmetro a ser passado é o objeto a ser copiado (no caso, uma Fila). O valor retornado é uma referência ao próprio objeto que foi o destino da atribuição (aquele que foi modificado). A implementação desse método segue as regras usadas nos demais métodos:
+
  // Aqui se cria o primeiro objeto Vetor
 +
  Vetor v1(x, y);
  
<syntaxhighlight lang=c>
+
  cout << "Coordenada X do vetor 2: ";
Fila& Fila::operator=( const Fila& outra ) {
+
  // Aqui se lê pela entrada padrão a coordenada X
   // desalocar a memória de "buffer"
+
  cin >> x;
   // copiar os valores de atributos de "outra"
+
  cout << "Coordenada Y do vetor 2: ";
   // alocar memória para "buffer"
+
  // Aqui se lê pela entrada padrão a coordenada Y
   // copiar conteúdo da "buffer" da "outra" fila
+
  cin >> y;
    
+
  cout << endl;
   // retorna uma referência ao próprio objeto
+
 
   return *this;
+
  // Aqui se cria o segundo objeto Vetor
 +
  Vetor v2(x, y);
 +
 
 +
   // Aqui se obtém a resultante dos vetores v1 e v2. Note como a operação "adicione"
 +
   // do objeto "v1" é chamada.
 +
   Vetor v3 = v1.adicione(v2);
 +
 
 +
  cout << "Vetor resultante: ";
 +
   // Aqui se escreve uma representação de "v3" na tela. "cout" é um objeto da biblioteca C++ padrão
 +
   // que representa a saída padrão de um processo. A saída padrão funciona como um arquivo. O objeto
 +
   // "cout" é similar ao "stdout" da linguagem C.
 +
  v3.mostreSe(cout);
 +
   cout << endl;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
''A implementação do método do operador de atribuição em fila.cpp''
 
  
=== Construtor de cópia ===
+
O programa em si se apresenta como um pequeno algoritmo que lê pela entrada padrão valores de coordenadas e cria os respectivos vetores (''v1'' e ''v2''), calculando sua resultante a apresentando na saída padrão o novo vetor (''v3'').
 +
 
  
Um [http://www.cplusplus.com/articles/y8hv0pDG/ construtor de cópia] é um método construtor para criar um objeto que é uma cópia idêntica a um objeto existente. Esse tipo de criação de objeto aparece em casos como este:
+
Além das regras de escrita da linguagem, o que se denomina ''sintaxe'', ressalta-se o uso dos objetos feito no programa. O pequeno programa criado trata de calcular a resultante de dois vetores bidimensionais. As ''coisas'' a serem diretamente manipuladas pelo programa são os vetores, sendo estes representado por objetos. Outros objetos usados são os arquivos que representam a entrada e saída padrão do programa (respectivamente ''cin'' e ''cout''). O programa '''usa''' os objetos da classe ''Vetor'', explorando as operações que eles oferecem. Assim, a declaração da classe ''Vetor'' se preocupa em definir o que é um objeto Vetor (seus atributos) e o que ele é capaz de fazer (seu comportamento). Já o programa apenas utiliza esses objetos.
  
<syntaxhighlight lang=c>
+
== O ponteiro predefinido ''this'' ==
Fila<int> f1(10); // cria uma fila capaz de guardar 10 números inteiros
 
  
f1.enfileira(5);
+
* [http://www.cplusplus.com/doc/tutorial/templates/ Ver seção ''the keyword this'' neste documento]
f1.enfileira(8);
 
f1.enfileira(2);
 
f1.enfileira(4);
 
  
Fila<int> f2 = f1; // cria a fila f2, que é uma cópia idêntica de f1
 
  
f1.esvazia(); // esvazia a fila f1
+
Ao se implementarem métodos de uma classe pode-se usar uma variável predefinida chamada ''this'', que é um ponteiro para o objeto no escopo do qual se executa um método. Sempre que dentro de um método for necessário eliminar uma ambiguidade quanto ao uso de um atributo do objeto, ou por clareza, pode-se usar esse ponteiro ''this''. O exemplo a seguir ilustra o uso de ''this''.
 
 
// Mostra o conteúdo da fila f2: deve ser igual ao que existia em f1
 
while (not f2.vazia()) {
 
  cout << f2.desenfileira() << endl;
 
}
 
</syntaxhighlight>
 
 
 
Para que o exemplo acima funcione, a classe Fila deve implementar seu construtor de cópia:
 
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
template <typename T> class Fila {
+
class Coisa {
 
  public:
 
  public:
   // construtor de cópia: o objeto criado deve ser idêntico ao objeto "outra"
+
   Coisa(int x, int y);
   Fila(const Fila<T> & outra);
+
   ~Coisa() {}
 
+
   // outros métodos da classe ...
   // demais declarações da Fila ...
+
private:
 +
  // os atributos dos objetos desta classe
 +
  int x,y;
 
};
 
};
</syntaxhighlight>
 
  
O construtor de cópia deve ser responsável por copiar o estado do ''objeto copiado'', de forma que o objeto criado seja idêntico a ele. Isso envolve copiar os valores dos atributos desses objetos, porém em certos casos isso não é suficiente. Se o objeto copiado tiver como atributos outros objetos ou variáveis criados dinamicamente, então o novo objeto deve também criar dinamicamente cópias desses objetos ou variáveis.
+
// construtor da classe Coisa: os parâmetros "x" e "y" são usados para iniciar os
 
+
// atributos "x" e "y". Para resolver a ambiguidade entre parâmetros e atributos, dentro do construtor
 
+
// se usa o ponteiro "this"
Deve-se observar que um construtor de cópia é muito parecido com um operador de atribuição. A diferença reside no fato de que uma atribuição ocorre entre dois objetos já existentes. Por isso um objeto que recebe uma atribuição deve primeiro limpar seu estado (se necessário, o que envolve destruir eventuais objetos e variáveis dinâmicos) para depois se tornar uma cópia do outro objeto. Apesar disso, se um operador de atribuição já existir ele pode ser usado para facilmente implementar um construtor de cópia. O exemplo a seguir mostra como isso pode ser feito para a Fila:
+
Coisa::Coisa(int x, int y) {
 
+
   this->x = x;
<syntaxhighlight lang=c>
+
   this->y = y;
// implementação do construtor de cópia
 
template <typename T> Fila<T>::Fila(const Fila<T> & outra) {
 
  // primeiro devem-se atribuir valores aos atributos desta Fila
 
  // para que ela pareça vazia
 
 
 
  // em seguida, basta atribuir a "outra" Fila a este objeto
 
  // Note o uso do ponteiro predefinido "this", que sempre aponta o objeto que recebe a operação
 
   // dentro de um método de sua classe
 
   *this = outra;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 
  
== Templates ==
+
Existem outros casos em que o ponteiro ''this'' tem utilidade. Um exemplo pode ser visto na [[Introdu%C3%A7%C3%A3o_C%2B%2B#Construtor_de_c.C3.B3pia|seção sobre construtor de cópia]].
  
* [http://www.cprogramming.com/tutorial/templates.html Template classes C++]
+
== Operador de atribuição ==
* [http://www.codeproject.com/Articles/257589/An-Idiots-Guide-to-Cplusplus-Templates-Part An Idiot's Guide to C++ Templates]
 
* [http://br.ccm.net/faq/10140-os-templates-em-c Os templates em C++]
 
  
 +
Em C++ pode-se definir como um operador específico atua sobre um objeto. Operadores típicos são: ''='' (atribuição), ''+'' (adição), ''-'' (subtração), e [http://www.cplusplus.com/doc/tutorial/operators/ outros]. Um operador é definido por uma função ou método que implementa sua operação. No caso da Fila, um operador útil é o de atribuição, pois pode ser usado para que uma fila existente seja modificada para se tornar idêntica a outra fila. Supondo que esse operador tenha sido implementado, o exemplo a seguir mostra a fila ''f1'' sendo atribuída a ''f2'' (linha 19), a qual já existe e tem inclusive dados armazenados, e demonstra em seguida como ficou o conteúdo de ''f2''.
  
''Templates'' possibilitam a parametrização de tipos em C++. Com isso, podem-se escrever funções, structs e classes que referenciam ou armazenam dados cujos tipos sejam desconhecidos de antemão. Esses tipos são denominados ''tipos genéricos''. No nosso caso, isso será usado para generalizar as estruturas de dados a serem criadas, para que elas possam armazenar qualquer tipo de dado. Na explicação a seguir, usa-se a estrutura de dados ''Fila'' como exemplo.
 
  
 +
<syntaxhighlight lang=c n>
 +
#include <iostream>
 +
#include "fila.h"
  
A declaração de uma classe template inicia com o prefixo ''template <typename T>''. A cláusula ''<typename T>'' serve para especificar um identificador para o tipo genérico a ser usado dentro da classe (no exemplo, escolheu-se o identificador ''T'', mas pode ser qualquer outra coisa). No caso da ''Fila'', deseja-se torná-la capaz de guardar qualquer tipo de dado. Quer dizer, seu funcionamento não depende do que ela estiver armazenando, portanto o tipo do dado armazenado pode ser generalizado. Esse é um bom exemplo de uso de classe template, estando exemplificado a seguir:
+
using namespace std;
  
<syntaxhighlight lang=c>
+
int main() {
template <typename T> class Fila {
+
  Fila f1(5);
 +
  Fila f2(7);
  
// declarações de atributos e métodos da classe
+
  f1.enfileira(2);
 +
  f1.enfileira(7);
 +
  f1.enfileira(4);
  
};
+
  f2.enfileira(3);
</syntaxhighlight>
 
  
 +
  cout << "Comprimento de f1: " << f1.comprimento() << endl;
 +
  cout << "Comprimento de f2: " << f2.comprimento() << endl;
  
Todas as referências ao tipo genérico devem ser escritas usando o identificador especificado (no caso, ''T''). Aproveitando o exemplo da ''Fila'', deve-se observar que o atributo ''buffer'' depende do tipo genérico, pois trata-se de um vetor onde devem ser armazenados os dados enfileirados. Os métodos ''enfileira'', ''desenfileira'' e ''frente'' também dependem do tipo genérico. A declaração da classe template ''Fila'' segue abaixo, porém com alguns detalhes omitidos para ficar mais evidente o uso de template.
+
  f2 = f1; // atribui f1 a f2 ... f2 torna-se uma cópia de f1
  
 +
  cout << "Comprimento de f1: " << f1.comprimento() << endl;
 +
  cout << "Comprimento de f2: " << f2.comprimento() << endl;
  
<syntaxhighlight lang=c>
+
  while (not f1.vazia()) cout << "Desenfileirou de f1: " << f1.desenfileira() << endl;
template <typename T> class Fila {
+
 
   public:
+
   cout << "Comprimento de f1: " << f1.comprimento() << endl;
    Fila(unsigned int N);
+
  cout << "Comprimento de f2: " << f2.comprimento() << endl;
    ~Fila();
 
  
    // enfileira um dado do tipo T
+
  while (not f2.vazia()) cout << "Desenfileirou de f2: " << f2.desenfileira() << endl;
    void enfileira(const T & algo);
+
}
 
 
    // desenfileira um dado da frente da fila, e retorna uma
 
    // cópia desse dado (cujo tipo é T)
 
    T desenfileira();
 
 
 
    // Retorna uma referência ao dado de tipo T que está na frente da fila
 
    T & frente() const;
 
 
 
    bool vazia() const {return itens == 0; }
 
    bool cheia() const { return itens == capacidade;}
 
    unsigned int tamanho() const { return itens;}
 
  private:
 
    int capacidade;
 
    int itens, inicio, fim;
 
    T* buffer; // área de memória para guardar os dados de tipo T enfileirados
 
};
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
Quando se declara uma classe template, tanto a declaração da classe (sua interface) quanto a implementação de seus métodos devem estar no mesmo arquivo com extensão ''.h''. Isso se deve a [http://stackoverflow.com/questions/19798325/template-compilation detalhes específicos da compilação do código-fonte]. Assim, a implementação dos métodos pode estar dentro da própria declaração da classe, como no caso dos métodos ''vazia'', ''cheia'' e ''tamanho'', ou fora da classe. Quando forem implementados fora da classe, deve-se repetir o prefixo ''template <typename T>'' na frente do nome de cada método, como mostrado a seguir:
+
O operador de atribuição da fila pode ser declarado como mostrado abaixo:
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
template <typename T> void Fila<T>::enfileira(const T& algo) {
+
class Fila {
   // implementação do método
+
public:
}
+
  Fila(int umaCapacidade); // cria uma fila capaz de guardar "capacidade" elementos
 +
  Fila(const Fila & outra);
 +
  ~Fila(); // destroi a fila
 +
  Fila& operator=( const Fila& outra ); // OPERADOR DE ATRIBUIÇÃO
 +
   // demais declarações da Fila ...
 +
};
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
Uma vez declarada uma classe template, ela pode ser utilizada. Somente no momento em que uma classe é usada que o tipo genérico é especificado. Por exemplo, a ''Fila'' acima declarada pode guardar qualquer coisa, o que inclui strings e números inteiros. Para declarar duas filas, de forma que uma armazene inteiros e outra strings, deve-se usar esta sintaxe:
+
Como se pode notar, sua declaração é muito parecida com a de um método qualquer da classe. A palavra-chave ''operator'' indica tratar-se de um método de operador, e o símbolo que aparece logo em seguida indica o operador a ser redefinido (no caso, ''=''). O único parâmetro a ser passado é o objeto a ser copiado (no caso, uma Fila). O valor retornado é uma referência ao próprio objeto que foi o destino da atribuição (aquele que foi modificado). A implementação desse método segue as regras usadas nos demais métodos:
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
int main() {
+
Fila& Fila::operator=( const Fila& outra ) {
   // Uma fila capaz de guardar até 10 números inteiros
+
   // desalocar a memória de "buffer"
   Fila<int> f1(10);
+
  // copiar os valores de atributos de "outra"
 +
  // alocar memória para "buffer"
 +
  // copiar conteúdo da "buffer" da "outra" fila
 +
    
 +
  // retorna uma referência ao próprio objeto
 +
  return *this;
 +
}
 +
</syntaxhighlight>
 +
''A implementação do método do operador de atribuição em fila.cpp''
  
  // uma fila capaz de guardar até 5 strings
+
== Construtor de cópia ==
  Fila<string> f2(5);
 
  
  // enfileira-se o número 23 na fila de inteiros
+
Um [http://www.cplusplus.com/articles/y8hv0pDG/ construtor de cópia] é um método construtor para criar um objeto que é uma cópia idêntica a um objeto existente. Esse tipo de criação de objeto aparece em casos como este:
  f1.enfileira(23);
+
 
 +
<syntaxhighlight lang=c>
 +
Fila<int> f1(10); // cria uma fila capaz de guardar 10 números inteiros
 +
 
 +
f1.enfileira(5);
 +
f1.enfileira(8);
 +
f1.enfileira(2);
 +
f1.enfileira(4);
 +
 
 +
Fila<int> f2 = f1; // cria a fila f2, que é uma cópia idêntica de f1
  
  // enfileira-se a string "uma coisa" na fila de strings
+
f1.esvazia(); // esvazia a fila f1
  string algo = "uma coisa";
 
  f2.enfileira(algo);
 
  
  // outras declarações da função main ...
+
// Mostra o conteúdo da fila f2: deve ser igual ao que existia em f1
 +
while (not f2.vazia()) {
 +
  cout << f2.desenfileira() << endl;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
+
Para que o exemplo acima funcione, a classe Fila deve implementar seu construtor de cópia:
Do ponto de vista da linguagem, um ''template'' define um padrão de geração de código para o compilador ([http://stackoverflow.com/questions/19798325/template-compilation maiores detalhes]). Quer dizer, um ''template'' em si não é compilável, pois ele representa código-fonte ainda incompleto. No entanto, no momento em que se usa um ''template'', o código-fonte é completado e pode ser compilado. Em outras palavras, o ''template'' da Fila se torna uma Fila de fato. O programa de teste abaixo aproveita o exemplo das filas, enfileirando e desenfileirando dados de duas filas.
 
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
#include <iostream>
+
template <typename T> class Fila {
#include "fila.h"
+
  public:
   
+
   // construtor de cópia: o objeto criado deve ser idêntico ao objeto "outra"
using namespace std;
+
   Fila(const Fila<T> & outra);
+
 
int main() {
+
   // demais declarações da Fila ...
   // Uma fila de strings
+
};
   Fila<string> fila1(10);
+
</syntaxhighlight>
+
 
   // Uma fila de inteiros
+
O construtor de cópia deve ser responsável por copiar o estado do ''objeto copiado'', de forma que o objeto criado seja idêntico a ele. Isso envolve copiar os valores dos atributos desses objetos, porém em certos casos isso não é suficiente. Se o objeto copiado tiver como atributos outros objetos ou variáveis criados dinamicamente, então o novo objeto deve também criar dinamicamente cópias desses objetos ou variáveis.
  Fila<int> fila2(10);
+
 
+
 
  // Armazena três strings na fila de strings
+
Deve-se observar que um construtor de cópia é muito parecido com um operador de atribuição. A diferença reside no fato de que uma atribuição ocorre entre dois objetos já existentes. Por isso um objeto que recebe uma atribuição deve primeiro limpar seu estado (se necessário, o que envolve destruir eventuais objetos e variáveis dinâmicos) para depois se tornar uma cópia do outro objeto. Apesar disso, se um operador de atribuição já existir ele pode ser usado para facilmente implementar um construtor de cópia. O exemplo a seguir mostra como isso pode ser feito para a Fila:
  for (int x=0; x < 3; x++) {
+
 
    string palavra;
+
<syntaxhighlight lang=c>
+
// implementação do construtor de cópia
    cout << "Digite uma palavra: ";
+
template <typename T> Fila<T>::Fila(const Fila<T> & outra) {
    cin >> palavra;
+
   // primeiro devem-se atribuir valores aos atributos desta Fila
    fila1.enfileira(palavra);
+
   // para que ela pareça vazia
  }
+
 
+
  // em seguida, basta atribuir a "outra" Fila a este objeto
  // Armazena alguns números na fila de inteiros
+
   // Note o uso do ponteiro predefinido "this", que sempre aponta o objeto que recebe a operação
  for(int x=0; x < 8; x += 1) lista2.enfileira(x);
+
   // dentro de um método de sua classe
+
   *this = outra;
   // Mostra os dados da fila de strings
 
   while (not fila1.vazia()) {
 
    cout << "desenfileirou: " << fila1.desenfileira() << endl;
 
   }
 
 
   // Mostra os dados da fila de inteiros
 
   while (not fila2.vazia()) {
 
    cout << "desenfileirou: " << fila2.desenfileira() << endl;
 
  }
 
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
= Passagem de parâmetros =
+
== Templates ==
  
A passagem de parâmetros em C++ apresenta alguns refinamentos, se comparado à linguagem C:<br><br>
+
* [http://www.cprogramming.com/tutorial/templates.html Template classes C++]
* Passagem de parâmetros de função [http://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Statements/Functions#by_value por valor] ou [http://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Statements/Functions#by_reference por referência]. Exemplos:
+
* [http://www.codeproject.com/Articles/257589/An-Idiots-Guide-to-Cplusplus-Templates-Part An Idiot's Guide to C++ Templates]
{|border=1
+
* [http://br.ccm.net/faq/10140-os-templates-em-c Os templates em C++]
!Passagem por valor
 
!Passagem por referência
 
|-
 
|<syntaxhighlight lang=c>
 
#include <iostream>
 
  
using namespace std;
 
  
int incrementa(int x) {
+
''Templates'' possibilitam a parametrização de tipos em C++. Com isso, podem-se escrever funções, structs e classes que referenciam ou armazenam dados cujos tipos sejam desconhecidos de antemão. Esses tipos são denominados ''tipos genéricos''. No nosso caso, isso será usado para generalizar as estruturas de dados a serem criadas, para que elas possam armazenar qualquer tipo de dado. Na explicação a seguir, usa-se a estrutura de dados ''Fila'' como exemplo.
  x++;
 
  return x;
 
}
 
  
int main() {
 
  int valor = 10;
 
  int resultado;
 
  
  resultado = incrementa(valor);
+
A declaração de uma classe template inicia com o prefixo ''template <typename T>''. A cláusula ''<typename T>'' serve para especificar um identificador para o tipo genérico a ser usado dentro da classe (no exemplo, escolheu-se o identificador ''T'', mas pode ser qualquer outra coisa). No caso da ''Fila'', deseja-se torná-la capaz de guardar qualquer tipo de dado. Quer dizer, seu funcionamento não depende do que ela estiver armazenando, portanto o tipo do dado armazenado pode ser generalizado. Esse é um bom exemplo de uso de classe template, estando exemplificado a seguir:
  cout << "Resultado=" << resultado;
 
  cout << ", e valor=" << valor << endl;
 
}
 
</syntaxhighlight> || <syntaxhighlight lang=c>
 
#include <iostream>
 
  
using namespace std;
+
<syntaxhighlight lang=c>
 +
template <typename T> class Fila {
  
// passagem por referência: observe o operador &
+
// declarações de atributos e métodos da classe
// antes do nome do parâmetro
 
int incrementa(int & x) {
 
  x++;
 
  return x;
 
}
 
  
int main() {
+
};
  int valor = 10;
 
  int resultado;
 
 
 
  resultado = incrementa(valor);
 
  cout << "Resultado=" << resultado;
 
  cout << ", e valor=" << valor << endl;
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
|}<br><br>
 
* [http://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Statements/Functions#Constant_Parameters Parâmetros somente-leitura]. Exemplo (experimente compilar estes programas):
 
{|border=1
 
!Programa 1
 
!Programa 2
 
|-
 
|<syntaxhighlight lang=c>
 
#include <iostream>
 
  
using namespace std;
 
  
int incrementa(const int & x) {
+
Todas as referências ao tipo genérico devem ser escritas usando o identificador especificado (no caso, ''T''). Aproveitando o exemplo da ''Fila'', deve-se observar que o atributo ''buffer'' depende do tipo genérico, pois trata-se de um vetor onde devem ser armazenados os dados enfileirados. Os métodos ''enfileira'', ''desenfileira'' e ''frente'' também dependem do tipo genérico. A declaração da classe template ''Fila'' segue abaixo, porém com alguns detalhes omitidos para ficar mais evidente o uso de template.
  x++;
 
  return x;
 
}
 
  
int main() {
 
  int valor = 10;
 
  int resultado;
 
  
  resultado = incrementa(valor);
+
<syntaxhighlight lang=c>
  cout << "Resultado=" << resultado;
+
template <typename T> class Fila {
  cout << ", e valor=" << valor << endl;
+
  public:
}
+
    Fila(unsigned int N);
</syntaxhighlight> || <syntaxhighlight lang=c>
+
    ~Fila();
#include <iostream>
 
  
using namespace std;
+
    // enfileira um dado do tipo T
 +
    void enfileira(const T & algo);
  
int incrementa(const int & x) {
+
    // desenfileira um dado da frente da fila, e retorna uma
  int y = x;
+
    // cópia desse dado (cujo tipo é T)
 +
    T desenfileira();
  
  y++;
+
    // Retorna uma referência ao dado de tipo T que está na frente da fila
  return y;
+
    T & frente() const;
}
 
  
int main() {
+
    bool vazia() const {return itens == 0; }
   int valor = 10;
+
    bool cheia() const { return itens == capacidade;}
  int resultado;
+
    unsigned int tamanho() const { return itens;}
 +
   private:
 +
    int capacidade;
 +
    int itens, inicio, fim;
 +
    T* buffer; // área de memória para guardar os dados de tipo T enfileirados
 +
};
 +
</syntaxhighlight>
 +
 
 +
 
 +
Quando se declara uma classe template, tanto a declaração da classe (sua interface) quanto a implementação de seus métodos devem estar no mesmo arquivo com extensão ''.h''. Isso se deve a [http://stackoverflow.com/questions/19798325/template-compilation detalhes específicos da compilação do código-fonte]. Assim, a implementação dos métodos pode estar dentro da própria declaração da classe, como no caso dos métodos ''vazia'', ''cheia'' e ''tamanho'', ou fora da classe. Quando forem implementados fora da classe, deve-se repetir o prefixo ''template <typename T>'' na frente do nome de cada método, como mostrado a seguir:
  
  resultado = incrementa(valor);
+
<syntaxhighlight lang=c>
  cout << "Resultado=" << resultado;
+
template <typename T> void Fila<T>::enfileira(const T& algo) {
   cout << ", e valor=" << valor << endl;
+
   // implementação do método
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
|}
 
  
= Alocação dinâmica de memória: operadores new e delete =
 
  
= string =
+
Uma vez declarada uma classe template, ela pode ser utilizada. Somente no momento em que uma classe é usada que o tipo genérico é especificado. Por exemplo, a ''Fila'' acima declarada pode guardar qualquer coisa, o que inclui strings e números inteiros. Para declarar duas filas, de forma que uma armazene inteiros e outra strings, deve-se usar esta sintaxe:
  
* [http://www.cplusplus.com/reference/string/string/ a classe string]
+
<syntaxhighlight lang=c>
 
+
int main() {
 
+
  // Uma fila capaz de guardar até 10 números inteiros
Na linguagem C++ podem-se trabalhar com duas representações de string:
+
  Fila<int> f1(10);
* ''vetor de char (char * ou char [])'': essa forma é a mesma utilizada na linguagem C.
+
 
* ''classe string'': objetos da classe ''string'' implementam strings com diversas facilidades, possibilitando um maior nível de abstração (fica mais fácil).
+
  // uma fila capaz de guardar até 5 strings
 +
  Fila<string> f2(5);
  
 +
  // enfileira-se o número 23 na fila de inteiros
 +
  f1.enfileira(23);
  
A classe ''string'' foi projetada para que se possa trabalhar com strings de forma simplificada. Por exemplo, o programa abaixo mostra como criar, mostrar na tela e concatenar strings:
+
  // enfileira-se a string "uma coisa" na fila de strings
 +
  string algo = "uma coisa";
 +
  f2.enfileira(algo);
  
{| border=1
+
  // outras declarações da função main ...
!Usando classe string
+
}
!Usando vetor de char
+
</syntaxhighlight>
|-
 
|<syntaxhighlight lang=c>
 
#include <string>
 
#include <iostream>
 
  
using namespace std;
 
  
int main(){
+
Do ponto de vista da linguagem, um ''template'' define um padrão de geração de código para o compilador ([http://stackoverflow.com/questions/19798325/template-compilation maiores detalhes]). Quer dizer, um ''template'' em si não é compilável, pois ele representa código-fonte ainda incompleto. No entanto, no momento em que se usa um ''template'', o código-fonte é completado e pode ser compilado. Em outras palavras, o ''template'' da Fila se torna uma Fila de fato. O programa de teste abaixo aproveita o exemplo das filas, enfileirando e desenfileirando dados de duas filas.
  // declaração dos objetos string:
 
  // inicialmente eles são com strings vazias
 
  string nome;
 
  string sobrenome;
 
  
  // atribui "Manuel" ao objeto string "nome",
+
<syntaxhighlight lang=c>
  // e "Alexievitvh" a "sobrenome"
 
  nome = "Manuel"
 
  sobrenome = "Alexievitch";
 
 
 
  // cria um novo objeto string, e o inicia com o
 
  // resultado da concatenação de nome e sobrenome
 
  string nome_completo = nome + ' ' + sobrenome;
 
 
 
  // mostra o objeto string na tela
 
  cout << "Nome: " << nome_completo << endl;
 
}
 
</syntaxhighlight>||<syntaxhighlight lang=c>
 
#include <string.h>
 
 
#include <iostream>
 
#include <iostream>
 
+
#include "fila.h"
 +
 
using namespace std;
 
using namespace std;
 +
 +
int main() {
 +
  // Uma fila de strings
 +
  Fila<string> fila1(10);
 +
 +
  // Uma fila de inteiros
 +
  Fila<int> fila2(10);
 +
 +
  // Armazena três strings na fila de strings
 +
  for (int x=0; x < 3; x++) {
 +
    string palavra;
 +
 +
    cout << "Digite uma palavra: ";
 +
    cin >> palavra;
 +
    fila1.enfileira(palavra);
 +
  }
 +
 +
  // Armazena alguns números na fila de inteiros
 +
  for(int x=0; x < 8; x += 1) lista2.enfileira(x);
 +
 +
  // Mostra os dados da fila de strings
 +
  while (not fila1.vazia()) {
 +
    cout << "desenfileirou: " << fila1.desenfileira() << endl;
 +
  }
 +
 +
  // Mostra os dados da fila de inteiros
 +
  while (not fila2.vazia()) {
 +
    cout << "desenfileirou: " << fila2.desenfileira() << endl;
 +
  }
 +
 +
}
 +
</syntaxhighlight>
  
int main(){
+
= Passagem de parâmetros =
  char nome[16];
 
  char sobrenome[32];
 
 
 
  strncpy(nome, "Manuel", 16);
 
  strncpy(sobrenome, "Alexievitch", 32);
 
 
 
  char nome_completo[64];
 
  strcpy(nome_completo, nome);
 
  nome_completo[strlen(nome_completo)] = ' ';
 
  nome_completo[strlen(nome_completo)] = 0;
 
  strcat(nome_completo, sobrenome);
 
 
 
  cout << "Nome: " << nome_completo << endl;
 
}
 
</syntaxhighlight>
 
|}
 
 
 
  
A classe string oferece [http://www.cplusplus.com/reference/string/string/ diversas operações], tais como:
+
* [https://pt.stackoverflow.com/questions/59437/qual-a-diferen%C3%A7a-entre-passagem-por-valor-e-passagem-por-refer%C3%AAncia Uma boa explicação no forum Stack Overflow]
* [http://www.cplusplus.com/reference/string/string/size/ size]: Obter o comprimento da string
 
* [http://www.cplusplus.com/reference/string/string/operator%5B%5D/ operator[]]: Obter um caractere em uma determinada posição. Esse tipo de acesso é sintaticamente idêntico ao acesso a uma posição de um vetor
 
* [http://www.cplusplus.com/reference/string/string/operator+=/ operator+=]: anexa uma string
 
* [http://www.cplusplus.com/reference/string/string/push_back/ push_back]: anexa um caractere
 
* [http://www.cplusplus.com/reference/string/string/erase/ erase]: remove caracteres de uma string
 
* [http://www.cplusplus.com/reference/string/string/replace/ replace]: substitui parte de uma string
 
* [http://www.cplusplus.com/reference/string/string/c_str/ c_str]: retorna uma representação em vetor de char
 
* [http://www.cplusplus.com/reference/string/string/find/ find]: encontra a primeira ocorrência de uma substring ou um caractere
 
* [http://www.cplusplus.com/reference/string/string/rfind/ rfind]: encontra a última ocorrência de uma substring ou um caractere
 
* [http://www.cplusplus.com/reference/string/string/substr/ substr]: gera uma substring
 
  
== Lendo ou escrevendo strings em streams ==
+
A passagem de parâmetros em C++ pode ser feita de duas maneiras:
 +
# '''[http://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Statements/Functions#by_value Por valor]''': o parâmetro recebe uma cópia do valor que foi passado. Se o parâmetro for alterado dentro da função, o valor original não é modificado.
 +
# '''[http://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Statements/Functions#by_reference Por referência]''': o parâmetro é uma referência ao valor que foi passado. Se o parâmetro for alterado, o valor original também será modificado.
  
Podem-se ler ou escrever strings em streams usando os operadores ''<<'' ou ''>>''. Por exemplo, para ler uma string da entrada padrão (ex: teclado), pode-se fazer assim:
+
'''OBS:''' o caso de um parâmetro passado por ponteiro funciona da mesma forma que em C. No caso, se o o valor apontado pelo ponteiro for alterado dentro da função, o valor original será modificado. Isso funciona de forma parecida com a passagem de parâmetro por referência, apesar de haver uma diferença sutil.
  
<syntaxhighlight lang=c>
+
Os exemplos a seguir ilustram ambas formas de passagem de parâmetros:
string algo;
+
{|border=1
 +
!Passagem por valor
 +
!Passagem por referência
 +
|-
 +
|<syntaxhighlight lang=c>
 +
#include <iostream>
  
// lê uma string da entrada padrão e grava-a em "algo"
+
using namespace std;
cin >> algo;
 
</syntaxhighlight>
 
  
 +
int incrementa(int x) {
 +
  x++;
 +
  return x;
 +
}
  
Para escrever uma string em um stream (ex: um arquivo), pode-se faz desta forma:
+
int main() {
 +
  int valor = 10;
 +
  int resultado;
  
<syntaxhighlight lang=c>
+
  resultado = incrementa(valor);
// abre um arquivo para escrita
+
  cout << "Resultado=" << resultado;
ofstream arq("teste.txt");
+
  cout << ", e valor=" << valor << endl;
 +
}
 +
</syntaxhighlight> || <syntaxhighlight lang=c>
 +
#include <iostream>
  
string algo = "Um teste";
+
using namespace std;
  
// escreve a string no arquivo
+
// passagem por referência: observe o operador &
arq << algo;
+
// antes do nome do parâmetro
</syntaxhighlight>
+
int incrementa(int & x) {
 +
  x++;
 +
  return x;
 +
}
  
 +
int main() {
 +
  int valor = 10;
 +
  int resultado;
  
No caso da leitura de uma string que contenha espaços, ou mesmo de uma linha completa, deve-se usar a função [http://www.cplusplus.com/reference/string/string/getline/ getline]:
+
  resultado = incrementa(valor);
 +
  cout << "Resultado=" << resultado;
 +
  cout << ", e valor=" << valor << endl;
 +
}
 +
</syntaxhighlight>
 +
|}<br><br>
  
<syntaxhighlight lang=c>
+
É possível também a passagem de parâmetros por referência, porém [http://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Statements/Functions#Constant_Parameters somente-leitura]. A utilidade disso é aproveitar a eficiência da passagem de parâmetro por referência, que evita uma cópia de valor, porém evitando que o valor original seja modificado dentro da função. Exemplo (experimente compilar estes programas):
string linha;
+
{|border=1
 +
!Programa 1
 +
!Programa 2
 +
|-
 +
|<syntaxhighlight lang=c>
 +
#include <iostream>
  
cout << "Escreva uma frase: ";
+
using namespace std;
  
getline(cin, linha);
+
int incrementa(const int & x) {
 +
  x++;
 +
  return x;
 +
}
  
cout << "Frase digitada é: " << linha;
+
int main() {
</syntaxhighlight>
+
  int valor = 10;
 +
  int resultado;
  
A função getline usa o caractere ''newline'' (''\n'') como delimitador. Se for desejado usar outro caractere como delimitador, deve-se fazer o seguinte:
+
  resultado = incrementa(valor);
 +
  cout << "Resultado=" << resultado;
 +
  cout << ", e valor=" << valor << endl;
 +
}
 +
</syntaxhighlight> || <syntaxhighlight lang=c>
 +
#include <iostream>
  
<syntaxhighlight lang=c>
+
using namespace std;
string linha;
 
char delimitador=',';
 
  
cout << "Escreva uma frase que contenha ao menos uma vírgula: ";
+
int incrementa(const int & x) {
 +
  int y = x;
 +
 
 +
  y++;
 +
  return y;
 +
}
  
getline(cin, linha, delimitador);
+
int main() {
 +
  int valor = 10;
 +
  int resultado;
  
cout << "Frase digitada até a vírgula é: " << linha;
+
  resultado = incrementa(valor);
 +
  cout << "Resultado=" << resultado;
 +
  cout << ", e valor=" << valor << endl;
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|}
 +
 +
= Alocação dinâmica de memória: operadores new e delete =
  
== Conversão de string para tipos numéricos ==
+
A alocação dinâmica de memória é uma técnica para reservar e liberar memória por demanda, e sob controle do programador. Ela funciona como se o programador criasse variáveis, porém tivesse que explicitamente destrui-las. Assim, as variáveis não deixariam de existir automaticamente, quando a execução do programa saísse do escopo em que foram criadas.
  
Em C++ há diferentes formas de converter strings para dados numéricos, e vice-versa. Podem-se, por exemplo, usar as funções da biblioteca C padrão ([http://manpages.ubuntu.com/manpages/precise/man3/scanf.3.html sscanf], [http://manpages.ubuntu.com/manpages/precise/man3/snprintf.3.html snprintf], [http://manpages.ubuntu.com/manpages/precise/man3/strtod.3.html strtod], ...). Mas uma forma particular existente em C++ explora o conceito de ''streams''.  
+
A alocação dinâmica está diretamente relacionada com variáveis do tipo ponteiro. Ao se alocar memória dinamicamente, obtém-se o endereço inicial da área de memória obtida, e esse endereço deve ser armazenado em um ponteiro. O uso dessa área de memória, portanto, se faz por meio desse ponteiro.
  
Arquivos abertos são acessados como ''streams'' em C++. Na verdade, essa é uma interface corriqueira para acesso a arquivos, estando disponível em diferentes linguagens (ex: as operações de arquivo em C, como [http://manpages.ubuntu.com/manpages/precise/man3/fscanf.3.html fscanf], [http://manpages.ubuntu.com/manpages/precise/man3/fprintf.3.html fprintf], [http://manpages.ubuntu.com/manpages/precise/man3/fread.3.html fread, fwrite], [http://manpages.ubuntu.com/manpages/precise/man3/fgets.3.html fgets], ...). Em C++ ''streams'' possibilitam ler texto de um arquivo e convertê-lo para um dado numérico, e vice-versa. Isso é feito normalmente em programas, como no exemplo abaixo:
+
A alocação dinâmica de memória na linguagem C++ se faz com o [http://www.cplusplus.com/reference/new/operator%20new/ operador new]. Esse operador aloca memória de acordo com o tipo de dados informado, e retorna um ponteiro para essa área de memória. Quando essa área de memória não for mais necessária, ela deve ser liberada com o [http://www.cplusplus.com/reference/new/operator%20delete/ operador delete]. Veja o exemplo a seguir, que mostra o uso dos operadores ''new'' e ''delete'' em uma situação muito simplificada.
  
<syntaxhighlight lang=c>
+
<syntaxhighlight lang="c++">
 
#include <iostream>
 
#include <iostream>
 +
#include <string>
  
 
using namespace std;
 
using namespace std;
 +
 +
template <typename T> void mostra(const string & varname, T * val) {
 +
  cout << "######################################################" << endl;
 +
  cout << "Dado apontado por " << varname << " = " << *val << endl;
 +
  cout << "Endereço da área de memória usada por " << varname << ": " << (void*)val << endl;
 +
  cout << "Memória alocada: " << sizeof(*val) << " bytes" << endl;
 +
}
  
 
int main() {
 
int main() {
   ifstream arq("numeros.txt");
+
   // algumas variáveis
   int numero;
+
  // a memória para elas é alocada automaticamente
   string palavra;
+
   int * x;
 +
   double  * h;
  
   arq >> numero;
+
   // aloca dinamicamente memória para um valor do tipo int
   arq >> palavra;
+
  x = new int;
 +
 
 +
  // aloca dinamicamente memória para um valor do tipo double
 +
   h = new double;
  
   cout << "numero=" << numero << endl;
+
   // armazena valores nas áreas de memória alocadas, usando-se
   cout << "palavra=" << palavra << endl;
+
  // os ponteiros
 +
  *x = 35;
 +
   *h = 6.6260715e-34;
  
   arq.close();
+
   mostra("x", x);
 +
  mostra("h", h);  
  
   return 0;
+
   // libera as áreas de memória
 +
  delete x;
 +
  delete h;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
Ao executá-lo, obtém-se isto:
  
Se o arquivo ''numeros.txt'' possuir este conteúdo:
+
<syntaxhighlight>
 
+
######################################################
<syntaxhighlight lang=text>
+
Dado apontado por x = 35
2015 Furious
+
Endereço da área de memória usada por x: 0x561931d5de70
1999 Matrix
+
Memória alocada: 4 bytes
 +
######################################################
 +
Dado apontado por h = 6.62607e-34
 +
Endereço da área de memória usada por h: 0x561931d5de90
 +
Memória alocada: 8 bytes
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
== Inicialização da área de memória alocada ==
  
... a execução do programa exemplo, que lê apenas a primeira linha, resultará em:
+
Uma forma alternativa (e importante, como se poderá observar mais pra frente) de usar o operador ''new'' possibilita inicializar a área de memória assim que for alocada. O interessante dessa inicialização é que ela depende do tipo de dados para que foi alocada a memória. Para tipos de dados primitivos, como int, float, double e char, essa inicialização se limita a copiar um valor inicial. Veja como ficaria a alocação de memória do exemplo anterior com esse uso do operador ''new'':
 
 
<syntaxhighlight lang=text>
 
numero=2015
 
palavra=Furious
 
</syntaxhighlight>
 
  
 +
<syntaxhighlight lang="c++">
 +
  // aloca dinamicamente memória para um valor do tipo int e depois para um double
 +
  // e armazena valores nas áreas de memória alocadas
 +
  x = new int(35);
  
Assim, o operador ''>>'' de ''streams'' possibilita converter texto para um outro tipo de dado, dependendo do tipo da variável onde o resultado deva ser gravado. No exemplo, esse operador é usado duas vezes:
+
  // aloca dinamicamente memória para um valor do tipo double
 +
  h = new double(6.6260715e-34);
  
<syntaxhighlight lang=c>
+
  mostra("x", x);
arq >> numero; // converte texto para inteiro, pois "numero"  
+
  mostra("h", h);  
              // é uma variável do tipo int
 
arq >> palavra;// converte texto para string, pois palavra é do tipo string
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
No caso de um tipos de dados que possui um [[Introdu%C3%A7%C3%A3o_C%2B%2B#Invoca.C3.A7.C3.A3o_do_construtor|construtor]], que é uma função-membro executada automaticamente quando se cria um valor desse tipo, o operador ''new'' o executa logo após alocar memória.
  
Parece que C++ possibilita converter facilmente texto (string) para dado numérico SE o texto estiver guardado em um arquivo. Isso não é conveniente para converter um valor que esteja guardado em uma variável string, pois tornaria necessário escrevê-la em um arquivo temporário para depois ler desse arquivo e efetuar a conversão. De fato, se fosse assim dificilmente ''streams'' seriam usadas para converter dados em C++. No entanto existe um tipo de ''stream'' que possibilita que se acesse uma string como se fosse um arquivo. Quer dizer, a leitura e a escrita dos caracteres de uma string podem ser feitas com operações de arquivo. O exemplo abaixo mostra o uso dessa [http://www.cplusplus.com/reference/sstream/istringstream/ ''stream de string'' (ou ''stringstream'')]:
+
<syntaxhighlight lang="c++">
 +
#include <iostream>
 +
#include <string>
  
<syntaxhighlight lang=c>
+
using namespace std;
#include <iostream>
+
 
// as classes stringstream estão em sstream
+
struct Nome {
#include <sstream>
+
  string nome, sobrenome;
  
using namespace std;
+
  // construtor: separa um nome completo,
 +
  // armazenando nome e sobrenome nos respectivos atributos
 +
  Nome(const string & nome_completo) {
 +
    int pos = nome_completo.find(' ');
 +
    nome = nome_completo.substr(0, pos);
 +
    int pos2 = nome_completo.find_first_not_of(" ", pos);
 +
    sobrenome = nome_completo.substr(pos2);
 +
  }
 +
};
  
 
int main() {
 
int main() {
   istringstream stream("2015 Furious");
+
   auto alguem = new Nome("Dona Bilica da Silva");
  int numero;
 
  string palavra;
 
  
  stream >> numero;
+
   cout << "Nome: " << alguem->nome << endl;
  stream >> palavra;
+
   cout << "Sobrenome: " << alguem->sobrenome << endl;
 
 
   cout << "numero=" << numero << endl;
 
   cout << "palavra=" << palavra << endl;
 
  
   return 0;
+
   delete alguem;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
== Alocação de array (vetor) ==
 +
 +
O exemplo a seguir mostra a alocação de memória para um vetor (''array''), em que o operador ''new'' aloca memória com capacidade de armazenar múltiplos valores de um certo tipo. Além disso, o exemplo mostra a persistência da área alocada de memória fora do escopo em que ela foi criada:
  
O exemplo mostra uma ''stringstream'' criada com conteúdo ''2015 Furious''. Em seguida, usa-se o operador ''>>'' para ler um número inteiro e uma string. O resultado desse exemplo é idêntico ao do exemplo anterior, em que se liam esses dados de um arquivo.
+
<syntaxhighlight lang="c++">
 +
#include <iostream>
 +
#include <string>
  
A possibilidade de usar ''stringstream'' para converter dados torna simples criar funções de conversão, como esta que converte string para inteiro:
+
using namespace std;
  
<syntaxhighlight lang=c>
+
// cria um vetor de inteiros com tamanho "size" e preenche
int converte_para_int(const string & dado) {
+
// todas suas posições com "valor_inicial"
   istringstream stream(dado);
+
int * cria_vetor(int size, int valor_inicial) {
  int r;
+
   if (size <= 0) return nullptr;
  
   stream >> r;
+
   // cria um vetor com o tamanho dado por "size"
   if (stream.fail()) throw 1; // erro de conversão
+
   auto v = new int[size];
  
   return r;
+
  // preenche o vetor com o valor inicial
 +
  for (int i=0; i < size; i++) v[i] = valor_inicial;
 +
   return v;
 
}
 
}
</syntaxhighlight>
 
  
 +
int main() {
 +
  int size;
  
Outras funções para tipos numéricos podem ser criadas da mesma forma:
+
  cout << "Qual o tamanho do vetor: ";
 +
  cin >> size;
  
<syntaxhighlight lang=c>
+
  // v é um ponteiro para int
float converte_para_float(const string & dado) {
+
   auto v = cria_vetor(size, 99);
   istringstream stream(dado);
 
  float r;
 
  
   stream >> r;
+
   cout << "Endereço do vetor: " << (void*)v << endl;
   if (stream.fail()) throw 1; // erro de conversão
+
  cout << "Conteúdo do vetor: ";
 +
   for (int x=0; x < size; x++) cout << v[x] << ',';
 +
  cout << endl;
  
   return r;
+
   // libera a memória do vetor
 +
  delete[] v;
 
}
 
}
 +
</syntaxhighlight>
  
 +
Um exemplo de execução é mostrado a seguir:
 +
<syntaxhighlight>
 +
Qual o tamanho do vetor: 5
 +
Endereço do vetor: 0x561a4d6a4690
 +
Conteúdo do vetor: 99,99,99,99,99,
 +
</syntaxhighlight>
  
double converte_para_double(const string & dado) {
 
  istringstream stream(dado);
 
  double r;
 
  
  stream >> r;
+
O uso do operador ''new'' nesse exemplo foi um pouco diferente. Como se pode notar, para criar um vetor indicou-se sua capacidade entre colchetes, logo após o tipo de dados. Isso pede que se aloque memória suficiente para conter aquela quantidade de valores do tipo informado:
  if (stream.fail()) throw 1; // erro de conversão
 
  
  return r;
+
<syntaxhighlight lang="c++">
}
+
// aloca memória suficiente oara guardar "size" valores do tipo int
 +
auto v = new int[size];
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
Além disso, a função ''cria_vetor'' retorna como resultado o vetor que foi alocado. Isso mostra que a área de memória reservada para o vetor sobrevive ao final da função ''cria_vetor''. Essa área de memória continuará alocada até que o [http://www.cplusplus.com/reference/new/operator%20delete/ operador delete] a libere (o que se faz ao final da função ''main'') ... ou até que o programa termine.
  
Note que são todas muito parecidas. A única diferença entre elas é o tipo do dado numérico da conversão. Assim, é natural substitui-las por uma função ''template'':
+
== Resumo ==
 +
* O operador ''new'' aloca uma área de memória com capacidade suficiente para um tipo de dados indicado, e retorna um ponteiro para essa área de memória
 +
* Após alocar a memória, o operador ''new'' a inicializa ao executar o construtor para o tipo de dados
 +
* Toda área de memória alocada deve ser liberada usando o operador ''delete'', quando não for mais necessária
  
<syntaxhighlight lang=c>
+
= Funções template =
template <class T> T converte(const string & dado) {
 
  istringstream stream(dado);
 
  T r;
 
  
  stream >> r;
+
* [http://www.cplusplus.com/doc/oldtutorial/templates/ Templates em maiores detalhes]
  if (stream.fail()) throw 1; // erro de conversão
 
  
  return r;
 
}
 
</syntaxhighlight>
 
  
 +
Funções, assim como classes, podem ser template, em que um ou mais tipos de dados são desconhecidos em tempo de programação. Em uma função template, os tipos de dados de um ou mais parâmetros, ou do valor devolvido como resultado, são desconhecidos em tempo de projeto. Outra forma de ver funções template é perceber que o algoritmo contido na função funciona com (quase) qualquer tipo de dados.
  
Essa função ''template'' seria usada da seguinte forma:
+
Por exemplo, imagine que uma função deva ser capaz de ordenar um vetor qualquer, independente do tipo de dados desse vetor. Tal função poderia ser declarada com um template como este:
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
#include <iostream>
+
// Função template "ordena": ordena um "vetor" de comprimento "N"
#include <sstream>
+
template <typename T> void ordena(T vetor[], int N);
 +
</syntaxhighlight>
  
using namespace std;
+
Sua implementação poderia ser esta:
  
template <class T> T converte(const string & dado) {
+
<syntaxhighlight lang=c>
   istringstream stream(dado);
+
template <typename T> void ordena(T vetor[], int N) {
  T r;
+
   int i, j;
  
   stream >> r;
+
   for (i=0; i < N-1; i++) {
  if (stream.fail()) throw 1; // erro de conversão
+
    for (j=i+1; j < N; j++) {
 +
      if (vetor[i] > vetor[j]) {
 +
        T aux = vetor[i];
 +
        vetor[i] = vetor[j];
 +
        vetor[j] = aux;
 +
      }
 +
    }
 +
  }   
 +
}
 +
</syntaxhighlight>
  
  return r;
+
A utilização de uma função template implica informar o tipo de dados desconhecido. Isso se faz ao se usar a função:
}
 
  
 +
<syntaxhighlight lang=c>
 
int main() {
 
int main() {
   string algo1 = "2015";
+
   long v1[5] = {45, 72, 12, 8, 0};
   string algo2 = "3.1416";
+
   string v2[7] = {"banana", "abacate", "sapoti", "maracuja", "abacaxi", "caju", "goiaba"};
  string algo3 = "xyz";
 
  int ano;
 
  float pi;
 
  
   ano = converte<int>(algo1);
+
   // aqui se usa a função ordena, e se informa que o tipo genérico é long
   pi = converte<float>(algo2);
+
   ordena<long>(v1, 5);
  
   cout << "ano=" << ano << endl;
+
   // ... e aqui o tipo genérico é string
  cout << "pi=" << pi << endl;
+
  ordena<string>(v2, 7);
 +
}
 +
</syntaxhighlight>
  
  try {
+
= string =
    ano = converte<int>(algo3);
 
    cout << "Outro ano=" << ano << endl;
 
  } catch (int e) {
 
    cout << "Erro de conversão: " << algo3 << " não é um int" << endl;
 
  }
 
  return 0;
 
}
 
</syntaxhighlight>
 
  
= ''streams'' e arquivos =
+
* [http://www.cplusplus.com/reference/string/string/ a classe string]
  
Em diversas linguagens de programação o conceito de ''stream'' (''correnteza'') é utilizado para o acesso a arquivos em disco. Por ''stream'' entende-se um mecanismo e leitura e escrita em que dados fluem sucessivamente. Dessa forma, ao se escrever em um arquivo fazem-se sucessivas operações de escrita, e os dados são armazenados na ordem em que essas operações se realizaram. Algo parecido acontece na leitura de um arquivo, em que as operações de leitura realizadas fornecem dados de acordo com a ordem em que estão gravados no arquivo.
 
  
 +
Na linguagem C++ podem-se trabalhar com duas representações de string:
 +
* ''vetor de char (char * ou char [])'': essa forma é a mesma utilizada na linguagem C.
 +
* ''classe string'': objetos da classe ''string'' implementam strings com diversas facilidades, possibilitando um maior nível de abstração (fica mais fácil).
  
O acesso a arquivos apresenta algumas diferenças nas linguagens C e C++. Em C++ existem algumas classes para a manipulação de arquivos. Desta forma, arquivos nesta linguagem são acessados por meio de objetos dessas classes. No exemplo abaixo, apresentam-se dois programas em C e C++ para ler as linhas de um arquivo e mostrá-las na tela.
 
  
 +
A classe ''string'' foi projetada para que se possa trabalhar com strings de forma simplificada. Por exemplo, o programa abaixo mostra como criar, mostrar na tela e concatenar strings:
  
{| border="1"
+
{| border=1
!Linguagem C
+
!Usando classe string
!Linguagem C++
+
!Usando vetor de char
 
|-
 
|-
 
|<syntaxhighlight lang=c>
 
|<syntaxhighlight lang=c>
#include <stdio.h>
+
#include <string>
#include <errno.h>
+
#include <iostream>
  
#define MAX_SIZE 10240
+
using namespace std;
  
int main() {
+
int main(){
   FILE * arq;
+
   // declaração dos objetos string:
   char linha[MAX_SIZE];
+
  // inicialmente eles são com strings vazias
 +
  string nome;
 +
   string sobrenome;
  
   // abre o arquivo
+
   // atribui "Manuel" ao objeto string "nome",
   arq = fopen("/etc/hosts", "r");
+
   // e "Alexievitvh" a "sobrenome"
 +
  nome = "Manuel"
 +
  sobrenome = "Alexievitch";
  
   // testa se conseguiu abrir o arquivo
+
   // cria um novo objeto string, e o inicia com o  
   if (arq == NULL) {
+
   // resultado da concatenação de nome e sobrenome
    perror("Ao abrir /etc/hosts");
+
  string nome_completo = nome + ' ' + sobrenome;
    return errno;
 
  }
 
  
   // lê cada linha do arquivo, mostrando-a na tela
+
   // mostra o objeto string na tela
   while (fgets(linha, MAX_SIZE, arq)) {
+
   cout << "Nome: " << nome_completo << endl;
    printf("%s", linha);
 
  }
 
 
 
  // fecha o arquivo
 
  fclose(arq);
 
 
}
 
}
</syntaxhighlight> || <syntaxhighlight lang=c>
+
</syntaxhighlight>||<syntaxhighlight lang=c>
 +
#include <string.h>
 
#include <iostream>
 
#include <iostream>
#include <fstream>
 
#include <stdio.h>
 
#include <errno.h>
 
 
#define MAX_SIZE 10240
 
  
 
using namespace std;
 
using namespace std;
 
int main() {
 
  // cria o objeto "arq" da classe "ifstream", que representa um arquivo para leitura
 
  // a criação desse objeto já abre o arquivo
 
  ifstream arq("/etc/hosts");
 
 
  // verifica se o arquivo foi de fato aberto
 
  if (not arq.is_open()) {
 
    perror("Ao abrir /etc/hosts");
 
    return errno;
 
  }
 
  
   // lê cada linha do arquivo, apresentando-a na tela
+
int main(){
   while (not arq.eof()) {
+
  char nome[16];
    char linha[MAX_SIZE];
+
  char sobrenome[32];
+
 
    arq.getline(linha, MAX_SIZE); // equivalente ao "fgets"
+
   strncpy(nome, "Manuel", 16);
    cout << linha << endl;
+
   strncpy(sobrenome, "Alexievitch", 32);
  }
+
 
  // observe que o arquivo não precisa ser fechado ... isso ocorre automaticamente
+
  char nome_completo[64];
  // quando o objeto for destruído, ficando a cargo de seu destrutor
+
  strcpy(nome_completo, nome);
 +
  nome_completo[strlen(nome_completo)] = ' ';
 +
  nome_completo[strlen(nome_completo)] = 0;
 +
  strcat(nome_completo, sobrenome);
 +
 
 +
  cout << "Nome: " << nome_completo << endl;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Linha 1 210: Linha 1 231:
  
  
Os exemplos acima demonstram que a sintaxe no acesso a arquivos pode ser diferente, porém funcionalmente parece haver uma equivalência entre as duas formas de acesso. De fato tudo que se consegue fazer em C++ é possível fazer em C, mudando apenas a praticidade ou conveniência na forma com que se expressa isso nessas linguagens. Porém como a linguagem C já é bem conhecida por vocês, interessa ver o que se pode fazer em C++.
+
A classe string oferece [http://www.cplusplus.com/reference/string/string/ diversas operações], tais como:
 
+
* [http://www.cplusplus.com/reference/string/string/size/ size]: Obter o comprimento da string
Em C++ existem três classes para acessar arquivos:
+
* [http://www.cplusplus.com/reference/string/string/operator%5B%5D/ operator[]]: Obter um caractere em uma determinada posição. Esse tipo de acesso é sintaticamente idêntico ao acesso a uma posição de um vetor
* [http://www.cplusplus.com/reference/fstream/ofstream/ ofstream]: acesso a arquivos em modo escrita. {{collapse top|Exemplo}}
+
* [http://www.cplusplus.com/reference/string/string/operator+=/ operator+=]: anexa uma string
<syntaxhighlight lang=c>
+
* [http://www.cplusplus.com/reference/string/string/push_back/ push_back]: anexa um caractere
#include <iostream>
+
* [http://www.cplusplus.com/reference/string/string/erase/ erase]: remove caracteres de uma string
#include <fstream>
+
* [http://www.cplusplus.com/reference/string/string/replace/ replace]: substitui parte de uma string
 +
* [http://www.cplusplus.com/reference/string/string/c_str/ c_str]: retorna uma representação em vetor de char
 +
* [http://www.cplusplus.com/reference/string/string/find/ find]: encontra a primeira ocorrência de uma substring ou um caractere
 +
* [http://www.cplusplus.com/reference/string/string/rfind/ rfind]: encontra a última ocorrência de uma substring ou um caractere
 +
* [http://www.cplusplus.com/reference/string/string/substr/ substr]: gera uma substring
 +
 
 +
== Lendo ou escrevendo strings em streams ==
  
using namespace std;
+
Podem-se ler ou escrever strings em streams usando os operadores ''<<'' ou ''>>''. Por exemplo, para ler uma string da entrada padrão (ex: teclado), pode-se fazer assim:
  
int main() {
+
<syntaxhighlight lang=c>
  ofstream arq("demo.txt");
+
string algo;
  
  if (not arq.is_open()) {
+
// lê uma string da entrada padrão e grava-a em "algo"
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
+
cin >> algo;
    return 0;
+
</syntaxhighlight>
  }
 
  
  arq << "Iniciando uma gravação de várias linhas inúteis ..." << endl;
 
  
  for (int i=0; i < 10; i++) {
+
Para escrever uma string em um stream (ex: um arquivo), pode-se faz desta forma:
    arq << "Linha " << i << endl;   
+
 
  }
 
}
 
</syntaxhighlight>
 
{{collapse bottom}}
 
* [http://www.cplusplus.com/reference/fstream/ifstream/ ifstream]: acesso a arquivos em modo leitura. {{collapse top|Exemplo}}
 
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
#include <iostream>
+
// abre um arquivo para escrita
#include <fstream>
+
ofstream arq("teste.txt");
 +
 
 +
string algo = "Um teste";
  
using namespace std;
+
// escreve a string no arquivo
 +
arq << algo;
 +
</syntaxhighlight>
  
int main() {
 
  ifstream arq("/etc/hosts");
 
  
  if (not arq.is_open()) {
+
No caso da leitura de uma string que contenha espaços, ou mesmo de uma linha completa, deve-se usar a função [http://www.cplusplus.com/reference/string/string/getline/ getline]:
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
 
    return 0;
 
  }
 
  
  while (not arq.eof()) {
+
<syntaxhighlight lang=c>
    string algo;
+
string linha;
  
    arq >> algo;
+
cout << "Escreva uma frase: ";
    cout << "Leu isto: " << algo << endl;
+
 
  }
+
getline(cin, linha);
}
+
 
 +
cout << "Frase digitada é: " << linha;
 
</syntaxhighlight>
 
</syntaxhighlight>
{{collapse bottom}}
+
 
* [http://www.cplusplus.com/reference/fstream/ fstream]: acesso a arquivos em modo leitura/escrita. {{collapse top|Exemplo}}
+
A função getline usa o caractere ''newline'' (''\n'') como delimitador. Se for desejado usar outro caractere como delimitador, deve-se fazer o seguinte:
 +
 
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
#include <iostream>
+
string linha;
#include <fstream>
+
char delimitador=',';
  
using namespace std;
+
cout << "Escreva uma frase que contenha ao menos uma vírgula: ";
  
#define MAX_LINE 10240
+
getline(cin, linha, delimitador);
  
int main() {
+
cout << "Frase digitada até a vírgula é: " << linha;
  fstream arq("demo.txt");
+
</syntaxhighlight>
  
  if (not arq.is_open()) {
+
== Conversão de string para tipos numéricos ==
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
 
    return 0;
 
  }
 
  
  for (int i=0; i < 10; i++) {
+
* [[Introdução_C%2B%2B#Leitura_e_escrita_formatada|Leitura e escrita com operadores << e >>]]
    arq << "Linha " << i << endl;   
 
  }
 
  arq.flush();
 
  
  arq.seekg(0);
 
 
 
  while (not arq.eof()) {
 
  char linha[MAX_LINE];
 
  
  arq.getline(linha, MAX_LINE);
+
Em C++ há diferentes formas de converter strings para dados numéricos, e vice-versa. Podem-se, por exemplo, usar as funções da biblioteca C padrão ([http://manpages.ubuntu.com/manpages/precise/man3/scanf.3.html sscanf], [http://manpages.ubuntu.com/manpages/precise/man3/snprintf.3.html snprintf], [http://manpages.ubuntu.com/manpages/precise/man3/strtod.3.html strtod], ...). Mas uma forma particular existente em C++ explora o conceito de ''streams''.
  cout << "Leu: " << linha << endl;
 
  }
 
}
 
</syntaxhighlight>
 
{{collapse bottom}}
 
  
 +
Arquivos abertos são acessados como ''streams'' em C++. Na verdade, essa é uma interface corriqueira para acesso a arquivos, estando disponível em diferentes linguagens (ex: as operações de arquivo em C, como [http://manpages.ubuntu.com/manpages/precise/man3/fscanf.3.html fscanf], [http://manpages.ubuntu.com/manpages/precise/man3/fprintf.3.html fprintf], [http://manpages.ubuntu.com/manpages/precise/man3/fread.3.html fread, fwrite], [http://manpages.ubuntu.com/manpages/precise/man3/fgets.3.html fgets], ...). Em C++ ''streams'' possibilitam ler texto de um arquivo e convertê-lo para um dado numérico, e vice-versa. Isso é feito normalmente em programas, como no exemplo abaixo:
  
O interessante em C++ é que a saída padrão e a entrada padrão são representados por objetos ''stream''. De fato, veja como estão declarados esses objetos na biblioteca C++ padrão:
+
<syntaxhighlight lang=c>
* [http://www.cplusplus.com/reference/iostream/cout/ cout]: saída padrão
+
#include <iostream>
* [http://www.cplusplus.com/reference/iostream/cin/ cin]: entrada padrão
 
* [http://www.cplusplus.com/reference/iostream/cerr/ cerr]: saída de erros padrão
 
  
== Operações de streams ==
+
using namespace std;
  
''streams'' oferecem várias operações para leitura e escrita de dados. Algumas mais comuns são listadas a seguir, junto com exemplos.
+
int main() {
 +
  ifstream arq("numeros.txt");
 +
  int numero;
 +
  string palavra;
  
{| border=1
+
  arq >> numero;
!Operação
+
  arq >> palavra;
!Entrada
 
!Saída
 
!Para que serve
 
!Exemplo
 
|-
 
| [http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/ >>] || x|| ||leitura formatada || <syntaxhighlight lang=c>int x;
 
double y;
 
string palavra;
 
  
// lê um número inteiro
+
  cout << "numero=" << numero << endl;
cin >> x;
+
  cout << "palavra=" << palavra << endl;
  
// lê um número double
+
  arq.close();
cin >> y;
 
  
// lê uma string
+
  return 0;
cin >> palavra;
+
}
 
</syntaxhighlight>
 
</syntaxhighlight>
|-
 
|[http://www.cplusplus.com/reference/ostream/ostream/operator%3C%3C/ <<] || ||x ||escrita formatada ||<syntaxhighlight lang=c>int x = 5;
 
double y=3.1416;
 
string palavra="alguma coisa";
 
  
// escreve um número inteiro seguido de uma vírgula
 
cout << x << ", ";
 
  
// escreve um número double seguido de uma vírgula
+
Se o arquivo ''numeros.txt'' possuir este conteúdo:
cout << y << ", ";
+
 
 +
<syntaxhighlight lang=text>
 +
2015 Furious
 +
1999 Matrix
 +
</syntaxhighlight>
 +
 
  
// escreve uma string seguida de nova linha
+
... a execução do programa exemplo, que lê apenas a primeira linha, resultará em:
cout << palavra << endl;
 
  
// outra forma de escrever os três valores
+
<syntaxhighlight lang=text>
cout << x << ", " << y << ", " << palavra << endl;
+
numero=2015
 +
palavra=Furious
 
</syntaxhighlight>
 
</syntaxhighlight>
|-
 
|[http://www.cplusplus.com/reference/string/string/getline/ getline] || x|| ||lê uma linha (até encontrar '\n') || <syntaxhighlight lang=c>string linha;
 
  
getline(cin, linha);
 
  
cout << "Linha: " << linha << endl;
+
Assim, o operador ''>>'' de ''streams'' possibilita converter texto para um outro tipo de dado, dependendo do tipo da variável onde o resultado deva ser gravado. No exemplo, esse operador é usado duas vezes:
</syntaxhighlight>
+
 
|-
+
<syntaxhighlight lang=c>
|[http://www.cplusplus.com/reference/istream/istream/ignore/ ignore] || x|| ||lê e descarta caracteres ||<syntaxhighlight lang=c>
+
arq >> numero; // converte texto para inteiro, pois "numero"  
cout << "Tecle ENTER para continuar";
+
              // é uma variável do tipo int
cin.ignore(256, '\n'); // lê e descarta até 256 caracteres, ou até ler '\n'
+
arq >> palavra;// converte texto para string, pois palavra é do tipo string
 
</syntaxhighlight>
 
</syntaxhighlight>
|-
 
| [http://www.cplusplus.com/reference/ios/ios/eof/ eof] || x||x ||Verifica se chegou ao fim da stream|| <syntaxhighlight lang=c>
 
ifstream arq("/etc/hosts");
 
  
while (not arq.eof()) {
 
  string linha;
 
  
  getline(arq, linha);
+
Parece que C++ possibilita converter facilmente texto (string) para dado numérico SE o texto estiver guardado em um arquivo. Isso não é conveniente para converter um valor que esteja guardado em uma variável string, pois tornaria necessário escrevê-la em um arquivo temporário para depois ler desse arquivo e efetuar a conversão. De fato, se fosse assim dificilmente ''streams'' seriam usadas para converter dados em C++. No entanto existe um tipo de ''stream'' que possibilita que se acesse uma string como se fosse um arquivo. Quer dizer, a leitura e a escrita dos caracteres de uma string podem ser feitas com operações de arquivo. O exemplo abaixo mostra o uso dessa [http://www.cplusplus.com/reference/sstream/istringstream/ ''stream de string'' (ou ''stringstream'')]:
  cout << linha << endl;
+
 
}
+
<syntaxhighlight lang=c>
</syntaxhighlight>
+
#include <iostream>
|-
+
// as classes stringstream estão em sstream
|[http://www.cplusplus.com/reference/fstream/ifstream/is_open/ is_open] || x||x||Verifica se arquivo foi aberto|| <syntaxhighlight lang=c>
+
#include <sstream>
ifstream arq("/etc/hosts");
 
  
if (not arq.is_open()) {
+
using namespace std;
  cout << "Erro: não abriu o arquivo" << endl;
 
  return 0;
 
}
 
</syntaxhighlight>
 
|}
 
  
== Entrada e saída padrão ==
+
int main() {
 +
  istringstream stream("2015 Furious");
 +
  int numero;
 +
  string palavra;
  
== Acessando strings como se fossem arquivos ==
+
  stream >> numero;
 +
  stream >> palavra;
  
Em C++ esse conceito de ''stream'' é levado um pouco além. Além de arquivos e a entrada e saída padrão, é possível também manipular ''strings'' como se fossem ''streams''. Quer dizer, pode-se escrever em uma ''string'' (criá-la) ou extrair partes de uma ''string'' como se estivesse acessando um arquivo. Isso possibilita que funções ou objetos que escrevem ou lêem em arquivos possam fazer o mesmo em memória, o que pode ser útil em diferentes programas.
+
  cout << "numero=" << numero << endl;
 +
  cout << "palavra=" << palavra << endl;
  
O uso de ''strings'' como se fossem ''streams'' é implementado por três classes:
+
  return 0;
* [http://www.cplusplus.com/reference/sstream/ostringstream/ ostringstream]: possibilita escrever em uma ''string''
+
}
* [http://www.cplusplus.com/reference/sstream/istringstream/ stringstream]: possibilita ler de uma ''string''
+
</syntaxhighlight>
* [http://www.cplusplus.com/reference/sstream/stringstream/ stringstream]: possibilita ler e escrever em uma ''string''
 
  
  
O exemplo mais simples envolve criar uma string a partir dos valores de algumas variáveis. Veja como isso era feito em C e como se faz em C++:
+
O exemplo mostra uma ''stringstream'' criada com conteúdo ''2015 Furious''. Em seguida, usa-se o operador ''>>'' para ler um número inteiro e uma string. O resultado desse exemplo é idêntico ao do exemplo anterior, em que se liam esses dados de um arquivo.
  
{| border="1"
+
A possibilidade de usar ''stringstream'' para converter dados torna simples criar funções de conversão, como esta que converte string para inteiro:
!Linguagem C
 
!Linguagem C++
 
|-
 
|<syntaxhighlight lang=c>
 
#include <stdio.h>
 
  
int main() {
+
<syntaxhighlight lang=c>
   int dia, mes, ano;
+
int converte_para_int(const string & dado) {
   char data[32];
+
   istringstream stream(dado);
 +
   int r;
  
   printf("Dia: ");
+
   stream >> r;
  scanf("%d", &dia);
+
   if (stream.fail()) throw 1; // erro de conversão
  printf("Mes: ");
 
   scanf("%d", &mes);
 
  printf("Ano: ");
 
  scanf("%d", &ano);
 
 
 
  snprintf(data, 32, "%d/%d/%d", dia, mes, ano);
 
 
 
  printf("Data: %s\n", data);
 
  
 +
  return r;
 
}
 
}
</syntaxhighlight> || <syntaxhighlight lang=c>
+
</syntaxhighlight>
#include <iostream>
 
#include <sstream>
 
  
using namespace std;
 
  
int main() {
+
Outras funções para tipos numéricos podem ser criadas da mesma forma:
  int dia, mes, ano;
 
  
  cout << "Dia: ";
+
<syntaxhighlight lang=c>
  cin >> dia;
+
float converte_para_float(const string & dado) {
  cout << "Mes: ";
+
   istringstream stream(dado);
  cin >> mes;
+
   float r;
   cout << "Ano: ";
 
   cin >> ano;
 
  
   //cria-se uma stringstream para escrita
+
   stream >> r;
   ostringstream out;
+
   if (stream.fail()) throw 1; // erro de conversão
  
   // escreve-se na stringstream
+
   return r;
  out << dia << "/";
 
  out << mes << "/";
 
  out << ano;
 
 
 
  // Aqui se obtém o conteúdo armazenado na stringstream
 
  cout << "Data: " << out.str() << endl;
 
 
}
 
}
</syntaxhighlight>
 
|}
 
  
  
Estes outros exemplos mostram a extração de dados que existem em strings:
+
double converte_para_double(const string & dado) {
 +
  istringstream stream(dado);
 +
  double r;
  
{| border="1"
+
  stream >> r;
!Linguagem C
+
  if (stream.fail()) throw 1; // erro de conversão
!Linguagem C++
+
 
|-
+
   return r;
| <syntaxhighlight lang=c>
+
}
#include <stdio.h>
+
</syntaxhighlight>
 
int main() {
 
   int dia, mes, ano;
 
  char data[32];
 
  
  printf("Data (dia/mes/ano): ");
 
  fgets(data, 32, stdin); // lê uma linha
 
  
  // extrai os dados da linha
+
Note que são todas muito parecidas. A única diferença entre elas é o tipo do dado numérico da conversão. Assim, é natural substitui-las por uma função ''template'':
  sscanf(data, "%d/%d/%d", &dia, &mes, &ano);
 
  
  // Aqui se mostram os dados extraídos da linha
+
<syntaxhighlight lang=c>
   printf("Dia: %d\n", dia);
+
template <class T> T converte(const string & dado) {
   printf("Mes: %d\n", mes);
+
   istringstream stream(dado);
   printf("Ano: %d\n", ano);
+
   T r;
 +
 
 +
  stream >> r;
 +
  if (stream.fail()) throw 1; // erro de conversão
 +
 
 +
   return r;
 
}
 
}
</syntaxhighlight>|| <syntaxhighlight lang=c>
+
</syntaxhighlight>
 +
 
 +
 
 +
Essa função ''template'' seria usada da seguinte forma:
 +
 
 +
<syntaxhighlight lang=c>
 
#include <iostream>
 
#include <iostream>
 
#include <sstream>
 
#include <sstream>
+
 
 
using namespace std;
 
using namespace std;
 
int main() {
 
  int dia, mes, ano;
 
  char data[32];
 
  
  cout << "Data (dia/mes/ano): ";
+
template <class T> T converte(const string & dado) {
   cin.getline(data, 32);
+
   istringstream stream(dado);
 +
  T r;
  
   //cria-se uma stringstream para leitura
+
   stream >> r;
   istringstream inp(data);
+
   if (stream.fail()) throw 1; // erro de conversão
  char separador;
 
  
   // lê-se da stringstream
+
   return r;
  inp >> dia;
 
  inp >> separador;
 
  inp >> mes;
 
  inp >> separador;
 
  inp >> ano;
 
 
  // Aqui se mostram os dados extraídos da stringstream
 
  cout << "Dia: " << dia << endl;
 
  cout << "Mes: " << mes << endl;
 
  cout << "Ano: " << ano << endl;
 
 
}
 
}
</syntaxhighlight>
 
|}
 
  
= Sobrecarga de operador =
+
int main() {
 +
  string algo1 = "2015";
 +
  string algo2 = "3.1416";
 +
  string algo3 = "xyz";
 +
  int ano;
 +
  float pi;
  
Operadores C++ podem ser redefinidos. Por exemplo, pode-se desejar que objetos de uma classe possam ser escritos em ''streams'' diretamente por meio do operador ''<<''. Para descobrir uma solução para esse problema, deve-se entender como o operador ''<<'' está definido. E basicamente ele está implementado de duas formas:
+
  ano = converte<int>(algo1);
# [http://www.cplusplus.com/reference/ostream/ostream/operator%3C%3C/ Na classe ostream]: a classe ''ostream'' representa arquivos/streams abertos para escrita, e já possui implementações desse operador para tipos básicos de C++.
+
  pi = converte<float>(algo2);
# '''Em funções de sobrecarga de operador ([http://www.tutorialspoint.com/cplusplus/cpp_overloading.htm operator overloading]):''' podem-se definir funções que implementam esse operador para novos tipos/classes. Por exemplo, existe [http://www.cplusplus.com/reference/string/string/operator%3C%3C/ uma dessas funções para a classe string]. Um exemplo de função de sobrecarga de operador para um novo tipo de dados segue abaixo: <syntaxhighlight lang=c>
 
#include <iostream>
 
  
using namespace std;
+
  cout << "ano=" << ano << endl;
 +
  cout << "pi=" << pi << endl;
 +
 
 +
  try {
 +
    ano = converte<int>(algo3);
 +
    cout << "Outro ano=" << ano << endl;
 +
  } catch (int e) {
 +
    cout << "Erro de conversão: " << algo3 << " não é um int" << endl;
 +
  }
 +
  return 0;
 +
}
 +
</syntaxhighlight>
 +
 
 +
= ''streams'' e arquivos =
 +
 
 +
Em diversas linguagens de programação o conceito de ''stream'' (''fluxo'' ou ''correnteza'') é utilizado para o acesso a arquivos em disco. Por ''stream'' entende-se um mecanismo e leitura e escrita em que dados fluem sucessivamente. Dessa forma, ao se escrever em um arquivo fazem-se sucessivas operações de escrita, e os dados são armazenados na ordem em que essas operações se realizaram. Algo parecido acontece na leitura de um arquivo, em que as operações de leitura realizadas fornecem dados de acordo com a ordem em que estão gravados no arquivo.
 +
 
 +
 
 +
O acesso a arquivos apresenta algumas diferenças nas linguagens C e C++. Em C++ existem algumas classes para a manipulação de arquivos. Desta forma, arquivos nesta linguagem são acessados por meio de objetos dessas classes. No exemplo abaixo, apresentam-se dois programas em C e C++ para ler as linhas de um arquivo e mostrá-las na tela.
 +
 
 +
 
 +
{| border="1"
 +
!Linguagem C
 +
!Linguagem C++
 +
|-
 +
|<syntaxhighlight lang=c>
 +
#include <stdio.h>
 +
#include <errno.h>
  
// Define-se um novo tipo de dados
+
#define MAX_SIZE 10240
 +
 
 +
int main() {
 +
  FILE * arq;
 +
  char linha[MAX_SIZE];
 +
 
 +
  // abre o arquivo
 +
  arq = fopen("/etc/hosts", "r");
 +
 
 +
  // testa se conseguiu abrir o arquivo
 +
  if (arq == NULL) {
 +
    perror("Ao abrir /etc/hosts");
 +
    return errno;
 +
  }
 +
 
 +
  // lê cada linha do arquivo, mostrando-a na tela
 +
  while (fgets(linha, MAX_SIZE, arq)) {
 +
    printf("%s", linha);
 +
  }
 +
 
 +
  // fecha o arquivo
 +
  fclose(arq);
 +
}
 +
</syntaxhighlight> || <syntaxhighlight lang=c>
 +
#include <iostream>
 +
#include <fstream>
 +
#include <stdio.h>
 +
#include <errno.h>
 +
 +
#define MAX_SIZE 10240
 +
 
 +
using namespace std;
 +
 +
int main() {
 +
  // cria o objeto "arq" da classe "ifstream", que representa um arquivo para leitura
 +
  // a criação desse objeto já abre o arquivo
 +
  ifstream arq("/etc/hosts");
 +
 +
  // verifica se o arquivo foi de fato aberto
 +
  if (! arq.is_open()) {
 +
    perror("Ao abrir /etc/hosts");
 +
    return errno;
 +
  }
 +
 
 +
  // lê cada linha do arquivo, apresentando-a na tela
 +
  string linha;
 +
  while (getline(arq, linha)) {
 +
    cout << linha << endl;
 +
  }
 +
 
 +
  // observe que o arquivo não precisa ser fechado ... isso ocorre automaticamente
 +
  // quando o objeto for destruído, ficando a cargo de seu destrutor
 +
}
 +
</syntaxhighlight>
 +
|}
 +
 
 +
 
 +
Os exemplos acima demonstram que a sintaxe no acesso a arquivos pode ser diferente, porém funcionalmente parece haver uma equivalência entre as duas formas de acesso. De fato tudo que se consegue fazer em C++ é possível fazer em C, mudando apenas a praticidade ou conveniência na forma com que se expressa isso nessas linguagens. Porém como a linguagem C já é bem conhecida por vocês, interessa ver o que se pode fazer em C++.
 +
 
 +
Em C++ existem três classes para acessar arquivos:
 +
* [http://www.cplusplus.com/reference/fstream/ofstream/ ofstream]: acesso a arquivos em modo escrita. {{collapse top|Exemplo}}
 +
<syntaxhighlight lang=c>
 +
#include <iostream>
 +
#include <fstream>
 +
 
 +
using namespace std;
 +
 
 +
int main() {
 +
  ofstream arq("demo.txt");
 +
 
 +
  if (not arq.is_open()) {
 +
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
 +
    return 0;
 +
  }
 +
 
 +
  arq << "Iniciando uma gravação de várias linhas inúteis ..." << endl;
 +
 
 +
  for (int i=0; i < 10; i++) {
 +
    arq << "Linha " << i << endl;   
 +
  }
 +
}
 +
</syntaxhighlight>
 +
{{collapse bottom}}
 +
* [http://www.cplusplus.com/reference/fstream/ifstream/ ifstream]: acesso a arquivos em modo leitura. {{collapse top|Exemplo}}
 +
<syntaxhighlight lang=c>
 +
#include <iostream>
 +
#include <fstream>
 +
 
 +
using namespace std;
 +
 
 +
int main() {
 +
  ifstream arq("/etc/hosts");
 +
 
 +
  if (not arq.is_open()) {
 +
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
 +
    return 0;
 +
  }
 +
 
 +
  while (not arq.eof()) {
 +
    string algo;
 +
 
 +
    arq >> algo;
 +
    cout << "Leu isto: " << algo << endl;
 +
  }
 +
}
 +
</syntaxhighlight>
 +
{{collapse bottom}}
 +
* [http://www.cplusplus.com/reference/fstream/ fstream]: acesso a arquivos em modo leitura/escrita. {{collapse top|Exemplo}}
 +
<syntaxhighlight lang=c>
 +
#include <iostream>
 +
#include <fstream>
 +
 
 +
using namespace std;
 +
 
 +
#define MAX_LINE 10240
 +
 
 +
int main() {
 +
  fstream arq("demo.txt");
 +
 
 +
  if (not arq.is_open()) {
 +
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
 +
    return 0;
 +
  }
 +
 
 +
  for (int i=0; i < 10; i++) {
 +
    arq << "Linha " << i << endl;   
 +
  }
 +
  arq.flush();
 +
 
 +
  arq.seekg(0);
 +
 
 +
  while (not arq.eof()) {
 +
  char linha[MAX_LINE];
 +
 
 +
  arq.getline(linha, MAX_LINE);
 +
  cout << "Leu: " << linha << endl;
 +
  }
 +
}
 +
</syntaxhighlight>
 +
{{collapse bottom}}
 +
 
 +
 
 +
O interessante em C++ é que a saída padrão e a entrada padrão são representados por objetos ''stream''. De fato, veja como estão declarados esses objetos na biblioteca C++ padrão:
 +
* [http://www.cplusplus.com/reference/iostream/cout/ cout]: saída padrão
 +
* [http://www.cplusplus.com/reference/iostream/cin/ cin]: entrada padrão
 +
* [http://www.cplusplus.com/reference/iostream/cerr/ cerr]: saída de erros padrão
 +
 
 +
== Leitura e escrita formatada ==
 +
 
 +
''streams'' implementam leitura e escrita formatadas por meio dos operadores [http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/ ''>>''] (leitura) e [http://www.cplusplus.com/reference/ostream/ostream/operator%3C%3C/ ''<<''] (escrita). Esses operadores fazem a conversão automática entre ''string'' e outros tipos de dados.
 +
 
 +
A leitura de um ''stream'' pode ser feita como no exemplo a seguir:
 +
 
 +
<syntaxhighlight lang=c>
 +
int x;
 +
 
 +
cout << "Digite um número inteiro: ";
 +
 
 +
// aqui se faz a leitura de um número inteiro
 +
cin >> x;
 +
 
 +
cout << "Você digitou " << x << endl;
 +
</syntaxhighlight>
 +
 
 +
 
 +
Nesse exemplo, a leitura é feita de ''cin'', uma ''stream'' que representa a console (usualmente o teclado). Lê-se um número inteiro digitado pelo usuário, o qual é armazenado na variável ''x''. A conversão de ''string'' para inteiro é feita automaticamente, porque a variável onde se deseja armazenar o valor lido é do tipo ''int''. Assim, a conversão depende do tipo da variável onde se deve armazenar o valor lido. Este outro exemplo reforça a questão da conversão de tipo:
 +
 
 +
<syntaxhighlight lang=c>
 +
char algo;
 +
int numero;
 +
 
 +
cout << "Digite um caractere qualquer seguido de um número (ex: !33): ";
 +
cin >> algo;
 +
cin >> numero;
 +
 
 +
cout << "Você digitou: " << algo << numero << endl;
 +
</syntaxhighlight>
 +
 
 +
 
 +
Nesse novo exemplo, dois valores são lidos: um valor do tipo char seguido de um int. O primeiro valor corresponde a um único caractere, e o segundo a um ou mais caracteres numéricos consecutivos. As conversões são desencadeadas pelos tipos das variáveis usados nas leituras. Algo similar acontece quando se escrevem dados em um ''stream''.
 +
 
 +
 
 +
O exemplo a seguir mostra a escrita de alguns valores em ''cout'', um ''stream'' que representa também a console (usualmente a tela):
 +
 
 +
<syntaxhighlight lang=c>
 +
double pi = 3.1416;
 +
char letra = 'H';
 +
 
 +
// escreve um valor do tipo char
 +
cout << letra;
 +
 
 +
// escreve um valor do tipo string (char*)
 +
cout << " = ";
 +
 
 +
// escreve um valor do tipo double
 +
cout << pi;
 +
 
 +
// endl corresponde a \n (newline)
 +
cout << endl;
 +
</syntaxhighlight>
 +
 
 +
 
 +
Como mostra o exemplo, são escritos valores do tipo ''char'', , ''char*'' e ''double''. Em todos os casos, a conversão automática entre esses valores e string depende do tipo da variável ou constante onde está o valor a ser escrito.
 +
 
 +
=== Lendo linha por linha ===
 +
 
 +
Este outro exemplo mostra uma forma simples de ler todas as linhas de um arquivo. A função [http://www.cplusplus.com/reference/string/string/getline/ getline] extrai uma linha a cada vez que é chamda. Neste exemplo, o efeito final será ler linha por linha do arquivo:
 +
 
 +
<syntaxhighlight lang=c>
 +
string linha;
 +
ifstream arq("teste.txt");
 +
 
 +
// enquanto conseguir ler uma string do arquivo ...
 +
// quando chegar ao fim do arquivo, a leitura irá falhar
 +
while (getline(arq, linha)) {
 +
  // faz algo com o conteúdo de "linha"
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== Lendo palavra por palavra ===
 +
 
 +
Este outro exemplo mostra uma forma simples de ler todos os valores de um arquivo. O operador ''>>'' extrai um valor a cada vez que é executado sobre o arquivo. Neste exemplo, o efeito final será ler palavra por palavra do arquivo:
 +
 
 +
<syntaxhighlight lang=c>
 +
string palavra;
 +
ifstream arq("teste.txt");
 +
 
 +
// enquanto conseguir ler uma string do arquivo ...
 +
// quando chegar ao fim do arquivo, a leitura irá falhar
 +
while (arq >> palavra) {
 +
  // faz algo com o conteúdo de "palavra"
 +
}
 +
</syntaxhighlight>
 +
 
 +
Deve-se notar que o operador ''>>'' considera como delimitadores os caracteres ''brancos'' (espaço, TAB, newline).
 +
 
 +
 
 +
A escrita e leitura formatada está implementada para os tipos elementares de C e C++ (int, long, short, char, char*, string, bool, float, double). Isso significa que essas operações não funcionam para novos tipos de dados ou classes classes, a não ser que se [[Introdução_C%2B%2B#Sobrecarga_de_operador|implementem esses operadores de leitura e escrita para esses novos tipos]].
 +
 
 +
== Operações de streams ==
 +
 
 +
''streams'' oferecem várias operações para leitura e escrita de dados. Algumas mais comuns são listadas a seguir, junto com exemplos.
 +
 
 +
{| border=1
 +
!Operação
 +
!Entrada
 +
!Saída
 +
!Para que serve
 +
!Exemplo
 +
|-
 +
| [http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/ >>] || x|| ||leitura formatada || <syntaxhighlight lang=c>int x;
 +
double y;
 +
string palavra;
 +
 
 +
// lê um número inteiro
 +
cin >> x;
 +
 
 +
// lê um número double
 +
cin >> y;
 +
 
 +
// lê uma string
 +
cin >> palavra;
 +
</syntaxhighlight>
 +
|-
 +
|[http://www.cplusplus.com/reference/ostream/ostream/operator%3C%3C/ <<] || ||x ||escrita formatada ||<syntaxhighlight lang=c>int x = 5;
 +
double y=3.1416;
 +
string palavra="alguma coisa";
 +
 
 +
// escreve um número inteiro seguido de uma vírgula
 +
cout << x << ", ";
 +
 
 +
// escreve um número double seguido de uma vírgula
 +
cout << y << ", ";
 +
 
 +
// escreve uma string seguida de nova linha
 +
cout << palavra << endl;
 +
 
 +
// outra forma de escrever os três valores
 +
cout << x << ", " << y << ", " << palavra << endl;
 +
</syntaxhighlight>
 +
|-
 +
|[http://www.cplusplus.com/reference/string/string/getline/ getline] || x|| ||lê uma linha (até encontrar '\n') || <syntaxhighlight lang=c>string linha;
 +
 
 +
getline(cin, linha);
 +
 
 +
cout << "Linha: " << linha << endl;
 +
</syntaxhighlight>
 +
|-
 +
|[http://www.cplusplus.com/reference/istream/istream/ignore/ ignore] || x|| ||lê e descarta caracteres ||<syntaxhighlight lang=c>
 +
cout << "Tecle ENTER para continuar";
 +
cin.ignore(256, '\n'); // lê e descarta até 256 caracteres, ou até ler '\n'
 +
</syntaxhighlight>
 +
|-
 +
| [http://www.cplusplus.com/reference/ios/ios/eof/ eof] || x||x ||Verifica se chegou ao fim da stream|| <syntaxhighlight lang=c>
 +
ifstream arq("/etc/hosts");
 +
 
 +
while (true) {
 +
  string linha;
 +
 
 +
  getline(arq, linha);
 +
  if (arq.eof()) break; // se não conseguir ler algo (fim de arquivo)
 +
  cout << linha << endl;
 +
}
 +
</syntaxhighlight>
 +
|-
 +
|[http://www.cplusplus.com/reference/fstream/ifstream/is_open/ is_open] || x||x||Verifica se arquivo foi aberto|| <syntaxhighlight lang=c>
 +
ifstream arq("/etc/hosts");
 +
 
 +
if (not arq.is_open()) {
 +
  cout << "Erro: não abriu o arquivo" << endl;
 +
  return 0;
 +
}
 +
</syntaxhighlight>
 +
|}
 +
 
 +
== Acessando strings como se fossem arquivos ==
 +
 
 +
Em C++ esse conceito de ''stream'' é levado um pouco além. Além de arquivos e a entrada e saída padrão, é possível também manipular ''strings'' como se fossem ''streams''. Quer dizer, pode-se escrever em uma ''string'' (criá-la) ou extrair partes de uma ''string'' como se estivesse acessando um arquivo. Isso possibilita que funções ou objetos que escrevem ou lêem em arquivos possam fazer o mesmo em memória, o que pode ser útil em diferentes programas.
 +
 
 +
O uso de ''strings'' como se fossem ''streams'' é implementado por três classes:
 +
* [http://www.cplusplus.com/reference/sstream/ostringstream/ ostringstream]: possibilita escrever em uma ''string''
 +
* [http://www.cplusplus.com/reference/sstream/istringstream/ istringstream]: possibilita ler de uma ''string''
 +
* [http://www.cplusplus.com/reference/sstream/stringstream/ stringstream]: possibilita ler e escrever em uma ''string''
 +
 
 +
 
 +
O exemplo mais simples envolve criar uma string a partir dos valores de algumas variáveis. Veja como isso era feito em C e como se faz em C++:
 +
 
 +
{| border="1"
 +
!Linguagem C
 +
!Linguagem C++
 +
|-
 +
|<syntaxhighlight lang=c>
 +
#include <stdio.h>
 +
 
 +
int main() {
 +
  int dia, mes, ano;
 +
  char data[32];
 +
 
 +
  printf("Dia: ");
 +
  scanf("%d", &dia);
 +
  printf("Mes: ");
 +
  scanf("%d", &mes);
 +
  printf("Ano: ");
 +
  scanf("%d", &ano);
 +
 
 +
  snprintf(data, 32, "%d/%d/%d", dia, mes, ano);
 +
 
 +
  printf("Data: %s\n", data);
 +
 
 +
}
 +
</syntaxhighlight> || <syntaxhighlight lang=c>
 +
#include <iostream>
 +
#include <sstream>
 +
 
 +
using namespace std;
 +
 
 +
int main() {
 +
  int dia, mes, ano;
 +
 
 +
  cout << "Dia: ";
 +
  cin >> dia;
 +
  cout << "Mes: ";
 +
  cin >> mes;
 +
  cout << "Ano: ";
 +
  cin >> ano;
 +
 
 +
  //cria-se uma stringstream para escrita
 +
  ostringstream out;
 +
 
 +
  // escreve-se na stringstream
 +
  out << dia << "/";
 +
  out << mes << "/";
 +
  out << ano;
 +
 
 +
  // Aqui se obtém o conteúdo armazenado na stringstream
 +
  cout << "Data: " << out.str() << endl;
 +
}
 +
</syntaxhighlight>
 +
|}
 +
 
 +
 
 +
Estes outros exemplos mostram a extração de dados que existem em strings:
 +
 
 +
{| border="1"
 +
!Linguagem C
 +
!Linguagem C++
 +
|-
 +
| <syntaxhighlight lang=c>
 +
#include <stdio.h>
 +
 +
int main() {
 +
  int dia, mes, ano;
 +
  char data[32];
 +
 
 +
  printf("Data (dia/mes/ano): ");
 +
  fgets(data, 32, stdin); // lê uma linha
 +
 
 +
  // extrai os dados da linha
 +
  sscanf(data, "%d/%d/%d", &dia, &mes, &ano);
 +
 
 +
  // Aqui se mostram os dados extraídos da linha
 +
  printf("Dia: %d\n", dia);
 +
  printf("Mes: %d\n", mes);
 +
  printf("Ano: %d\n", ano);
 +
}
 +
</syntaxhighlight>|| <syntaxhighlight lang=c>
 +
#include <iostream>
 +
#include <sstream>
 +
 +
using namespace std;
 +
 +
int main() {
 +
  int dia, mes, ano;
 +
  char data[32];
 +
 
 +
  cout << "Data (dia/mes/ano): ";
 +
  cin.getline(data, 32);
 +
 
 +
  //cria-se uma stringstream para leitura
 +
  istringstream inp(data);
 +
  char separador;
 +
 
 +
  // lê-se da stringstream
 +
  inp >> dia;
 +
  inp >> separador;
 +
  inp >> mes;
 +
  inp >> separador;
 +
  inp >> ano;
 +
 +
  // Aqui se mostram os dados extraídos da stringstream
 +
  cout << "Dia: " << dia << endl;
 +
  cout << "Mes: " << mes << endl;
 +
  cout << "Ano: " << ano << endl;
 +
}
 +
</syntaxhighlight>
 +
|}
 +
 
 +
= Sobrecarga de operador =
 +
 
 +
* [http://www.cplusplus.com/doc/tutorial/templates/#overloading_operators Operator overloading]: uma explicação mais detalhada sobre o assunto. Inclui também uma tabela com os operadores que podem ser redefinidos.
 +
 
 +
 
 +
Operadores C++ podem ser redefinidos, o que se chama sobrecarga de operador (''operator overloading''). Assim, dependendo do tipo de dados ou da classe do valor, variável ou objeto que recebe a operação, o operador pode funcionar de uma forma específica. Um operador pode ser implementado como uma função, que pode ou não ser membro de uma classe.
 +
 
 +
 
 +
O nome da função que implementa um operador é formado pela palavra chave ''operator'' seguida do operador que se deseja implementar. Por exemplo, para o operador ''=='' a função se chama ''operator==''. O tipo do valor de retorno dessa função depende do operador a ser implementado (ex: o operador == retorna um valor booleano, mas o operador = retorna uma referência à variável ou objeto que foi alvo da operação). A quantidade de parâmetros dessa função também dependem do operador em questão, e os tipos de desses parâmetros dependem dos tipos de dados envolvidos na operação.
 +
 
 +
 
 +
Um operador pode ser implementado de duas formas:
 +
# '''Como um método de uma classe:''' um método implementa a operação a ser realizada quando se usa o operador com um objeto dessa classe. A execução desse método acontece no escopo do objeto que recebe a operação (ex: em operadores unários é o próprio objeto alvo do operador, e em operadores binários é o objeto que aparece à esquerda do operador).
 +
# '''Como uma função:''' uma função implementa a operação a ser realizada quando se usa o operador. Essa função recebe como parâmetros as variáveis ou objetos envolvidos na operação.
 +
 
 +
 
 +
Dois exemplos são apresentados para esclarecer a sobrecarga de operadores usando funções que não são membros de classe (i.e. não são métodos):
 +
* '''Operador unário !:''' o operador ''!'' corresponde à operação NÃO, mas ele pode ser redefinido para um tipo de dados específico. Suponha-se que se deseje que o operador ''!'' aplicado a uma string deve retornar ''true'' se ela for vazia, e ''false'' caso contrário. Isso pode ser feito redefinido o operador ''!'' para string da seguinte forma:
 +
 
 +
<syntaxhighlight lang=c>
 +
bool operator!(const string & s) {
 +
  if (s.size() == 0) return true;
 +
  return false;
 +
}
 +
</syntaxhighlight>
 +
 
 +
* '''Operador binário ==''': o operador ''=='' serve para comparar dois valores, resultando em ''true'' se eles forem considerados iguais, e ''false'' caso contrário.  Suponha-se que se deseje que esse operador, quando aplicado a string, resulte em ''true'' se duas string tiverem mesmo comprimento. Isso pode ser feito da seguinte forma:
 +
 
 +
<syntaxhighlight lang=c>
 +
bool operator==(constr string & s1, const string & s2) {
 +
  return s1.size() == s2.size();
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
Esses dois operadores podem ser exemplificados também como métodos uma classe. Usando-se a classe Fila, ambos operadores podem ser implementados da seguinte forma. O operador unário ''!'' deve retornar ''true'' se fila estiver vazia, e o operador ''=='' deve retornar ''true'' se as filas forem iguais:
 +
 
 +
<syntaxhighlight lang=c>
 +
template <typename T> class Fila {
 +
private:
 +
  // atributos da fila
 +
public:
 +
  // métodos da fila
 +
 
 +
  bool operator!() const {
 +
    return vazia();
 +
  }
 +
 
 +
  bool operator==(const Fila<T> & outra) const {
 +
    // para serem iguais, devem ter mesma quantidade de dados
 +
    if (itens == outra.itens) {
 +
      // ... e os dados devem ser iguais e estarem na mesma ordem
 +
      int n = itens;
 +
 
 +
      while (n > 0) {
 +
        T dado1 = desenfileira();
 +
        T dado2 = outra.desenfileira();
 +
        enfileira(dado1);
 +
        enfileira(dado2);
 +
        if (dado1 != dado2) return false;
 +
        n--;
 +
      }
 +
      return true;
 +
    }
 +
    return false;
 +
  }
 +
};
 +
</syntaxhighlight>
 +
 
 +
== Sobrecarga de operadores << e >> de streams ==
 +
 
 +
Pode-se desejar que objetos de uma classe possam ser escritos em ''streams'' diretamente por meio do operador ''<<''. Para descobrir uma solução para esse problema, deve-se entender como o operador ''<<'' está definido. E basicamente ele está implementado de duas formas:
 +
# [http://www.cplusplus.com/reference/ostream/ostream/operator%3C%3C/ Na classe ostream]: a classe ''ostream'' representa arquivos/streams abertos para escrita, e já possui implementações desse operador para tipos básicos de C++.
 +
# '''Em funções de sobrecarga de operador ([http://www.tutorialspoint.com/cplusplus/cpp_overloading.htm operator overloading]):''' podem-se definir funções que implementam esse operador para novos tipos/classes. Por exemplo, existe [http://www.cplusplus.com/reference/string/string/operator%3C%3C/ uma dessas funções para a classe string]. Um exemplo de função de sobrecarga de operador para um novo tipo de dados segue abaixo: <syntaxhighlight lang=c>
 +
#include <iostream>
 +
 
 +
using namespace std;
 +
 
 +
// Define-se um novo tipo de dados
 
struct Ponto {
 
struct Ponto {
   int x,y;
+
   int x,y;
};
+
};
 
+
 
// Define-se o operador << de ostream  para poder escrever um Ponto.
+
// Define-se o operador << de ostream  para poder escrever um Ponto.
// Esse operador mostra o ponto da seguinte forma: (x,y)
+
// Esse operador mostra o ponto da seguinte forma: (x,y)
ostream& operator<<(ostream &out, const Ponto& p) {
+
ostream& operator<<(ostream &out, const Ponto& p) {
   out << "(" << p.x << "," << p.y << ")";
+
   out << "(" << p.x << "," << p.y << ")";
   return out;
+
   return out;
}
+
}
 +
 
 +
// Agora pode-se usar o operador << para valor do tipo "Ponto"
 +
int main() {
 +
  Ponto p1 = {1,1}, p2 = {10,20};
 +
 
 +
  cout << "Ponto 1: " << p1 << endl;
 +
  cout << "Ponto 2: " << p2 << endl;
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
O mesmo vale para o operador ''>>''.  Para que se possa ler diretamente de um ''stream'' um valor de um novo tipo de dados ou classe, esse operador precisa ser definido.
 +
 
 +
= Exceções =
 +
 
 +
* [http://www.cplusplus.com/doc/tutorial/exceptions/ Uma explicação detalhada sobre exceções]
 +
 
 +
 
 +
Condições de erro podem ser avisadas por meio de exceções. Por exemplo, no caso da lista considera-se erro tentar obter um valor em uma posição inexistente. A operação para obter um dado da lista deve, portanto, comunicar de alguma forma que esse acesso inválido aconteceu. Outro exemplo mais simples envolve a conversão de uma string numérica com a função stoi. Se a string não contiver um número inteiro, ocorre um erro. O programa a seguir demonstra esse caso, mostrando o erro:
 +
 
 +
<syntaxhighlight lang=c>
 +
#include <string>
 +
#include <iostream>
 +
 
 +
using namespace std;
 +
 
 +
int main() {
 +
  string n1 = "123";
 +
  string n2 = "abc";
 +
  int x1, x2;
 +
 
 +
  // esta conversão é feita com sucesso
 +
  x1 = stoi(n1);
 +
 
 +
  cout << "Primeira conversão: " << x1 << endl;
 +
 
 +
  // ... mas esta causa um erro
 +
  x2 = stoi(n2);
 +
 
 +
  cout << "Segunda conversão: " << x2 << endl;
 +
 
 +
  return 0;
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
Ao se compilar e executar esse programa, obtém-se este resultado, que revela o erro de execução:
 +
 
 +
<syntaxhighlight lang=bash>
 +
aluno@curl:~$ g++ -o erro -std=c++11 erro.cpp
 +
aluno@curl:~$ ./erro
 +
Primeira conversão: 123
 +
terminate called after throwing an instance of 'std::invalid_argument'
 +
  what():  stoi
 +
Abortado (imagem do núcleo gravada)
 +
</syntaxhighlight>
 +
 
 +
 
 +
O erro manifestado no programa, e que causou sua terminação abrupta, se chama exceção. O mecanismo de exceções existente na linguagem C++ possibilita expressar e tratar condições de erro de uma forma mais prática. Um algoritmo pode disparar uma exceção se detectar uma situação anômala, interrompendo a execução do programa. Essa interrupção causa a execução de um outro trecho de código, responsável por tratar a exceção. Sendo assim, na parte do código onde pode ocorrer uma exceção, deve-se capturá-la e tratá-la. Para isso usa-se a construção ''try ... catch'', que possibilita executar um tratador de exceção. No exemplo de conversão de string numérica, o uso de ''try .. catch'' ficaria assim:
 +
 
 +
<syntaxhighlight lang=c>
 +
  string n1 = "123";
 +
  string n2 = "abc";
 +
  int x1, x2;
 +
 
 +
  try {
 +
    // esta conversão é feita com sucesso
 +
    x1 = stoi(n1);
 +
    cout << "Primeira conversão: " << x1 << endl;
 +
  } catch (...) {
 +
    cout << "Erro de conversão: " << n1 << " não é uma string numérica inteira !" << endl;
 +
  }
 +
 
 +
  try {
 +
    // esta conversão dispara uma exceção
 +
    x2 = stoi(n2);
 +
    cout << "Segunda conversão: " << x2 << endl;
 +
  } catch (...) {
 +
    cout << "Erro de conversão: " << n2 << " não é uma string numérica inteira !" << endl;
 +
  }
 +
 
 +
  return 0;
 +
</syntaxhighlight>
 +
 
 +
 
 +
No exemplo acima, o bloco ''try .. catch'' foi usado de forma a capturar qualquer exceção, o que se define com ''catch (...)''. No entanto, exceções podem ser de diferentes tipos, tais como números, string, ou mesmo objetos. Uma exceção portanto é representada por algum valor informativo, e esse valor pertence a algum tipo de dados ou classe. Com isso, diferentes tipos de exceção podem ser disparadas em um trecho de código, e para capturá-las e tratá-las individualmente é necessário definir tratadores de exceção específicos. Por exemplo, considere que um trecho de código possa disparar exceções do tipo int, string, e possivelmente outras. A forma de tratá-las poderia ser assim:
 +
 
 +
<syntaxhighlight lang=c>
 +
  try {
 +
    // trecho de código que pode disparar diferentes tipos de exceções
 +
  } catch (int e1) {
 +
    // aqui se tratam exceções do tipo int
 +
  } catch (string e2) {
 +
    // aqui se tratam exceções do tipo string
 +
  } catch (...) {
 +
    // aqui se tratam demais exceções de tipos quaisquer
 +
  }
 +
</syntaxhighlight>
 +
 
 +
 
 +
Por outro lado, quando se programa um algoritmo (embutido em uma função ou método de classe), erros ou situações inesperadas que forem detectadas implicam o disparo de uma exceção por meio da palavra chave ''throw''. O valor da exceção pode ser qualquer, e no exemplo a seguir é representado por um número inteiro:
 +
 
 +
<syntaxhighlight lang=c>
 +
void maiusculas(string & palavra) {
 +
  if (palavra.size() == 0) throw 1; // string vazia !!!
  
// Agora pode-se usar o operador << para valor do tipo "Ponto"
+
  for (int i=0; i < palavra.size(); i++) palavra[i] = toupper(palavra[i]);
int main() {
 
  Ponto p1 = {1,1}, p2 = {10,20};
 
 
 
  cout << "Ponto 1: " << p1 << endl;
 
  cout << "Ponto 2: " << p2 << endl;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
= Exceções =
 
 
* [http://www.cplusplus.com/doc/tutorial/exceptions/ Uma explicação detalhada sobre exceções]
 
 
 
Condições de erro podem ser avisadas por meio de exceções. Por exemplo, no caso da lista considera-se erro tentar obter um valor em uma posição inexistente. A operação para obter um dado da lista deve, portanto, comunicar de alguma forma que esse acesso inválido aconteceu. Até o momento, uma situação como essa era resolvida fazendo com que o valor retornado informasse que algo errado ocorreu (no exemplo da lista, retornava-se uma string vazia). Mas o mecanismo de exceções possibilita expressar e tratar essas condições de erro de uma forma mais prática.
 
 
O uso de exceções implica basicamente duas coisas:
 
# Na parte do código onde se detecta um erro, deve-se ''lançar'' uma exceção por meio da palavra chave ''throw''. O valor da exceção pode ser qualquer, e no exemplo a seguir é representado por um número inteiro: <syntaxhighlight lang=c>
 
string &Lista::operator[](int pos) {
 
  if (pos >= comp) throw 1; // posição inválida !!!
 
 
  Nodo * p;
 
 
  for (p=inicio; pos > 0; pos--, p=p->proximo);
 
  return p->dado;
 
}
 
</syntaxhighlight>
 
# Na parte do código que pode receber uma exceção, deve-se capturá-la e tratá-la. Para isso usa-se a construção ''try ... catch'', que possibilita executar um tratador de exceção: <syntaxhighlight lang=c>
 
  try {
 
    string palavra = list[5];
 
    cout << "Palavra=" << palavra << endl;
 
  } catch (int excecao) { // o corpo do "catch" contém o tratador da exceção ...
 
    cout << "Ops: acho que posição é inválida (lançada exceção" << excecao << "!)" << endl;
 
  }
 
</syntaxhighlight>
 
  
 
'''OBS:''' se uma exceção não for capturada usando ''try ... catch'', ela causará o término do programa.
 
'''OBS:''' se uma exceção não for capturada usando ''try ... catch'', ela causará o término do programa.

Edição atual tal como às 14h29min de 20 de novembro de 2020

Bibliografia


Introdução

A linguagem de programação C++ estende a linguagem C, de forma a aderir ao paradigma de orientação a objetos. Com isso, a linguagem oferece construções que possibilitam a expressão de programas modelados segundo uma análise orientada a objetos. A linguagem se apresenta como um superconjunto da linguagem C, o que significa que um programa em C pode ser visto também como um programa C++, mas não o contrário.

Este primeiro exemplo apresenta o clássico programa de demonstração hello world escrito em C++:

#include <iostream>

using namespace std;

int main() {
  cout << "Hello world !" << endl;

  return 0;
}

Para compilar este exemplo, deve-se gravá-lo em um arquivo com extensão .cc ou .cpp. O compilador C++ disponível no Linux se chama g++, sendo da mesma família de compiladores do gcc. Supondo que o nome do arquivo seja hello.cc, pode-se compilá-lo assim:

g++ -o hello hello.cc

O comando acima deve gerar um arquivo executável chamado hello. Para executá-lo, faça o seguinte:

./hello

O exemplo acima pode ser estendido para mostrar como ler dados do teclado:

#include <iostream>
#include <stdio.h>
 
using namespace std;
 
int main() {
  int x;

  cout << "Hello world !" << endl;
  cout << "x: ";
  cin >> x;
  cout << "Voce digitou " << x << endl; 
  return 0;
}

Organização de código-fonte


Programas em linguagens C e C++ são usualmente organizados em um conjunto de arquivos de código-fonte de dois tipos:

  • Arquivos de cabeçalho (extensão .h): nesse tipo de arquivo se escrevem declarações necessárias em outros arquivos de código-fonte. Essas declarações incluem tipos de dados, protótipos de funções, classes e templates, variáveis globais e macros. Com exceção de templates e macros, nesses arquivos não se escrevem implementações (corpos de funções e métodos).
  • Arquivos de implementação (extensão .cpp, .cc ou .c): nesses arquivos se escrevem as implementações de funções e métodos de classes. Arquivos de cabeçalhos podem ser incluídos com a diretiva #include para obter declarações necessárias à implementação.

Argumentos de linha de comando


Argumentos de linha de comando são dados passados a um programa no momento de sua execução. Na época em que a linguagem C foi inventada, e quando então os sistemas Unix se popularizaram, a interface com usuários era em modo texto. Usuários interagiam com o sistema operacional por meio de um programa chamado de interpretador de comandos, ou simplesmente shell. Para um usuário executar um programa, ele deveria digitar o nome desse programa no prompt do shell e pressionar a tecla ENTER (ou RETURN). Cabia então ao shell ordenar ao sistema operacional que o arquivo de programa solicitado fosse executado. Além do nome do programa, um usuário poderia opcionalmente especificar um ou mais argumentos, que são strings a serem passadas ao programa no momento de sua execução. Os dois exemplos a seguir mostram a execução do programa ls por meio de um shell.


aluno@M1:$ ls
Área de trabalho  Downloads  Modelos  Público   Vídeos
Documentos        Imagens    Música   teste.sh
aluno@M1:$

Execução do programa ls sem argumentos de linha de comando. O prompt do shell é a string aluno@M1:$.


aluno@M1:$ ls -l
total 48
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Área de trabalho
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Documentos
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Downloads
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Imagens
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Modelos
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Música
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Público
-rw-r--r-- 1 aluno aluno 14076 abr 12  2018 teste.sh
drwxr-xr-x 2 aluno aluno  4096 abr 12  2018 Vídeos
aluno@M1:$

Execução do programa ls com um argumento de linha de comando (a string -l). Cabe ao programa ls interpretar esse argumento, e usá-lo para desempenhar sua funcionalidade.


Do ponto de vista do programador, os argumentos de linha de comando são acessíveis por meio de dois parâmetros da função main, os quais são tradicionalmente denominados:

  • argc: um número inteiro que informa quantos argumentos de linha de comando foram passados ao programa. Esse parâmetro tem no mínimo o valor 1 (um), pois o próprio nome do programa é considerado seu primeiro argumento.
  • argv: um vetor de strings que contém os argumentos. A primeira string desse vetor é sempre o próprio nome do programa. A posição no vetor em seguida ao último argumento contém o ponteiro NULL.


O programa C a seguir mostra como esses parâmetros são especificados na função main, e como podem ser usados. Neste exemplo, apresentam-se na tela todos os argumentos de linha de comando (um por linha), na ordem em que foram passados ao programa.

#include <stdio.h>

int main(int argc, char * argv[]) {
  int n;

  for (n=0; n < argc; n++) {
    printf("argv[%d]=%s\n", n, argv[n]);
  }

  return 0;
}


A versão em C++ desse exemplo é praticamente idêntica, mudando somente a forma com que os dados são mostrados na tela.

#include <iostream>

using namespace std;

int main(int argc, char * argv[]) {
  int n;

  for (n=0; n < argc; n++) {
    cout << "argv[" << n << "]=" << argv[n] << endl;
  }

  return 0;
}


Apesar de hoje em dia as interfaces de usuário em modo texto serem menos usadas, o modelo de execução de programas continua o mesmo. Mesmo programas gráficos se valem de argumentos de linha de comando para modularem a forma com que devem funcionar (ver, por exemplo, o manual do firefox). Independente do tipo de interface de usuário, argumentos de linha de comando são de grande utilidade para passar informação a programas quando forem executados.

Classes e objetos

A linguagem C foi projetada segundo um paradigma de programação chamado de programação estruturada. Essa forma de expressar programas se baseia em subrotinas (funções no caso da linguagem C), blocos de instruções e laços de repetição, os quais atuam sobre variáveis que contêm os dados a serem acessados e transformados. O que se deve notar é a forma com que se costuma modelar um programa nesse caso, dando-se ênfase aos algoritmos que modificam os dados. Não é à toa que uma ferramenta de modelagem tradicional e ainda muito usada seja o fluxograma. Porém existem outras formas de pensar os problemas a serem resolvidos e expressar programas, dentre elas o paradigma de orientação a objetos seguido por diversas linguagens tais como C++, Java e Python.


Um programa orientado a objetos é composto por objetos que interagem. Um objeto é uma abstração que representa uma coisa que faz parte do problema a ser resolvido. Um objeto contém dados que representam seu estado (chamados de atributos), e procedimentos que representam seu comportamento (chamados de métodos). No caso do exemplo dos horários, um horário pode ser representado por um objeto. O estado desse objeto Horario é definido pelos valores de hora, minuto e segundo, e seu comportamento é dado pelos métodos mostreSe e acrescenta. Isso deve ser entendido da seguinte forma: um objeto 'Horario pode ser mostrado, ao se chamar seu método 'mostreSe, e pode ser somado a outro objeto Horario, ao se chamar seu método acrescenta. Essas são as únicas operações definidas para esse objeto, e assim nada mais se consegue fazer com ele ... a não ser que se definam novas operações.


Muitos objetos similares podem existir. Por exemplo, pode haver muitos diferentes objetos Horario, que se diferenciam por seus estados (valores de hora, minuto e segundo). Todos os objetos cujos estados são definidos pelo mesmo conjunto de atributos, e que possuem o mesmo comportamento (conjunto de métodos), pertencem à mesma classe. O conceito de classe é semelhante ao de tipo de dados, porém tem uma implicação mais profunda. Um tipo de dados define como o conteúdo de uma variável deve ser interpretado (ex: um número inteiro, ponto flutuante, uma string, ...), porém não define os procedimentos que podem manipular essas variáveis. Desta forma, uma classe vincula a seus objetos os algoritmos que atuam sobre eles. Isso tem uma importante implicação no acoplamento e coesão de um programa, que são métricas fundamentais de qualidade de um projeto de software modular.


Declaração de classes

Uma classe é declarada usando-se a palavra-chave class seguida do nome da classe:

class MinhaClasse {
 // declarações internas da classe
};

De certa forma, isso se assemelha à declaração de struct. No entanto, as declarações internas da classe têm um significado um pouco diferente. Por exemplo, suponha-se que a classe MinhaClasse represente um número de telefone, incluindo seu código de área. A declaração seria estendida desta forma:


class MinhaClasse {
 private:
  string numero;
  string ddd;
 public:
 // declarações dos procedimentos da classe
};

A classe agora possui dois atributos:

  • string numero: armazena o número de telefone
  • string ddd: armazena o código DDD

No exemplo, as declarações desses atributos na classe são precedidas pela cláusula private:. Isso significa que esses atributos não podem ser acessados diretamente de fora da classe (por procedimentos implementados fora da classe). Logo após a declaração desses atributos, aparece a cláusula public:. Ela define que o que for declarado em seguida pode ser acessado diretamente de fora da classe. Por exemplo, os métodos da classe podem ser declarados logo após public:.


class MinhaClasse {
 private:
  string numero;
  string ddd;
 public:
  // Construtor: executado quando se cria um objeto desta classe
  MinhaClasse(string umNumero, string umDDD);

  // Destrutor: executado quando se destrói um objeto desta classe
  ~MinhaClasse();

  // métodos para acessar os valores dos atributos
  string obtemNumero();
  string obtemDDD();
  string obtemNumeroDDD();
};

Um método é uma função que pertence a uma classe. A idéia é que um objeto de uma classe possui um estado dado pelos valores de seus atributos, e somente os métodos daquela classe podem acessar ou modificar esse estado. Quer dizer, somente métodos da classe podem acessar diretamente esses atributos. Dois métodos são especiais:

  • construtor: este método é executado sempre que se cria um objeto de uma classe. Ele tem por finalidade iniciar o estado daquele objeto. Isso envolve definir os valores iniciais dos atributos, e conferir se os parâmetros que porventura tenham sido passados são válidos (ex: se o número tem somente caracteres numéricos).
  • destrutor: este método é executado quando um objeto é destruído. Ele é responsável por limpar o estado desse objeto, desfazendo qualquer ação que ele tenha iniciado. Por exemplo, se o objeto alocou memória dinamicamente em algum momento, o destrutor deve liberá-la.


A declaração da classe apresentada apenas informa quais métodos ela implementa, mas não mostra como eles são implementados. Essa implementação pode ser feita de duas formas:

  • inline: a implementação de um método é feita dentro da própria declaração da classe. Por exemplo, o método obtemNumeroDDD poderia estar implementado assim:
    class MinhaClasse {
     private:
      string numero;
      string ddd;
     public:
      // Construtor: executado quando se cria um objeto desta classe
      MinhaClasse(string umNumero, string umDDD);
    
      // Destrutor: executado quando se destrói um objeto desta classe
      ~MinhaClasse();
    
      // métodos para acessar os valores dos atributos
      string obtemNumero();
      string obtemDDD();
    
      // implementação inline
      string obtemNumeroDDD() {
        string num = ddd + '-' + numero;
        return num;
      }
    };
    
  • fora da classe: a implementação é feita externamente, possivelmente em outro arquivo. Deve-se declarar a classe a que pertence o método, prefixando seu nome com Nome_da_classe::. No caso de obtemNumeroDDD, isso poderia ser feito assim:
    // deve-se prefixar o nome do método com o nome da classe, para indicar que é um método dessa classe
    string MinhaClasse::obtemNumeroDDD() {
      string num = ddd + '-' + numero;
      return num;
    }
    

Criação de objetos

Uma vez tendo declarado e implementado uma classe, podem-se criar objetos. A isso se chama instanciação, e os objetos também podem ser chamados de instâncias. Objetos podem ser criados diretamente, como se fossem variáveis usuais, como mostrado a seguir:

#include "minhaclasse.h"
#include <iostream>

using namespace std;

int main() {
  string num, ddd;

  cout << "Numero: ";
  cin >> num;
  cout << "DDD: ";
  cin >> ddd;

  // o construtor da classe é chamado quando se cria um objeto
  // Aqui, o objeto é dado pela variável "fone"
  MinhaClasse fone(num, ddd);

  cout << "Numero com DDD é " << fone.obtemNumeroDDD() << endl;

  // objetos são destruídos automaticamente ao final do escopo em que foram criados
  // o destrutor é então executado 
}


Objetos podem também ser criados dinamicamente, em que a memória necessária é alocada. Para isso usa-se o operador new (e não a função malloc !). Objetos criados dessa forma são referenciados por ponteiros, e devem ser explicitamente destruídos usando-se o operador delete (e não a função free !). O exemplo a seguir mostra a criação e destruição de objetos com new e delete:

#include "minhaclasse.h"
#include <iostream>

using namespace std;

int main() {
  string num, ddd;
  // o ponteiro fone é capaz de apontar objetos de MinhaClasse
  MinhaClasse * fone;

  cout << "Numero: ";
  cin >> num;
  cout << "DDD: ";
  cin >> ddd;

  // o construtor da classe é chamado quando se cria um objeto com o operador "new"
  // Aqui, o objeto é dado pelo ponteiro "fone"
  fone = new MinhaClasse(num, ddd);

  cout << "Numero com DDD é " << fone->obtemNumeroDDD() << endl;

  // objetos dinâmico devem ser destruídos explicitamente usando o operador "delete"
  // o destrutor é então executado 
  delete fone;
}

Invocação do construtor

A criação de um objeto causa a execução do método construtor da classe. Esse método é responsável por iniciar os valores dos atributos do objeto (chamados de variáveis de instância), tornando-o pronto para ser utilizado. O construtor recebe os parâmetros passados na criação do objeto (ver linhas 16 do primeiro exemplo e 18 do segundo). No exemplo em questão, o método construtor poderia fazer algo assim:

MinhaClasse::MinhaClasse(string umNumero, string umDDD) {
  numero = umNumero;
  ddd = umDDD;
}

... quer dizer, apenas copiar os valores dos parâmetros para os respectivos atributos. Outra forma de implementar o construtor e iniciar os valores dos atributos é esta:

MinhaClasse::MinhaClasse(string umNumero, string umDDD) : numero(umNumero), ddd(umDDD) {
}


Deve-se observar que os atributos são iniciados usando uma sintaxe especial. Logo após o nome do método construtor declara-se uma lista de inicialização de atributos. Essa lista é iniciada com :, seguida dos nomes dos atributos invocados como se fosse outros objetos sendo criados. Na verdade é exatamente isso que acontece nessa forma de iniciar os atributos: eles são criados como objetos que fazem parte do objeto criado. Essa forma de iniciar atributos é imprescindível quando uma classe possui um ou mais atributos que são de fato objetos de alguma classe, e que precisam receber parâmetros ao serem criados.

Destruição de objetos

A destruição de um objeto ocorre em duas situações:

  1. Quando se chega ao fim o escopo dentro do qual o objeto foi criado: isso vale para objetos declarados e instanciados diretamente. Ex:
    void ecoa() {
      string palavra; // aqui se cria um objeto string
    
      cout << "Digite uma palavra: ";
      cin >> palavra;
      cout << "Você digitou: " << palavra << endl;
    } // fim do escopo ... o objeto string "palavra" é destruido automaticamente
    
  2. Quando se destroi o objeto com o operador delete: isso vale somente para objetos criados dinamicamente com o operador new. Ex:
    void ecoa() {
      string * palavra; // aqui se declara um ponteiro para string
    
      palavra = new string; // aqui se cria dinamicamente um objeto string
      cout << "Digite uma palavra: ";
      cin >> *palavra;
      cout << "Você digitou: " << *palavra << endl;
    
      delete palavra; // aqui se destroi o objeto string
    }
    


A destruição de um objeto implica liberar as áreas de memória a ele alocadas dinamicamente em sua criação, ou durante seu tempo de vida. Essa faxina deve ser feita pelo método destrutor da classe (chamado simplesmente de destructor). Tal método é declarado da seguinte forma em uma classe:

classe Demo {
 public:
  Demo(); // constructor da classe
  ~Demo(); // destructor da classe

  // demais métodos da classe
};

// implementação do destructor
Demo::~Demo() { 
  // limpeza do objeto
}

A implementação desse método deve ser responsável pela limpeza do objeto. Apenas áreas de memória alocadas dinamicamente precisam ser liberadas. O exemplo a seguir mostra uma classe com memória alocada dinamicamente:

classe Demo {
 public:
  Demo(const string & name); // constructor da classe
  ~Demo(); // destructor da classe

  // demais métodos da classe
 private:
  // atributos privativos da classe
  string nome;
  char * buffer;
};

// implementação do constructor
Demo::Demo(const string & name) { 
  nome = name;
  buffer = new char[1024]; // aloca dinamicamente 1kB
}

// implementação do destructor
Demo::~Demo() { 
  // libera a área de memória alocada no constructor
  delete buffer;
}

Um exemplo

O exemplo abaixo pode esclarecer melhor os conceitos de classe e objeto. Ele declara uma classe Vetor, que representa vetores bidimensionais. As operações sobre tais objetos são: módulo, produto escalar, subtração e escrita em um arquivo. Veja como pode ser escrita essa classe.

// a classe Vetor define objetos que representam vetores bidimensionais
class Vetor {
 private: // as declarações abaixo são privativas (podem ser acessadas somente dentro da classe)

  double x, y;

 public: // as declarações abaixo são públicas, podendo ser acessadas de fora da classe

  // construtor da classe: um método especial que cria objetos desta classe
  Vetor(double px, double py) {
    // inicia o estado de um objeto
    x = px;
    y = py;
  }

  // destrutor da classe: um método especial que destrói objetos desta classe
  ~Vetor() {} // nada demais precisa ser feito

  // este método retorna o módulo do vetor
  double modulo() {
    return sqrt(x*x + y*y);
  }

  // este método retorna o produto escalar deste vetor com outro vetor
  double produto_escalar(const Vetor & outro) {
    return x*outro.x + y*outro.y;
  }

  // este método calcula a soma entre este e outro vetor (este + outro),
  // retornando um novo vetor como resultado
  Vetor adicione(const Vetor & outro) {
    Vetor novo(x+outro.x, y+outro.y);

    return novo;
  }

  // este método calcula a diferença entre este e outro vetor (este - outro),
  // retornando um novo vetor como resultado
  Vetor subtraia(const Vetor & outro) {
    Vetor novo(x-outro.x, y-outro.y);

    return novo;
  }

  // este método escreve uma representação do objeto no arquivo "out"
  // (que é um objeto da classe ostream)
  void mostreSe(ostream & out) {
    out << "(";
    out << x;
    out << ", ";
    out << y;
    out << ")";
  }

};

Uma vez existindo a classe Vetor, pode-se escrever um programa que use objetos dessa classe. No exemplo abaixo, calcula-se o vetor resultante de dois outros vetores:

int main() {
  int x, y;

  cout << "Coordenada X do vetor 1: ";
  cin >> x;
  cout << "Coordenada Y do vetor 1: ";
  cin >> y;
  cout << endl;

  // Aqui se cria o primeiro objeto Vetor
  Vetor v1(x, y);

  cout << "Coordenada X do vetor 2: ";
  // Aqui se lê pela entrada padrão a coordenada X
  cin >> x;
  cout << "Coordenada Y do vetor 2: ";
  // Aqui se lê pela entrada padrão a coordenada Y
  cin >> y;
  cout << endl;

  // Aqui se cria o segundo objeto Vetor
  Vetor v2(x, y);

  // Aqui se obtém a resultante dos vetores v1 e v2. Note como a operação "adicione"
  // do objeto "v1" é chamada.
  Vetor v3 = v1.adicione(v2);

  cout << "Vetor resultante: ";
  // Aqui se escreve uma representação de "v3" na tela. "cout" é um objeto da biblioteca C++ padrão
  // que representa a saída padrão de um processo. A saída padrão funciona como um arquivo. O objeto 
  // "cout" é similar ao "stdout" da linguagem C.
  v3.mostreSe(cout);
  cout << endl;
}

O programa em si se apresenta como um pequeno algoritmo que lê pela entrada padrão valores de coordenadas e cria os respectivos vetores (v1 e v2), calculando sua resultante a apresentando na saída padrão o novo vetor (v3).


Além das regras de escrita da linguagem, o que se denomina sintaxe, ressalta-se o uso dos objetos feito no programa. O pequeno programa criado trata de calcular a resultante de dois vetores bidimensionais. As coisas a serem diretamente manipuladas pelo programa são os vetores, sendo estes representado por objetos. Outros objetos usados são os arquivos que representam a entrada e saída padrão do programa (respectivamente cin e cout). O programa usa os objetos da classe Vetor, explorando as operações que eles oferecem. Assim, a declaração da classe Vetor se preocupa em definir o que é um objeto Vetor (seus atributos) e o que ele é capaz de fazer (seu comportamento). Já o programa apenas utiliza esses objetos.

O ponteiro predefinido this


Ao se implementarem métodos de uma classe pode-se usar uma variável predefinida chamada this, que é um ponteiro para o objeto no escopo do qual se executa um método. Sempre que dentro de um método for necessário eliminar uma ambiguidade quanto ao uso de um atributo do objeto, ou por clareza, pode-se usar esse ponteiro this. O exemplo a seguir ilustra o uso de this.

class Coisa {
 public:
  Coisa(int x, int y);
  ~Coisa() {}
  // outros métodos da classe ...
 private:
  // os atributos dos objetos desta classe
  int x,y;
};

// construtor da classe Coisa: os parâmetros "x" e "y" são usados para iniciar os 
// atributos "x" e "y". Para resolver a ambiguidade entre parâmetros e atributos, dentro do construtor
// se usa o ponteiro "this"
Coisa::Coisa(int x, int y) {
  this->x = x;
  this->y = y;
}


Existem outros casos em que o ponteiro this tem utilidade. Um exemplo pode ser visto na seção sobre construtor de cópia.

Operador de atribuição

Em C++ pode-se definir como um operador específico atua sobre um objeto. Operadores típicos são: = (atribuição), + (adição), - (subtração), e outros. Um operador é definido por uma função ou método que implementa sua operação. No caso da Fila, um operador útil é o de atribuição, pois pode ser usado para que uma fila existente seja modificada para se tornar idêntica a outra fila. Supondo que esse operador tenha sido implementado, o exemplo a seguir mostra a fila f1 sendo atribuída a f2 (linha 19), a qual já existe e tem inclusive dados armazenados, e demonstra em seguida como ficou o conteúdo de f2.


#include <iostream>
#include "fila.h"

using namespace std;

int main() {
  Fila f1(5);
  Fila f2(7);

  f1.enfileira(2);
  f1.enfileira(7);
  f1.enfileira(4);

  f2.enfileira(3);

  cout << "Comprimento de f1: " << f1.comprimento() << endl;
  cout << "Comprimento de f2: " << f2.comprimento() << endl;

  f2 = f1; // atribui f1 a f2 ... f2 torna-se uma cópia de f1

  cout << "Comprimento de f1: " << f1.comprimento() << endl;
  cout << "Comprimento de f2: " << f2.comprimento() << endl;

  while (not f1.vazia()) cout << "Desenfileirou de f1: " << f1.desenfileira() << endl;
  
  cout << "Comprimento de f1: " << f1.comprimento() << endl;
  cout << "Comprimento de f2: " << f2.comprimento() << endl;

  while (not f2.vazia()) cout << "Desenfileirou de f2: " << f2.desenfileira() << endl;
}


O operador de atribuição da fila pode ser declarado como mostrado abaixo:

class Fila {
 public:
  Fila(int umaCapacidade); // cria uma fila capaz de guardar "capacidade" elementos
  Fila(const Fila & outra);
  ~Fila(); // destroi a fila
  Fila& operator=( const Fila& outra ); // OPERADOR DE ATRIBUIÇÃO
  // demais declarações da Fila ...
};


Como se pode notar, sua declaração é muito parecida com a de um método qualquer da classe. A palavra-chave operator indica tratar-se de um método de operador, e o símbolo que aparece logo em seguida indica o operador a ser redefinido (no caso, =). O único parâmetro a ser passado é o objeto a ser copiado (no caso, uma Fila). O valor retornado é uma referência ao próprio objeto que foi o destino da atribuição (aquele que foi modificado). A implementação desse método segue as regras usadas nos demais métodos:

Fila& Fila::operator=( const Fila& outra ) {
  // desalocar a memória de "buffer"
  // copiar os valores de atributos de "outra"
  // alocar memória para "buffer"
  // copiar conteúdo da "buffer" da "outra" fila
  
  // retorna uma referência ao próprio objeto 
  return *this;
}

A implementação do método do operador de atribuição em fila.cpp

Construtor de cópia

Um construtor de cópia é um método construtor para criar um objeto que é uma cópia idêntica a um objeto existente. Esse tipo de criação de objeto aparece em casos como este:

Fila<int> f1(10); // cria uma fila capaz de guardar 10 números inteiros

f1.enfileira(5);
f1.enfileira(8);
f1.enfileira(2);
f1.enfileira(4);

Fila<int> f2 = f1; // cria a fila f2, que é uma cópia idêntica de f1

f1.esvazia(); // esvazia a fila f1

// Mostra o conteúdo da fila f2: deve ser igual ao que existia em f1
while (not f2.vazia()) {
  cout << f2.desenfileira() << endl;
}

Para que o exemplo acima funcione, a classe Fila deve implementar seu construtor de cópia:

template <typename T> class Fila {
 public:
  // construtor de cópia: o objeto criado deve ser idêntico ao objeto "outra"
  Fila(const Fila<T> & outra);

  // demais declarações da Fila ...
};

O construtor de cópia deve ser responsável por copiar o estado do objeto copiado, de forma que o objeto criado seja idêntico a ele. Isso envolve copiar os valores dos atributos desses objetos, porém em certos casos isso não é suficiente. Se o objeto copiado tiver como atributos outros objetos ou variáveis criados dinamicamente, então o novo objeto deve também criar dinamicamente cópias desses objetos ou variáveis.


Deve-se observar que um construtor de cópia é muito parecido com um operador de atribuição. A diferença reside no fato de que uma atribuição ocorre entre dois objetos já existentes. Por isso um objeto que recebe uma atribuição deve primeiro limpar seu estado (se necessário, o que envolve destruir eventuais objetos e variáveis dinâmicos) para depois se tornar uma cópia do outro objeto. Apesar disso, se um operador de atribuição já existir ele pode ser usado para facilmente implementar um construtor de cópia. O exemplo a seguir mostra como isso pode ser feito para a Fila:

// implementação do construtor de cópia
template <typename T> Fila<T>::Fila(const Fila<T> & outra) {
  // primeiro devem-se atribuir valores aos atributos desta Fila 
  // para que ela pareça vazia

  // em seguida, basta atribuir a "outra" Fila a este objeto
  // Note o uso do ponteiro predefinido "this", que sempre aponta o objeto que recebe a operação
  // dentro de um método de sua classe
  *this = outra;
}

Templates


Templates possibilitam a parametrização de tipos em C++. Com isso, podem-se escrever funções, structs e classes que referenciam ou armazenam dados cujos tipos sejam desconhecidos de antemão. Esses tipos são denominados tipos genéricos. No nosso caso, isso será usado para generalizar as estruturas de dados a serem criadas, para que elas possam armazenar qualquer tipo de dado. Na explicação a seguir, usa-se a estrutura de dados Fila como exemplo.


A declaração de uma classe template inicia com o prefixo template <typename T>. A cláusula <typename T> serve para especificar um identificador para o tipo genérico a ser usado dentro da classe (no exemplo, escolheu-se o identificador T, mas pode ser qualquer outra coisa). No caso da Fila, deseja-se torná-la capaz de guardar qualquer tipo de dado. Quer dizer, seu funcionamento não depende do que ela estiver armazenando, portanto o tipo do dado armazenado pode ser generalizado. Esse é um bom exemplo de uso de classe template, estando exemplificado a seguir:

template <typename T> class Fila {

// declarações de atributos e métodos da classe

};


Todas as referências ao tipo genérico devem ser escritas usando o identificador especificado (no caso, T). Aproveitando o exemplo da Fila, deve-se observar que o atributo buffer depende do tipo genérico, pois trata-se de um vetor onde devem ser armazenados os dados enfileirados. Os métodos enfileira, desenfileira e frente também dependem do tipo genérico. A declaração da classe template Fila segue abaixo, porém com alguns detalhes omitidos para ficar mais evidente o uso de template.


template <typename T> class Fila {
  public:
    Fila(unsigned int N);
    ~Fila();

    // enfileira um dado do tipo T
    void enfileira(const T & algo);

    // desenfileira um dado da frente da fila, e retorna uma 
    // cópia desse dado (cujo tipo é T)
    T desenfileira();

    // Retorna uma referência ao dado de tipo T que está na frente da fila
    T & frente() const;

    bool vazia() const {return itens == 0; }
    bool cheia() const { return itens == capacidade;}
    unsigned int tamanho() const { return itens;}
  private:
    int capacidade;
    int itens, inicio, fim;
    T* buffer; // área de memória para guardar os dados de tipo T enfileirados
};


Quando se declara uma classe template, tanto a declaração da classe (sua interface) quanto a implementação de seus métodos devem estar no mesmo arquivo com extensão .h. Isso se deve a detalhes específicos da compilação do código-fonte. Assim, a implementação dos métodos pode estar dentro da própria declaração da classe, como no caso dos métodos vazia, cheia e tamanho, ou fora da classe. Quando forem implementados fora da classe, deve-se repetir o prefixo template <typename T> na frente do nome de cada método, como mostrado a seguir:

template <typename T> void Fila<T>::enfileira(const T& algo) {
  // implementação do método
}


Uma vez declarada uma classe template, ela pode ser utilizada. Somente no momento em que uma classe é usada que o tipo genérico é especificado. Por exemplo, a Fila acima declarada pode guardar qualquer coisa, o que inclui strings e números inteiros. Para declarar duas filas, de forma que uma armazene inteiros e outra strings, deve-se usar esta sintaxe:

int main() {
  // Uma fila capaz de guardar até 10 números inteiros
  Fila<int> f1(10);

  // uma fila capaz de guardar até 5 strings
  Fila<string> f2(5);

  // enfileira-se o número 23 na fila de inteiros
  f1.enfileira(23);

  // enfileira-se a string "uma coisa" na fila de strings
  string algo = "uma coisa";
  f2.enfileira(algo);

  // outras declarações da função main ...
}


Do ponto de vista da linguagem, um template define um padrão de geração de código para o compilador (maiores detalhes). Quer dizer, um template em si não é compilável, pois ele representa código-fonte ainda incompleto. No entanto, no momento em que se usa um template, o código-fonte é completado e pode ser compilado. Em outras palavras, o template da Fila se torna uma Fila de fato. O programa de teste abaixo aproveita o exemplo das filas, enfileirando e desenfileirando dados de duas filas.

#include <iostream>
#include "fila.h"
 
using namespace std;
 
int main() {
  // Uma fila de strings
  Fila<string> fila1(10);
 
  // Uma fila de inteiros
  Fila<int> fila2(10);
 
  // Armazena três strings na fila de strings
  for (int x=0; x < 3; x++) {
    string palavra;
 
    cout << "Digite uma palavra: ";
    cin >> palavra;
    fila1.enfileira(palavra);
  }
 
  // Armazena alguns números na fila de inteiros
  for(int x=0; x < 8; x += 1) lista2.enfileira(x);
 
  // Mostra os dados da fila de strings
  while (not fila1.vazia()) {
    cout << "desenfileirou: " << fila1.desenfileira() << endl;
  }
 
  // Mostra os dados da fila de inteiros
  while (not fila2.vazia()) {
    cout << "desenfileirou: " << fila2.desenfileira() << endl;
  }
 
}

Passagem de parâmetros

A passagem de parâmetros em C++ pode ser feita de duas maneiras:

  1. Por valor: o parâmetro recebe uma cópia do valor que foi passado. Se o parâmetro for alterado dentro da função, o valor original não é modificado.
  2. Por referência: o parâmetro é uma referência ao valor que foi passado. Se o parâmetro for alterado, o valor original também será modificado.

OBS: o caso de um parâmetro passado por ponteiro funciona da mesma forma que em C. No caso, se o o valor apontado pelo ponteiro for alterado dentro da função, o valor original será modificado. Isso funciona de forma parecida com a passagem de parâmetro por referência, apesar de haver uma diferença sutil.

Os exemplos a seguir ilustram ambas formas de passagem de parâmetros:

Passagem por valor Passagem por referência
#include <iostream>

using namespace std;

int incrementa(int x) {
  x++;
  return x;
}

int main() {
  int valor = 10;
  int resultado;

  resultado = incrementa(valor);
  cout << "Resultado=" << resultado;
  cout << ", e valor=" << valor << endl;
}
#include <iostream>

using namespace std;

// passagem por referência: observe o operador & 
// antes do nome do parâmetro
int incrementa(int & x) {
  x++;
  return x;
}

int main() {
  int valor = 10;
  int resultado;

  resultado = incrementa(valor);
  cout << "Resultado=" << resultado;
  cout << ", e valor=" << valor << endl;
}



É possível também a passagem de parâmetros por referência, porém somente-leitura. A utilidade disso é aproveitar a eficiência da passagem de parâmetro por referência, que evita uma cópia de valor, porém evitando que o valor original seja modificado dentro da função. Exemplo (experimente compilar estes programas):

Programa 1 Programa 2
#include <iostream>

using namespace std;

int incrementa(const int & x) {
  x++;
  return x;
}

int main() {
  int valor = 10;
  int resultado;

  resultado = incrementa(valor);
  cout << "Resultado=" << resultado;
  cout << ", e valor=" << valor << endl;
}
#include <iostream>

using namespace std;

int incrementa(const int & x) {
  int y = x;

  y++;
  return y;
}

int main() {
  int valor = 10;
  int resultado;

  resultado = incrementa(valor);
  cout << "Resultado=" << resultado;
  cout << ", e valor=" << valor << endl;
}

Alocação dinâmica de memória: operadores new e delete

A alocação dinâmica de memória é uma técnica para reservar e liberar memória por demanda, e sob controle do programador. Ela funciona como se o programador criasse variáveis, porém tivesse que explicitamente destrui-las. Assim, as variáveis não deixariam de existir automaticamente, quando a execução do programa saísse do escopo em que foram criadas.

A alocação dinâmica está diretamente relacionada com variáveis do tipo ponteiro. Ao se alocar memória dinamicamente, obtém-se o endereço inicial da área de memória obtida, e esse endereço deve ser armazenado em um ponteiro. O uso dessa área de memória, portanto, se faz por meio desse ponteiro.

A alocação dinâmica de memória na linguagem C++ se faz com o operador new. Esse operador aloca memória de acordo com o tipo de dados informado, e retorna um ponteiro para essa área de memória. Quando essa área de memória não for mais necessária, ela deve ser liberada com o operador delete. Veja o exemplo a seguir, que mostra o uso dos operadores new e delete em uma situação muito simplificada.

#include <iostream>
#include <string>

using namespace std;

template <typename T> void mostra(const string & varname, T * val) {
  cout << "######################################################" << endl;
  cout << "Dado apontado por " << varname << " = " << *val << endl;
  cout << "Endereço da área de memória usada por " << varname << ": " << (void*)val << endl;
  cout << "Memória alocada: " << sizeof(*val) << " bytes" << endl;
}

int main() {
  // algumas variáveis
  // a memória para elas é alocada automaticamente
  int * x;
  double  * h;

  // aloca dinamicamente memória para um valor do tipo int
  x = new int;

  // aloca dinamicamente memória para um valor do tipo double
  h = new double;

  // armazena valores nas áreas de memória alocadas, usando-se
  // os ponteiros
  *x = 35;
  *h = 6.6260715e-34;

  mostra("x", x); 
  mostra("h", h); 

  // libera as áreas de memória
  delete x;
  delete h;
}

Ao executá-lo, obtém-se isto:

######################################################
Dado apontado por x = 35
Endereço da área de memória usada por x: 0x561931d5de70
Memória alocada: 4 bytes
######################################################
Dado apontado por h = 6.62607e-34
Endereço da área de memória usada por h: 0x561931d5de90
Memória alocada: 8 bytes

Inicialização da área de memória alocada

Uma forma alternativa (e importante, como se poderá observar mais pra frente) de usar o operador new possibilita inicializar a área de memória assim que for alocada. O interessante dessa inicialização é que ela depende do tipo de dados para que foi alocada a memória. Para tipos de dados primitivos, como int, float, double e char, essa inicialização se limita a copiar um valor inicial. Veja como ficaria a alocação de memória do exemplo anterior com esse uso do operador new:

  // aloca dinamicamente memória para um valor do tipo int e depois para um double
  // e armazena valores nas áreas de memória alocadas
  x = new int(35);

  // aloca dinamicamente memória para um valor do tipo double
  h = new double(6.6260715e-34);

  mostra("x", x); 
  mostra("h", h);

No caso de um tipos de dados que possui um construtor, que é uma função-membro executada automaticamente quando se cria um valor desse tipo, o operador new o executa logo após alocar memória.

#include <iostream>
#include <string>

using namespace std;

struct Nome {
  string nome, sobrenome;

  // construtor: separa um nome completo, 
  // armazenando nome e sobrenome nos respectivos atributos
  Nome(const string & nome_completo) {
    int pos = nome_completo.find(' ');
    nome = nome_completo.substr(0, pos);
    int pos2 = nome_completo.find_first_not_of(" ", pos);
    sobrenome = nome_completo.substr(pos2);
  }
};

int main() {
  auto alguem = new Nome("Dona Bilica da Silva");

  cout << "Nome: " << alguem->nome << endl;
  cout << "Sobrenome: " << alguem->sobrenome << endl;

  delete alguem;
}

Alocação de array (vetor)

O exemplo a seguir mostra a alocação de memória para um vetor (array), em que o operador new aloca memória com capacidade de armazenar múltiplos valores de um certo tipo. Além disso, o exemplo mostra a persistência da área alocada de memória fora do escopo em que ela foi criada:

#include <iostream>
#include <string>

using namespace std;

// cria um vetor de inteiros com tamanho "size" e preenche
// todas suas posições com "valor_inicial"
int * cria_vetor(int size, int valor_inicial) {
  if (size <= 0) return nullptr;

  // cria um vetor com o tamanho dado por "size"
  auto v = new int[size];

  // preenche o vetor com o valor inicial
  for (int i=0; i < size; i++) v[i] = valor_inicial;
  return  v;
}

int main() {
  int size;

  cout << "Qual o tamanho do vetor: ";
  cin >> size;

  // v é um ponteiro para int
  auto v = cria_vetor(size, 99);

  cout << "Endereço do vetor: " << (void*)v << endl;
  cout << "Conteúdo do vetor: ";
  for (int x=0; x < size; x++) cout << v[x] << ',';
  cout << endl;

  // libera a memória do vetor
  delete[] v;
}

Um exemplo de execução é mostrado a seguir:

Qual o tamanho do vetor: 5
Endereço do vetor: 0x561a4d6a4690
Conteúdo do vetor: 99,99,99,99,99,


O uso do operador new nesse exemplo foi um pouco diferente. Como se pode notar, para criar um vetor indicou-se sua capacidade entre colchetes, logo após o tipo de dados. Isso pede que se aloque memória suficiente para conter aquela quantidade de valores do tipo informado:

// aloca memória suficiente oara guardar "size" valores do tipo int
auto v = new int[size];

Além disso, a função cria_vetor retorna como resultado o vetor que foi alocado. Isso mostra que a área de memória reservada para o vetor sobrevive ao final da função cria_vetor. Essa área de memória continuará alocada até que o operador delete a libere (o que se faz ao final da função main) ... ou até que o programa termine.

Resumo

  • O operador new aloca uma área de memória com capacidade suficiente para um tipo de dados indicado, e retorna um ponteiro para essa área de memória
  • Após alocar a memória, o operador new a inicializa ao executar o construtor para o tipo de dados
  • Toda área de memória alocada deve ser liberada usando o operador delete, quando não for mais necessária

Funções template


Funções, assim como classes, podem ser template, em que um ou mais tipos de dados são desconhecidos em tempo de programação. Em uma função template, os tipos de dados de um ou mais parâmetros, ou do valor devolvido como resultado, são desconhecidos em tempo de projeto. Outra forma de ver funções template é perceber que o algoritmo contido na função funciona com (quase) qualquer tipo de dados.

Por exemplo, imagine que uma função deva ser capaz de ordenar um vetor qualquer, independente do tipo de dados desse vetor. Tal função poderia ser declarada com um template como este:

// Função template "ordena": ordena um "vetor" de comprimento "N"
template <typename T> void ordena(T vetor[], int N);

Sua implementação poderia ser esta:

template <typename T> void ordena(T vetor[], int N) {
  int i, j;

  for (i=0; i < N-1; i++) {
    for (j=i+1; j < N; j++) {
      if (vetor[i] > vetor[j]) {
        T aux = vetor[i];
        vetor[i] = vetor[j];
        vetor[j] = aux;
      }
    }
  }     
}

A utilização de uma função template implica informar o tipo de dados desconhecido. Isso se faz ao se usar a função:

int main() {
  long v1[5] = {45, 72, 12, 8, 0};
  string v2[7] = {"banana", "abacate", "sapoti", "maracuja", "abacaxi", "caju", "goiaba"};

  // aqui se usa a função ordena, e se informa que o tipo genérico é long
  ordena<long>(v1, 5);

  // ... e aqui o tipo genérico é string
  ordena<string>(v2, 7);
}

string


Na linguagem C++ podem-se trabalhar com duas representações de string:

  • vetor de char (char * ou char []): essa forma é a mesma utilizada na linguagem C.
  • classe string: objetos da classe string implementam strings com diversas facilidades, possibilitando um maior nível de abstração (fica mais fácil).


A classe string foi projetada para que se possa trabalhar com strings de forma simplificada. Por exemplo, o programa abaixo mostra como criar, mostrar na tela e concatenar strings:

Usando classe string Usando vetor de char
#include <string>
#include <iostream>

using namespace std;

int main(){
  // declaração dos objetos string: 
  // inicialmente eles são com strings vazias
  string nome;
  string sobrenome;

  // atribui "Manuel" ao objeto string "nome", 
  // e "Alexievitvh" a "sobrenome"
  nome = "Manuel"
  sobrenome = "Alexievitch";

  // cria um novo objeto string, e o inicia com o 
  // resultado da concatenação de nome e sobrenome
  string nome_completo = nome + ' ' + sobrenome;

  // mostra o objeto string na tela
  cout << "Nome: " << nome_completo << endl;
}
#include <string.h>
#include <iostream>

using namespace std;

int main(){
  char nome[16];
  char sobrenome[32];

  strncpy(nome, "Manuel", 16);
  strncpy(sobrenome, "Alexievitch", 32);

  char nome_completo[64];
  strcpy(nome_completo, nome);
  nome_completo[strlen(nome_completo)] = ' ';
  nome_completo[strlen(nome_completo)] = 0;
  strcat(nome_completo, sobrenome);

  cout << "Nome: " << nome_completo << endl;
}


A classe string oferece diversas operações, tais como:

  • size: Obter o comprimento da string
  • operator[]: Obter um caractere em uma determinada posição. Esse tipo de acesso é sintaticamente idêntico ao acesso a uma posição de um vetor
  • operator+=: anexa uma string
  • push_back: anexa um caractere
  • erase: remove caracteres de uma string
  • replace: substitui parte de uma string
  • c_str: retorna uma representação em vetor de char
  • find: encontra a primeira ocorrência de uma substring ou um caractere
  • rfind: encontra a última ocorrência de uma substring ou um caractere
  • substr: gera uma substring

Lendo ou escrevendo strings em streams

Podem-se ler ou escrever strings em streams usando os operadores << ou >>. Por exemplo, para ler uma string da entrada padrão (ex: teclado), pode-se fazer assim:

string algo;

// lê uma string da entrada padrão e grava-a em "algo"
cin >> algo;


Para escrever uma string em um stream (ex: um arquivo), pode-se faz desta forma:

// abre um arquivo para escrita
ofstream arq("teste.txt");

string algo = "Um teste";

// escreve a string no arquivo
arq << algo;


No caso da leitura de uma string que contenha espaços, ou mesmo de uma linha completa, deve-se usar a função getline:

string linha;

cout << "Escreva uma frase: ";

getline(cin, linha);

cout << "Frase digitada é: " << linha;

A função getline usa o caractere newline (\n) como delimitador. Se for desejado usar outro caractere como delimitador, deve-se fazer o seguinte:

string linha;
char delimitador=',';

cout << "Escreva uma frase que contenha ao menos uma vírgula: ";

getline(cin, linha, delimitador);

cout << "Frase digitada até a vírgula é: " << linha;

Conversão de string para tipos numéricos


Em C++ há diferentes formas de converter strings para dados numéricos, e vice-versa. Podem-se, por exemplo, usar as funções da biblioteca C padrão (sscanf, snprintf, strtod, ...). Mas uma forma particular existente em C++ explora o conceito de streams.

Arquivos abertos são acessados como streams em C++. Na verdade, essa é uma interface corriqueira para acesso a arquivos, estando disponível em diferentes linguagens (ex: as operações de arquivo em C, como fscanf, fprintf, fread, fwrite, fgets, ...). Em C++ streams possibilitam ler texto de um arquivo e convertê-lo para um dado numérico, e vice-versa. Isso é feito normalmente em programas, como no exemplo abaixo:

#include <iostream>

using namespace std;

int main() {
  ifstream arq("numeros.txt");
  int numero;
  string palavra;

  arq >> numero;
  arq >> palavra;

  cout << "numero=" << numero << endl;
  cout << "palavra=" << palavra << endl;

  arq.close();

  return 0;
}


Se o arquivo numeros.txt possuir este conteúdo:

2015 Furious
1999 Matrix


... a execução do programa exemplo, que lê apenas a primeira linha, resultará em:

numero=2015
palavra=Furious


Assim, o operador >> de streams possibilita converter texto para um outro tipo de dado, dependendo do tipo da variável onde o resultado deva ser gravado. No exemplo, esse operador é usado duas vezes:

arq >> numero; // converte texto para inteiro, pois "numero" 
               // é uma variável do tipo int
arq >> palavra;// converte texto para string, pois palavra é do tipo string


Parece que C++ possibilita converter facilmente texto (string) para dado numérico SE o texto estiver guardado em um arquivo. Isso não é conveniente para converter um valor que esteja guardado em uma variável string, pois tornaria necessário escrevê-la em um arquivo temporário para depois ler desse arquivo e efetuar a conversão. De fato, se fosse assim dificilmente streams seriam usadas para converter dados em C++. No entanto existe um tipo de stream que possibilita que se acesse uma string como se fosse um arquivo. Quer dizer, a leitura e a escrita dos caracteres de uma string podem ser feitas com operações de arquivo. O exemplo abaixo mostra o uso dessa stream de string (ou stringstream):

#include <iostream>
// as classes stringstream estão em sstream
#include <sstream>

using namespace std;

int main() {
  istringstream stream("2015 Furious");
  int numero;
  string palavra;

  stream >> numero;
  stream >> palavra;

  cout << "numero=" << numero << endl;
  cout << "palavra=" << palavra << endl;

  return 0;
}


O exemplo mostra uma stringstream criada com conteúdo 2015 Furious. Em seguida, usa-se o operador >> para ler um número inteiro e uma string. O resultado desse exemplo é idêntico ao do exemplo anterior, em que se liam esses dados de um arquivo.

A possibilidade de usar stringstream para converter dados torna simples criar funções de conversão, como esta que converte string para inteiro:

int converte_para_int(const string & dado) {
  istringstream stream(dado);
  int r;

  stream >> r;
  if (stream.fail()) throw 1; // erro de conversão

  return r;
}


Outras funções para tipos numéricos podem ser criadas da mesma forma:

float converte_para_float(const string & dado) {
  istringstream stream(dado);
  float r;

  stream >> r;
  if (stream.fail()) throw 1; // erro de conversão

  return r;
}


double converte_para_double(const string & dado) {
  istringstream stream(dado);
  double r;

  stream >> r;
  if (stream.fail()) throw 1; // erro de conversão

  return r;
}


Note que são todas muito parecidas. A única diferença entre elas é o tipo do dado numérico da conversão. Assim, é natural substitui-las por uma função template:

template <class T> T converte(const string & dado) {
  istringstream stream(dado);
  T r;

  stream >> r;
  if (stream.fail()) throw 1; // erro de conversão

  return r;
}


Essa função template seria usada da seguinte forma:

#include <iostream>
#include <sstream>

using namespace std;

template <class T> T converte(const string & dado) {
  istringstream stream(dado);
  T r;

  stream >> r;
  if (stream.fail()) throw 1; // erro de conversão

  return r;
}

int main() {
  string algo1 = "2015";
  string algo2 = "3.1416";
  string algo3 = "xyz";
  int ano;
  float pi;

  ano = converte<int>(algo1);
  pi = converte<float>(algo2);

  cout << "ano=" << ano << endl;
  cout << "pi=" << pi << endl;

  try {
    ano = converte<int>(algo3);
    cout << "Outro ano=" << ano << endl;
  } catch (int e) {
    cout << "Erro de conversão: " << algo3 << " não é um int" << endl;
  }
  return 0;
}

streams e arquivos

Em diversas linguagens de programação o conceito de stream (fluxo ou correnteza) é utilizado para o acesso a arquivos em disco. Por stream entende-se um mecanismo e leitura e escrita em que dados fluem sucessivamente. Dessa forma, ao se escrever em um arquivo fazem-se sucessivas operações de escrita, e os dados são armazenados na ordem em que essas operações se realizaram. Algo parecido acontece na leitura de um arquivo, em que as operações de leitura realizadas fornecem dados de acordo com a ordem em que estão gravados no arquivo.


O acesso a arquivos apresenta algumas diferenças nas linguagens C e C++. Em C++ existem algumas classes para a manipulação de arquivos. Desta forma, arquivos nesta linguagem são acessados por meio de objetos dessas classes. No exemplo abaixo, apresentam-se dois programas em C e C++ para ler as linhas de um arquivo e mostrá-las na tela.


Linguagem C Linguagem C++
#include <stdio.h>
#include <errno.h>

#define MAX_SIZE 10240

int main() {
  FILE * arq;
  char linha[MAX_SIZE];

  // abre o arquivo
  arq = fopen("/etc/hosts", "r");

  // testa se conseguiu abrir o arquivo
  if (arq == NULL) {
    perror("Ao abrir /etc/hosts");
    return errno;
  }

  // lê cada linha do arquivo, mostrando-a na tela
  while (fgets(linha, MAX_SIZE, arq)) {
    printf("%s", linha);
  }

  // fecha o arquivo
  fclose(arq);
}
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <errno.h>
 
#define MAX_SIZE 10240

using namespace std;
 
int main() {
  // cria o objeto "arq" da classe "ifstream", que representa um arquivo para leitura
  // a criação desse objeto já abre o arquivo
  ifstream arq("/etc/hosts");
 
  // verifica se o arquivo foi de fato aberto
  if (! arq.is_open()) {
    perror("Ao abrir /etc/hosts");
    return errno;
  }

  // lê cada linha do arquivo, apresentando-a na tela
  string linha;
  while (getline(arq, linha)) {
    cout << linha << endl;
  }

  // observe que o arquivo não precisa ser fechado ... isso ocorre automaticamente
  // quando o objeto for destruído, ficando a cargo de seu destrutor
}


Os exemplos acima demonstram que a sintaxe no acesso a arquivos pode ser diferente, porém funcionalmente parece haver uma equivalência entre as duas formas de acesso. De fato tudo que se consegue fazer em C++ é possível fazer em C, mudando apenas a praticidade ou conveniência na forma com que se expressa isso nessas linguagens. Porém como a linguagem C já é bem conhecida por vocês, interessa ver o que se pode fazer em C++.

Em C++ existem três classes para acessar arquivos:

  • ofstream: acesso a arquivos em modo escrita.
Exemplo
#include <iostream>
#include <fstream>

using namespace std;

int main() {
  ofstream arq("demo.txt");

  if (not arq.is_open()) {
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
    return 0;
  }

  arq << "Iniciando uma gravação de várias linhas inúteis ..." << endl;

  for (int i=0; i < 10; i++) {
    arq << "Linha " << i << endl;    
  }
}
  • ifstream: acesso a arquivos em modo leitura.
Exemplo
#include <iostream>
#include <fstream>

using namespace std;

int main() {
  ifstream arq("/etc/hosts");

  if (not arq.is_open()) {
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
    return 0;
  }

  while (not arq.eof()) {
    string algo;

    arq >> algo;
    cout << "Leu isto: " << algo << endl;
  }
}
  • fstream: acesso a arquivos em modo leitura/escrita.
Exemplo
#include <iostream>
#include <fstream>

using namespace std;

#define MAX_LINE 10240

int main() {
  fstream arq("demo.txt");

  if (not arq.is_open()) {
    cerr << "Algum erro ao abrir o arquivo ..." << endl;
    return 0;
  }

  for (int i=0; i < 10; i++) {
    arq << "Linha " << i << endl;    
  }
  arq.flush();

  arq.seekg(0);
  
  while (not arq.eof()) {
   char linha[MAX_LINE];

   arq.getline(linha, MAX_LINE);
   cout << "Leu: " << linha << endl;
  }
}


O interessante em C++ é que a saída padrão e a entrada padrão são representados por objetos stream. De fato, veja como estão declarados esses objetos na biblioteca C++ padrão:

  • cout: saída padrão
  • cin: entrada padrão
  • cerr: saída de erros padrão

Leitura e escrita formatada

streams implementam leitura e escrita formatadas por meio dos operadores >> (leitura) e << (escrita). Esses operadores fazem a conversão automática entre string e outros tipos de dados.

A leitura de um stream pode ser feita como no exemplo a seguir:

int x;

cout << "Digite um número inteiro: ";

// aqui se faz a leitura de um número inteiro
cin >> x;

cout << "Você digitou " << x << endl;


Nesse exemplo, a leitura é feita de cin, uma stream que representa a console (usualmente o teclado). Lê-se um número inteiro digitado pelo usuário, o qual é armazenado na variável x. A conversão de string para inteiro é feita automaticamente, porque a variável onde se deseja armazenar o valor lido é do tipo int. Assim, a conversão depende do tipo da variável onde se deve armazenar o valor lido. Este outro exemplo reforça a questão da conversão de tipo:

char algo;
int numero;

cout << "Digite um caractere qualquer seguido de um número (ex: !33): ";
cin >> algo;
cin >> numero;

cout << "Você digitou: " << algo << numero << endl;


Nesse novo exemplo, dois valores são lidos: um valor do tipo char seguido de um int. O primeiro valor corresponde a um único caractere, e o segundo a um ou mais caracteres numéricos consecutivos. As conversões são desencadeadas pelos tipos das variáveis usados nas leituras. Algo similar acontece quando se escrevem dados em um stream.


O exemplo a seguir mostra a escrita de alguns valores em cout, um stream que representa também a console (usualmente a tela):

double pi = 3.1416;
char letra = 'H';

// escreve um valor do tipo char
cout << letra;

// escreve um valor do tipo string (char*)
cout << " = ";

// escreve um valor do tipo double
cout << pi;

// endl corresponde a \n (newline)
cout << endl;


Como mostra o exemplo, são escritos valores do tipo char, , char* e double. Em todos os casos, a conversão automática entre esses valores e string depende do tipo da variável ou constante onde está o valor a ser escrito.

Lendo linha por linha

Este outro exemplo mostra uma forma simples de ler todas as linhas de um arquivo. A função getline extrai uma linha a cada vez que é chamda. Neste exemplo, o efeito final será ler linha por linha do arquivo:

string linha;
ifstream arq("teste.txt");

// enquanto conseguir ler uma string do arquivo ...
// quando chegar ao fim do arquivo, a leitura irá falhar
while (getline(arq, linha)) {
  // faz algo com o conteúdo de "linha"
}

Lendo palavra por palavra

Este outro exemplo mostra uma forma simples de ler todos os valores de um arquivo. O operador >> extrai um valor a cada vez que é executado sobre o arquivo. Neste exemplo, o efeito final será ler palavra por palavra do arquivo:

string palavra;
ifstream arq("teste.txt");

// enquanto conseguir ler uma string do arquivo ...
// quando chegar ao fim do arquivo, a leitura irá falhar
while (arq >> palavra) {
  // faz algo com o conteúdo de "palavra"
}

Deve-se notar que o operador >> considera como delimitadores os caracteres brancos (espaço, TAB, newline).


A escrita e leitura formatada está implementada para os tipos elementares de C e C++ (int, long, short, char, char*, string, bool, float, double). Isso significa que essas operações não funcionam para novos tipos de dados ou classes classes, a não ser que se implementem esses operadores de leitura e escrita para esses novos tipos.

Operações de streams

streams oferecem várias operações para leitura e escrita de dados. Algumas mais comuns são listadas a seguir, junto com exemplos.

Operação Entrada Saída Para que serve Exemplo
>> x leitura formatada
int x;
double y;
string palavra;

// lê um número inteiro
cin >> x;

// lê um número double
cin >> y;

// lê uma string
cin >> palavra;
<< x escrita formatada
int x = 5;
double y=3.1416;
string palavra="alguma coisa";

// escreve um número inteiro seguido de uma vírgula
cout << x << ", ";

// escreve um número double seguido de uma vírgula
cout << y << ", ";

// escreve uma string seguida de nova linha
cout << palavra << endl;

// outra forma de escrever os três valores 
cout << x << ", " << y << ", " << palavra << endl;
getline x lê uma linha (até encontrar '\n')
string linha;

getline(cin, linha);

cout << "Linha: " << linha << endl;
ignore x lê e descarta caracteres
cout << "Tecle ENTER para continuar";
cin.ignore(256, '\n'); // lê e descarta até 256 caracteres, ou até ler '\n'
eof x x Verifica se chegou ao fim da stream
ifstream arq("/etc/hosts");

while (true) {
  string linha;

  getline(arq, linha);
  if (arq.eof()) break; // se não conseguir ler algo (fim de arquivo)
  cout << linha << endl;
}
is_open x x Verifica se arquivo foi aberto
ifstream arq("/etc/hosts");

if (not arq.is_open()) {
  cout << "Erro: não abriu o arquivo" << endl;
  return 0;
}

Acessando strings como se fossem arquivos

Em C++ esse conceito de stream é levado um pouco além. Além de arquivos e a entrada e saída padrão, é possível também manipular strings como se fossem streams. Quer dizer, pode-se escrever em uma string (criá-la) ou extrair partes de uma string como se estivesse acessando um arquivo. Isso possibilita que funções ou objetos que escrevem ou lêem em arquivos possam fazer o mesmo em memória, o que pode ser útil em diferentes programas.

O uso de strings como se fossem streams é implementado por três classes:


O exemplo mais simples envolve criar uma string a partir dos valores de algumas variáveis. Veja como isso era feito em C e como se faz em C++:

Linguagem C Linguagem C++
#include <stdio.h>

int main() {
  int dia, mes, ano;
  char data[32];

  printf("Dia: ");
  scanf("%d", &dia);
  printf("Mes: ");
  scanf("%d", &mes);
  printf("Ano: ");
  scanf("%d", &ano);

  snprintf(data, 32, "%d/%d/%d", dia, mes, ano);

  printf("Data: %s\n", data);

}
#include <iostream>
#include <sstream>

using namespace std;

int main() {
  int dia, mes, ano;

  cout << "Dia: ";
  cin >> dia;
  cout << "Mes: ";
  cin >> mes;
  cout << "Ano: ";
  cin >> ano;

  //cria-se uma stringstream para escrita
  ostringstream out;

  // escreve-se na stringstream
  out << dia << "/";
  out << mes << "/";
  out << ano;

  // Aqui se obtém o conteúdo armazenado na stringstream
  cout << "Data: " << out.str() << endl;
}


Estes outros exemplos mostram a extração de dados que existem em strings:

Linguagem C Linguagem C++
#include <stdio.h>
 
int main() {
  int dia, mes, ano;
  char data[32];

  printf("Data (dia/mes/ano): ");
  fgets(data, 32, stdin); // lê uma linha

  // extrai os dados da linha
  sscanf(data, "%d/%d/%d", &dia, &mes, &ano);

  // Aqui se mostram os dados extraídos da linha
  printf("Dia: %d\n", dia);
  printf("Mes: %d\n", mes);
  printf("Ano: %d\n", ano);
}
#include <iostream>
#include <sstream>
 
using namespace std;
 
int main() {
  int dia, mes, ano;
  char data[32];

  cout << "Data (dia/mes/ano): "; 
  cin.getline(data, 32);

  //cria-se uma stringstream para leitura
  istringstream inp(data);
  char separador;

  // lê-se da stringstream
  inp >> dia;
  inp >> separador;
  inp >> mes;
  inp >> separador;
  inp >> ano;
 
  // Aqui se mostram os dados extraídos da stringstream
  cout << "Dia: " << dia << endl;
  cout << "Mes: " << mes << endl;
  cout << "Ano: " << ano << endl;
}

Sobrecarga de operador

  • Operator overloading: uma explicação mais detalhada sobre o assunto. Inclui também uma tabela com os operadores que podem ser redefinidos.


Operadores C++ podem ser redefinidos, o que se chama sobrecarga de operador (operator overloading). Assim, dependendo do tipo de dados ou da classe do valor, variável ou objeto que recebe a operação, o operador pode funcionar de uma forma específica. Um operador pode ser implementado como uma função, que pode ou não ser membro de uma classe.


O nome da função que implementa um operador é formado pela palavra chave operator seguida do operador que se deseja implementar. Por exemplo, para o operador == a função se chama operator==. O tipo do valor de retorno dessa função depende do operador a ser implementado (ex: o operador == retorna um valor booleano, mas o operador = retorna uma referência à variável ou objeto que foi alvo da operação). A quantidade de parâmetros dessa função também dependem do operador em questão, e os tipos de desses parâmetros dependem dos tipos de dados envolvidos na operação.


Um operador pode ser implementado de duas formas:

  1. Como um método de uma classe: um método implementa a operação a ser realizada quando se usa o operador com um objeto dessa classe. A execução desse método acontece no escopo do objeto que recebe a operação (ex: em operadores unários é o próprio objeto alvo do operador, e em operadores binários é o objeto que aparece à esquerda do operador).
  2. Como uma função: uma função implementa a operação a ser realizada quando se usa o operador. Essa função recebe como parâmetros as variáveis ou objetos envolvidos na operação.


Dois exemplos são apresentados para esclarecer a sobrecarga de operadores usando funções que não são membros de classe (i.e. não são métodos):

  • Operador unário !: o operador ! corresponde à operação NÃO, mas ele pode ser redefinido para um tipo de dados específico. Suponha-se que se deseje que o operador ! aplicado a uma string deve retornar true se ela for vazia, e false caso contrário. Isso pode ser feito redefinido o operador ! para string da seguinte forma:
bool operator!(const string & s) {
  if (s.size() == 0) return true;
  return false;
}
  • Operador binário ==: o operador == serve para comparar dois valores, resultando em true se eles forem considerados iguais, e false caso contrário. Suponha-se que se deseje que esse operador, quando aplicado a string, resulte em true se duas string tiverem mesmo comprimento. Isso pode ser feito da seguinte forma:
bool operator==(constr string & s1, const string & s2) {
  return s1.size() == s2.size();
}


Esses dois operadores podem ser exemplificados também como métodos uma classe. Usando-se a classe Fila, ambos operadores podem ser implementados da seguinte forma. O operador unário ! deve retornar true se fila estiver vazia, e o operador == deve retornar true se as filas forem iguais:

template <typename T> class Fila {
 private:
  // atributos da fila
 public:
  // métodos da fila

  bool operator!() const {
    return vazia();
  }

  bool operator==(const Fila<T> & outra) const {
    // para serem iguais, devem ter mesma quantidade de dados
    if (itens == outra.itens) { 
      // ... e os dados devem ser iguais e estarem na mesma ordem
      int n = itens;

      while (n > 0) {
        T dado1 = desenfileira();
        T dado2 = outra.desenfileira();
        enfileira(dado1);
        enfileira(dado2);
        if (dado1 != dado2) return false;
        n--;
      }
      return true;
    }
    return false;
  }
};

Sobrecarga de operadores << e >> de streams

Pode-se desejar que objetos de uma classe possam ser escritos em streams diretamente por meio do operador <<. Para descobrir uma solução para esse problema, deve-se entender como o operador << está definido. E basicamente ele está implementado de duas formas:

  1. Na classe ostream: a classe ostream representa arquivos/streams abertos para escrita, e já possui implementações desse operador para tipos básicos de C++.
  2. Em funções de sobrecarga de operador (operator overloading): podem-se definir funções que implementam esse operador para novos tipos/classes. Por exemplo, existe uma dessas funções para a classe string. Um exemplo de função de sobrecarga de operador para um novo tipo de dados segue abaixo:
    #include <iostream>
    
    using namespace std;
    
    // Define-se um novo tipo de dados
    struct Ponto {
      int x,y;
    };
    
    // Define-se o operador << de ostream  para poder escrever um Ponto.
    // Esse operador mostra o ponto da seguinte forma: (x,y)
    ostream& operator<<(ostream &out, const Ponto& p) {
      out << "(" << p.x << "," << p.y << ")";
      return out;
    }
    
    // Agora pode-se usar o operador << para valor do tipo "Ponto"
    int main() {
      Ponto p1 = {1,1}, p2 = {10,20};
    
      cout << "Ponto 1: " << p1 << endl;
      cout << "Ponto 2: " << p2 << endl;
    }
    


O mesmo vale para o operador >>. Para que se possa ler diretamente de um stream um valor de um novo tipo de dados ou classe, esse operador precisa ser definido.

Exceções


Condições de erro podem ser avisadas por meio de exceções. Por exemplo, no caso da lista considera-se erro tentar obter um valor em uma posição inexistente. A operação para obter um dado da lista deve, portanto, comunicar de alguma forma que esse acesso inválido aconteceu. Outro exemplo mais simples envolve a conversão de uma string numérica com a função stoi. Se a string não contiver um número inteiro, ocorre um erro. O programa a seguir demonstra esse caso, mostrando o erro:

#include <string>
#include <iostream>

using namespace std;

int main() {
  string n1 = "123";
  string n2 = "abc";
  int x1, x2;

  // esta conversão é feita com sucesso
  x1 = stoi(n1);

  cout << "Primeira conversão: " << x1 << endl;

  // ... mas esta causa um erro 
  x2 = stoi(n2);

  cout << "Segunda conversão: " << x2 << endl;

  return 0;
}


Ao se compilar e executar esse programa, obtém-se este resultado, que revela o erro de execução:

aluno@curl:~$ g++ -o erro -std=c++11 erro.cpp 
aluno@curl:~$ ./erro
Primeira conversão: 123
terminate called after throwing an instance of 'std::invalid_argument'
  what():  stoi
Abortado (imagem do núcleo gravada)


O erro manifestado no programa, e que causou sua terminação abrupta, se chama exceção. O mecanismo de exceções existente na linguagem C++ possibilita expressar e tratar condições de erro de uma forma mais prática. Um algoritmo pode disparar uma exceção se detectar uma situação anômala, interrompendo a execução do programa. Essa interrupção causa a execução de um outro trecho de código, responsável por tratar a exceção. Sendo assim, na parte do código onde pode ocorrer uma exceção, deve-se capturá-la e tratá-la. Para isso usa-se a construção try ... catch, que possibilita executar um tratador de exceção. No exemplo de conversão de string numérica, o uso de try .. catch ficaria assim:

  string n1 = "123";
  string n2 = "abc";
  int x1, x2;

  try {
    // esta conversão é feita com sucesso
    x1 = stoi(n1);
    cout << "Primeira conversão: " << x1 << endl;
  } catch (...) {
    cout << "Erro de conversão: " << n1 << " não é uma string numérica inteira !" << endl;
  }

  try {
    // esta conversão dispara uma exceção
    x2 = stoi(n2);
    cout << "Segunda conversão: " << x2 << endl;
  } catch (...) {
    cout << "Erro de conversão: " << n2 << " não é uma string numérica inteira !" << endl;
  }

  return 0;


No exemplo acima, o bloco try .. catch foi usado de forma a capturar qualquer exceção, o que se define com catch (...). No entanto, exceções podem ser de diferentes tipos, tais como números, string, ou mesmo objetos. Uma exceção portanto é representada por algum valor informativo, e esse valor pertence a algum tipo de dados ou classe. Com isso, diferentes tipos de exceção podem ser disparadas em um trecho de código, e para capturá-las e tratá-las individualmente é necessário definir tratadores de exceção específicos. Por exemplo, considere que um trecho de código possa disparar exceções do tipo int, string, e possivelmente outras. A forma de tratá-las poderia ser assim:

  try {
    // trecho de código que pode disparar diferentes tipos de exceções
  } catch (int e1) { 
    // aqui se tratam exceções do tipo int
  } catch (string e2) {
    // aqui se tratam exceções do tipo string
  } catch (...) {
    // aqui se tratam demais exceções de tipos quaisquer
  }


Por outro lado, quando se programa um algoritmo (embutido em uma função ou método de classe), erros ou situações inesperadas que forem detectadas implicam o disparo de uma exceção por meio da palavra chave throw. O valor da exceção pode ser qualquer, e no exemplo a seguir é representado por um número inteiro:

void maiusculas(string & palavra) {
  if (palavra.size() == 0) throw 1; // string vazia !!!

  for (int i=0; i < palavra.size(); i++) palavra[i] = toupper(palavra[i]);
}


OBS: se uma exceção não for capturada usando try ... catch, ela causará o término do programa.