使用Boost進行數據轉換

以下代碼來源於《深入實踐Boost:Boost程序庫開發的94個祕笈》一書

將字符串轉換爲數值

在C++中,將字符串轉換爲數值,效率底下,如,將字符串100轉換爲int類型時:

#include <sstream>  // for C++ method
#include <cstdlib>  // for C method
int main()
{
#pragma region C++
    std::istringstream iss("100");
    int icplus;
    iss >> icplus;
    //而現在,'iss'變量會一直妨礙直到作用域的末尾
#pragma endregion C++

#pragma region C
    char* end;
    // long int strtol(const char *nptr,char **endptr,int base);
    // nptr:等待轉換的字符串
    // endptr:傳出參數,若不爲NULL,則會將遇到不合條件而終止的nptr中的字符指針返回,這裏爲'\0'
    // base:代表採用的進制方式
    int ic = std::strtol("100", &end, 10);
#pragma endregion C

    return 0;
}

Boost中有一個庫Boost.LexicalCast,可用於解決字符串向數值轉換的困難,它由一個boost::bad_lexical_cast異常類和少數boost::lexical_cast函數組成:

#include <boost/lexical_cast.hpp>
int i = boost::lexical_cast<int>("100");

//甚至可以用於非零結尾的字符串:
char chars[] = {'1','0','0'};
int i = boost::lexical_cast<int>(chars,3);
assert(i == 100);

工作原理
boost::lexical_cast函數接受字符串作爲輸入,並將其轉換成尖括號中指定的類型。
boost::lexical_cast函數甚至還會檢查邊界:

try
    {
        // 在x86鍾,short通常不能存儲大於32767的值
        short s = boost::lexical_cast<short>("1000000");
        assert(false);  // 不能到達這裏
    }
    catch (const boost::bad_lexical_cast&)
    {
        std::cout << "catch a bad_lexical_cast here" << std::endl;
    }

並且還能檢查輸入的語法是否正確:

try
    {
        int i = boost::lexical_cast<int>("This is not a number");
        assert(false);  //不能到達這裏
        (void)i;        //抑制有關未使用的變量的警告
    }
    catch (const boost::bad_lexical_cast&)
    {
        std::cout << "catch a bad_lexical_cast here" << std::endl;
    }

詞彙強制轉換(lexical_cast)就像所有使用std::locale的std::stringstreams類一樣,它也使用std::locale,並且能對本地化數值做轉換,還有一系列引人注目的優化,專門針對C locale(語言環境)以及對數值不進行分節的語言環境:

#include <locale>
    std::locale::global(std::locale("ru_RU.UTF8"));
    // 在俄語中用逗號作爲小數點(分隔符)
    float f = boost::lexical_cast<float>("1,0");
    assert(f < 1.01 && f > 0.99);

boost::lexical_cast可以用於創建將其他類型轉換爲數值的模板函數。

// 將包含一些string值的容器轉換爲long int值的向量
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <deque>
#include <iterator>
#include <boost/lexical_cast.hpp>

template <class ContainerT>
std::vector<long int> container_to_longs(const ContainerT& container)
{
    typedef typename ContainerT::value_type value_type;
    std::vector<long int> ret;
    typedef long int(*func_t)(const value_type&);
    func_t f = &boost::lexical_cast<long int, value_type>;
    std::transform(container.begin(), container.end(), std::back_inserter(ret), f);
    return ret;
}

int main()
{
    std::set<std::string> str_set;
    str_set.insert("1");
    assert(container_to_longs(str_set).front() == 1);

    std::deque<const char*> char_deque;
    char_deque.push_front("1");
    char_deque.push_back("2");
    assert(container_to_longs(char_deque).front() == 1);
    assert(container_to_longs(char_deque).back() == 2);

    typedef boost::array<unsigned char, 2> element_t;
    boost::array<element_t, 2> arrays = { { {{'1','0'}},{{'2','0'}}} };
    assert(container_to_longs(arrays).front() = 10);
    assert(container_to_longs(arrays).back() == 20);
    system("pause");
    return 0;
}

將數值轉換爲字符串

1.使用boost::lexical_cast將整數100轉換爲std::string:

#include <boost/lexical_cast.hpp>
    std::string s = boost::lexical_cast<std::string>(100);
    assert(s == "100");

2.此法與傳統的C++的轉換方法相比較:

#include <sstream>
    // C++轉換喂字符串的方式
    std::stringstream ss;
    ss << 100;
    std::string s;
    ss >> s;
    // 變量'ss'直到作用域的末尾都是多餘的
    // 多個虛擬方法再轉換期間被調用
    assert(s == "100");

