Dúvida gerais em C++

72 respostas
S

Oi.

Tenho uma pequena aplicação em PHP. Inicialmente pensei em rodá-la na Web, mas daí mudei de ideia e resolvi que seria desktop. Como seria ruim para os usuários ter que baixar um runtime, achei melhor converter as classes e testes para C++. Estou usando a IDE Code::Blocks e querendo usar wxWidgets.

Andei lendo um pouco sobre C++, mas não entendi muito. A começar pela separação de uma classe em interface (h) e implementação (cpp). Olha a classe que a IDE gerou: Char.h
#ifndef CHAR_H
#define CHAR_H

class Char {
    public:
        Char();
        virtual ~Char();
    protected:
    private:
};

#endif // CHAR_H
Char.cpp
#include "../include/Char.h"

Char::Char() {
    //ctor
}

Char::~Char() {
    //dtor
}
Dúvida: 1- O código dentro de Char.cpp pertencerá obrigatoriamente à classe descrita no .h? 2- Essa estrutura é adequada? Qual a vantagem de separar a classe em 2 arquivos? 3- Tentei incluir um método com o uso de "this" e não consigo. Por quê? (abaixo) Char.h
public:
        void setLevel(int);
        int getLevel();
        Char();
        virtual ~Char();
Char.cpp
int level;
void setLevel(int level) {
    this.level = level;
}
invalid use of "this" in non-member function
Tanto faz setLevel ou Char::setLevel.

72 Respostas

A

Olá,
já faz tempo que não mecho com C++ mas acho que consigo responder… :smiley:

  1. Sim, pois o .h é o mapeamento de tudo que deve ser acessível no cpp
  2. É adequada e necessária, pois é através do .h que o .cpp pode ser usado como biblioteca para outros códigos
  3. A sintaxe está incorreta… no c++ usa-se o operador ->:

segue um exemplo de uso do this http://gracianotorrao.wordpress.com/2009/01/01/o-apontador-this-c/

Att.

S

Então this é um ponteiro.
Funcionou dessa forma:

void Char::setLevel(int level) { this->level = level; }
Mas precisei declarar o level como private no Char.h.

Meio estranho para mim ter que reclarar membros privados lá. Então o .h não é só a interface da classe.
Também é bem esquisito ter que usar o operador de escopo o tempo inteiro para implementar a classe.

Obrigado.

A

No caso do C++ há duas formas de implementar os métodos (ou funções): dentro ou fora da classe. Daí o operador :: é utilizado para indicar de qual classe o método pertence. Já a primeira forma é geralmente utilizada para pequenos códigos.

Att.

V

Não necessariamente. Não há nenhuma obrigação quanto à isso no cpp, embora seja uma boa prática. O cpp apenas exige que a definição (feita no .h) venha antes da implementação. Você pode inclusive declarar os dois no mesmo arquivo, ou fazer a implementação de um .h em dois arquivos .cpp, embora isso não seja uma boa prática.

Você separa a definição da implementação. Isso evita que seu código seja recompilado quando somente a implementação, e não a interface da classe, muda. Dá uma lida nesse artigo:
http://www.pontov.com.br/site/index.php/cpp/46-conceitos-basicos/95-compilacao-de-programas-cc

E, principalmente, nesse aqui:
http://www.caloni.com.br/blog/archives/declaracao-x-definicao

O C++ deixa as partes privadas junto da interface da classe, pois ele precisa saber o tamanho físico da classe, para que possa alocar memória corretamente. E para conhecer exatamente esse tamanho, é necessário conhecer o que há de privado. Infelizmente, isso quebra um pouco o encapsulamento, já que um programador de posse do .h, sabe o que a classe tem de privado.

É necessário entender um pouco melhor como o C++ gerencia isso.

Outra coisa. O arquivo que implementa o .h não precisa ser um código fonte. Pode ser uma library estática, já compilada.

Tome cuidado que o c++ é muito diferente do Java. Não tente programar em um como você faz no outro. Isso vai levar a um código ruim. Procure pegar um bom livro de cpp (como o do Deitel) e ver as diferenças. E, claro, depois disso, pegar um effective c++ da vida não faz mal a ninguém. :wink:

S

Beleza, deu pra entender um pouco melhor.

ViniGodoy:

Procure pegar um bom livro de cpp (como o do Deitel) e ver as diferenças.

Não tem uma sugestão melhor, não? Eu tinha esse livro, mas passei adiante pra alguém aqui do GUJ porque é insuportável.
5 páginas pra explicar o que é um for pode ser aceitável pra um iniciante, mas pra quem já programa em alguma coisa, não dá :mrgreen:

Pelo menos por agora não vou fazer nada avançado. O programa é praticamente manipulação de um arquivo binário. É um editor de um jogo.
Vou precisar ver como fazer a interface gráfica com o wxWidgets de forma portável.

V

Schuenemann:
Não tem uma sugestão melhor, não? Eu tinha esse livro, mas passei adiante pra alguém aqui do GUJ porque é insuportável.
5 páginas pra explicar o que é um for pode ser aceitável pra um iniciante, mas pra quem já programa em alguma coisa, não dá :mrgreen:

