Especificamente, estou interessado em istream& getline ( istream& is, string& str );
. Existe uma opção para o construtor ifstream dizer a ele para converter todas as codificações de nova linha para '\ n' nos bastidores? Eu quero ser capaz de ligar getline
e lidar com todas as terminações de linha normalmente.
Atualização : para esclarecer, eu quero ser capaz de escrever código que compile em quase qualquer lugar e receba entrada de quase qualquer lugar. Incluindo os arquivos raros que possuem '\ r' sem '\ n'. Minimizando a inconveniência para qualquer usuário do software.
É fácil contornar o problema, mas ainda estou curioso para saber a maneira correta, no padrão, de lidar com flexibilidade com todos os formatos de arquivo de texto.
getline
lê em uma linha completa, até um '\ n', em uma string. O '\ n' é consumido do stream, mas getline não o inclui na string. Tudo bem até agora, mas pode haver um '\ r' logo antes do '\ n' que é incluído na string.
Existem três tipos de terminações de linha vistas em arquivos de texto: '\ n' é a terminação convencional em máquinas Unix, '\ r' era (eu acho) usado em sistemas operacionais Mac antigos e o Windows usa um par, '\ r' seguido por '\ n'.
O problema é que getline
deixa o '\ r' no final da string.
ifstream f("a_text_file_of_unknown_origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
// BUT, there might be an '\r' at the end now.
}
Editar Obrigado a Neil por apontar que f.good()
não era o que eu queria.!f.fail()
é o que eu quero.
Posso removê-lo manualmente (veja a edição desta questão), o que é fácil para os arquivos de texto do Windows. Mas estou preocupado que alguém insira um arquivo contendo apenas '\ r'. Nesse caso, presumo que getline consumirá todo o arquivo, pensando que é uma única linha!
.. e isso nem mesmo considerando Unicode :-)
.. talvez Boost tenha uma boa maneira de consumir uma linha de cada vez de qualquer tipo de arquivo de texto?
Editar Estou usando isso para lidar com os arquivos do Windows, mas ainda acho que não deveria! E isso não bifurcará para os arquivos somente '\ r'.
if(!line.empty() && *line.rbegin() == '\r') {
line.erase( line.length()-1, 1);
}
Respostas:
Como Neil apontou, "o tempo de execução C ++ deve lidar corretamente com qualquer convenção de finalização de linha para sua plataforma particular."
No entanto, as pessoas movem arquivos de texto entre plataformas diferentes, então isso não é bom o suficiente. Aqui está uma função que lida com todas as três terminações de linha ("\ r", "\ n" e "\ r \ n"):
std::istream& safeGetline(std::istream& is, std::string& t) { t.clear(); // The characters in the stream are read one-by-one using a std::streambuf. // That is faster than reading them one-by-one using the std::istream. // Code that uses streambuf this way must be guarded by a sentry object. // The sentry object performs various tasks, // such as thread synchronization and updating the stream state. std::istream::sentry se(is, true); std::streambuf* sb = is.rdbuf(); for(;;) { int c = sb->sbumpc(); switch (c) { case '\n': return is; case '\r': if(sb->sgetc() == '\n') sb->sbumpc(); return is; case std::streambuf::traits_type::eof(): // Also handle the case when the last line has no line ending if(t.empty()) is.setstate(std::ios::eofbit); return is; default: t += (char)c; } } }
E aqui está um programa de teste:
int main() { std::string path = ... // insert path to test file here std::ifstream ifs(path.c_str()); if(!ifs) { std::cout << "Failed to open the file." << std::endl; return EXIT_FAILURE; } int n = 0; std::string t; while(!safeGetline(ifs, t).eof()) ++n; std::cout << "The file contains " << n << " lines." << std::endl; return EXIT_SUCCESS; }
fonte
t
está vazio antes de definir o eofbit. Essa parte não deveria ser definida independentemente de outros caracteres terem sido lidos?std::get_line
que ignora uma última linha vazia. Usei o seguinte código no caso eof para emular ostd::get_line
comportamento:is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
O tempo de execução C ++ deve lidar corretamente com qualquer convenção de linha final para sua plataforma específica. Especificamente, este código deve funcionar em todas as plataformas:
#include <string> #include <iostream> using namespace std; int main() { string line; while( getline( cin, line ) ) { cout << line << endl; } }
Claro, se você estiver lidando com arquivos de outra plataforma, todas as apostas estão canceladas.
Como as duas plataformas mais comuns (Linux e Windows) terminam as linhas com um caractere de nova linha, com o Windows precedendo-o com um retorno de carro, você pode examinar o último caractere da
line
string no código acima para ver se é\r
e se é remova-o antes de fazer o processamento específico do aplicativo.Por exemplo, você pode fornecer a si mesmo uma função de estilo getline parecida com esta (não testada, uso de índices, substr etc. apenas para fins pedagógicos):
ostream & safegetline( ostream & os, string & line ) { string myline; if ( getline( os, myline ) ) { if ( myline.size() && myline[myline.size()-1] == '\r' ) { line = myline.substr( 0, myline.size() - 1 ); } else { line = myline; } } return os; }
fonte
safegetline
é uma parte importante de uma solução. Mas se este programa estiver sendo compilado no Windows, também precisarei abrir o arquivo no formato binário? Os compiladores do Windows (em modo de texto) permitem que '\ n' se comporte como '\ r' '\ n'?ifstream f("f.txt", ios_base :: binary | ios_base::in );
Você está lendo o arquivo em modo BINÁRIO ou TEXTO ? No modo TEXTO , o par retorno de carro / alimentação de linha, CRLF , é interpretado como TEXTO de fim de linha ou caractere de fim de linha, mas em BINÁRIO você busca apenas UM byte por vez, o que significa que qualquer um dos caracteres DEVEser ignorado e deixado no buffer para ser obtido como outro byte! Retorno de carro significa, na máquina de escrever, que o carro da máquina de escrever, onde está o braço de impressão, atingiu a borda direita do papel e voltou à borda esquerda. Este é um modelo muito mecânico, o da máquina de escrever mecânica. Em seguida, o avanço de linha significa que o rolo de papel é girado um pouco para cima, de forma que o papel esteja em posição de iniciar outra linha de digitação. Pelo que me lembro, um dos dígitos mais baixos em ASCII significa mover um caractere para a direita sem digitar, o caractere morto e, claro, \ b significa retroceder: mover o carro um caractere para trás. Dessa forma, você pode adicionar efeitos especiais, como subjacente (digite sublinhado), tachado (digite menos), acentos diferentes aproximados, cancelar (digite X), sem a necessidade de um teclado estendido, apenas ajustando a posição do carro ao longo da linha antes de entrar na alimentação de linha. Portanto, você pode usar voltagens ASCII de byte para controlar automaticamente uma máquina de escrever sem um computador no meio. Quando a máquina de escrever automática é introduzida,AUTOMÁTICO significa que uma vez que você atinge a borda mais distante do papel, o carro é retornado para a esquerda E o avanço de linha é aplicado, ou seja, o carro é assumido como retornado automaticamente conforme o rolo sobe! Portanto, você não precisa de ambos os caracteres de controle, apenas um, o \ n, nova linha ou alimentação de linha.
Isso não tem nada a ver com programação, mas ASCII é mais antigo e HEY! parece que algumas pessoas não estavam pensando quando começaram a fazer coisas de texto! A plataforma UNIX assume uma máquina de tipo elétrica automática; o modelo do Windows é mais completo e permite o controle de máquinas mecânicas, embora alguns caracteres de controle se tornem cada vez menos úteis em computadores, como o caractere de sino, 0x07 se bem me lembro ... Alguns textos esquecidos devem ter sido originalmente capturados com caracteres de controle para máquinas de escrever eletricamente controladas e perpetuou o modelo ...
Na verdade, a variação correta seria incluir apenas o \ r, alimentação de linha, o retorno do carro sendo desnecessário, ou seja, automático, portanto:
char c; ifstream is; is.open("",ios::binary); ... is.getline(buffer, bufsize, '\r'); //ignore following \n or restore the buffer data if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c); ...
seria a maneira mais correta de lidar com todos os tipos de arquivos. Observe, entretanto, que \ n no modo TEXT é na verdade o par de bytes 0x0d 0x0a, mas 0x0d IS apenas \ r: \ n inclui \ r no modo TEXT , mas não no BINARY , então \ ne \ r \ n são equivalentes ... ou deveria estar. Esta é uma confusão muito básica da indústria, na verdade, inércia típica da indústria, já que a convenção é falar de CRLF, em TODAS as plataformas, então cair em diferentes interpretações binárias. A rigor, os arquivos que incluem SOMENTE 0x0d (retorno de carro) como sendo \ n (CRLF ou alimentação de linha) estão malformados em TEXTmodo (máquina de escrever: basta retornar o carro e tachar tudo ...), e são um formato binário não orientado por linha (\ r ou \ r \ n significando orientado por linha) então você não deve ler como texto! O código deve falhar, talvez com alguma mensagem do usuário. Isso não depende apenas do sistema operacional, mas também da implementação da biblioteca C, aumentando a confusão e as possíveis variações ... (particularmente para camadas de tradução UNICODE transparentes adicionando outro ponto de articulação para variações confusas).
O problema com o trecho de código anterior (máquina de escrever mecânica) é que ele é muito ineficiente se não houver \ n caracteres após \ r (texto de máquina de escrever automática). Em seguida, também assume o modo BINÁRIO , onde a biblioteca C é forçada a ignorar as interpretações de texto (local) e fornecer os bytes absolutos. Não deve haver diferença nos caracteres de texto reais entre os dois modos, apenas nos caracteres de controle, portanto, de modo geral, ler BINÁRIO é melhor do que o modo TEXTO . Esta solução é eficiente para BINARYmodo arquivos de texto típicos do sistema operacional Windows, independentemente das variações da biblioteca C, e ineficiente para outros formatos de texto de plataforma (incluindo traduções da web em texto). Se você se preocupa com a eficiência, o caminho a percorrer é usar um ponteiro de função, fazer um teste para \ r vs \ r \ n controles de linha da maneira que quiser, então selecione o melhor código de usuário getline no ponteiro e invoque-o de isto.
A propósito, lembro que encontrei alguns \ r \ r \ n arquivos de texto também ... o que se traduz em texto de linha dupla, assim como ainda é exigido por alguns consumidores de texto impresso.
fonte
Uma solução seria primeiro pesquisar e substituir todas as terminações de linha por '\ n' - como, por exemplo, o Git faz por padrão.
fonte
Além de escrever seu próprio manipulador personalizado ou usar uma biblioteca externa, você está sem sorte. A coisa mais fácil a fazer é verificar se
line[line.length() - 1]
não é '\ r'. No Linux, isso é supérfluo, pois a maioria das linhas termina com '\ n', o que significa que você perderá um bom tempo se ocorrer um loop. No Windows, isso também é supérfluo. No entanto, e os arquivos clássicos do Mac que terminam em '\ r'? std :: getline não funcionaria para esses arquivos no Linux ou Windows porque '\ n' e '\ r' '\ n' ambos terminam com '\ n', eliminando a necessidade de verificar por '\ r'. Obviamente, essa tarefa que funciona com esses arquivos não funcionaria bem. Claro, existem os numerosos sistemas EBCDIC, algo que a maioria das bibliotecas não se atreverá a enfrentar.Verificar '\ r' é provavelmente a melhor solução para o seu problema. A leitura no modo binário permitiria a você verificar todas as três terminações de linha comuns ('\ r', '\ r \ n' e '\ n'). Se você se preocupa apenas com o Linux e o Windows, já que as terminações de linha do Mac antigo não devem durar muito mais tempo, verifique apenas '\ n' e remova o caractere '\ r' à direita.
fonte
Se for conhecido quantos itens / números cada linha tem, pode-se ler uma linha com, por exemplo, 4 números como
string num; is >> num >> num >> num >> num;
Isso também funciona com outras terminações de linha.
fonte