String.Format для C ++

Ищу реализацию для C ++ такой функции, как .NET String.Format. Очевидно, есть printf и его разновидности, но я ищу что-то позиционное, например:

String.Format ("Привет, {0}. Вам {1} лет. Каково это быть {1}?", Имя, возраст);

Это необходимо, потому что мы собираемся попытаться упростить локализацию нашего приложения, а предоставить переводчикам {0} и {1} для размещения в любом месте предложения намного проще, чем дать им% s,% d,% d, которые должны быть расположены в таком порядке в их переводе.

Я полагаю, что поиск и замена с переменными входами (va_start, va_end и т. Д.) - это то, что я в конечном итоге построю, но если уже есть твердое решение, это было бы предпочтительнее.

Спасибо :)


person DougN    schedule 20.01.2009    source источник


Ответы (13)


Взгляните на библиотеку ускоренного формата.

person Eddie Parker    schedule 20.01.2009
comment
Формат Boost работает очень медленно по сравнению с альтернативными решениями с аналогичной функциональностью (accu.org/index.php/journals / 1539, github.com/c42f/tinyformat#speed-tests , github.com/vitaut/format#speed-tests), поэтому я бы не рекомендую его. - person vitaut; 26.12.2012

QT's QString позволяет вам сделать это:

QString("Hi there %1. You are %2 years old. How does it feel \
         to be %2?").arg(name).arg(age)
person shoosh    schedule 20.01.2009
comment
QtCore поддерживает интернационализацию с помощью QObject :: tr. Он возвращает QString с заполнителями% 1,% 2 и т. Д. Это также позволяет использовать специальный% n, который позволяет правильно использовать множественное число слов. - person strager; 23.01.2009

Вы не поверите, но printf и друзья поддерживают позиционные аргументы.

 #include <stdio.h>

 int main() {
   char *name = "Logan";
   int age = 25;
   printf("Hi there %1$s, you are %2$d years old. How does it feel to be %2$d?\n", name, age);
  return 0;
 }
person Logan Capaldo    schedule 08.03.2009
comment
В стандартах замечательно то, что есть из чего выбирать. Например. opengroup.org/onlinepubs/000095399/functions/printf.html it это стандарт XSI. - person Logan Capaldo; 16.03.2009
comment
Согласно en.wikipedia.org/wiki/Printf#printf_format_placeholder, это расширение POSIX - person Meinersbur; 27.11.2010
comment
Я думаю, вы упускаете суть, а что, если вы не хотите его печатать? С вариативными шаблонами вы можете легко это сделать, просто этого еще нет в библиотеке - person ; 05.03.2012
comment
Вы можете использовать sprintf / snprintf, если не хотите его печатать. - person Logan Capaldo; 05.03.2012

Вы можете ознакомиться с библиотекой FastFormat.

person Christof Schardt    schedule 21.01.2009

Я думаю, вы можете использовать FastFormat как

std::string result;

fastformat::fmt(result, "Hi there {0}. You are {1} years old. How does it feel to be {1}?", name, age);

что почти идентично синтаксису.

person dcw    schedule 16.03.2009
comment
Облом, на сегодня эта ссылка мертва :( - person TravisWhidden; 26.11.2014

Выше есть множество хороших рекомендаций, которые сработают в большинстве ситуаций. В моем случае я в конечном итоге хотел загрузить строки из ресурса И сохранить строковые ресурсы как можно ближе к .NET String.Format, поэтому я свернул свой собственный. После ознакомления с некоторыми из приведенных выше реализаций в поисках идей конечная реализация была довольно короткой и простой.

Существует класс String, который в моем случае является производным от Microsoft CString, но он может быть производным от любого строкового класса. Также существует класс StringArg - его задача - взять любой тип параметра и превратить его в строку (т.е. он имитирует ToString в .NET). Если новый объект должен быть ToString'd, вы просто добавляете еще один конструктор. Конструктор позволяет использовать спецификатор формата в стиле printf для нестандартного форматирования.

Затем класс String принимает идентификатор таблицы строк для исходной строки, ряд параметров StringArg и, наконец, необязательный HINSTANCE (я использую множество библиотек DLL, каждая из которых может содержать таблицу строк, поэтому это позволило мне передать ее, или используйте по умолчанию HINSTANCE, специфичную для DLL).

Примеры использования:

dlg.m_prompt = String(1417); //"Welcome to Stackoverflow!"
MessageBox(String(1532, m_username)); //"Hi {0}"

Как бы то ни было, для ввода требуется только идентификатор строки, но было бы тривиально добавить строку ввода вместо идентификатора строки:

CString s = String.Format("Hi {0}, you are {1} years old in Hexidecimal", m_userName, StringArg(m_age, "%0X"));

Теперь о классе StringArg, который выполняет эквивалент ToString для переменных:

class StringArg
{
StringArg(); //not implemented
        StringArg(const StringArg&); //not implemented
        StringArg& operator=(const StringArg&); //not implemented

    public:
        StringArg(LPCWSTR val);
    StringArg(const CString& val);
    StringArg(int val, LPCWSTR formatSpec = NULL);
    StringArg(size_t val, LPCWSTR formatSpec = NULL);
    StringArg(WORD val, LPCWSTR formatSpec = NULL);
    StringArg(DWORD val, LPCWSTR formatSpec = NULL);
    StringArg(__int64 val, LPCWSTR formatSpec = NULL);
    StringArg(double val, LPCWSTR formatSpec = NULL);
    CString ToString() const;
private:
    CString m_strVal;
};

extern HINSTANCE GetModuleHInst(); //every DLL implements this for getting it's own HINSTANCE -- scenarios with a single resource DLL wouldn't need this

Для класса String существует набор функций-членов и конструкторов, которые принимают до 10 аргументов. В конечном итоге они вызывают CentralFormat, который выполняет реальную работу.

class String : public CString
{
public:
    String() { }
    String(WORD stringTableID, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, hInst); }
    String(WORD stringTableID, const StringArg& arg1, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, hInst); }
    String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, const StringArg& arg9, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, hInst); }


    CString& Format(WORD stringTableID, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, HINSTANCE hInst = GetModuleHInst());
    CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, const StringArg& arg9, HINSTANCE hInst = GetModuleHInst());
