Qt QTextStreamの基数設定:setIntegerBase()から学ぶ数値フォーマット

2025-05-27

QTextStream::setIntegerBase()は、QtのQTextStreamクラスが整数を読み書きする際の基数(進数)を設定するための関数です。

QTextStreamは、ファイルやデバイス(QIODevice)、バイト配列(QByteArray)、文字列(QString)などに対して、テキスト形式でデータをストリーム入出力するための非常に便利なクラスです。数値の読み書きにおいて、デフォルトでは自動的に基数を検出しますが、この関数を使うことでその挙動を制御できます。

  • 自動検出の無効化: デフォルトでは、QTextStreamは読み込む数値のプレフィックス(例: "0x"は16進数、「0」で始まる場合は8進数など)に基づいて基数を自動的に検出します。setIntegerBase()を使うと、この自動検出を無効にし、指定された基数のみを使用するように強制できます。
  • 基数の設定: QTextStreamが整数を読み込んだり、ストリームに整数を書き込んだりする際に、どの基数(10進数、16進数、8進数、2進数など)を使用するかを指定します。

使用例

通常、QTextStreamで整数をストリームに書き込む場合、デフォルトでは10進数で出力されます。

#include <QTextStream>
#include <QString>

int main() {
    QString output;
    QTextStream stream(&output);

    int value = 255;
    stream << "Decimal: " << value << "\n"; // デフォルトは10進数

    stream.setIntegerBase(16); // 基数を16進数に設定
    stream << "Hex: " << value << "\n";

    stream.setIntegerBase(8);  // 基数を8進数に設定
    stream << "Octal: " << value << "\n";

    stream.setIntegerBase(10); // 基数を10進数に戻す
    stream << "Decimal (again): " << value << "\n";

    qDebug() << output;
    return 0;
}

上記のコードを実行すると、以下のような出力が得られます。

Decimal: 255
Hex: ff
Octal: 377
Decimal (again): 255

引数

setIntegerBase()は、引数として整数の基数を取ります。有効な値は以下の通りです。

  • 2: 2進数
  • 8: 8進数
  • 16: 16進数
  • 10 (デフォルト): 10進数
  • 数値を読み込む際にも、setIntegerBase()を使用することで、特定の基数でのみ数値を解釈するように強制できます。例えば、setIntegerBase(16)と設定してから数値を読み込むと、"ff"は16進数として解釈され、10進数の255に変換されます。
  • 基数を設定しても、QTextStreamの他の書式設定オプション(例えば、フィールド幅、パディング文字、数値フラグなど)とは独立して機能します。
  • setIntegerBase()は、その後のストリーム操作に影響を与えます。基数を変更したい場合は、再度この関数を呼び出す必要があります。


QTextStream::setIntegerBase()は、整数を読み書きする際の基数を制御するための便利な機能ですが、その使い方を誤ると意図しない結果を招くことがあります。

基数の誤解(読み込み時)

エラー: ファイルから数値を読み込む際に、setIntegerBase()で指定した基数と、実際にファイルに書かれている数値の形式が一致しない。

: ファイルに"0xFF"という文字列が書かれているのに、stream.setIntegerBase(10);を設定してから読み込もうとする。

// NG例: ファイル内容 "0xFF"
QString input = "0xFF";
QTextStream stream(&input);
stream.setIntegerBase(10); // 10進数として読み込もうとする
int value;
stream >> value;
qDebug() << value; // 0xFFを10進数として解釈できず、意図しない値(通常は0)になるか、読み込みが失敗する

トラブルシューティング:

  • エラーチェック: 読み込み後にQTextStream::status()QTextStream::atEnd()、または変数に実際に値が読み込まれたかを確認することで、読み込みの成功/失敗を判断できます。
  • 明示的な基数指定と整合性: もし特定の基数でしか読み込まないことが確実な場合(例: 常に16進数で書かれているファイル)、その基数をsetIntegerBase()で明示的に指定します。ただし、その場合、ファイルの内容がその基数に厳密に従っていることを確認する必要があります。
  • 自動検出の活用: QTextStreamはデフォルトで基数を自動検出します(0xは16進数、0で始まる場合は8進数など)。特別な理由がない限り、読み込み時にはsetIntegerBase()を呼び出さず、自動検出に任せるのが最も安全です。

