Dúvida gerais em C++

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

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.

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?

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?[/quote]

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

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 ?

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.

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.

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.

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.

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

[quote=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 [/quote]
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.

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.

Hmm… mas eu não estava usando literais. Eu lia de um arquivo:

[code]
#include
#include

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;

}[/code]
Esses 3 primeiros bytes são 1F 8B 08.
Ele imprime 31, -117, 8.

O 8B não é lido como 139.

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.

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.

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?

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.

Cara, parece brincadeira, mas eu realmente não consegui entender bem. Olha só:
0x8B = 139. Estou usando o literal ‘\x8B’, como me indicou.

[code]#include

using namespace std;

int main (int argc, char *argv[]) {
unsigned char c = ‘\x8b’;
int i = c;
cout << i << endl;
}[/code]
Ok, imprime 139.

Agora, com a desejada string:

[code]#include
#include

using namespace std;

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

[code]#include
#include

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;
}[/code]
O mesmo -117! Por quê?

Mais um teste (repare o segundo cast):

[code]
#include
#include

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;
}[/code]
Imprime 139. É como se a string não aceitasse um unsigned char. Funciona também com apenas o segundo cast.

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.

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.