private:
    void CentralFormat(WORD stringTableID, std::vector<const StringArg*>& args, HINSTANCE hInst);
};

Наконец, реализация (надеюсь, это нормально, чтобы опубликовать столько на StackOverflow, хотя основная часть очень проста):

StringArg::StringArg(LPCWSTR val)
{
    m_strVal = val;
}

StringArg::StringArg(const CString& val)
{
    m_strVal = (LPCWSTR)val;
}

StringArg::StringArg(int val, LPCWSTR formatSpec)
{
    if(NULL == formatSpec)
        formatSpec = L"%d"; //GLOK
    m_strVal.Format(formatSpec, val);
}

StringArg::StringArg(size_t val, LPCWSTR formatSpec)
{
    if(NULL == formatSpec)
        formatSpec = L"%u"; //GLOK
    m_strVal.Format(formatSpec, val);
}

StringArg::StringArg(WORD val, LPCWSTR formatSpec)
{
    if(NULL == formatSpec)
        formatSpec = L"%u"; //GLOK
    m_strVal.Format(formatSpec, val);
}

StringArg::StringArg(DWORD val, LPCWSTR formatSpec)
{
    if(NULL == formatSpec)
        formatSpec = L"%u"; //GLOK
    m_strVal.Format(formatSpec, val);
}

StringArg::StringArg(__int64 val, LPCWSTR formatSpec)
{
    if(NULL == formatSpec)
        formatSpec = L"%I64d"; //GLOK
    m_strVal.Format(formatSpec, val);
}

StringArg::StringArg(double val, LPCWSTR formatSpec)
{
    if(NULL == formatSpec)
        formatSpec = L"%f"; //GLOK
    m_strVal.Format(formatSpec, val);
}

CString StringArg::ToString() const
{ 
    return m_strVal; 
}


