【C++】テキストファイルを1行毎に読み取る

 C++を使用して、テキストファイルを1行毎に読み取る方法を説明します。

開発環境Visual Studio 2022
言語C++
動作確認Windows 11
Windows 10
Windows 7
Ubuntu (g++)
FreeBSD (clang++)

 以下の文字コードの組み合わせに対応します。また、UTFはBOM有り/無し、改行コードはCR+LF/LF/CRのいずれにも対応します。

変数ファイルShift_JISUTF-8UTF-16UTF-32
Shift_JIS(1)両方(2)Windows(※A)(2)Windows×
UTF-8×(1)両方(1)Windows(※B)(2)UNIX系OS
UTF-16×(3)Windows(※A)(3)Windows×
UTF-32×××(3)UNIX系OS

 (※A) UTF-16として読み込んだ後、UTF-8に変換します。
 (※B) UTF-8として読み込んだ後、UTF-16に変換します。
 変換方法は、「【C++/Win32】UTF-16とUTF-8を相互に変換する」を参照してください。

 サンプルソースは、std::ifstreamまたはstd::wifstreamにてファイルをオープンした後、std::getlineにて各行を取り出しています。その際、WindowsはCR+LFまたはLF、UNIX系OSはLFを改行コードとして認識します。よって認識されないCRは、そのままテキストの一部として読み込まれてしまいます。そのため、外側のwhileループは各OSの改行コード、内側のwhileループはCRにて1行を取り出すために、2重ループになっています。該当の改行コードが無い場合は、丸ごと1行として取り出すため、このロジックで問題ありません。

(1)マルチバイト文字をそのまま

 Shift_JIS、UTF-8を、そのままchar配列(std::string)に読み込みます。その他、1byte部分がUS-ASCIIと互換性があり、null終端以外の0x00が無い文字コードなら、同様に対応します。


#include <cstring>
#include <fstream>
#include <sstream>


    std::ifstream ifs("ファイルのパス");

    if (!ifs)
    {
        // オープン失敗
        return 1;
    }

    // UTF-8のBOM判定
    const unsigned char bom[]{0xEF, 0xBB, 0xBF};

    char b[sizeof(bom)]{};
    ifs.read(b, sizeof(b));
    std::streamsize s = ifs.gcount();

    if (!(s == sizeof(bom) && 0 == std::memcmp(b, bom, sizeof(bom))))
    {
        // BOM無し
        // 読み取り位置を先頭に戻す
        ifs.clear();
        ifs.seekg(0, ifs.beg);
    }

    std::string linelf;
    std::string linecr;

    // (ここのロジックは、本文にて解説しています)
    while (std::getline(ifs, linelf))
    {
        std::istringstream iss(linelf);

        while (std::getline(iss, linecr, '\r'))
        {
            // linecrに1行分のテキストが入る
        }
    }

    ifs.close();

(2)マルチバイト文字をワイド文字に変換

 Shift_JIS、UTF-8を、wchar_t配列(std::wstring)に格納できる文字コードに変換して読み込みます。WindowsはShift_JISをUTF-16に、UNIX系OSはUTF-8をUTF-32に変換します。参考までにwchar_tのサイズは、Windowsは2byte、UNIX系OSは4byteです。


#include <fstream>
#include <sstream>


    // OSのデフォルトの文字コードを設定する
    std::locale::global(std::locale(""));

    std::wifstream ifs("ファイルのパス");

    if (!ifs)
    {
        // オープン失敗
        return 1;
    }

#ifndef _WIN32
    // BOM判定
    // UTF-8にBOMが存在する場合、UTF-32のBOMに変換される
    wchar_t b = 0;
    ifs.read(&b, 1);

    if (b != 0x0000FEFF)
    {
        // BOM無し
        // 読み取り位置を先頭に戻す
        ifs.clear();
        ifs.seekg(0, ifs.beg);
    }
