Curso completo de linguagem C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
track21.html

index << >>

21. Lendo e escrevendo em arquivos binários

21.1 Visão geral Uma das grandes vantagens do arquivo texto é a visibilidade e a legibilidade, isto é, você pode lê-lo e editá-lo com qualquer editor de texto. Entretanto essa mesma vantagem é totalmente desfavorável quando há necessidade de proteger alguma informação. Por exemplo, em nosso arquivo de savegame anterior, o jogador poderia hackeá-lo com o simples notepad. É claro que um arquivo aberto pode ser uma estratégia para prover maior diversão ao usuário. As desvantagens mesmo do arquivo texto são tamanho e velocidade lenta na leitura e gravação dos dados. Essas desvantagens podem ser resolvidas com o uso do arquivo binário. Em resumo o arquivo binário permite esconder o conteúdo uma vez que ele não é legível por qualquer programa, sendo apenas legível pela aplicação que conhece o seu formato, que conhece o tipo e a disposição de seus dados. Geralmente o arquivo é mais compacto e o processo de leitura e gravação de dados é mais rápido e mais elaborado, você pode ter acesso direto aos registros.
// saveloadbin.cpp // Ilustra leitura e gravaççao de arquivos binários #include <iostream> // permite usar cin e cout #include <fstream> // permite ler e gravar arquivos #include <string> // vamos usar strings #include <cstdlib> // vamos gerar valor de energia aleatório #include <ctime> // vamos gerar valores aleatórios sempre diferentes using namespace std; // ****************** classe Config para dados de configuração do jogo ******** class Config { public: int m_fase; int m_estagio; int m_energia; string m_nome; // Nosso construtor Config (int fase = 1, int estagio = 0, int hp = 100, string snome = "Player") { m_fase = fase; m_estagio = estagio; m_energia = hp; m_nome = snome; } // ::Config().end // Mostra dados de status do jogo void mostrar() { char txt[255]; sprintf (txt, "\t(jogador:%s energia:%d tela:%d estagio:%d)", m_nome.c_str(), m_energia, m_fase, m_estagio); cout << txt << "\n"; } // ::mostrar().end // Resseta para os valores originais os dados de status do jogo void reset() { m_fase = 1; m_estagio = 0; m_energia = 100; m_nome = "Player"; } // ::reset().end // -------------- loadgame() - carrega dados de configuração int loadgame() { ifstream hfile("binfile.dat", ios::binary); // arquivo existe? if (!hfile) { cout << "\t *** erro na abertura do arquivo *** \n"; hfile.close(); return -1; } // endif // faz a leitura dos dados // cuidado na hora de ler a string char temp[255]; hfile.read ( (char *) temp, 6); m_nome.assign(temp); hfile.read ( (char *) &m_fase, sizeof(int)); hfile.read ( (char *) &m_estagio, sizeof(int)); hfile.read ( (char *) &m_energia, sizeof(int)); cout << "\t .loadgame() ok \n"; hfile.close(); return 1; } // ::loadgame().end // -------------- savegame() - grava dados de configuração int savegame() { ofstream hfile("binfile.dat",ios::binary); // arquivo existe? if (!hfile) { cout << "\t *** erro na abertura do arquivo *** \n"; hfile.close(); return -1; } // endif // Salvando os dados - repare no cuidado da gravação da string // A string Mario ocupa 5 letras + um byte para a marca de fim de string hfile.write ( m_nome.c_str(), m_nome.size()+1); hfile.write ( (char *) &m_fase, sizeof(int)); hfile.write ( (char *) &m_estagio, sizeof(int)); hfile.write ( (char *) &m_energia, sizeof(int)); cout << "\t .savegame() ok \n"; hfile.close(); return 1; } // ::savegame().end }; // fim da classe config // -------------------------------------------------------------------------- // Protótipo das nossas funções básicas int menu(); // -------------------- inicio da função principal --------------------------- int main() { system("color f0"); system("title saveloadbin.cpp"); cout << "\n"; int nEscolha; const int carregar = 1; const int gravar = 2; int sts = 0; Config jogo_situacao (7,3,45, "Mario"); // gera um valor aleatório na faixa 0-100 diferente a cada execução do programa srand (time(0)); jogo_situacao.m_energia = rand() % 100; nEscolha = menu(); if ( nEscolha == gravar) { sts = jogo_situacao.savegame(); jogo_situacao.mostrar(); } // endif gravando dados no disco if ( nEscolha == carregar) { jogo_situacao.reset(); cout << "\n\tDados atuais do registro: \n"; jogo_situacao.mostrar(); cout << "\n\tAgora, dados carregados do arquivo: \n"; sts = jogo_situacao.loadgame(); jogo_situacao.mostrar(); } // endif carregando dados do disco cout << "\n"; system("pause"); } // endmain // ************ Implementação das funções declaradas ************************* // --------------- n = menu() gera um menu de escolhas ----------------------- int menu() { int nEscolha = 0; string txt_carregar = "\t 1- Carregar arquivo \n"; string txt_gravar = "\t 2- Gravar arquivo \n"; string txt_escolher = "\n\t------------------------ \n\t Digite sua escolha: "; cout << txt_carregar; cout << txt_gravar; cout << txt_escolher; cin >> nEscolha; return nEscolha; } // fim da funcao menu()
Abertura do arquivo binário para leitura e gravação ifstream hfile("binfile.dat", ios::binary); // leitura ofstream hfile("binfile.dat", ios::binary); // gravação Perceba no código acima que para abrir um arquivo no modo binário você precisa apenas passar o argumento ios::binary para o contrutor das classes ifstream/ofstream ou no método .open(). Lendo e gravando os dados no arquivo binário O processo de leitura e gravação de dados é mais trabalhoso e exige um pouco mais de cuidado: para gravação, você precisa passar para o método .write() do objeto da classe ofstream um ponteiro (char *) para o item que será gravado e o tamanho em bytes desse item: ofstream.write ( char *dado, int nTamanho); hfile.write ( (char *) &m_fase, sizeof(int)); Note que aplicamos um cast tranformando a propriedade inteira m_fase em um ponteiro ((char *)) e passando para esse ponteiro o endereço de referência (&m_fase) dessa propriedade; sizeof(int) informa o tamanho em bytes que o tipo inteiro ocupa de memória. O procedimento de leitura é semelhante, vai um ponteiro de memória com a referência de endereço para guardar o dado e o tamanho desse dado: ifstream.read ( char *dado, int nTamanho); hfile.read ( (char *) &m_fase, sizeof(int)); Cuidado na gravação e leitura do tipo c-string De forma geral o tipo de dado c-string geralmente se apresenta como uma array de dados do tipo char (char *), e essa array de dados contém um byte a mais para armazenar a marca de final de string, o byte zero: // cuidado na hora de ler a string char temp[255]; // c-string temporária hfile.read ( (char *) temp, 6); // A string lida 'Mario' tem 6 bytes de tamanho! m_nome.assign(temp); // Joga a c-string na string c++ // Salvando os dados - repare no cuidado da gravacao da string // A string Mario ocupa 5 letras + um byte para a marca de fim de string hfile.write ( m_nome.c_str(), m_nome.size()+1);
index << >>


Produzido por Gameprog: Jair Pereira - Fev/2006 - Junho/2013 © gameprog.br@gmail.com http://www.gameprog.com.br