基数の設定忘れ(書き込み時)

エラー: 特定の基数(例: 16進数)で数値をファイルに書き込みたいのに、setIntegerBase()を呼び忘れている、または間違ったタイミングで呼び出している。

: 常に16進数で出力されることを期待しているのに、設定が抜けている。

// NG例:
QString output;
QTextStream stream(&output);
int value = 255;
stream << value; // デフォルトの10進数で出力される
qDebug() << output; // "255" が出力される

トラブルシューティング:

  • 一時的な設定: 特定の数値だけを異なる基数で出力したい場合は、その数値の前後でsetIntegerBase()を呼び出して基数を一時的に変更し、その後元に戻すようにします。
  • 出力前の設定: 数値を出力するに、必ずsetIntegerBase()を呼び出して目的の基数を設定してください。
// OK例:
QString output;
QTextStream stream(&output);
int value = 255;

stream << "Decimal: " << value << "\n";

stream.setIntegerBase(16); // 16進数に設定
stream << "Hex: " << value << "\n";

stream.setIntegerBase(10); // 10進数に戻す
stream << "Decimal (again): " << value << "\n";

qDebug() << output;

不適切な基数の指定

エラー: setIntegerBase()に、2, 8, 10, 16以外の不正な基数を渡してしまう。

: stream.setIntegerBase(3);のように無効な値を設定する。

トラブルシューティング:

  • 有効な基数のみを使用: QTextStreamがサポートする基数は2 (2進数), 8 (8進数), 10 (10進数), 16 (16進数) のみです。これらの値以外を渡すと、未定義の動作を引き起こす可能性があります。Qtのドキュメントでサポートされている基数を確認してください。

QTextStreamの他のフォーマットオプションとの混同

エラー: setIntegerBase()が数値の基数だけでなく、他の書式設定(例: 符号の表示、大文字/小文字、ゼロパディングなど)も制御すると誤解する。

トラブルシューティング:

  • パディングやフィールド幅はsetFieldWidth()setPadChar()を使います。
  • 16進数の0xプレフィックスや大文字/小文字はsetNumberFlags()QTextStream::ShowBase, QTextStream::UppercaseDigits, QTextStream::UppercaseBaseを使います。
  • 符号の表示 (+など) はsetNumberFlags()QTextStream::ForceSignを使います。
  • setIntegerBase()は、数値の基数のみを制御します。

これらのオプションはそれぞれ独立して設定する必要があります。

ストリームバッファリングと同期の問題

エラー: QTextStreamは内部的にバッファを使用しているため、QFileなどの下位デバイスの操作とQTextStreamの操作を混在させると、データの一貫性が失われることがあります。

: QTextStreamでファイルに書き込んだ後、同じQFileオブジェクトを使って直接読み込もうとすると、QTextStreamのバッファがまだ書き込まれていないために最新のデータが得られない。

トラブルシューティング:

  • QTextStreamの一貫した利用: 基本的には、一度QTextStreamを作成したら、そのストリームを介してのみ読み書きを行い、下位デバイス(例: QFile)を直接操作することは避けるべきです。これにより、内部のバッファリングとポインタの同期が保たれます。
  • flush()の利用: QTextStreamで書き込みを行った後、下位デバイスを直接操作する前にstream.flush();を呼び出し、バッファの内容をデバイスに書き出します。


文字列への書き込み (出力)

QTextStream を使って QString に数値を書き込む際、異なる基数で出力する例です。

#include <QCoreApplication>
#include <QTextStream>
#include <QString>
#include <QDebug> // qlDebug() のために必要

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString outputString;
    QTextStream stream(&outputString);

    int decimalValue = 255; // 10進数で255

    // デフォルト(10進数)で出力
    stream << "Decimal (default): " << decimalValue << "\n";

    // 16進数に設定して出力
    stream.setIntegerBase(16);
    stream << "Hexadecimal: " << decimalValue << "\n";

    // 8進数に設定して出力
    stream.setIntegerBase(8);
    stream << "Octal: " << decimalValue << "\n";

    // 2進数に設定して出力
    stream.setIntegerBase(2);
    stream << "Binary: " << decimalValue << "\n";

    // 基数を10進数に戻す
    stream.setIntegerBase(10);
    stream << "Decimal (again): " << decimalValue << "\n";

    qDebug() << outputString;

    return 0;
}

