このページは、練習問題の解答例や解説のページです。
ASCII で正常に読めるように作られたテキストファイルを、UTF-8 (BOM付き) で保存しなおしたら、ファイルの大きさは増えますか?
UTF-8 では、ASCII で表現できる文字は、ASCII とまったく同じ表現になります(本編解説)。したがって、ASCII で表現できていたのなら、UTF-8 にしても変化しないので、大きさは変わりません。
しかし、BOM付きにする場合は、BOM の分だけ(3バイト)大きさが増えます(本編解説)。
テキストエディタで適当なファイルを作って、まず ASCII で保存したときのサイズを調べ、UTF-8 (BOM付き) で保存しなおしてサイズを調べてみるといいでしょう。Windows なら「メモ帳」で試すとやりやすいです。【ファイル】>【名前を付けて保存】を選び、【文字コード】を切り替えて保存します。「ANSI」が ASCII を意味しています。「UTF-8 (BOM付き)」で保存しなおすと、3バイト増えます。
UTF-8 (BOM付き) のテキストファイルを用意して、そのファイルの内容を読み取ると、どうなるか確認してみてください。また、BOM を取り除いた部分だけを読み取るにはどうすれば良いでしょうか?
UTF-8 (BOM付き) のテキストファイルは、適当なテキストエディタで作成します。Windows の「メモ帳」でも、【ファイル】>【名前を付けて保存】を選び、【文字コード】を「UTF-8 (BOM付き)」で保存すれば作成できます。
ここでは、「あいうえお」とだけ書き込んだファイルにしたとします。この場合、「あいうえお」の手前に BOM の 3バイトが付加された状態になります。そのため、ファイル全体を普通に読み込んで出力させると、「あいうえお」の手前に余計なゴミのようなものが出力されます。
#include <fstream>
#include <iostream>
#include <string>
#ifdef _WIN32
#define NOMINMAX
#include <Windows.h>
#endif
int main()
{
#ifdef _WIN32
(CP_UTF8); // コマンドプロンプトでの表示に UTF-8 を使う
SetConsoleOutputCP#endif
std::ifstream ifs("test.txt");
std::string s {};
>> s;
ifs std::cout << s << "\n";
}
実行結果(UTF-8 で表示):
?あいうえお
BOM を取り除くためには、先頭の 3バイトを捨てればいいです。これまでのページの知識でできるのは、1度普通に読み込んだあと、std::string の substr関数(「マルチバイト文字」のページを参照)を使って、先頭の 3文字を捨てることです。
#include <fstream>
#include <iostream>
#include <string>
#ifdef _WIN32
#define NOMINMAX
#include <Windows.h>
#endif
int main()
{
#ifdef _WIN32
(CP_UTF8); // コマンドプロンプトでの表示に UTF-8 を使う
SetConsoleOutputCP#endif
std::ifstream ifs("test.txt");
std::string s {};
>> s;
ifs = s.substr(3); // BOM を捨てる
s std::cout << s << "\n";
}
実行結果(UTF-8 で表示):
あいうえお
UTF-8 の BOM は 16進数表記で 0xEF 0xBB 0xBF です。そこで、本当に先頭の 3バイトが BOM であることを確認したうえで取り除くほうが確実といえます。16進数表記の整数を扱う方法はまだ説明していませんが、次のようにして実現できます。
#include <fstream>
#include <iostream>
#include <string>
#ifdef _WIN32
#define NOMINMAX
#include <Windows.h>
#endif
int main()
{
#ifdef _WIN32
(CP_UTF8);
SetConsoleOutputCP#endif
std::ifstream ifs("test.txt");
std::string s {};
>> s;
ifs
// BOM があれば捨てる
if (s.length() >= 3) {
if (static_cast<unsigned char>(s[0]) == 0xEF
&& static_cast<unsigned char>(s[1]) == 0xBB
&& static_cast<unsigned char>(s[2]) == 0xBF
) {
= s.substr(3);
s }
}
std::cout << s << "\n";
}
実行結果(UTF-8 で表示):
あいうえお
BOM をチェックするにあたって、まず 3バイト以上読み込めていることを確認します。BOM が付いていない場合、ファイルの大きさが 3バイトに満たない可能性があるため、範囲外アクセスを起こしかねないからです。
std::string の要素は
char型ですが、これは符号の有無が処理系定義です(「整数型」のページを参照)。BOM
を構成する各バイト (0xEF 0xBB 0xBF) はいずれも、10進数でいえば
+128以上の数なので(それぞれ、239、187、191)、もし符号付き整数型
(signed char に相当)
する型であった場合、表現の上限値(+127)を超えてしまうため、負数であるかのように扱われることになります。そのため、そのまま
s[0] == 0xEF
のような比較を行ってしまうと正しい結果になりません。そこで、unsigned
char型にキャストして、確実に 0xEF 0xBB 0xBF
を表現できるようにしたうえで比較させています。
UTF-8 で、トランプのマークを1つ表現するには、何バイト必要でしょうか?
インターネットで UTF-8 の文字の一覧を探してみるのもいいですし、プログラムで試してみるのもいいでしょう。
「♠」を調べてみると、UTF-8 では 16進数で e299a0 であることが分かります。Windows の「電卓」で、16進数 e299a0 が、2進数で何桁あるのか調べてみると、24ビットであることが分かるので、これは 3バイトのようです(「電卓」の使い方は、Windows編>「電卓で基数変換する」を参照してください)。
2660 を見つけたかもしれませんが、それは Unicode のコードポイントです。UTF-8 での表現とは別のものです(本編解説)。
std::string の変数に入れて、length関数を使っていくつが返ってくるか見るのも一つの手です。
#include <iostream>
#include <string>
int main()
{
std::string s {u8"♠"};
std::cout << s.length() << "\n";
}
実行結果:
3
std::string の length関数が返す値は、「見た目どおりの文字数」ではなく、「char型で何個分の文字があるか」です(「マルチバイト文字」のページを参照)。3 が返されてきたということは、char型で 3個分、つまり 3バイトということになります。
#define NOMINMAX
を追加はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
RSS | 管理者情報 | プライバシーポリシー |