void String::CentralFormat(WORD stringTableID, std::vector<const StringArg*>& args, HINSTANCE hInst)
{
    size_t argsCount = args.size();
    _ASSERT(argsCount < 10); //code below assumes a single character position indicator

    CString tmp;
    HINSTANCE hOld = AfxGetResourceHandle();
    AfxSetResourceHandle(hInst);
    BOOL b = tmp.LoadString(stringTableID);
    AfxSetResourceHandle(hOld);
    if(FALSE == b)
    {
#ifdef _DEBUG

        //missing string resource, or more likely a bad stringID was used -- tell someone!!
    CString s;
        s.Format(L"StringID %d could not be found!  %s", stringTableID, hInst == ghCommonHInst ? L"CommonHInst was passed in" : L"CommonHInst was NOT passed in"); //GLOK
        ::MessageBeep(MB_ICONHAND);
        ::MessageBeep(MB_ICONEXCLAMATION);
        ::MessageBeep(MB_ICONHAND);
        _ASSERT(0);
        ::MessageBox(NULL, s, L"DEBUG Error - Inform Development", MB_ICONSTOP | MB_OK | MB_SERVICE_NOTIFICATION); //GLOK
        }
#endif //_DEBUG

    CString::Format(L"(???+%d)", stringTableID); //GLOK
        return;
    }

    //check for the degenerate case
    if(0 == argsCount)
    {
        CString::operator=(tmp);
        return;
    }

    GetBuffer(tmp.GetLength() * 3); //pre-allocate space
    ReleaseBuffer(0);
    LPCWSTR pStr = tmp;
    while(L'\0' != *pStr)
    {
        bool bSkip = false;

        if(L'{' == *pStr)
        {
            //is this an incoming string position?
            //we only support 10 args, so the next char must be a number
            if(wcschr(L"0123456789", *(pStr + 1))) //GLOK
            {
                if(L'}' == *(pStr + 2)) //and closing brace?
                {
                    bSkip = true;

                    //this is a replacement
                    size_t index = *(pStr + 1) - L'0';
                    _ASSERT(index < argsCount);
                    _ASSERT(index >= 0);
                    if((index >= 0) && (index < argsCount))
                        CString::operator+=(args[index]->ToString());
                    else
                    {
//bad positional index

                        CString msg;
                        msg.Format(L"(??-%d)", index); //GLOK
                        CString::operator+=(msg);
                    }
                    pStr += 2; //get past the two extra characters that we skipped ahead and peeked at
                }
            }
        }

        if(false == bSkip)
            CString::operator+=(*pStr);
        pStr++;
    }
}


CString& String::Format(WORD stringTableID, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    args.push_back(&arg4);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    args.push_back(&arg4);
    args.push_back(&arg5);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    args.push_back(&arg4);
    args.push_back(&arg5);
    args.push_back(&arg6);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    args.push_back(&arg4);
    args.push_back(&arg5);
    args.push_back(&arg6);
    args.push_back(&arg7);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    args.push_back(&arg4);
    args.push_back(&arg5);
    args.push_back(&arg6);
    args.push_back(&arg7);
    args.push_back(&arg8);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}

CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, const StringArg& arg9, HINSTANCE hInst)
{
    std::vector<const StringArg*> args;
    args.push_back(&arg1);
    args.push_back(&arg2);
    args.push_back(&arg3);
    args.push_back(&arg4);
    args.push_back(&arg5);
    args.push_back(&arg6);
    args.push_back(&arg7);
    args.push_back(&arg8);
    args.push_back(&arg9);
    CentralFormat(stringTableID, args, hInst);
    return *this;
}
person DougN    schedule 26.03.2009

Несколько вариантов:

  • библиотека ускоренного формата (уже упоминалось)
  • струнные потоки
  • устаревшие функции printf / sprintf
  • пользовательская реализация с использованием регулярных выражений или встроенных строковых функций

Кстати, то, о чем вы говорите, совершенно неадекватно для локализации.

person Joel Coehoorn    schedule 20.01.2009
comment
Почему ты так говоришь? Приведенный выше пример строки будет извлечен в файл ресурсов, поэтому нам нужно что-то, что можно передать обратно в функцию с правильными входными данными. Естественно, нам также необходимо решить проблемы с датой / временем и т. Д. - person DougN; 20.01.2009
comment
Он также игнорирует правила слева направо / справа налево, но если вы используете литерал в файле ресурсов, все может быть в порядке. Если бы вы только заменяли переведенные слова, у вас была бы настоящая проблема. - person Joel Coehoorn; 21.01.2009
comment
Ах да. Я тяну за ниточки из ресурсов. Плюс позиционные замены, и все должно работать. - person DougN; 26.03.2009