與C的轉換方法對比:

#include <cstdlib>
    // C轉換爲字符串的方式
    char buffer[100];
    std::sprintf(buffer,"%i",100);
    // 像對所有函數一樣,你將需要一個unsigned long long int 類型
    // 來對'printf'中出現了多少個錯誤進行計算
    // 'printf'函數是固有的安全威脅!
    std::string s(buffer);
    // 現在,不再使用buffer變量
    assert(s == "100");

將數值轉換爲數值

通常情況下,我們會有這樣的代碼:

void some_function(unsigned short param);
int foo();
    some_function(foo());
    // 將其顯式轉換喂無符號短整數的數據類型來忽略這些警告
    some_function(static_cast<unsigned short>(foo()));

這樣的會使得它的錯誤非常難以檢測

Boost.NumericConversion庫提供瞭解決方案,只需將static_cast替換爲boost::numeric_cast。當源值不能在目標中存儲時,它會拋出一個異常。

#include <boost/numeric/conversion/cast.hpp>
void correct_implementation()
{
    some_function(boost::numeric_cast<unsigned short>(foo()));
}

void test_function()
{
    for (unsigned int i = 0; i < 100; ++i)
    {
        try
        {
            correct_implementation();
        }
        catch (const boost::numeric::bad_numeric_cast& e)
        {
            std::cout << '#' << i << ' ' << e.what() << std::endl;
        }
    }
}
// 甚至可以檢測到特定的溢出類型:
void test_function1()
{
    for (unsigned int i = 0; i < 100; ++i)
    {
        try
        {
            correct_implementation();
        }
        catch (const boost::numeric::positive_overflow& e)
        {
            // 正的溢出
            std::cout << "POS OVERFLOW in #" << i << ' ' << e.what() << std::endl;
        }
        catch (const boost::numeric::negative_overflow& e)
        {
            // 負的溢出
            std::cout << "NEG OVERFLOW in #" << i << ' ' << e.what() << std::endl;
        }
    }
}

Boost.NumericConversion庫有一個非常快的實現,它可以在編譯時做很多工作。例如,當轉換喂範圍更寬的類型時,源代碼將只調用static_cast方法。還有更多boost::numeric_cast函數是通過boost::numeric::converter實現的,它可以被調整爲使用不同的溢出、範圍檢查和四捨五入策略。

下面例子演示瞭如何爲boost::numeric::cast製作自己的mythrow_overflow_handler處理程序:

#include <iostream>
#include <boost/numeric/conversion/cast.hpp>

template <class SourceT,class TargetT>
struct mythrow_overflow_handler
{
    void operator()(boost::numeric::range_check_result t)
    {
        if (r != boost::numeric::cInRange)
        {
            throw std::logic_error("Not in range!");
        }
    }
};

template <class TargetT,class SourceT>
TargetT my_numeric_cast(const SourceT& in)
{
    using namespace boost;
    typedef numeric::conversion_traits<TargetT, SourceT> conv_traits;
    typedef numeric::numeric_cast_traits<TargetT, SourceT> cast_traits;
    typedef boost::numeric::converter
        <
        TargetT,
        SourceT,
        conv_traits,
        mythrow_overflow_handler<SourceT, TargetT>
        > converter;
    return converter::convert(in);
}
int main()
{
    try
    {
        my_numeric_cast<short>(10000);
    }
    catch (const std::logic_error& e)
    {
        std::cout << "It works!" << e.what() << std::endl;
    }
    system("pause");
    return 0;
}

用戶定義類型與字符串的相互轉換

Boost.LexicalCast還有一個功能就是允許用戶在lexical_cast中使用他們自己的類型。此功能只需用戶爲他們的類型編寫正確的std::ostream和std::istream操作符。

#include <iostream>
#include <iosfwd>
#include <stdexcept>
#include <boost/lexical_cast.hpp>

class negative_number
{
    unsigned short number_;
public:
    explicit negative_number(unsigned short number) :number_(number) {}

    unsigned short value_without_sign() const
    {
        return number_;
    }
};

std::ostream& operator<<(std::ostream& os, const negative_number& num)
{
    os << "-" << num.value_without_sign();
    return os;
}
std::istream& operator >> (std::istream& is, negative_number& num)
{
    char ch;
    is >> ch;
    if (ch != '-')
    {
        throw std::logic_error("negative_number class designed to store ONLY negative values");
    }
    unsigned short s;
    is >> s;
    num = negative_number(s);
    return is;
}

