62
Escola Politécnica da Universidade de São Paulo Laboratório de Programação Orientada a Objetos para Engenharia Elétrica Aula 9: Manipulação de dados e operações em arquivos PCS3111

Laboratório de Programação Orientada a Objetos para … · Agenda 1. Noções de arquivos 2. Entrada e Saída (modo texto) 3. Entrada e Saída (modo binário) 4. Visão geral C

Embed Size (px)

Citation preview

Escola Politécnica da Universidade de São Paulo

Laboratório de Programação

Orientada a Objetos para

Engenharia Elétrica

Aula 9: Manipulação de dados e

operações em arquivos

PCS3111

Agenda

1. Noções de arquivos

2. Entrada e Saída (modo texto)

3. Entrada e Saída (modo binário)

4. Visão geral C C++

5. Abertura de arquivos e tratamento de erros

2

Noções de arquivos

3

Arquivo

Noção intuitiva

• Sequência de bytes (stream)

• Armazenamento não-volátil

• Identificados por uma string (nome de arquivo)

Visão geral

• Muito mais abrangente do que simplesmente

armazenamento

• Abstração unificadora em sistemas operacionais

Abstração de hardware (rede, video, hds, ...)

Comunicação entre processos

Obs.: processo = programa em execução

4

Sistema Operacional

Camada de software responsável por fornecer

serviços básicos de forma unificada a

programas em execução

Responsável pelos detalhes de manipulação de

arquivos

Disponibiliza uma API unificada para operações

sobre arquivos

5

Sistema Operacional

Comunicação

1. O processo requisita ao SO a abertura de um arquivo

2. O S.O. realiza a abertura do arquivo e devolve ao

processo um identificador do arquivo aberto

3. O processo pede ao SO uma operação (leitura/escrita)

utilizando o identificador obtido na etapa 2

4. O processo requisita ao SO o fechamento de arquivo

Algumas mudanças podem não ter sido efetuadas sobre o

arquivo

Número limitado de arquivos que podem ser abertos por um

processo

6

Tipos de Arquivo

Seqüencial

• Exemplo: fitas magnéticas

• Necessário percorrer toda a seqüência de bytes em

busca de uma informação

Acesso Aleatório

• Exemplo: disco rígido

• Permitem o acesso eficiente a informação em qualquer

posição na sequência de bytes

Dispositivos

• Exemplo: monitor, interface de rede, comunicação serial

• Leitura e escrita realizam ações de E/S

• Geralmente só escrita ou só leitura (dificilmente ambos) 7

Modos de Leitura / Escrita

Modo Texto

• Informações codificadas em texto

• Decodificação automática de caracteres

• Suporte a leitura de linhas e palavras

• Compreensível pelo ser humano

Modo Binário

• Informações codificadas em bytes (similar a memória)

• Dependentes de arquitetura

Little endian (byte menos significativo no menor endereço)

Big endian (byte menos significativo no maior endereço)

• Leitura e escrita rápidas 8

Acesso a arquivo em C

Arquivos padrão

• Entrada Padrão (stdin)

Arquivo aberto para leitura

Fonte de dados para o processo

• Saída Padrão (stdout)

Arquivo aberto para escrita

Saída de dados para o processo

• Saída de Erro (stderr)

Arquivo aberto para escrita

Saída de dados alternativa para o processo (geralmente

para comunicar erros)

9

Arquivos Padrão

Entrada e Saída Padrão podem ser qualquer

tipo de arquivo

A Entrada Padrão de um processo pode ser

conectada à Saída Padrão de um outro

processo

Utilizaremos nessa aula o caso mais simples

• Entrada Padrão

Leituras sobre esse arquivo causam a interrupção do

processo e espera pelo usuário fornecer dados via teclado

• Saída Padrão

Escritas nesse arquivo causam a escrita de dados no

terminal em que o programa está executando

10

Entrada e saída

(modo texto)

11

Entrada e Saída Padrão em C

(Apenas para efeito de comparação)

Também disponível em C++, incluir <cstdio>

No namespace std temos

• stdin → Entrada Padrão

• stdout → Saída Padrão

• stderr → Saída de Erro

stdin, stdout e stderr são variáveis do tipo

FILE*

12