Если вы собираетесь писать свои собственные, поиск и замена, вероятно, не лучший подход, поскольку большинство методов поиска / замены позволяют заменять только по одному за раз и делают очень плохую работу по разрешению символов escpae (например, если вы хотите включить в вывод буквальную строку {0}.

Намного лучше написать свой собственный конечный автомат, который будет проходить по входной строке, генерируя выходную строку на лету за один проход. Это позволяет вам обрабатывать escape-символы и более сложные функции вывода (например, локализованные даты {0:dd\MM\yyyy}). Это даст вам больше гибкости в дополнение к тому, что он будет быстрее, чем подход поиска / замены или регулярного выражения.

person Eclipse    schedule 20.01.2009

iostream:

stringstream s;
string a;
s << "this is string a: " << a << endl;

Вы можете форматировать как sprintf (google для "iostream format") и его в стандарте C ++.

person Rodrigo Strauss    schedule 20.01.2009
comment
Он ищет позиционные аргументы, как в C #. - person Eddie Parker; 21.01.2009
comment
позиционный в том смысле, что он может сказать 'printPositional ({0} это строка a {1}, s, a)' и 'printPositional ({1} это строка b {0}, b , s) 'и получите правильные результаты. - person Max Lybbert; 21.01.2009
comment
Использование строкового потока действительно работает (а также поддерживает многие другие типы данных), поэтому +1 - но я считаю, что это может стать очень беспорядочным, когда вы хотите объединить много переменных, поэтому лучше использовать sprintf (даже если он из C lib). Тем не менее, у sprintf есть недостаток; вам нужно использовать буфер с заданным размером. Так что буст - лучшая альтернатива, я думаю. - person Nick Bolton; 25.01.2010

Ориентируетесь на Windows? FormatMessage () - ваш друг

person Serge Wautier    schedule 22.01.2009

Если вы должны быть кроссплатформенными, я бы проголосовал за boost :: format или, может быть, за ICU. Если вы должны поддерживать только Windows, тогда FormatMessage (или удобная оболочка этого CString :: FormatMessage, если вы используете MFC)

Можно посмотреть здесь для сравнения: http://www.mihai-nita.net/article.php?artID=20060430a

person Mihai Nita    schedule 10.11.2009

Некоторое время назад я пытался сделать что-то подобное, но с некоторыми дополнительными предположениями:

  • нет поддержки позиционного форматирования (так что я думаю, вам это не подходит)
  • c ++ 2k3 (чтобы иметь возможность включать его в некоторые старые коды)
  • (почти) никаких зависимостей (даже crt, поэтому никаких зависимостей sprintf)

Мне это не удалось, и он полностью не закончен, но вы все равно можете посмотреть на некоторые результаты:

http://code.google.com/p/pileofcrap/source/browse/tests_format.cpp

http://code.google.com/p/pileofcrap/source/browse/format/Format.h

person GiM    schedule 01.04.2013

В дополнение к вариантам, предложенным другими, я могу порекомендовать библиотеку fmt, которая реализует форматирование строк, подобное _ 1_ в Python и String.Format в C #. Вот пример:

string result = fmt::format("Hi {0}. You are {1} years old.", name, age);

Библиотека полностью типобезопасна и намного быстрее, чем Boost Format.

Отказ от ответственности: я являюсь автором этой библиотеки.

person vitaut    schedule 26.12.2012
comment
Хороший, спасибо! Но мне пришлось изменить расширение на cpp, чтобы оно работало, иначе это была странная неопределенная ошибка при связывании. - person ; 10.12.2014
comment
@AlekDepler Рад, что вам понравилось. Что касается расширения, оно должно работать со всеми основными компиляторами (GCC, Clang, MSVC). Какой компилятор вы используете? - person vitaut; 10.12.2014