int main()
{
    negative_number n = boost::lexical_cast<negative_number>("-100");
    std::cout << n.value_without_sign() << std::endl;

    int i = boost::lexical_cast<int>(n);
    std::cout << i << std::endl;

    typedef boost::array<char, 10> arr_t;
    arr_t arr = boost::lexical_cast<arr_t>(n);
    assert(arr[0] == '-');
    assert(arr[1] == '1');
    assert(arr[2] == '0');
    assert(arr[3] == '0');
    assert(arr[4] == '\0');
    system("pause");
    return 0;
}

強制轉換多態對象

一個非常可怕的接口:

struct object{
    virtual ~object(){}
};

struct banana:public object{
    void eat() const{}
    virtual ~banana(){}
};

struct pidgin:public object{
    void fly() const{}
    virtual ~pidgin(){}
};
object* try_produce_banana();

寫一個吃香蕉的函數,如果傳過來的東西不是香蕉,拋出異常。如果對try_produce_banana()函數返回的一個值解引用,那麼就有對一個空指針解引用的危險。
可以這樣做:

void try_eat_banana_impl1()
{
    const object* obj = try_produce_banana();
    if(!obj)
    {
        throw std::bad_cast();
    }
    // dynamic_cast將一個基類對象指針(或引用)cast到繼承類指針,
    // dynamic_cast會根據基類指針是否真正指向繼承類指針來做相應處理
    dynamic_cast<const banana&>(*obj).eat();
}

使用Boost.Conversion的解決方案:

#include <boost/cast.hpp>
void try_eat_banana_impl2()
{
    const object* obj = try_produce_banana();
    boost::polymorphic_cast<const banana*>(obj)->eat();
    // polymorphic_cast函數包裝了第一個實現的代碼,
    // 它檢查輸入是否爲空,然後試圖做一個動態轉換。
    // 在這些操作中的任何錯誤都將拋出一個std::bad_cast異常。
}

解析簡單的輸入

從一個簡單的任務開始,解析如下一個ISO格式的日期:YYYY-MM-DD
以下是可能的輸入例子:
2013-03-01
2012-12-31
從地址 http://www.ietf.org/rfc/rfc3339.txt 查看解析器的語法:
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
full-date = date-fullyear “-” date-month “-” date-mday

Boost.Spirit庫
它允許直接用C++代碼的格式編寫解析器(以及詞法分析器和代碼生成器),它是可以立即執行的(也就是說不需要額外的C++代碼生成工具)
Boost.Spirit的語法與擴展巴克斯範式(Extended Backus-Naur Form,EBNF)非常接近,很多標準都用它來表達語法,並且其他流行的解析器都理解它。

使用時需要包括的頭文件:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <assert.h>

製作一個date結構來保存解析出的數據:

struct date
{
    unsigned short year;
    unsigned short month;
    unsigned short day;
};

解析器編寫:

date parse_date_time1(const std::string& s)
{
    using boost::spirit::qi::_1;
    using boost::spirit::qi::ushort_;
    using boost::spirit::qi::char_;
    using boost::phoenix::ref;

    date res;
    const char* first = s.data();
    const char* const end = first + s.size();
    bool success = boost::spirit::qi::parse(first, end, 
        ushort_[ref(res.year) = _1] >> char('-')
        >> ushort_[ref(res.month) = _1] >> char('-')
        >> ushort_[ref(res.day) = _1]);
    if (!success || first != end)
    {
        throw std::logic_error("Parsing failed");
    }
    return res;
}

使用解析器:

int main()
{
    date d = parse_date_time1("2012-12-31");
    std::cout << "year:" << d.year << " month:" << d.month << " day:" << d.day << std::endl;
    // d = parse_data_time1("2012-132-652");也能解析成功
    system("pause");
    return 0;
}

修改以上的解析器,以便可以處理數字的計數,採用unit_parser模板類,並設置正確的參數。

date parse_date_time2(const std::string& s)
{
    using boost::spirit::qi::_1;
    using boost::spirit::qi::uint_parser;
    using boost::spirit::qi::char_;
    using boost::phoenix::ref;

    // 使用unsigned short 作爲輸出類型,需要十進制,並用2位到2位的數字
    uint_parser<unsigned short, 10, 2, 2> u2_;
    // 使用unsigned short 作爲輸出類型,需要十進制,並用4位到4位的數字
    uint_parser<unsigned short, 10, 4, 4> u4_;

    date res;
    const char* first = s.data();
    const char* const end = first + s.size();
    bool success = boost::spirit::qi::parse(first, end,
        u4_[ref(res.year) = _1] >> char('-')
        >> u2_[ref(res.month) = _1] >> char('-')
        >> u2_[ref(res.day) = _1]);
    if (!success || first != end)
    {
        throw std::logic_error("Parsing failed");
    }
    return res;
}