出力結果:

"Decimal (default): 255
Hexadecimal: ff
Octal: 377
Binary: 11111111
Decimal (again): 255
"

解説:

  • qDebug() << outputString; で、最終的に outputString に格納された内容が出力されます。
  • stream << 演算子で文字列や数値をストリームに挿入できます。
  • stream.setIntegerBase(16); のように setIntegerBase() を呼び出すことで、その後の数値出力が指定された基数で行われます。

文字列からの読み込み (入力)

QTextStream を使って QString から数値を読み込む際、異なる基数で解釈する例です。

#include <QCoreApplication>
#include <QTextStream>
#include <QString>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 読み込み元の文字列
    QString inputString = "10 0xFF 0377 11111111"; // 10進数、16進数、8進数、2進数の値を含む

    QTextStream stream(&inputString);

    int value1, value2, value3, value4;

    // デフォルト(自動検出)で読み込み
    // "10" は10進数、"0xFF" は16進数、"0377" は8進数、"11111111" は10進数として解釈される
    // QTexStreamは"0b"プレフィックスを2進数として自動検出する機能は持っていません。
    // そのため、"11111111"はデフォルトでは10進数として扱われます。
    stream >> value1 >> value2 >> value3 >> value4;
    qDebug() << "Default (Auto-detect):";
    qDebug() << "Value 1 (10):" << value1; // 10
    qDebug() << "Value 2 (0xFF):" << value2; // 255
    qDebug() << "Value 3 (0377):" << value3; // 255
    qDebug() << "Value 4 (11111111):" << value4; // 11111111 (10進数として解釈)
    qDebug() << "--------------------";

    // ストリームの位置をリセット
    stream.seek(0);

    // 全て10進数として読み込みを試みる
    stream.setIntegerBase(10);
    stream >> value1 >> value2 >> value3 >> value4;
    qDebug() << "Set to Decimal (10):";
    qDebug() << "Value 1 (10):" << value1; // 10
    qDebug() << "Value 2 (0xFF):" << value2; // 0 (0xFFは10進数として無効なため)
    qDebug() << "Value 3 (0377):" << value3; // 0 (0377は10進数として無効なため)
    qDebug() << "Value 4 (11111111):" << value4; // 11111111
    qDebug() << "--------------------";

    // ストリームの位置をリセット
    stream.seek(0);

    // グローバルマニピュレータを使用
    // `dec`, `hex`, `oct`, `bin` はそれぞれ `setIntegerBase(10)`, `(16)`, `(8)`, `(2)` と同じ効果
    // QTextStreamは2進数(`bin`)のプレフィックスを自動検出しないため、この例では明示的に`bin`マニピュレータを使用。
    // ただし、入力文字列に"0b"プレフィックスがないため、"11111111"は2進数として正しく解釈されません。
    // "11111111" が2進数として解釈されるためには、入力文字列が "0b11111111" のように記述されている必要があります。
    // 現状のQtのQTextStreamでは、"0b"プレフィックスでの2進数自動検出はサポートされていません。
    // `bin`マニピュレータは、あくまで「その後の読み込みを2進数として解釈しようと試みる」ものであり、
    // "11111111"という文字列を2進数と解釈する保証はありません。このため、`value4`は通常`0`になるでしょう。
    stream >> dec >> value1 >> hex >> value2 >> oct >> value3 >> bin >> value4;
    qDebug() << "Using manipulators (dec, hex, oct, bin):";
    qDebug() << "Value 1 (10):" << value1; // 10
    qDebug() << "Value 2 (0xFF):" << value2; // 255
    qDebug() << "Value 3 (0377):" << value3; // 255
    qDebug() << "Value 4 (11111111 as binary):" << value4; // おそらく 0

    return 0;
}

出力結果例:

Default (Auto-detect):
Value 1 (10): 10
Value 2 (0xFF): 255
Value 3 (0377): 255
Value 4 (11111111): 11111111
--------------------
Set to Decimal (10):
Value 1 (10): 10
Value 2 (0xFF): 0 // 10進数として無効なため、読み込みが失敗し、0になる
Value 3 (0377): 0 // 10進数として無効なため、読み込みが失敗し、0になる
Value 4 (11111111): 11111111
--------------------
Using manipulators (dec, hex, oct, bin):
Value 1 (10): 10
Value 2 (0xFF): 255
Value 3 (0377): 255
Value 4 (11111111 as binary): 0 // "11111111"を2進数として解釈できず、0になる

解説:

  • 重要な注意点: QTextStream は、bin (2進数) の入力について、"0b" のようなプレフィックスを自動検出するわけではありません。setIntegerBase(2)bin マニピュレータを使用しても、"11111111" のようなプレフィックスなしの文字列は2進数として正しく解釈されない場合があります。これは QString::toInt() などが持つ機能とは異なります。確実な2進数変換が必要な場合は、文字列として読み込んでから QString::toInt(nullptr, 2) のように明示的に変換する必要があります。
  • グローバルマニピュレータ (dec, hex, oct, bin) は、setIntegerBase() を呼び出すのと同じ効果があります。これらはストリーム内に直接挿入でき、その後の読み書きに影響を与えます。
  • setIntegerBase(10); のように明示的に10進数を指定すると、それ以外の形式(0xFF など)は無効な数値として扱われ、通常は0が読み込まれるか、読み込みが失敗します。
  • QTextStream はデフォルトで 0x を16進数、0 で始まる数値を8進数として自動検出します。
  • 読み込みの場合、setIntegerBase() はストリームが数値をどのように解釈するかを指示します。

QFileQTextStream を組み合わせて、ファイルに異なる基数で書き込み、その後読み込む例です。

#include <QCoreApplication>
#include <QTextStream>
#include <QFile>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString fileName = "numbers.txt";
    int valueToWrite = 12345;

    // ファイルに書き込み
    QFile writeFile(fileName);
    if (writeFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
        QTextStream outStream(&writeFile);

        outStream << "Decimal: " << valueToWrite << "\n";

        outStream.setIntegerBase(16); // 16進数に設定
        outStream << "Hex: " << valueToWrite << "\n";

        outStream.setIntegerBase(8);  // 8進数に設定
        outStream << "Octal: " << valueToWrite << "\n";

        // 2進数の出力は、QTextStreamのデフォルトでは`setIntegerBase(2)`だけではプレフィックスが付かないため、
        // 他の書式設定(QTextStream::ShowBaseなど)と組み合わせる必要があります。
        // ここでは簡単に2進数として出力します。
        outStream.setIntegerBase(2);
        outStream << "Binary: " << valueToWrite << "\n";

        qDebug() << "Numbers written to" << fileName;
        writeFile.close();
    } else {
        qDebug() << "Failed to open file for writing:" << writeFile.errorString();
        return 1;
    }

    // ファイルから読み込み
    QFile readFile(fileName);
    if (readFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream inStream(&readFile);

        QString line;
        int readValue;

        // 1行目を読み込む (Decimal: 12345)
        line = inStream.readLine();
        inStream >> line >> readValue; // "Decimal:" と "12345" を読み込む
        qDebug() << "Read line 1 (Decimal):" << readValue; // 12345

        // 2行目を読み込む (Hex: 3039)
        inStream.seek(inStream.pos()); // seek(pos()) は現在の位置に留まるが、ストリームの状態をリフレッシュする
        line = inStream.readLine();
        // QTextStreamはデフォルトで"0x"プレフィックスを自動検出するため、
        // setIntegerBase()を呼び出す必要はありません。
        // ただし、明示的に16進数として読み込むには setIntegerBase(16) も有効です。
        inStream.setIntegerBase(16);
        inStream >> line >> readValue; // "Hex:" と "3039" を読み込む
        qDebug() << "Read line 2 (Hex):" << readValue; // 12345

        // 3行目を読み込む (Octal: 30071)
        inStream.seek(inStream.pos());
        line = inStream.readLine();
        inStream.setIntegerBase(8); // 8進数として読み込み
        inStream >> line >> readValue; // "Octal:" と "30071" を読み込む
        qDebug() << "Read line 3 (Octal):" << readValue; // 12345

        // 4行目を読み込む (Binary: 11000000111001)
        inStream.seek(inStream.pos());
        line = inStream.readLine();
        // 2進数として読み込もうと試みるが、QTextStreamは"0b"プレフィックスを自動検出しないため、
        // 明示的に2進数として扱わせても期待通りに動作しない場合があります。
        // この例では、"11000000111001"が10進数として解釈される可能性が高いです。
        inStream.setIntegerBase(2);
        inStream >> line >> readValue; // "Binary:" と "11000000111001" を読み込む
        qDebug() << "Read line 4 (Binary attempt):" << readValue; // おそらく 0

        readFile.close();
    } else {
        qDebug() << "Failed to open file for reading:" << readFile.errorString();
        return 1;
    }

    return 0;
}