#endif

    std::wstring linelf;
    std::wstring linecr;

    // (ここのロジックは、本文にて解説しています)
    while (std::getline(ifs, linelf))
    {
        std::wistringstream iss(linelf);

        while (std::getline(iss, linecr, L'\r'))
        {
            // linecrに1行分のテキストが入る
        }
    }

    ifs.close();

(3)ワイド文字をそのまま

 UTF-16、UTF-32を、そのままwchar_t配列(std::wstring)に読み込みます。WindowsはUTF-16に、UNIX系OSはUTF-32に対応します。

 以下のソースは、ファイルサイズの取得に<filesystem>を使用しているので、標準C++17(以降)でビルドしてください。Visual Studio 2022の場合、プロジェクトのプロパティにて、[全般]-[C++言語標準]に「ISO C++17標準(/std:c++17)」以降を選択します。UNIX系OSの場合、コンパイラのオプションに「-std=c++17」を付けます。


#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>


    // バイナリ形式にてオープン
    std::ifstream ifs("ファイルのパス", std::ios_base::binary);

    if (!ifs)
    {
        // オープン失敗
        return 1;
    }

    std::uintmax_t size = std::filesystem::file_size("ファイルのパス");

    if (!(size > 0 && size % sizeof(wchar_t) == 0))
    {
        // ファイルサイズ不正
        return 1;
    }

    // BOM判定
    char c[sizeof(wchar_t)]{};
    ifs.read(c, sizeof(c));

#ifdef _WIN32
    // UTF-16
    const unsigned char bomBE[]{0xFE, 0xFF};
    const unsigned char bomLE[]{0xFF, 0xFE};
#else
    // UTF-32
    const unsigned char bomBE[]{0x00, 0x00, 0xFE, 0xFF};
    const unsigned char bomLE[]{0xFF, 0xFE, 0x00, 0x00};
#endif

    // BOM無しの場合、ビッグエンディアンとする
    bool isBE = true;

    if (0 == std::memcmp(c, bomBE, sizeof(bomBE)))
    {
        // ビッグエンディアン
        size = size - sizeof(bomBE);
    }
    else if (0 == std::memcmp(c, bomLE, sizeof(bomLE)))
    {
        // リトルエンディアン
        size = size - sizeof(bomLE);
        isBE = false;
    }
    else
    {
        // BOM無し
        // 読み取り位置を先頭に戻す
        ifs.clear();
        ifs.seekg(0, ifs.beg);
    }

    // char配列をwchar_t配列に載せ替える
    std::vector<wchar_t> w(size / sizeof(wchar_t) + 1);

    for (std::size_t i = 0; i < size / sizeof(wchar_t); i++)
    {
        ifs.read(c, sizeof(c));

        if (isBE)
        {
            // ビッグエンディアン
#ifdef _WIN32
            // UTF-16
            w[i] = (static_cast<unsigned char>(c[0]) << 8) |
                static_cast<unsigned char>(c[1]);
#else
            // UTF-32
            w[i] = (static_cast<unsigned char>(c[0]) << 24) |
                (static_cast<unsigned char>(c[1]) << 16) |
                (static_cast<unsigned char>(c[2]) << 8) |
                static_cast<unsigned char>(c[3]);
#endif
        }
        else
        {
            // リトルエンディアン
#ifdef _WIN32
            // UTF-16
            w[i] = (static_cast<unsigned char>(c[1]) << 8) |
                static_cast<unsigned char>(c[0]);
#else
            // UTF-32
            w[i] = (static_cast<unsigned char>(c[3]) << 24) |
                (static_cast<unsigned char>(c[2]) << 16) |
                (static_cast<unsigned char>(c[1]) << 8) |
                static_cast<unsigned char>(c[0]);
#endif
        }
    }

    ifs.close();

    std::wistringstream f(&w[0]);
    std::wstring linelf;
    std::wstring linecr;

    // (ここのロジックは、本文にて解説しています)
    while (std::getline(f, linelf))
    {
        std::wistringstream iss(linelf);

        while (std::getline(iss, linecr, L'\r'))
        {
            // linecrに1行分のテキストが入る
        }
    }