实现一个简单的字符串格式化方法

c#中格式化字符串很简单,比如我们可以这样格式化一个字符串:

string str = string.format("test {0}, {1}, {2}, {1}, {0} sample", 1, 2.3, "ok");
Console.WriteLine(str);

将输出:test 1, 2.3, ok, 2.3, 1 sample

这个格式化方法用起来很简单,支持基本类型的参数,比如int、double和string等,用起来很方便。遗憾的是c++中目前还没有类似的格式化方法。boost库提供了一个format方法,但用起来没有c#的format方法简单和灵活。让我们来看看boost.format如何实现上面的格式化:

string str = boost::format("test %1%, %2%, %3%, %4%, %5% sample")%1%2.3%"ok"%2.3%1;
cout<<str<<endl;

  boost::format的问题是需要写很多%,用起来繁琐又不直观。c++还缺少一个类似于c#的format方法,要实现一个类似的简单的format也不难,我将实现一个简单的format,基本用法和c#一致,为了保持简单,不支持复杂的功能,只支持基本类型的转换,转换的格式控制就不支持了,简单够用就好。下面来看看format的具体实现吧:

复制代码
#include <tuple>
#include <type_traits>
#include <string>
#include<cctype>

using namespace std;
#include "Variant.hpp"


namespace detail
{
    using Value = Variant<uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double, string, char*, const char*>;
    char g_buf[2000] = {};


    template<size_t k, typename Tuple, typename F>
    typename std::enable_if < (k == std::tuple_size<Tuple>::value)>::type GetArgByIndex(size_t, Tuple&, F&, char*&)
    {
        throw std::invalid_argument("arg index out of range");
    }

    template<size_t k = 0, typename Tuple, typename F>
    typename std::enable_if < (k < std::tuple_size<Tuple>::value)>::type GetArgByIndex(size_t index, Tuple& tp, F& f, char*& p)
    {
        if (k == index)
        {
            f(p, std::get<k>(tp));
        }
        else
        {
            GetArgByIndex<k + 1>(index, tp, f, p);
        }
    }

    inline int GetIndex(char*& p)
    {
        char temp[3] = {};
        int i = 0;
        while (*p != '}'&&*p != '\0')
        {
            if (i >= 2)
                throw std::invalid_argument("index is out of range.");

            if (std::isdigit(*p))
            {
                //push digit
                temp[i++] = *p;
                char next = *(p + 1);
                if (std::isdigit(next))
                {
                    temp[i++] = next;
                    p += 2;
                    continue;
                }

                //validate arg
                if (!std::isspace(next) && next != '}')
                {
                    throw std::invalid_argument("invalid argument.");
                }
            }

            p++;
        }

        return i == 0 ? -1 : std::atoi(temp);
    }

    inline void Fun(char*& buf, Value t)
    {
        t.Visit([&buf](int i)
        {
            _itoa(i, buf, 10);
       buf += strlen(buf);
        },
            [&buf](double i)
        {
            auto r = sprintf(buf, "%.6f", i);
            buf += r;
        },
            [&buf](int64_t i)
        {
            auto r = sprintf(buf, "%"PRId64, i);
            buf += r;
        },
            [&buf](uint64_t i)
        {
            auto r = sprintf(buf, "%"PRIu64, i);
            buf += r;
        },
            [&buf](const char* p)
        {
            int len = strlen(p);
            memcpy(buf, p, len);
            buf += len;
        },
            [&buf](string& s)
        {
            memcpy(buf, s.data(), s.size());
            buf += s.size();
        });
    }
}

template<typename... Args>
inline string format(string& str, Args... args)
{
    using namespace detail;
    char* buf = g_buf;
    auto tp = std::tuple<Args...>(args...);

    char* p = (char*) str.c_str();
    char* original = p;
    int len = str.size() + 1;
    int last = 0;
    while (true)
    {
        if (*p == '{')
        {
            //copy content befor {
            last = p - original;
            memcpy(buf, original, last);
            buf += last;

            //format args
            int index = GetIndex(p);
            if (index >= 0)
            {
                GetArgByIndex<0>(index, tp, Fun, buf);
            }

            //skip }
            original = p + 1;
        }
     else if(*p=='\0')
        {        
      last = p - original;
      memcpy(buf, original, last);
      break;
     }  

        p++;
    }

    string s = g_buf;
    memset(g_buf, 0, buf - g_buf);
    return s;
}
复制代码