出力結果例:

Numbers written to "numbers.txt"
Read line 1 (Decimal): 12345
Read line 2 (Hex): 12345
Read line 3 (Octal): 12345
Read line 4 (Binary attempt): 0

解説:

  • 2進数入力については、上述の通り QTextStream の自動検出は限定的であり、setIntegerBase(2) を使っても常に期待通りに動作するとは限りません。このような場合は、一度文字列として読み込み、QString::toInt(nullptr, 2) のように変換するのが確実です。
  • setIntegerBase() を明示的に指定することで、自動検出を上書きし、指定された基数でのみ数値を解釈するように強制できます。ただし、文字列がその基数として有効でない場合、読み込みは失敗します(例えば、10進数に設定されているのに「0xFF」を読み込もうとする場合など)。
  • ファイルからの読み込みでは、QTextStream はデフォルトで一般的なプレフィックス(0x0 で始まる数値)を自動検出して基数を判断します。
  • ファイルへの書き込みでは、setIntegerBase() を使って数値の出力形式を制御できます。


QString::number() (数値から文字列への変換)

QString::number() は、整数や浮動小数点数を指定した基数の文字列に変換するための静的メソッドです。QTextStream を使ってファイルやストリームに直接書き込むのではなく、まず文字列として生成してから別の用途で使いたい場合に非常に便利です。

特徴:

  • 直接変換: QTextStream を介さずに、数値から直接 QString を生成できます。
  • 柔軟な書式設定: 桁数(フィールド幅)、パディング文字、小数点以下の桁数、科学的記法なども指定できます。
  • 基数指定: 10進数(デフォルト)、16進数、8進数、2進数など、様々な基数を直接指定できます。

使用例:

#include <QString>
#include <QDebug>

int main() {
    int value = 255;

    // 10進数
    QString strDecimal = QString::number(value);
    qDebug() << "Decimal:" << strDecimal; // "255"

    // 16進数 (小文字)
    QString strHex = QString::number(value, 16);
    qDebug() << "Hex (lowercase):" << strHex; // "ff"

    // 16進数 (大文字)
    // QString::number()単体では大文字指定ができませんが、toUpperCase()と組み合わせます。
    QString strHexUpper = QString::number(value, 16).toUpper();
    qDebug() << "Hex (uppercase):" << strHexUpper; // "FF"

    // 8進数
    QString strOctal = QString::number(value, 8);
    qDebug() << "Octal:" << strOctal; // "377"

    // 2進数
    QString strBinary = QString::number(value, 2);
    qDebug() << "Binary:" << strBinary; // "11111111"

    // ゼロパディング (16進数で4桁になるまでゼロで埋める)
    QString strPaddedHex = QString::number(value, 16).rightJustified(4, '0');
    qDebug() << "Padded Hex:" << strPaddedHex; // "00ff"

    return 0;
}

QString::toInt(), toLong(), toLongLong(), toUInt(), など (文字列から数値への変換)

QString クラスは、文字列を様々な数値型(int, long, double など)に変換するための便利なメソッドを提供しています。これらのメソッドは、変換元の文字列の基数を指定できます。