Mas era o de C++ que você tinha? Pq o de C++ do Deitel é 100x melhor que o de Java.

De qualquer forma, você pode baixar o livro indicado no meu roadmap:
http://www.pontov.com.br/site/index.php/cpp/46-conceitos-basicos/88-roadmap-c

Ele é bem mais direto.

Schuenemann:
Pelo menos por agora não vou fazer nada avançado. O programa é praticamente manipulação de um arquivo binário. É um editor de um jogo.
Vou precisar ver como fazer a interface gráfica com o wxWidgets de forma portável.

Boa sorte. Qualquer dúvida é só perguntar por aqui. :slight_smile:

S

Ele mesmo. Acho os Deitel muito chatos.
Só recomendo pra quem é completamente iniciante, não só na linguagem, como também em programação. Qualquer um que programe sabe o que é uma variável, uma estrutura de repetição etc. Só falta saber a sintaxe e as particularidades daquela linguagem.

Vou ver sua sugestão.

V

Se você sabe programar, pegue um livro mais de referência sobre a sintaxe.
Pode ser o do André Bueno, indicado ali no site, ou mesmo uma apostila.

E, assim que dominar a sintaxe básica, parta para um Effective da vida.

S

Surgiu outra dúvida:

Estou pesquisando há algum tempo, mas não achei um jeito de encontrar o tamanho (quantidade de posições) de um array (no caso, de char).
Ele tem 5 posições. sizeof retorna 8. Achei lugares dizendo para dividir esse valor por (* (array)) ou (array[0]), mas retorna 8 de qualquer jeito (o tamanho do char deve ser 1).

Já outro lugar diz que não tem como encontrar o tamanho de um array em tempo de execução. Alguma dica?

A
Olá, segue um exemple de como encontrar o tamanho de um vetor de um tipo qualquer:
T vetor[<QualquerTamanho>];

size_t tamanho = (sizeof vetor)/(sizeof vetor[0]);
O valor de tamanho deverá ser igual ao de . Estou sem um compilador para testar mas acho que é isso.

Att.

S

Pois é, não dá certo:

char * mem = new char[5]; size_t s = (sizeof mem) / (sizeof mem[0]); cout << s << endl << endl << flush;

Imprime 8. Eu quero o número de posições (5).

A

Neste caso, quando o array é alocado dinamicamente, não sei se é possível saber o tamanho dele usando sizeof.

Att.

S

Putz… qual seria o motivo dessa limitação? Não é só armazenar o número de posições num atributo do array?

E quais são minhas alternativas? Guardar o tamanho numa variável, usar outro tipo, … ?

A

Schuenemann:
Putz… qual seria o motivo dessa limitação? Não é só armazenar o número de posições num atributo do array?

E quais são minhas alternativas? Guardar o tamanho numa variável, usar outro tipo, … ?

O que o operador sizeof retorna o número de bytes ocupados pelo operando, que pode ser uma variável ou um tipo genérico de dado. O problema é como fazer com que o operador ache a região de memória correspondente ao ponteiro. Faz já um tempo que não uso C/C++, mas não me lembro disso ser possível.

Sobre a segunda pergunta, não é possível. Vetores/arrays em C/C++ são somente ponteiros para regiões de memória. Não existem atributos atrelados a estes igual existem em Java. O que pode ser feito é armazenar o tamanho em uma variável de tipo inteiro (tipos int ou long).

A variável inteira seria passada como argumento para as funções que recebem o vetor, da mesma forma que é feita na função main:

#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { printf("%d parametros:\n", argc); int i; for(i=0; i < argc; i++) { printf("%s %d\n", argv[i], strlen(argv[i])); } return 0; }
Nela o argc indica quantos argumentos foram passados no vetor de ponteiros argv (no qual cada posição aponta para um vetor de char). Não sei se há uma forma melhor, mas vendo pela própria função main do C/C++ tenho minhas dúvidas…

No código acima, a função strlen é utilizada para saber o tamanho das strings de cada uma das posições do vetor. Esta conta a quantidade de caracteres desde a primeira posição até o null (’\0’), que indica o final das strings no C/C++.

Att.

V

Em C++, existe o tipo vector, dentro da biblioteca padrão, a STL. Ele é como um ArrayList do Java. Além de crescer dinamicamente, possui um atributo para saber seu tamanho. Por ser feito baseado em template metaprogramming, sua performance é comparável a um array primitivo.

Se você está usando C++, recomendo fortemente que use vectors no lugar de arrays primitivos.

Além disso, a STL também tem a classe string. Ela é parecida com a string do java, com a diferença de por padrão ser mutável. Isso não é um grande problema em C++, pois objetos podem ser facilmente convertidos em imutáveis através da palavra const. Também recomendo fortemente o uso da classe String no lugar dos char*.

Como a classe string guarda o tamanho da string de uma forma eficiente, a performance geral dela costuma a ser superior a dos char*. Isso porque a função strlen tem a péssima performance de O(n) enquanto o método size() tem performance de O(1).

A classe string também suporta acesso a um caracter individual pelos [] e concatenação através do sinal de +. Pode ser declarada como const e não tem tanta apurrinhação quanto você tem com o char* na hora de criar vetores de strings.