再来看看测试代码:

string str = "it is { 0 }, and {01}, {2}, {01}, {1}";
cout<<format(str, 11, 2.1, "tt")<<endl;

将输出:it is 11, and 2.1, tt, 2.1 1

  用法和c#的一致,比boost的format用起来更方便。这个format支持最多100个参数(0-99),格式化的最长的字符串为2K。

 

/**********************更新,做简化和修改bug*******************************/

复制代码
#include <tuple>
#include <type_traits>
#include <string>
#include <inttypes.h>
#include <cctype>

using namespace std;

namespace detail
{
    char g_buf[2000] = {};
    inline void FormatArg(char*& buf, int i)
    {
        _itoa(i, buf, 10);
        buf += strlen(buf);
    }

    inline void FormatArg(char*& buf, double i)
    {
        auto r = sprintf(buf, "%.6f", i);
        buf += r;
    }

    inline void FormatArg(char*& buf, int64_t i)
    {
        auto r = sprintf(buf, "%"PRId64, i);
        buf += r;
    }

    inline void FormatArg(char*& buf, uint64_t i)
    {
        auto r = sprintf(buf, "%"PRIu64, i);
        buf += r;
    }

    inline void FormatArg(char*& buf, const char* p)
    {
        int len = strlen(p);
        memcpy(buf, p, len);
        buf += len;
    }

    inline void FormatArg(char*& buf, string& s)
    {
        memcpy(buf, s.data(), s.size());
        buf += s.size();
    }

    template<size_t k, typename Tuple>
    typename std::enable_if < (k == std::tuple_size<Tuple>::value)>::type
    inline GetArgByIndex(size_t, Tuple&, char*&)
    {
        throw std::invalid_argument("arg index out of range");
    }

    template<size_t k = 0, typename Tuple>
    typename std::enable_if < (k < std::tuple_size<Tuple>::value)>::type
    inline GetArgByIndex(size_t index, Tuple& tp, char*& p)
    {
        if (k == index)
        {
            FormatArg(p, std::get<k>(tp));
        }
        else
        {
            GetArgByIndex<k + 1>(index, tp, p);
        }
    }

    inline int GetIndex(char*& p)
    {
        char temp[3] = {};
        int i = 0;
        while (*p != '}'&&*p != '\0')
        {
            if (i >= 2)
                throw std::invalid_argument("index is out of range.");

            if (std::isdigit(*p))
            {
                //push digit
                temp[i++] = *p;
                char next = *(p + 1);
                if (std::isdigit(next))
                {
                    temp[i++] = next;
                    p += 2;
                    continue;
                }

                //validate arg
                if (!std::isspace(next) && next != '}')
                {
                    throw std::invalid_argument("invalid argument.");
                }
            }

            p++;
        }

        return i == 0 ? -1 : std::atoi(temp);
    }
}

template<typename... Args>
inline string format(const string& src, Args... args)
{
    return format((char*) src.c_str(), args...);
}

template<typename... Args>
inline string format(char* src, Args... args)
{
    using namespace detail;
    char* buf = g_buf;
    auto tp = std::tuple<Args...>(args...);

    char* p = src;
    char* original = p;
    int len = strlen(src) + 1;
    int last = 0;
  while (true)
    {
        if (*p == '{')
        {
            //copy content befor {
            last = p - original;
            memcpy(buf, original, last);
            buf += last;

            //format args
            int index = GetIndex(p);
            if (index >= 0) { GetArgByIndex<0>(index, tp, Fun, buf); } //skip } original = p + 1; }      else if(*p=='\0') {        
 
 
      last = p - original;
 
 
      memcpy(buf, original, last);
      break;
     }  

        p++;
    }
  string s = g_buf; 
  memset(g_buf, 0, buf - g_buf); 
  return s; 
}
复制代码

简化代码的实现,移除对Variant的依赖,直接通过重载函数来格式化参数。经简单测试效率比boost::format要高六倍以上。

如果你发现还有更好的format请告诉我,如果没有请点一下推荐,谢谢。^_^

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值