特徴:

  • 柔軟な数値型: int, long, qlonglong, double など、様々な数値型に対応しています。
  • 変換の成功/失敗チェック: オプションで bool *ok 引数を受け取り、変換が成功したかどうかを確認できます。これは特にユーザー入力のパースなどで重要です。
  • 基数指定: 変換したい文字列がどの基数で表現されているかを指定できます。

使用例:

#include <QString>
#include <QDebug>

int main() {
    QString strDecimal = "255";
    QString strHex = "FF";
    QString strOctal = "377";
    QString strBinary = "11111111"; // "0b" プレフィックスがなくても指定した基数で解釈を試みる

    bool ok;
    int value;

    // 10進数として変換
    value = strDecimal.toInt(&ok, 10);
    qDebug() << "Decimal ('255'):" << value << " (ok:" << ok << ")"; // 255 (ok: true)

    // 16進数として変換
    value = strHex.toInt(&ok, 16);
    qDebug() << "Hex ('FF'):" << value << " (ok:" << ok << ")";     // 255 (ok: true)

    // 8進数として変換
    value = strOctal.toInt(&ok, 8);
    qDebug() << "Octal ('377'):" << value << " (ok:" << ok << ")";    // 255 (ok: true)

    // 2進数として変換
    value = strBinary.toInt(&ok, 2);
    qDebug() << "Binary ('11111111'):" << value << " (ok:" << ok << ")"; // 255 (ok: true)

    // 無効な形式の変換(例: 10進数として"FF"を変換)
    value = strHex.toInt(&ok, 10);
    qDebug() << "Hex ('FF') as Decimal:" << value << " (ok:" << ok << ")"; // 0 (ok: false)

    return 0;
}

Qt固有の機能に加えて、C++の標準ライブラリにも数値と文字列を相互変換する機能があります。

  • std::stoi() (std::stol(), std::stoll(), std::stoul() など): std::string を数値に変換します。基数を指定できます。
  • std::to_string(): 数値を10進数の std::string に変換します。基数指定はできません。

特徴:

  • std::stoi() は、変換元の文字列の基数を指定できます。
  • Qtのクラスに依存しないため、Qtを使用しないC++プロジェクトでも利用できます。

使用例:

#include <iostream> // std::cout, std::endl
#include <string>   // std::string, std::to_string, std::stoi
#include <stdexcept> // std::invalid_argument, std::out_of_range

int main() {
    int value = 255;

    // 数値から文字列へ (10進数のみ)
    std::string strDecimal = std::to_string(value);
    std::cout << "Decimal (std::to_string): " << strDecimal << std::endl;

    // 文字列から数値へ (基数指定可能)
    std::string hexStr = "FF";
    std::string octalStr = "377";
    std::string binaryStr = "11111111";

    try {
        int valHex = std::stoi(hexStr, nullptr, 16);
        std::cout << "Hex ('FF') to int: " << valHex << std::endl; // 255

        int valOctal = std::stoi(octalStr, nullptr, 8);
        std::cout << "Octal ('377') to int: " << valOctal << std::endl; // 255

        int valBinary = std::stoi(binaryStr, nullptr, 2);
        std::cout << "Binary ('11111111') to int: " << valBinary << std::endl; // 255

        // エラーハンドリングの例
        std::string invalidStr = "Hello";
        int valInvalid = std::stoi(invalidStr); // 例外が発生
        std::cout << "Invalid string to int: " << valInvalid << std::endl;
    } catch (const std::invalid_argument& e) {
        std::cerr << "Invalid argument: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "Out of range: " << e.what() << std::endl;
    }

    return 0;
}

QTextStream::setIntegerBase() は、ストリーム入出力のコンテキストで基数を設定する際に便利ですが、Qtで単に数値と文字列を異なる基数で相互変換したい場合は、以下の方法が代替としてよく使われます。

  • 汎用C++コード: Qtに依存しないコードが必要な場合は、C++11以降の std::to_string()std::stoi() シリーズを使用できます。
  • 文字列入力: QString::toInt() などの QStringtoXxx() メソッドが、基数指定とエラーチェックの点で優れています。
  • 文字列出力: QString::number() が最も直接的で柔軟な方法です。