#include <vector>
#include <string>
#include <iostream>

using namespace std;

int main(int argc, char* argv[]) 
{
   vector<string> teste;
   teste.push_back("ViniGodoy");
   teste.push_back("Schuenemann");
   teste.push_back("Adelar");
   for (unsigned i = 0; i < teste.size(); i++) 
   {
      cout << "O nome " << teste[i] << " tem " << teste[i].size() << " caracteres." << endl;
   }
}

Existe uma série de algoritmos prontos para se lidar com vector. A boost também tem meios utilitários como o BOOST_FOREACH, que faz um for each parecido com o do java.

Uma dica é. Se você vai usar C++, conheça a biblioteca padrão e a boost. Programar sem isso hoje em dia, é como usar somente a linguagem Java, sem usar nada que a API dele tem. Também é uma boa prática não programar em C++ como se fosse em C. São linguagens diferentes, então, procure usar as boas práticas da linguagem que você está usando.

O link da boost é: http://www.boost.org/

A STL não precisa ser baixada. Ela vem com qualquer compilador C++.

V

O mesmo programa acima usando um dos algoritmos prontos da STL para eliminar o for:

#include &lt;vector&gt;
#include &lt;string&gt;
#include &lt;iostream&gt;
#include &lt;algorithm&gt;

using namespace std;

void imprimeNome(const string& nome) 
{
	cout &lt;&lt; &quot;O nome &quot; &lt;&lt; nome &lt;&lt; &quot; tem &quot; &lt;&lt; nome.size() &lt;&lt; &quot; caracteres.&quot; &lt;&lt; endl;
}

int main(int argc, char* argv[]) 
{
   vector&lt;string&gt; teste;
   teste.push_back("ViniGodoy");
   teste.push_back("Schuenemann");
   teste.push_back("Adelar");

   std::for_each(teste.begin(), teste.end(), imprimeNome);
}
B

Uma coisa que adoro na Boost é que não perco o investimento mental que fiz com o Java.

Vejam o exemplo do ViniGodoy, mas usando também o Boost:

#include <vector>  
#include <string>  
#include <iostream>  
#include <boost/foreach.hpp>

using namespace std;  
using namespace boost;
   
 int main(int argc, char* argv[])   
{  
   vector<string> teste;  
   teste.push_back("ViniGodoy");  
   teste.push_back("Schuenemann");  
   teste.push_back("Adelar");  
  
   BOOST_FOREACH (const string& nome, teste) 
   {
      cout << "O nome " << nome << " tem " << nome.size() << " caracteres." << endl;  
   }
}

O equivalente do BOOST_FOREACH no C++0X é o for ( : ) que infelizmente não foi implementado ainda em várias versões dos compiladores C++, como o Microsoft Visual Studio 2010 - nem sei se foi já implementado no g++.
O for ( : ) funciona igualzinho ao do Java.

S

ViniGodoy:

Isso porque a função strlen tem a péssima performance de O(n) enquanto o método size() tem performance de O(1).

Pensei logo nisso quando vi a explicação. E ainda percorre inteira, toda vez que quiser saber o tamanho :?

Vou dar uma olhada no resto. Eu até ia usar a Boost.Test, mas troquei por UnitTest++.

A

Schuenemann:
ViniGodoy:

Isso porque a função strlen tem a péssima performance de O(n) enquanto o método size() tem performance de O(1).

Pensei logo nisso quando vi a explicação. E ainda percorre inteira, toda vez que quiser saber o tamanho :?

Caso utilize a função strlen o recomendado é armazenar o valor em uma variável auxiliar. Como o Vini citou sempre que possível utilize a STL. Na maioria dos casos a performance é boa.

Att.

S

Pessoas, se puderem me esclarecer mais essa:

#ifndef STATE_H
#define STATE_H

#include <string>

using namespace std;

class State
{
    public:
        State(string);
        virtual ~State();
    protected:
    private:
};

#endif // STATE_H
#include "Helper.h"

Helper::Helper(const State & state) {
    this->state = state;
}

Helper::~Helper() {
    //dtor
}
/home/otto/ps4/src/helper/Helper.cpp||In constructor ?Helper::Helper(const State&)?:| /home/otto/ps4/src/helper/Helper.cpp|3|error: no matching function for call to ?State::State()?| /home/otto/ps4/src/helper/../../include/State.h|11|note: candidates are: State::State(std::string)| /home/otto/ps4/src/helper/../../include/State.h|9|note: State::State(const State&)| ||=== Build finished: 1 errors, 0 warnings ===|

É como se eu estivesse tentando instanciar State com o construtor padrão.

V

Primeiro algumas correções:

#ifndef STATE_H
#define STATE_H

#include &lt;string&gt;

using namespace std;

class State
{
    public:
        explicit State(string);
        virtual ~State();
    protected:
    private:
};

#endif // STATE_H

Aqui está o problema. Você não está usando a lista de inicialização, e sim atribuindo com = dentro do construtor. Com isso, o C++ tenta primeiro inicializar a referência com o construtor padrão. E depois (se você tivesse um construtor padrão) te daria erro dentro do construtor dizendo que você não pode alterar o valor da referência.