Entrada e Saída Padrão em C

Entrada Padrão

Saída Padrão

13

int i = 0; double x = 0; scanf("%d", &i); // leitura de um inteiro scanf("%lf", &x); // leitura de um ponto flutuante

int i = 42; double x = 3.14159; printf("%d", i); printf("%lf", x);

Leitura de uma linha em C

Necessita de uma região de memória para

armazenar o conteúdo da linha (buffer)

A função fgets precisa ser informada do total

de caracteres

Número máximo de caracteres que serão lidos no

código acima serão 79

Após o último caractere será armazenado o caractere

\0 para seguir a codificação de strings em C

14

char buffer[80]; fgets(buffer, 80, stdin);

Entrada e Saída Padrão em C++

Incluir header <iostream>

No namespace std temos

• cin → Entrada Padrão ("C" in)

• cout → Saída Padrão ("C" out)

• cerr → Saída de Erro ("C" err)

cin é uma variável global do tipo istream

cout e cerr são variáveis globais do tipo

ostream

15

int i = 0; double x = 0; cin >> i; // leitura de um inteiro cin >> x; // leitura de um ponto flutuante

Entrada e Saída Padrão em C++

16

//cin01.cpp #include <iostream> using namespace std; int main() { int i; cout << "Entre i: "; if (cin >> i) cout << "i=" << i << endl; else cout << "Erro de leitura i" << endl; } C:\hae>cin01 Entre i: 20 i=20 C:\hae>cin01 Entre i: vcvdfg Erro de leitura i C:\hae>cin01 Entre i: 123dfdfdwe i=123

Entrada e Saída Padrão em C++

17

//cin03.cpp #include <iostream> using namespace std; int main() { string st; cout << "Entre st: "; cin >> st; cout << "st=" << st << endl; } C:\hae>cin03 Entre st: abc def st=abc Nota: Branco, tab e fim de linha são separadores.

Saída Padrão em C++

18

int i = 42;

double x = 3.14159;

cout << i; // escrita de um inteiro

cout << x; // escrita de um ponto flutuante

cout << "Hello World"; // escrita de uma sequência de

// caracteres terminada em \0

Saída Padrão em C++

19

//cout01.cpp

#include <iostream>

using namespace std;

int main() {

int i = 42;

double x = 3.14159;

cout << i << endl;

cout << x << endl;

cout << "Hello World";

}

Saída:

42

3.14159

Hello World

Redirecionar saída padrão

20

C:\hae>cout01

42

3.14159

Hello World

C:\hae>cout01 > saida.txt

C:\hae>type saida.txt [Linux=cat saida.txt]

42

3.14159

Hello World

C:\hae>

Leitura de uma linha da entrada

padrão em C++ Incluir header <string>

• (using namespace std)

• Usar a função getline

Exemplo

21

string name;

getline(std::cin, name);

Permite ler até o fim da linha, mesmo com branco.

Leitura de uma linha da entrada

Redirecionar entrada

22

//getline01.cpp

#include <iostream>

using namespace std;

int main()

{ string nome;

cout << "Entre nome completo: ";

getline(cin,nome);

cout << "Nome: " << nome << endl;

}

C:\hae>getline01

Entre nome completo: Hae Yong Kim

Nome: Hae Yong Kim

C:\hae>getline01 < getline01.txt

Imprimir para string

23

#include <iostream>

#include <sstream>

using namespace std;

int main()

{ ostringstream sai;

int i=3;

sai << "Testando saida " << "i=" << i << endl;

cout << sai.str();

}

Curiosidade: << e >>

O operador << quando aplicado a números realiza

o shift à esquerda enquanto que o operador >>

realiza o shift à direita

C++ permite a definição de comportamento para a

maioria dos operadores

Os operadores << e >> foram escolhidos para os objetos

ostream e istream por melhor indicarem o deslocamento

de informação

Os operadores << e >> são sobrecarregados

conforme o seu operando

No exemplo anterior para ler ou escrever um int e um

double apresentaram o mesmo formato de invocação 24

Entrada e saída

(modo binário)

25

Modo binário

Usaremos apenas modo texto nesta disciplina

Leitura e escrita de seqüências de bytes

Não há interpretação dos bytes, se quiser um

