Ler arquivo linha por linha usando ifstream em C ++


612

O conteúdo de file.txt é:

5 3
6 4
7 1
10 5
11 6
12 3
12 4

Onde 5 3está um par de coordenadas. Como processar esses dados linha por linha em C ++?

Consigo obter a primeira linha, mas como obtenho a próxima linha do arquivo?

ifstream myfile;
myfile.open ("text.txt");

Respostas:


916

Primeiro, faça um ifstream:

#include <fstream>
std::ifstream infile("thefile.txt");

Os dois métodos padrão são:

  1. Suponha que cada linha consista em dois números e leia token por token:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Análise baseada em linha, usando fluxos de sequência:

    #include <sstream>
    #include <string>
    
    std::string line;
    while (std::getline(infile, line))
    {
        std::istringstream iss(line);
        int a, b;
        if (!(iss >> a >> b)) { break; } // error
    
        // process pair (a,b)
    }

Você não deve misturar (1) e (2), pois a análise baseada em token não devora novas linhas; portanto, você pode acabar com linhas vazias falsas se usar getline()após a extração baseada em token o levar ao final de um linha já.


1
@ EdwardKarak: Eu não entendo o que "vírgulas como o token" significa. Vírgulas não representam números inteiros.
Kerrek SB

8
o OP usou um espaço para delimitar os dois números inteiros. Eu queria saber se while (infile >> um >> b) iria funcionar se o OP usado um como uma vírgula um delimitador, porque esse é o cenário em meu próprio programa
Edward Karak

30
@ EdwardKarak: Ah, então quando você disse "token", você quis dizer "delimitador". Direita. Com uma vírgula, você diria:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB

11
@KerrekSB: Hein. Eu estava errado. Eu não sabia que poderia fazer isso. Talvez eu tenha algum código próprio para reescrever.
Mark H

4
Para obter uma explicação do while(getline(f, line)) { }construto e sobre o tratamento de erros, consulte este (meu) artigo: gehrcke.de/2011/06/… (acho que não preciso ter má consciência postando isso aqui, é um pouco data esta resposta).
Dr. Jan-Philip Gehrcke

175

Use ifstreampara ler dados de um arquivo:

std::ifstream input( "filename.ext" );

Se você realmente precisa ler linha por linha, faça o seguinte:

for( std::string line; getline( input, line ); )
{
    ...for each line in input...
}

Mas você provavelmente só precisa extrair pares de coordenadas:

int x, y;
input >> x >> y;

Atualizar:

Em seu código você usa ofstream myfile;, no entanto, o oem ofstreamstands para output. Se você quiser ler do arquivo (entrada), use ifstream. Se você deseja ler e escrever, use fstream.


8
Sua solução foi um pouco melhorada: sua variável de linha não é visível após a leitura do arquivo, em contraste com a segunda solução do Kerrek SB, que também é uma solução boa e simples.
DanielTuzes

3
getlineestá em string ver , por isso, não se esqueça do#include <string>
mxmlnkn

55

A leitura de um arquivo linha por linha no C ++ pode ser feita de várias maneiras diferentes.

[Rápido] Loop com std :: getline ()

A abordagem mais simples é abrir um std :: ifstream e um loop usando chamadas std :: getline (). O código é limpo e fácil de entender.

#include <fstream>

std::ifstream file(FILENAME);
if (file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        // using printf() in all tests for consistency
        printf("%s", line.c_str());
    }
    file.close();
}

[Rápido] Use o file_description_source do Boost

Outra possibilidade é usar a biblioteca Boost, mas o código fica um pouco mais detalhado. O desempenho é bastante semelhante ao código acima (Loop com std :: getline ()).

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>

namespace io = boost::iostreams;

void readLineByLineBoost() {
    int fdr = open(FILENAME, O_RDONLY);
    if (fdr >= 0) {
        io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
        io::stream <io::file_descriptor_source> in(fdDevice);
        if (fdDevice.is_open()) {
            std::string line;
            while (std::getline(in, line)) {
                // using printf() in all tests for consistency
                printf("%s", line.c_str());
            }
            fdDevice.close();
        }
    }
}

[Mais rápido] Use o código C

Se o desempenho for crítico para o seu software, considere usar a linguagem C. Esse código pode ser 4-5 vezes mais rápido que as versões C ++ acima, consulte a referência abaixo

FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
    exit(EXIT_FAILURE);

char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
    // using printf() in all tests for consistency
    printf("%s", line);
}
fclose(fp);
if (line)
    free(line);

Referência - Qual é o mais rápido?

Fiz alguns benchmarks de desempenho com o código acima e os resultados são interessantes. Testei o código com arquivos ASCII que contêm 100.000 linhas, 1.000.000 de linhas e 10.000.000 de linhas de texto. Cada linha de texto contém 10 palavras em média. O programa é compilado com -O3otimização e sua saída é encaminhada /dev/nullpara remover a variável do tempo de registro da medição. Por último, mas não menos importante, cada parte do código registra cada linha com a printf()função de consistência.

Os resultados mostram o tempo (em ms) que cada parte do código levou para ler os arquivos.

A diferença de desempenho entre as duas abordagens de C ++ é mínima e não deve fazer nenhuma diferença na prática. O desempenho do código C é o que torna o benchmark impressionante e pode mudar o jogo em termos de velocidade.

                             10K lines     100K lines     1000K lines
Loop with std::getline()         105ms          894ms          9773ms
Boost code                       106ms          968ms          9561ms
C code                            23ms          243ms          2397ms

insira a descrição da imagem aqui


1
O que acontece se você remover a sincronização do C ++ com o C nas saídas do console? Você pode estar se medir uma desvantagem conhecida do comportamento padrão std::coutvs printf.
user4581301