#include "Helper.h"
Helper::Helper(const State& _state) : state(_state) {
}

Helper::~Helper() {
}

Use sempre a lista de inicialização para declarar seus atributos, e não o corpo do construtor. O corpo deve ser usado só para inicializar atributos complexos, que tem valores carregados de arquivos, por exemplo.

S

Pelo que procurei agora, se o construtor tiver só 1 parâmetro e não for declarado como explicit, ele sempre tenta converter o parâmetro para o mesmo tipo da classe do construtor?

Sobre a lista de inicialização, qual a explicação para essa (aparente) loucura?
Com a atribuição com =, ele primeiro tentou instanciar o atributo de Helper para depois atribuir ao parâmetro? Por quê?

E se não fosse uma simples atribuição? Se tivesse uma validação antes, por exemplo?

V

Se o construtor não for explicit, ele automaticamente será chamado. Por exemplo, você poderia fazer isso aqui:
State x = “Novo estado”;

E o C++ automaticamente converteria para isso aqui:
State x = new State(novoEstado);

Já que você tem um construtor que aceita um std::string. O mesmo vale pra métodos que aceitem um State como parâmetro.

Como isso aí gera um código sujeito a erros, você só deve declarar o construtor sem explicit caso sua classe efetivamente represente algo que deva ser implicitamente convertido. Por exemplo, a classe BigDecimal do Java poderia se beneficiar desse recurso.

Schuenemann:
Sobre a lista de inicialização, qual a explicação para essa (aparente) loucura?
Com a atribuição com =, ele primeiro tentou instanciar o atributo de Helper para depois atribuir ao parâmetro? Por quê?
E se não fosse uma simples atribuição? Se tivesse uma validação antes, por exemplo?

Por que é assim que o C++ funciona. Antes de começar o construtor, ele faz a inicialização padrão de todos os atributos da classe. Quem comanda essa inicialização padrão é a lista de inicialização.
O java, no fundo, faz exatamente a mesma coisa. Tanto que quando você roda o construtor, todos os atributos valem 0, e todas as referências valem null, ou seja, uma inicialização padrão foi executada.

A diferença é que a lista de inicialização te dá a oportunidade de alterar essa inicialização padrão, poupando alguns milisegundos de processamento.

O Java não suporta o conceito de referência, como o C++, e não suporta também o conceito de objetos por valor. Nesses casos, você tem que tomar especial cuidado, pois na inicialização padrão o C++ irá tentar criar um objeto novo, usando o construtor padrão. A lista de inicialização se torna importante, pois a construção de um objeto, diferente da de um tipo primitivo, pode ser custosa.

Se você precisar validar valores de atributos, faça isso dentro do corpo do construtor. A única diferença é que os atributos não irão valer 0, pois os valores já estarão atribuídos, graças a lista de inicialização. Lembre-se que referências em C++ obrigatoriamente devem conter um valor de objeto, pois não podem ser nulas. No caso de ponteiros, é uma prática comum inicializa-los com null na lista de inicialização, fazer as validações de atributos no construtor, para tentar evitar dar o new, como o java faz.

S

É, faz sentido.

Sobre essa parte:

ViniGodoy:

Se você precisar validar valores de atributos, faça isso dentro do corpo do construtor. A única diferença é que os atributos não irão valer 0, pois os valores já estarão atribuídos, graças a lista de inicialização. Lembre-se que referências em C++ obrigatoriamente devem conter um valor de objeto, pois não podem ser nulas. No caso de ponteiros, é uma prática comum inicializa-los com null na lista de inicialização, fazer as validações de atributos no construtor, para tentar evitar dar o new, como o java faz.

É melhor eu ter atributos ponteiros ao invés de objetos? Ponteiro para State ao invés de State propriamente dito. Senão, não vai ter jeito de evitar a inicialização padrão, se não fosse uma simples atribuição.

Aproveitando o abuso, me apareceu outro problema. Eu preciso de um atributo arquivo:
#ifndef STATE_H
#define STATE_H

#include <string>
#include <fstream>

using namespace std;

class State {
    public:
        explicit State(string);
        virtual ~State();
    protected:
    private:
        fstream file;
        void readData();
};

#endif // STATE_H
Dá um erro dentro de ios_base.h:
/usr/include/c++/4.4/bits/ios_base.h|790|error: ?std::ios_base::ios_base(const std::ios_base&)? is private
Eu já tinha trabalhado com fstream antes dentro de um método:
fstream myfile ("/home/otto/ps4/ps4.gs0", ios::out | ios::in | ios::binary);

Obrigado.

V

Se você puder passar uma referência const, é melhor passar uma referência const. Ponteiro você só usa se quiser passar o valor null, ou se o objeto que recebe o ponteiro for se responsabilizar por excluí-lo.
A inicialização vai fazer uma atribuição à referência, não uma cópia do objeto.

Quanto ao outro erro, como está o arquivo .cpp? Aparentemente ele tentou fazer uma cópia do seu fstream.

S
Não tem nada no cpp:
#include "State.h"
#include <string>

State::State(string path) {
    // Abrir o arquivo
    this->readData();
}

State::~State() {
    //dtor
}

