Help us understand the problem. What is going on with this article?

組み込みでも使える、scanf に代わる C++ input.hpp

More than 1 year has passed since last update.

scanf に似た仕様で文字列から数値へ変換する C++ クラス

概要

C++ では、C 言語で一般的な可変引数を扱う関数は使わない文化があります。
一番の問題は、引数の引渡しはスタックを経由する点、又、スタックに引数が何個格納
されているのか判らない点です、これによりスタックをオーバーロードさせ、システム
に悪影響を与える事が出来てしまいます。

代表的な実装は「printf」関数です。
コンパイラはフォーマット文を解析して、引数の整合性をチェックしますが、完全にはチェックできません。

一方、「printf」は優れた柔軟性をもたらし、文字、数値を扱う事をたやすくします。

boost には、printf の柔軟性と、安全性を考慮した、format.hpp があります。
※「%」オペレーターのオーバーロード機構を利用して、複数の引数を受け取る事ができます。
※boost::format は優れた実装ですが、iostream に依存していて組み込みマイコンでは問題があります。
※iostream を取り込むと、容量が肥大化します。

その為、組み込みマイコン向けの軽量な「format.hpp」を実装しています。

同じように、printf とは逆の動作をする scanf も安全性においては printf と同じ問題を抱えています。

そこで、format.hpp の仕組みをまねて、scanf に代わる input クラスを実装しました。

仕様

  • 基本的な仕様
    2進数、8進数、10進数、16進数、浮動小数点、文字などを受け取る事が出来ます。
  • 名前空間は「utils」です。
  • 文字列の受け取りは、ファンクタを定義して、テンプレートパラメーターとします。
    ※標準的なファンクタ「def_chainp」クラスが定義されており、以下のように
    typedef されています。
    typedef basic_input<def_chainp> input;

※「def_chainp」クラスも、「input.hpp」に含まれています。

標準のファンクタは、標準入力から文字を受け取りますが、キャラクター型ポインター

を定義する事ができ、「sscanf」のように機能します。

組み込みマイコンで使う事を考えて、エラーに関する処理では、「例外」を送出しません。
入力変換時に起こったエラーは、エラー種別として取得する事ができます。

  • 一般的に、例外を使うと多くのメモリを消費します。
  • 例外を使った場合、エラーが発生して、正しい受取先が無い場合、致命的な問題を引き起こします。
  • 複数の変換で、エラーが同時に発生すると、最後のエラーが残ります。
  • 複数の分離キャラクターを扱える正規表現があります。
  • %b ---> 2進の数値
  • %o ---> 8進の数値
  • %d ---> 10進の数値
  • %u ---> 符号無し10進
  • %x ---> 16進の数値
  • %f ---> 浮動小数点数(float、double)
  • %c ---> 1文字のキャラクター
  • %a ---> 自動、2進(bnnn)、8進(onnn)、10進、16進(xnnn)、を判別

※ %c は、半角文字のみ対応

  • %、[、]、などの特殊文字を、分離キャラクターとして使う場合は、「バックスラッシュ」を使う。

使い方

input.hpp をインクルードします。

#include "input.hpp"

※エクスポーネント算術に「std::pow」関数を利用する為、算術ライブラリのリンクが必要です。

※全ての機能を使うのに必要なヘッダーは「input.hpp」のみです。

名前空間は「utils」です。

