Вопрос

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

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

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

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

Спасибо :)

Это было полезно?

Решение 6

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

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

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

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

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;
}

Другие советы

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

QString("Hi there %1. You are %2 years old. How does it feel \
         to be %2?").arg(name).arg(age)

Хотите верьте, хотите нет, но 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;
 }

Вы можете взглянуть на FastFormat -библиотеку.

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

std::string result;

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

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

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

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

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

йопоток:

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

Вы можете форматировать как sprintf (Google для «формата iostream») и в стандарте C++.

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

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

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

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

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

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

  • нет поддержки позиционного форматирования (так что, думаю, вам это не подойдет)
  • 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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top