inteiro terá que compor o valor do inteiro a partir

de seus bytes

Comuns em formatos de mídias (imagens, sons,

vídeos, etc.)

26

Visão geral C C++

27

Visão geral C C++

C

Vantagens

• Amplamente conhecido

por desenvolvedores

• (Imitados por outras

linguagens)

Desvantagens

• Compilador C

normalmente não verifica

• Se o número de

parâmetros estão

corretos

• Se os tipos de

parâmetros passados

estão corretos

C++

Vantagens

• O compilador C++ é

capaz de verificar

sobrecargas

• Sem necessidade de

manipulação de ponteiros

Desvantagens

• Overhead comparado ao

C

• Discutível alteração da

semântica dos operadores

<< e >>

28

Abertura de arquivos e tratamento

de erros

29

Abertura de Arquivos

Limites no uso da entrada e saída padrão

• Entrada e Saída Padrão podem estar associadas a

arquivos voláteis (terminal, /dev/null)

• É comum uma aplicação trabalhar com vários

arquivos

• Log, configuração, internacionalização, arquivos de dados,

etc.

Arquivos em C++

• Incluir header <fstream>

• Objetos std::ifstream para leitura (derivados de

std::istream)

• Objetos std::ofstream para escrita (derivados de

std::ostream)

30

Abertura de arquivo

Formato geral

• in_or_out_stream.open(arquivo, parâmetros)

• Os parâmetros in e out são adicionados

automaticamente no método open para objetos do

tipo ifstream e ofstream respectivamente

31

Parâmetros

(múltiplos possíveis usando “|”)

Significado

app (append) escritas no final de arquivo

ate (at end) move para final após abertura

binary (binary) modo binário

in (input) operações de leitura

out (output) operações de escrita

trunc (truncate) apaga conteúdo atual do arquivo

Leitura

"input" ao sair de escopo fecha o arquivo

automaticamente

(RAII – Resource Aquisition Is Initialization)

32

ifstream input;

input.open("/home/user/teste.txt");

int i;

double x;

input >> i; // lê um valor inteiro

input >> x; // lê um valor ponto flutuante

Leitura

33

//leitura01.cpp

#include <iostream>

#include <fstream>

using namespace std;

int main()

{ ifstream ent("leitura01.txt");

int i; ent >> i;

cout << "i=" << i << endl;

double x; ent >> x;

cout << "x=" << x << endl;

}

2

3.14

C:\hae>leitura01

i=2

x=3.14

O arquivo é fechado automaticamente quando “ent” sai do escopo

pelo destruidor.

Tratamento de erros em C++

Erros ao se trabalhar com arquivos

• Arquivo não existe

• Fim de arquivo

• Erro na leitura (string quando esperava inteiro)

• Erro no dispositivo

34

Tratamento de erros em C++

Baseado na verificação de estado

Muito antes de C++ incorporar exceções

Estado composto por 4 bits (mais de um bit

pode estar ativo)

• goodbit: sem erros

• eofbit: fim de arquivo encontrado em operações de

leitura

• failbit: falha na leitura de um valor; erro lógico

• badbit: erro no dispositivo de E/S

35

Dica: pode-se recuperar de um failbit mas não de um badbit

Tratamento de erros em C++

Métodos auxiliares

Conversão automática para bool ajuda a

verificar erros

36

rdstate() good() eof() fail() bad()

goodbit 1 0 0 0

eofbit 0 1 0 0

failbit 0 0 1 0

badbit 0 0 1 1

Exemplo (média)

37

#include <iostream>

#include <fstream>

using namespace std;

int main() {

ifstream in;

in.open("avg.txt");

if (!in) {

cerr << "Arquivo nao encontrado\n";

return EXIT_FAILURE;

}

double total = 0;

int n = 0;

while (in) {

double x;

in >> x;

if (in) {

total += x;

n++;

}

}

if (in.bad()) {

cerr << "Erro de E/S\n";

return EXIT_FAILURE;

}

if (n > 0) {

in.clear();

double avg = total / n;

cout << "media: "

<< avg << "\n";

}

return EXIT_SUCCESS;

}

Exemplo (média)

38

//media01.cpp

#include <iostream>

#include <fstream>