2
Obrigado por trazer esta preocupação. Eu refiz os testes e o desempenho ainda é o mesmo. Eu editei o código para usar a printf()função em todos os casos por consistência. Eu também tentei usar std::coutem todos os casos e isso não fez absolutamente nenhuma diferença. Como acabei de descrever no texto, a saída do programa vai para /dev/nullque o tempo para imprimir as linhas não seja medido.
HugoTeixeira 31/07

6
Groovy. Obrigado. Gostaria de saber onde está a desaceleração.
user4581301

4
Oi @HugoTeixeira Eu sei que isto é uma discussão antiga, eu tentei replicar seus resultados e não podia ver nenhuma diferença significativa entre C e C ++ github.com/simonsso/readfile_benchmarks
Simson

Por padrão, os fluxos de entrada e saída do C ++ são sincronizados com cstdio. Você deveria ter tentado com a configuração std::ios_base::sync_with_stdio(false). Eu acho que você teria desempenhos muito melhores (ainda não é garantido, pois é definido pela implementação quando a sincronização é desativada).
Fareanor 9/12/19

11

Como suas coordenadas pertencem juntas como pares, por que não escrever uma estrutura para elas?

struct CoordinatePair
{
    int x;
    int y;
};

Em seguida, você pode gravar um operador de extração sobrecarregado para istreams:

std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
    is >> coordinates.x >> coordinates.y;

    return is;
}

E então você pode ler um arquivo de coordenadas diretamente em um vetor como este:

#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    char filename[] = "coordinates.txt";
    std::vector<CoordinatePair> v;
    std::ifstream ifs(filename);
    if (ifs) {
        std::copy(std::istream_iterator<CoordinatePair>(ifs), 
                std::istream_iterator<CoordinatePair>(),
                std::back_inserter(v));
    }
    else {
        std::cerr << "Couldn't open " << filename << " for reading\n";
    }
    // Now you can work with the contents of v
}

1
O que acontece quando não é possível ler dois inttokens do fluxo operator>>? Como alguém pode fazê-lo funcionar com um analisador de operator>>retorno (ou seja, quando falhar, reverta o fluxo para a posição anterior e retorne falso ou algo assim)?
Fferri 01/12

Se não for possível ler dois inttokens, o isfluxo será avaliado falsee o loop de leitura terminará nesse ponto. Você pode detectar isso lá dentro operator>>, verificando o valor de retorno das leituras individuais. Se você deseja reverter o fluxo, você ligaria is.clear().
Martin Broadhurst

no operator>>, é mais correto dizer is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;que, caso contrário, você está assumindo que seu fluxo de entrada está no modo de pular espaço em branco.
Darko Veberic 27/03

7

Expandir a resposta aceita, se a entrada for:

1,NYC
2,ABQ
...

você ainda poderá aplicar a mesma lógica, assim:

#include <fstream>

std::ifstream infile("thefile.txt");
if (infile.is_open()) {
    int number;
    std::string str;
    char c;
    while (infile >> number >> c >> str && c == ',')
        std::cout << number << " " << str << "\n";
}
infile.close();

2

Embora não seja necessário fechar o arquivo manualmente, é uma boa ideia fazê-lo se o escopo da variável do arquivo for maior:

    ifstream infile(szFilePath);

    for (string line = ""; getline(infile, line); )
    {
        //do something with the line
    }

    if(infile.is_open())
        infile.close();

Não tenho certeza se isso mereceu um voto negativo. O OP pediu uma maneira de obter cada linha. Esta resposta faz isso e fornece uma ótima dica para garantir que o arquivo seja fechado. Para um programa simples, pode não ser necessário, mas pelo menos um GRANDE hábito de se formar. Talvez possa ser melhorado adicionando algumas linhas de código para processar as linhas individuais que ele puxa, mas no geral é a resposta mais simples para a pergunta dos OPs.
Xandor 18/09/19

2

Esta resposta é para o visual studio 2017 e se você quiser ler do arquivo de texto qual local é relativo ao seu aplicativo de console compilado.

primeiro, coloque seu arquivo de texto (test.txt neste caso) na pasta da solução. Após a compilação, mantenha o arquivo de texto na mesma pasta com applicationName.exe

C: \ Users \ "nome de usuário" \ source \ repos \ "solutionName" \ "solutionName"

#include <iostream>
#include <fstream>

using namespace std;
int main()
{
    ifstream inFile;
    // open the file stream
    inFile.open(".\\test.txt");
    // check if opening a file failed
    if (inFile.fail()) {
        cerr << "Error opeing a file" << endl;
        inFile.close();
        exit(1);
    }
    string line;
    while (getline(inFile, line))
    {
        cout << line << endl;
    }
    // close the file stream
    inFile.close();
}

1

Esta é uma solução geral para carregar dados em um programa C ++ e usa a função readline. Isso pode ser modificado para arquivos CSV, mas o delimitador é um espaço aqui.

int n = 5, p = 2;

int X[n][p];

ifstream myfile;

myfile.open("data.txt");

string line;
string temp = "";
int a = 0; // row index 

while (getline(myfile, line)) { //while there is a line
     int b = 0; // column index
     for (int i = 0; i < line.size(); i++) { // for each character in rowstring
          if (!isblank(line[i])) { // if it is not blank, do this
              string d(1, line[i]); // convert character to string
              temp.append(d); // append the two strings
        } else {
              X[a][b] = stod(temp);  // convert string to double
              temp = ""; // reset the capture
              b++; // increment b cause we have a new number
        }
    }

  X[a][b] = stod(temp);
  temp = "";
  a++; // onto next row
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.