C++を使用して、テキストファイルを1行毎に読み取る方法を説明します。
開発環境 | Visual Studio 2022 |
---|---|
言語 | C++ |
動作確認 | Windows 11 Windows 10 Windows 7 |
Ubuntu (g++) FreeBSD (clang++) |
以下の文字コードの組み合わせに対応します。また、UTFはBOM有り/無し、改行コードはCR+LF/LF/CRのいずれにも対応します。
変数ファイル | Shift_JIS | UTF-8 | UTF-16 | UTF-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行分のテキストが入る
}
}