using namespace std;

int main() {

ifstream in("media01.txt");

if (!in) {

cerr << "Arquivo nao encontrado\n";

exit(1);

}

double total = 0;

int n = 0;

double x;

while (in >> x) {

total += x;

n++;

}

if (n > 0) {

double avg = total / n;

cout << "media: " << avg << endl;

}

}

media.txt

2.5

2.5

3.5

3.5

Saída:

media: 3

Fechamento de arquivos

Um arquivo pode ser desconectado de um

programa, chamando o método close( );

39

#include <fstream>

using namespace std;

const int fileCnt = 3;

const char* fileTable [fileCnt] = { "ClariceLispector.txt",

“MachadoDeAssis.txt”, “ManuelBandeira.txt” };

int main() {

ifstream inFile; // não está associado a nenhum arquivo

for (int ix = 0; ix < fileCnt; ix++) {

inFile.open (fileTable[ix]);

// ... Verifica se a abertura teve sucesso

// ... Processa o arquivo

inFile.close( );

}

}

Exercício

(baseado nas duas últimas aulas)

40

Exercício

Considere um pequeno sistema que simula o

controle de temperatura de um Local

(residência, prédio, etc)

• Atua sobre uma quantidade de espaços (quartos,

salas, escritórios, etc)

• Cada espaço controlado pode conter dispositivos de

refrigeração e também sensores de temperatura e/ou

de presença

Valores podem ser lidos pelo sistema

41

Exercício

O objetivo do exercício é lidar com sensores em um local, tratando dois aspectos básicos do sistema • Gravação de um arquivo de registro (CSV)

A leitura dos dados de cada sensor em cada espaço será simulada por meio de arquivos e o CSV é um arquivo texto

• O sistema deverá possuir um arquivo de configuração, a ser lido ao início de funcionamento do sistema

Obs: Não trataremos de controle de ar-condicionado ou de mudanças na estrutura do local ou dos espaços, apenas SENSORES.

42

Esquema de um Local controlado

43

Sala

“ 55 “

( Ar Condicionado

com Ventilação)

(Sensor de

Temperatura e de

presença)

Copa

“ 11 "

(Ventilador)

(Sensor

Temperatura e de

presença)

Escritorio B

“ 44 “

(Ar condicionado)

(Sensor de

Temperatura)

Escritório A

“ 33 “

(Ar condicionado

com ventilação)

(Sensor de

Temperatura)

Cozinha

“ 22 “

(Ventilador)

(Sensor de

Temperatura e de

presença)

Quarto 4

“ 99 “

(Ventilador)

(Sensor

Temperatura)

Quarto 3

“ 88 “

(Ventilador)

(Sensor

Temperatura)

Quarto 2

“ 77 “

(Ar condicionado)

(Sensor de

Temperatura)

Quarto 1

“ 66 “

( Ar Condicionado com

Ventilação)

(Sensor de

Temperatura)

Características dos Refrigeradores e

Sensores

44

Ar condicionado com

ventilação:

Temperatura: de 10 oC a 30

oC

Ventilação: de 0 a 4

Ventilador:

Não muda a temperatura.

Apenas a percepcão do

usuário.

Ventilação: de 0 a 8.

Ar condicionado simples:

Não tem ventilação.

Temperatura: de 5oC a 30 oC

Sensor de Temperatura/Presença:

Cada espaço controlado pode ter um

ou mais sensores. O sistema coleta

esta informação e o valor lido permite

verificar se o respectivo refrigerados

está funcionando.

Atenção: No exercício, o seu

funcionamento será simulado por

meio de valores aleatórios

Esqueleto

O esqueleto do exercício permite definir sensores

simples com seus identificadores (números

inteiros) e representar o seu estado de alarme em

um painel

O estado do sensor será produzido por meio de

um gerador aleatório

Para iniciar o funcionamento o sistema solicita a

leitura de um arquivo de configuração, que descreve a estrutura do Local

45

Formato do arquivo

A <faixa de valores> depende do tipo do sensor:

• Se o tipo é “T”, então há dois inteiros, mínimo e

máximo

• Se o tipo é “P” então há um inteiro, 1 - ativado, ou 0 -

não

46