void State::readData() {

}
V

Então, não tá faltando chamar o construtor daquele fstream não?
Você não pode criar um fstream sem file nenhum associado.

S

Putz, preciso me acostumar.
Esse seria o caso de usar ponteiro? O caminho do arquivo é aquela string, não dá (acho) pra usar a lista de inicialização.

Agradeço sua paciência :slight_smile:

V

Por que vc tem um fstream de atributo? Isso é equivalente a ter um InputStream no java, não um File.

Geralmente você só cria o fstream na hora de ler pra valer o arquivo.
Até porque ele vai abrir o seu arquivo.

Ainda assim, daria para usar na lista de inicialização:

State::State(string path) : file(path, ios::out | ios::in | ios::binary) { // Abrir o arquivo this-&gt;readData(); }

V

PS: Recomendo FORTEMENTE que você leia o Effective C++. Ele explica em detalhes esses conceitos (lista de inicialização, construtores de cópia, uso de const& no lugar de ponteiros, etc).

S

Eu preciso ler o arquivo ao criar o State e depois gravar nesse arquivo.
Ao procurar sobre o assunto, achei e segui esse tutorial: http://www.cplusplus.com/doc/tutorial/files/
Mas não tem um equivalente ao File.

Você já me recomendou o livo, sei que preciso ler. Até hoje não consegui ler o de Java e sei que me faz bastante falta também.

V

Sim, mas ainda não entendi pq o ifstream tem que ser um atributo da sua classe. Não era melhor simplesmente cria-lo, ler o arquivo, e depois descarta-lo?

S

Mas depois eu vou precisar gravar nele de novo. A outra opção é guardar o caminho… faz diferença?

S

Eu tento achar as respostas, mas sempre acabo voltando aqui :slight_smile:

Meu problema é:
1- Quero ler alguns bytes de um arquivo.
2- Os bytes são lidos como string.
3- O tipo string é signed.

Resultado: se o valor do byte for acima de 127, tenho problemas.

Tem como usar algo tipo unsigned string ou terei que voltar aos ponteiros?

Edit: pensando melhor, tem ainda a opção de usar vector.
Edit2: não dá certo, pois o método read recebe char *, não unsigned char *… =/

V

Como assim o tipo string é signed? :shock:

S

Cada posição da string é char, não unsigned char. Pelo menos foi o que pude concluir.

string s = lerArquivo(); // fstream.read() int i = s[0];

Se aquele primeiro byte teve valor 0xFF, por exemplo, i vale -1 ao invés de 255. Notei que isso só acontece para valores acima de 127, que é o limite de um char com sinal.

Daí vi que existe um cast chamado reinterpret_cast, mas ainda não deu tempo de testar.

Ou não tem nada a ver?

V

Teste esse truque:

string s = lerArquivo(); // fstream.read() int i = s[0] & 0xFF;

S

Não deu pra testar no código real, mas nesse exemplo funcionou. Com avisos:

char c = 'º'; int i = c & 0xFF; cout << i << endl; // 186

novo.cpp:25:13: aviso: constante de caracter multi-caracter
novo.cpp: In function ‘int main()’:
novo.cpp:25: aviso: overflow in implicit constant conversion

Qual seria a melhor opção pra mim? Usar um basic_string e copiar cada caractere (aplicando o and) da string lida do arquivo?

V

A notação com unsigned_char não é muito usual, pois até uns anos atrás o padrão só exigia que a classe std::string considerasse os tipos char e wchar_t (você pode ler mais sobre isso aqui).

Como o and é uma operação super barata, talvez seja uma boa você utiliza-lo.

S

Não entendi bem. Você fala em não guardar em outra string?

E

Um basic_string, pelo menos no Microsoft Visual C++, é muito mais lento que uma std::string (que é, como visto, um basic_string, mas tem vários de seus métodos especializados para métodos intrínsecos do compilador. Use uma std::string mesmo, e faça os casts de char para (unsigned char). Isso não irá roubar nenhum desempenho.

S

Eu não entendi essa parte.
Você sugere que eu mantenha a string e toda vez que quiser ler caracteres dela eu faça o cast? Armazenando no que o resultado desse cast?

V

Eu não entendi essa parte.
Você sugere que eu mantenha a string e toda vez que quiser ler caracteres dela eu faça o cast? Armazenando no que o resultado desse cast?

Sim. E era o que eu sugeria também.

S

Desculpem a insistência, é que não ficou claro pra mim.
Eu leio o arquivo numa string s.

Daí, quando eu precisar ler 30 caracteres dessa string, eu uso um loop pra fazer o cast pra cada caractere. Mas guardo esse resultado numa variável de que tipo, já que não é basic_string ?

E

Agora eu que não estou entendendo direito.
Você quer ler um arquivo usando strings ou arrays de bytes?
No primeiro caso (strings), normalmente você usa os métodos de modo texto (por exemplo, se usar istreams, você pode usar getline() para ler as linhas).
No segundo caso, você pode alocar arrays de bytes mesmo.

S

O arquivo é binário. Não há a ideia de linhas nele.
Eu estou usando string porque é o que o fstream.read() recebe como parâmetro. E nas linguagens que mexi (PHP, Python), os arquivos (mesmo binários) são sempre lidos/escritos como string.

E

Ok. Então vamos começar pelo começo. (É que eu não vi o começo)

Quando você usa fstream para ler arquivos binários, você não deve jogar as coisas em uma string, porque fatalmente haverá uma tradução (que ocorre no Windows mas talvez não ocorra no Linux) da sequência \r\n para \n, a menos que você passe um parâmetro especial para abrir o arquivo.

A seguir, se você vai ler o arquivo em blocos usando o método read(), passe um array de bytes mesmo, ou talvez um std::vector.

http://www.cplusplus.com/reference/iostream/istream/read/

Não é problema para o read(char ) se você passar um read (unsigned char), basta você fazer o cast.

Digamos que você prefira usar o std::vector. Não tem problema, basta você prealocar o vector corretamente:

std::vector<unsigned char> bytes;
bytes.resize (1024); // digamos que você queira ler 1024 bytes
int bytesLidos = fs.readsome ((char *) &bytes[0], bytes.size()); // evite o uso de "read" se for lícito ler menos que 1024 bytes aqui

Se por algum motivo (por exemplo, você sabe que o arquivo não tem zeros binários e você pode usar uma std::string para conter o arquivo completo), esteja à vontade para usar uma std::string.

E

fstream.read recebe um char*, não uma std::string&.
Não faça confusão de conceitos.
Um char * é um char *, não uma string.
É por abuso de linguagem que chamamos um char * de string.

E

http://www.cplusplus.com/reference/iostream/ifstream/ifstream/ - o parâmetro especial é “ios_base::openmode::binary”

S

entanglement:

Não é problema para o read(char ) se você passar um read (unsigned char), basta você fazer o cast.

Digamos que você prefira usar o std::vector. Não tem problema, basta você prealocar o vector corretamente:

std::vector<unsigned char> bytes; bytes.resize (1024); // digamos que você queira ler 1024 bytes int bytesLidos = fs.readsome ((char *) &bytes[0], bytes.size()); // evite o uso de "read" se for lícito ler menos que 1024 bytes aqui


Velho, era isso mesmo que eu estava precisando.

Mas foi isso que gerou toda a discussão. Dá problema com os caracteres > 127 :smiley:
http://guj.com.br/posts/list/30/216754.java#1139133

Estou ciente do resto, obrigado.

E

O problema não é com os caracteres maiores que 127. Tanto é que é possível você pô-los e tirá-los perfeitamente de uma std::string. Vou escrever um programa para mostrar isso.

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main (int argc, char *argv[]) {
    string s;
    s.resize (256);
    cout << "Preenchendo um std::string com os bytes de 0 a 255" << endl;
    for (int i = 0; i < 256; ++i) {
        s[i] = (unsigned char) i;
    }
    cout << "Agora verificando que os bytes estao onde devem estar..." << endl;
    vector<int> listaBytesErrados;
    for (int i = 0; i < 256; ++i) {
        if ((unsigned char) s[i] != i) {
            listaBytesErrados.push_back (i); // guarda as posições onde os bytes não bateram
        }
    }
    if (listaBytesErrados.empty()) {
        cout << "Todos os bytes foram armazenados corretamente na std::string." << endl;
    } else {
        cout << "Bytes não bateram nas posições :" << endl;
        for (int i = 0; i < listaBytesErrados.size();++i) {
            int pos = listaBytesErrados[i];
            cout << i << ": esperado = " << pos << ", encontrado = " << s[pos] << endl;
        }
    }
}

O problema, na verdade, é representá-los como literais em um programa C / C++. Se precisar fazer isso, use a notação “\x” para ter certeza absoluta do byte que você quis usar.

S
Hmm... mas eu não estava usando literais. Eu lia de um arquivo:
#include <iostream>
#include <fstream>

using namespace std;

int main() {
	fstream file("moo.tar.gz", ios::binary | ios::in);
	
	string vet;
	vet.resize(3);
	file.read(&vet[0], vet.size());

	cout << (int) vet[0] << endl;
	cout << (int) vet[1] << endl;
	cout << (int) vet[2] << endl;

  	return 0;
}
Esses 3 primeiros bytes são 1F 8B 08. Ele imprime 31, -117, 8.

O 8B não é lido como 139.

E

Ah, mas quando você converte um (signed) char para um int direto, como você fez, vai ocorrer isso mesmo. É parecido com aquele tipo de dados “byte” do Java, que é igualzinho, nesse ponto, ao signed char do C/C++.

O que você tem de fazer, obviamente, é isto aqui:

#include <iostream>  
#include <fstream>  
   
using namespace std;  
   
int main() {  
     fstream file("moo.tar.gz", ios::binary | ios::in);  
       
     string vet;  
     vet.resize(3);  
     file.read(&vet[0], vet.size());  
   
     cout << (unsigned char) vet[0] << endl;  
     cout << (unsigned char) vet[1] << endl;  
     cout << (unsigned char) vet[2] << endl;  
   
     return 0;  
 }

Ou então,

cout << (vet[0] & 0xFF) << endl;  
     cout << (vet[1] & 0xFF) << endl;  
     cout << (vet[2] & 0xFF) << endl;

Uma std::string não estraga o que se põe dentro dela (diferentemente do Java). Entretanto, se você usar o método errado para imprimir o que há dentro dela, você pode ter problemas.

S

Tudo bem, mas o desejável seria ter os valores unsigned já dentro da string (não irei imprimir na tela).
E isso parece não ter nenhum efeito:

for (int i = 0; i < vet.size(); i++) { vet[i] = (unsigned char) vet[i]; }

Edit: nem se for em outra string. É como se tivesse estragado mesmo.

E

Você está fazendo uma confusão federal. Pegue o meu programa:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main (int argc, char *argv[]) {
    string s;
    s.resize (256);
    cout << "Preenchendo um std::string com os bytes de 0 a 255" << endl;
    for (int i = 0; i < 256; ++i) {
        s[i] = (unsigned char) i;
    }
    cout << "Agora imprimindo todos os bytes" << endl;
    for (int i = 0; i < 256; ++i) {
        cout << (unsigned char) s[i] << endl;
    }
    cout << "Agora jogando em uma outra string" << endl;
    string t = s;
    for (int i = 0; i < 256; ++i) {
        cout << (unsigned char) t[i] << endl;
    }
}

Se tiver paciência suficiente, vai ver que a string não estragou absolutamente nada. É você que está confundindo o byte em si (0x80 = -128 se você converter para int), com o que você faz sem fazer os casts adequados. E diferentemente do Java. um cast de (signed char) para (unsigned char) não gasta absolutamente nenhuma CPU. Se olhar o assembly gerado pelo compilador C, vai ver que não faz absolutamente nada. Quando você simplesmente escreve isto aqui:

for (int i = 0; i < 256; ++i) {
        cout <<  t[i] << endl;
    }

então o compilador irá usar a regra: promover o signed char para um signed int, usando a “sign extension” (ou seja, é uma instrução em Assembly que converte um byte entre 0 e 0x7F para um valor int positivo, e entre 0x80 e 0xFF para um valor int negativo. No processador Intel, a instrução em Assembly é a “movsx”, se não me engano.

http://webster.cs.ucr.edu/AoA/Windows/HTML/DataRepresentationa5.html

Ou seja, você usar um cast pode até gastar MENOS CPU que não usar o cast, em C. Isso não parece um contrassenso?

E

Oops, falei uma besteira. Quando você usa o cast para unsigned char, o C deve estar promovendo o unsigned char para um unsigned int, e isso é feito via uma instrução MOVZX.
Essa instrução é igualzinha à MOVSX, exceto pelo comportamento (completar com zeros em vez de completar com o sinal), e gasta exatamente o mesmo tempo.

S
Cara, parece brincadeira, mas eu realmente não consegui entender bem. Olha só: 0x8B = 139. Estou usando o literal '\x8B', como me indicou.
#include <iostream>

using namespace std;

int main (int argc, char *argv[]) {
	unsigned char c = '\x8b';
	int i = c;
	cout << i << endl;
}
Ok, imprime 139. Agora, com a desejada string:
#include <iostream>
#include <string>

using namespace std;

int main (int argc, char *argv[]) {
	string s;
	s.resize(1);
	s[0] = '\x8b';
	int i = s[0];
	cout << i << endl;
}
Imprime -117. Ok, não teve o cast:
#include <iostream>
#include <string>

using namespace std;

int main (int argc, char *argv[]) {
	string s;
	s.resize(1);
	s[0] = (unsigned char) '\x8b';
	int i = s[0];
	cout << i << endl;
}
O mesmo -117! Por quê? Mais um teste (repare o segundo cast):
#include <iostream>
#include <string>

using namespace std;

int main (int argc, char *argv[]) {
	string s;
	s.resize(1);
	s[0] = (unsigned char) '\x8b';
	int i = (unsigned char) s[0];
	cout << i << endl;	
}
Imprime 139. É como se a string não aceitasse um unsigned char. Funciona também com apenas o segundo cast.
E

Puxa, não tinha testado esse caso:

int i = (unsigned char) s[0];

É que na verdade seu compilador (note que isso está um pouco obscuro na especificação) provavelmente está fazendo a seguinte promoção:

unsigned char -> int

usando a instrução errada do Assembly (MOVSX, que estende o sinal, em vez de MOVZX, que completa com zeros). Então, para evitar um comportamento não esperado, use sempre “& 0xFF”:

int i = s[0] & 0xFF;

Se o compilador for suficientemente esperto (essa é uma regra comum em compiladores, porque esse código é mais comum que se imagina), ele em vez de carregar s[0] para um registrador e efetuar um AND com o inteiro 0x000000FF, vai simplesmente usar a instrução MOVZX e fazer o que você espera.

V

Isso explica pq na Siemens sempre usavamos 0xFF e não casts.
Como vc sugeriu o cast, e você tem um bom conhecimento de cpp também, nem me manifestei.

S

Vocês leem como array de char e depois passam para um array de int, aplicando o and em cada posição?
E, se for escrever no arquivo, passar novamente para um array de char?

E

Schuenemann:
Vocês leem como array de char e depois passam para um array de int, aplicando o and em cada posição?
E, se for escrever no arquivo, passar novamente para um array de char?

Não precisa fazer isso.

V

Na verdade, na maior parte das vezes, tratamos diretamente com os bits. O valor numérico, quando você trabalha com arquivos e sockets, costuma a ser irrelevante. No máximo convertíamos para inteiro em logs.

S

Então, qual sugestão?

No meu caso, eu trabalho com os inteiros mesmo.
Tenho que abrir o arquivo e trocar um byte específico de 3 para 5, por exemplo. Ou vários contíguos.

Existe alguma contra-indicação nisso?

vector<unsigned char> vet;
file.read((char *) &vet[0], vet.size());

Pelo que testei, não dá o problema citado com o cast (ao menos nesse compilador).

Acho que não fica legal (mesmo que tenha desempenho melhor) eu manter uma string e aplicar o and em todo código que acesse o conteúdo da string.

S

Como se converte um número para uma string hexadecimal (ou outra base qualquer) ?
Vi que a maneira padrão é usando sprintf(string_destino, formato, numero), mas como eu posso saber antecipadamente qual o tamanho que a string terá? Exemplo:

string s;
s.resize(2);
sprintf(&s[0], "%X", 255);
cout << s << endl;

Imprime “FF”, mas só funciona com o resize() (e nesse caso eu já sabia que teria tamanho 2 saída).

V

O sprintf é usado em C. Em C++ geralmente usa-se o stringstream (ou as funções de string da boost).

Veja:
http://www.cplusplus.com/reference/iostream/manipulators/hex/

Mas, basicamente:

#include &lt;iostream&gt; 
#include &lt;sstream&gt; 

int main(int argc, char* argv[]) {
   std::stringstream s;
   s &lt;&lt; std::hex &lt;&lt; 255;
   std::cout &lt;&lt; s &lt;&lt; std::endl;
}

O stringstream, por ser um stream, tem a mesma sintaxe do cout.

S

Valeu.

#include <sstream>

ostringstream ss;
ss << hex << 255;
cout << ss.str() << endl;

Vou considerar o uso da Boost. Parece simplificar muita coisa em relação à API padrão.

Edit: só depois notei que você já tinha dado o exemplo exato...

S

Passei a ter um problema "estranho".
O erro é:

multiple definition of `State::State(std::basic_string, std::allocator >)'

Vou tentar colocar de forma simplificada as classes que tenho. E vou tirar os includes da lib padrão pra diminuir o código.

Classe Ata:
#ifndef ATA_H
#define ATA_H
#include "State.h"
#include "helper/Helper.h"

class Ata {
    public:
        explicit Ata(string);
        virtual ~Ata();
    protected:
    private:
        State state;
        Helper helper;
};
#endif // ATA_H

// Ata.cpp
#include "Ata.h"
#include "State.h"
#include "State.cpp"

Ata::Ata(string path) : state(path), helper(state) { }
Ata::~Ata() { }
classe State:
#ifndef STATE_H
#define STATE_H

class State {
    public:
        explicit State(string);
        virtual ~State();
    protected:
    private:
};
#endif // STATE_H

// State.cpp
#include "State.h"

State::State(string path) { }
State::~State() { }
classe Helper:
#ifndef HELPER_H
#define HELPER_H

#include "../State.h"

class Helper {
    public:
        explicit Helper(const State &);
        virtual ~Helper();
    protected:
    private:
        State state;
};
#endif // HELPER_H

// Helper.cpp
#include "Helper.h"
Helper::Helper(const State & state) : state(state) { }
Helper::~Helper() { }

Segundo a busca que fiz, quando você declara uma variável num arquivo header, cada arquivo-fonte que incluir aquele header terá uma cópia separada da variável.

D

Não sou expert em C++, mas vc tem certeza disso?
Afinal, para que serve então o seguinte código do preprocessador?

#ifndef XXX_H  
#define XXX_H  

#endif XXX_H

[]'s

D

Vc está implementando os construtores dentro do header? É normal fazer isso? O ideal não é implementar a classe em um arquivo .cpp?
Se você inserí-los dentro do bloco de compilação condicional, acredito que o erro vá desaparecer, pois eles serão implementados apenas uma vez.

[]'s

Ops… Agora que vi o comentário no código.

Pq vc ta dando um include no State.cpp?

S

Acho que o erro é aquele include mesmo. Eu acabei incluindo aquilo porque dava erro no construtor de Ata, mas agora estou percebendo que só acontece o erro quando faço build no outro projeto.
Amanhã eu vou verificar as dependências entre os projetos, acho que o erro é meu mesmo.

Obrigado.

S

Eu confirmo que era aquilo mesmo. Na minha ignorância em resolver o problema, acabei dando include num .cpp.

Pra resolver no Code::Blocks, uma forma é clicar no segundo projeto, ir em Add files e adicionar os .h e .cpp.
Adicionar em project dependencies não resolve. Parece que ali só vale para a compilação, não pro linking.

Tava acostumado com Eclipse/Netbeans.

S

Vou rever o que escrevi.

Criado 28 de agosto de 2010
Ultima resposta 27 de mar. de 2011
Respostas 72
Participantes 6