int main()
{
    try
    {
        date d = parse_date_time2("2012-122-31");
        std::cout << "year:" << d.year << " month:" << d.month << " day:" << d.day << std::endl;
    }
    catch (const std::logic_error& e)
    {
        std::cout << e.what() << std::endl;
    }

    system("pause");
    return 0;
}

解析輸入

前面是一個簡單的解析日期的輸入,也許STL手工實現解析更簡單,但現在,假設需求改變,需要支持多種輸入格式並加上時區偏移的日期和時間的解析器,如,應該能支持以下輸入:
2012-10-20T10:00:00Z // 帶有零時區偏移的日期和時間
2012-10-20T10:00:00 // 未指定時區偏移的日期和時間
2012-10-20T10:00:00+09:15 // 帶有時區偏移的日期和時間
2012-10-20-09:15 // 帶有時區偏移的日期和時間
10:00:09+09:15 // 帶有時區偏移的時間

開始:
編寫一個保存解析結果的“日期-時間”結構體

struct datetime
{
    enum zone_offsets_t
    {
        OFFSET_NOT_SET,
        OFFSET_Z,
        OFFSET_UTC_PLUS,
        OFFSET_UTC_MINUS
    };
private:
    unsigned short year;
    unsigned short month;
    unsigned short day;
    unsigned short hours;
    unsigned short minutes;
    unsigned short seconds;
    zone_offsets_t zone_offset_type;
    unsigned int zone_offset_in_min;

    static void dt_assert(bool v, const char* msg)
    {
        if (!v)
        {
            throw std::logic_error("Assertion failed:" + std::string(msg));
        }
    }

public:
    datetime():year(0),month(0),day(0)
        ,hours(0),minutes(0),seconds(0)
        ,zone_offset_type(OFFSET_NOT_SET),zone_offset_in_min(0)
    {}
    // 獲取器
    // ...

    // 設置器
    void set_year(unsigned short y)
    {
        year = y;
    }
    void set_month(unsigned short m)
    {
        month = m;
    }
    void set_day(unsigned short d)
    {
        day = d;
    }
    void set_hours(unsigned short h)
    {
        hours = h;
    }
    void set_minutes(unsigned short min)
    {
        minutes = min;
    }
    void set_seconds(unsigned short s)
    {
        seconds = s;
    }
    void set_zone_offset_type(zone_offsets_t zonetype)
    {
        zone_offset_type = zonetype;
    }
    void set_zone_offset_in_min(unsigned int min)
    {
        zone_offset_in_min = min;
    }
};

編寫解析器:

// 使用Boost.Spirit中的bind()函數,因爲它能更好地遍歷解析器
#include <boost/spirit/include/phoenix_bind.hpp>

datetime parse_datetime(const std::string& s)
{
    using boost::spirit::qi::_1;
    using boost::spirit::qi::_2;
    using boost::spirit::qi::_3;
    using boost::spirit::qi::uint_parser;
    using boost::spirit::qi::char_;
    using boost::phoenix::bind;
    using boost::phoenix::ref;

    datetime ret;

    uint_parser<unsigned short, 10, 2, 2> u2_;
    uint_parser<unsigned short, 10, 4, 4> u4_;

    // 時區偏移解析器
    boost::spirit::qi::rule<const char*, void()> timezone_parser = 
        -(  // 一元減號表示可選規則
                // 零偏移
            char_('Z')[bind(&datetime::set_zone_offset_type, &ret, datetime::OFFSET_Z)]
            |   // 或指定時區偏移量
            ((char_('+') | char_('-')) >> u2_ >> ':' >> u2_)[bind(&set_zone_offset, ref(ret), _1, _2, _3)]
            );

    // 日期解析器
    boost::spirit::qi::rule<const char*, void()> date_parser =
        u4_[bind(&datetime::set_year, &ret, _1)] >> char_('-')
        >> u2_[bind(&datetime::set_month, &ret, _1)] >> char_('-')
        >> u2_[bind(&datetime::set_day, &ret, _1)];

    // 時間解析器
    boost::spirit::qi::rule<const char*, void()> time_parser =
        u2_[bind(&datetime::set_hours, &ret, _1)] >> char_(':')
        >> u2_[bind(&datetime::set_minutes, &ret, _1)] >> char_(':')
        >> u2_[bind(&datetime::set_seconds, &ret, _1)];

    const char* first = s.data();
    const char* const end = first + s.size();
    bool success = boost::spirit::qi::parse(first, end,
        ((date_parser >> char_('T') >> time_parser) |
            date_parser | time_parser) >> timezone_parser
    );
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章