<nome do local> <número de espaços> <nome do espaço> <número de sensores> <tipo do sensor> <nome > <faixa de valores> . . . <nome do espaço> <número de sensores> <tipo do sensor> <nome > <faixa de valores> . . .

Exemplo de arquivo de entrada

47

Residencia 3 Sala 3 T SensorTemperatura_1 0 40 T SensorTemperatura_2 0 40 P SensorPresenca_1 1 Quarto 2 T SensorTemperatura_3 0 40 P SensorPresenca_2 1 Banheiro 1 T SensorTemperatura_4 0 40

Exemplo de Painel

48

Residencia(alarme: LIGADO) Sala(alarme: LIGADO) SensorTemperatura_1(0 <= t <= 40) leitura: 10°C seguro: sim SensorTemperatura_2(0 <= t <= 40) leitura: -8°C seguro: nao SensorPresenca_1(ativa o alarme: sim) leitura: MOVIMENTO seguro: nao Quarto(alarme: LIGADO) SensorTemperatura_3(0 <= t <= 40) leitura: 3°C seguro: sim SensorPresenca_2(ativa o alarme: sim) leitura: MOVIMENTO seguro: nao Banheiro(alarme: DESLIGADO) SensorTemperatura_4(0 <= t <= 40) leitura: 17°C seguro: sim

Exemplo de arquivo de saída (CSV)

49

Local;Espaþo;Sensor;Leitura Residencia;Sala;SensorTemperatura_1;-6 Residencia;Sala;SensorTemperatura_2;47 Residencia;Sala;SensorPresenca_1;1 Residencia;Quarto;SensorTemperatura_3;17 Residencia;Quarto;SensorPresenca_2;0 Residencia;Banheiro;SensorTemperatura_4;34

Exercício

Suponha que o Local possa ter dois tipos de sensores

• SensorTemperatura, que dá alarme quando a

temperatura ambiente cai abaixo de um valor limite

• SensorPresença, que dá alarme quando algum

objeto interrompe o feixe de infravermelho

1. Em main, implemente a função carregarArquivo que

ao iniciar ler a configuração de um arquivo, que deverá

ser construído com o padrão descrito anteriormente

2. Implemente a função de CSV no sistema. Ela deverá ser implementada na classe Registro. Construa tanto o

header quanto a implementação

3. Implemente o caso 5 do menu prompt em main 50

Observação sobre o item 3 – Visitor

• Padrão de projeto que permite desvincular a descrição

de uma estrutura da especificação das ações que

devem ser executadas sobre ela;

• Através de uma INTERFACE, torna-se obrigatória a

implementação da ação a ser executada para todos os

elementos (tipos) que compõem a estrutura em questão.

• Cria-se uma INTERFACE com métodos abstratos de

visitação para todos os elementos:

a) Todos os elementos deverão estar associados ao seu

correspondente método de visitação;

b) O nome do método deverá fazer referência ao tipo de

elemento visitado;

c) O método deverá receber como argumento um objeto

correspondente ao tipo de nó visitado.

51

Continuação - Visitor

• Acrescentar, em todas classes que representam os elementos um método de visitação:

a) Todos os métodos deverão ter o mesmo nome;

b) Todos os métodos deverão receber como argumento um objeto da classe Visitor;

c) O corpo desses métodos será composto por um único comando, responsável pela chamada no método de visitação específico, no argumento, conforme o tipo de nó em questão.

• Para cada tipo de operação diferente que se deseje executar sobre um elemento cria-se uma nova classe que implementa a interface VISITOR:

a) Todas as classes assim especificadas deverão conter implementações para todos os métodos relacionados na interface;

b) Cada método é responsável por executar a operação requerida sobre o tipo de elemento a que se refere;

c) A referência THIS deverá ser passada como argumento em toda chamada de métodos de visitação.

52

Exemplo

53

#include <cstdlib>

#include <iostream>

using namespace std;

class Gato;

class Cachorro;

class AnimalVisitor {

public:

virtual void visit(Gato *gato) = 0;

virtual void visit(Cachorro *cachorro) = 0;

};

class Animal {

private:

int idade;

public:

Animal(int age) {

idade = age;

}

virtual void accept(AnimalVisitor *visitor) = 0;

};