サンプル

  • 標準入力から、変数「a」に10進数を受け取ります。
    ※標準入力から受け取る場合、入力の終端は、'\n'(改行)とします。
   int a;
   utils::input("%d") % a;
  • 以下のように変換ステートを受け取る事が出来るので、変換に失敗した場合を検出できます。
   int a;
   if((utils::input("%d") % a).state()) {
       // OK
   } else {
       // NG
   }
  • 受け取り時、どんなエラーがあったのかを判別したい場合、以下のようにエラー種別を取得
    する事ができます。
   int a;
   auto err = (utils::input("%d") % a).get_error();
   if(err == utils::input::error::none) {
       // OK
   } else {
       // NG: 「err」種別により、相当する処理
   }
  • エラー種別は enum class で定義されます。
    none,            ///< エラー無し
    cha_sets,        ///< 文字セットの不一致
    partition,       ///< 分離キャラクターの不一致
    input_type,      ///< 無効な入力タイプ
    not_integer,     ///< 整数型の不一致
    different_sign,  ///< 符号の不一致
    sign_type,       ///< 符号無し整数にマイナス符号
    not_float,       ///< 浮動小数点型の不一致
    terminate,       ///< 終端文字の不一致
    overflow,        ///< オーバーフロー    
  • 文字列から受け取る場合(sscanf 相当)は、以下のようにします。
    ※第二引数が省略された場合、上の例のように、標準入力となります。
   int a;
   static const char* inp = { "1234" };
   utils::input("%d", inp) % a;
  • 特殊文字「%, [, ]」を分離キャラクターとして使う場合、「\」(バックスラッシュ)で除外します。 ※「\」を文字列に1文字含める場合、「\」とします。
    static const char* inp = { "123%456[789]5678" };
    int a[4] = { -1 };
    auto err = (input("%d\\%%d\\[%d\\]%d", inp) % a[0] % a[1] % a[2] % a[3]).get_error();
    std::cout << "Test23, Special character separator as '" << inp << "': "
        << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3];
    if(err == input::error::none) {

    }

拡張機能、分離キャラクタ

数値ブロックを分離するキャラクターには、数値(10進の場合 0 から 9)以外を指定が出来ます。
さらに。「[」、「]」で囲まれた任意のキャラクターを指定出来ます。
以下のサンプルでは、分離キャラクターとして、「 」(スペース)、「,」のどちらかを指定出来ます。

    int a = 0;
    uint32_t b = 0;
    int c = 0;
    static const char* inp = { "-99 100,200" };
    auto n = (input("%d[, ]%x[ ,]%d", inp) % a % b % c).num();
    std::cout << "Test05, multi scan for integer: " << a << ", " << b << ", " << c;
    if(n == 3 && a == -99 && b == 0x100 && c == 200) {
        std::cout << "  Pass." << std::endl;
        ++pass;
    } else {
        std::cout << "  Scan NG!" << std::endl;
    }

拡張機能、「%a」オート

接頭子として、「b,0b」二進数、「o,0o」八進数、「x,0x」16進数、「なし」10進数、を受け付ける機能。
※整数のみで、浮動小数点は受け取れません。
※先頭の「0」は省略可能

    static const char* inp = { "100 0x9a 0b1101 0o775" };
    int a[4] = { -1 };
    auto n = (input("%a %a %a %a", inp) % a[0] % a[1] % a[2] % a[3]).num();
    std::cout << "Test10, multi scan for 'auto': "
        << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << " (" << n << ")";
    if(n == 4 && a[0] == 100 && a[1] == 0x9a && a[2] == 0b1101 && a[3] == 0775) {
        std::cout << "  Pass." << std::endl;
        ++pass;
    } else {
        std::cout << "  Scan NG!" << std::endl;
    }

変換エラーが発生した場合:

  • 変換エラーが発生した場合、参照へは、結果を返さず、破棄されます。
    int a = -1;
    input("%d", "1O5") % a;

上記のように、変換に失敗した(文字の O が含まれる)場合は、初期化の値「-1」が保持されます。

オーバーフローエラーの挙動:

  • 浮動小数点の小数点以下は、桁あふれ以降の数値は無視され、オーバーフローエラーは返しません。
  • 実数部では、「utils::input::error::overflow」がセットされます。
    ※オーバーフローエラーの場合、オペレーターで指定された参照へは、オーバーフローする前の値を返します。

プロジェクト(全体テスト)

input クラスは、全体テストと共に提供されます。

make

で全体テストがコンパイルされます。

make run

全体テストが走り、全てのテストが通過すると、正常終了となります。

※テストに失敗すると、-1 を返します。


https://github.com/hirakuni45/input_class

License

MIT

hira_kuni_45
ハード、ソフト、金属加工、内燃機関、色々やってます。
http://www.rvf-rc45.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away