class EmissorDeSom : public AnimalVisitor {

void visit(Gato *gato){

cout << "Miau\n";

}

void visit(Cachorro *cachorro){

cout << "Au!\n";

}

};

Exemplo

54

class Cachorro : public Animal {

public:

Cachorro(int age) : Animal(age) {

}

void accept(AnimalVisitor *visitor){

visitor->visit(this);

}

};

class Gato : public Animal {

public:

Gato(int age) : Animal(age) {

}

void accept(AnimalVisitor *visitor){

visitor->visit(this);

}

};

void emitirSom(Animal *animal){

AnimalVisitor *visitor = new EmissorDeSom();

animal->accept(visitor);

// faltou delete

}

int main(int argc, char** argv) {

Gato gato(3);

Cachorro cachorro(5);

Animal* animal[2] = {&gato, &cachorro};

for (int i = 0; i < 2; i++)

emitirSom(animal[i]);

return 0;

}

55

//animal03b.cpp #include <cstdlib> #include <iostream> using namespace std; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< class Gato; class Cachorro; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< class AnimalVisitor { public: virtual void visit(Gato *gato) = 0; virtual void visit(Cachorro *cachorro) = 0; virtual ~AnimalVisitor() {} // Faltou }; class EmissorDeSom : public AnimalVisitor { void visit(Gato *gato); void visit(Cachorro *cachorro); }; class ImprimirIdade : public AnimalVisitor { void visit(Gato *gato); void visit(Cachorro *cachorro); };

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< class Animal { public: int idade; Animal(int age) : idade(age) {} virtual void accept(AnimalVisitor *visitor) = 0; }; class Cachorro : public Animal { public: Cachorro(int age) : Animal(age) {} void accept(AnimalVisitor *visitor); }; class Gato : public Animal { public: Gato(int age) : Animal(age) {} void accept(AnimalVisitor *visitor); };

56

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< void EmissorDeSom::visit(Gato *gato) { cout << "Miau!\n"; } void EmissorDeSom::visit(Cachorro *cachorro) { cout << "Au!\n"; } void ImprimirIdade::visit(Gato *gato){ cout << "Gato com " << gato->idade << " anos\n"; } void ImprimirIdade::visit(Cachorro *cachorro){ cout << "Cachorro com " << cachorro->idade << " anos\n"; } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< void Cachorro::accept(AnimalVisitor *visitor){ visitor->visit(this); } void Gato::accept(AnimalVisitor *visitor){ visitor->visit(this); }

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< void emitirSom(Animal *animal){ AnimalVisitor *visitor = new EmissorDeSom(); animal->accept(visitor); delete visitor; // Faltou } void imprimirIdade(Animal *animal){ AnimalVisitor *visitor = new ImprimirIdade(); animal->accept(visitor); delete visitor; } int main() { Gato gato(3); Cachorro cachorro(5); Animal* animal[2] = {&gato, &cachorro}; for (int i = 0; i < 2; i++) emitirSom(animal[i]); for (int i = 0; i < 2; i++) imprimirIdade(animal[i]); }

Diagrama de Classes

57

Vector

Container seqüencial, implementado como um

array que automaticamente altera seu tamanho

ao se acrescentar elementos ao seu final.

private:

typedef std::vector<int> IntVect;

Nesse caso o apelido IntVect foi definido como

um vetor de inteiros, que o permite alterar o

tamanho dinamicamente (diferentemente de um

array).

58

Unique_ptr

unique_ptr é um tipo de ponteiro especial

que automaticamente

invoca o delete sobre uma memória alocada

dinamincamente ao sair do escopo.

Muito útil para gerência de memória.

59

Passagem por referência

void f(int& a) {

a=a+1;

}

void g() {

a=2;

f(a);

// aqui a vale 3

}

60

Referência para acelerar

void f(const string& st) {

cout << st << endl;

}

void g() {

st=“abcdefdfsdfdfdfdfdfdfdfdfdfdfdfdf”;

f(a);

// O programa roda mais rápido

}

61

Bibliografia

Lafore, R. Object-Oriented Programming in

C++. Sams, 4th ed. 2002.

Savitch, W. C++ Absoluto. Pearson, 1st ed.

2003.

62