sprintf 와 std::string의 결합
글쓴이: aswip / 작성시간: 월, 2005/06/27 - 2:46오후
얼마전부터 c언어의 sprintf 와 같은 함수를 c++ stl 적용하기 위해서 이런 정보를 찾고 있었습니다. 물론 예전 부터, ostringstream 같은 것을 알고 있었지만, 제 목적은 ansi-c의 sprintf와 string과의 결합이었습니다.
이것저것 찾아보니, asprintf 와 같은 함수가 가변길이 문자열의 예상 길이를 계산하여주는 편리함을 제공하기는 하지만, win32에는 그런함수가 없어서, asprintf는 포기 ㅠㅠ;;
결국 적절한 해법을 찾지 못하고, 다음과 같이 구현하고 말았습니다.
움... 찾아보면, 무언가 더 쉬운 방법이 있을것 같은데, 설마, 저와 같은 고민을 해본 c++ 개발자가 한명이라도 있었겠죠? ^^;;;
#include <iostream>
#include <stdarg.h>
#include <sstream>
using namespace std;
unsigned int GetDigit ( const char* ptrFormat, int &nIdx, int nFmtLen )
{
if ( ( ptrFormat == NULL ) || ( nIdx >= nFmtLen ) )
return 0;
ostringstream oDigit;
while ( isdigit ( ptrFormat[nIdx] ) > 0 )
{
oDigit << ptrFormat[nIdx];
nIdx++;
if ( nIdx >= nFmtLen )
break;
}
return atoi ( oDigit.str().c_str() );
}
string::size_type strprintf ( string& rstr, const char *pFmt, ... )
{
bool bFMT = false;
char* psz = NULL;
int nExpChars = 0, nIdx = 0, nFmtLen = strlen ( pFmt );
if ( nFmtLen <= 0 )
return 0;
va_list arg;
va_start (arg, pFmt);
// 1. vsprintf 를 사용하기 위해서 포멧 문자열 길이 계산
while( pFmt[nIdx] )
{
switch ( pFmt[nIdx] )
{
case 's': //string
if ( bFMT )
{
nExpChars += strlen ( (char*)va_arg(arg, char*) );
bFMT = false;
}
break;
case 'f': // Decimal floating point
case 'd': // Signed decimal integer
case 'i': // Signed decimal integer
case 'g': // Use shorter %e or %f
case 'G': // Use shorter %E or %F
case 'o': // Signed octal
case 'x': // Unsigned hexadecimal integer
case 'X': // Unsigned hexadecimal integer (capital letters)
case 'p': // Prints the address of the argument in hexadecimal digits.
nExpChars += 16;
bFMT = false;
break;
case 'c': // char
nExpChars++;
bFMT = false;
break;
case '.':
nIdx++;
nExpChars += GetDigit ( pFmt, nIdx, nFmtLen );
nIdx--;
break;
case '%':
if ( nIdx > 0 )
{
bFMT = ( pFmt[nIdx-1] == '%' ) ? false : true;
}
else
{
bFMT = true;
}
nIdx++;
nExpChars += GetDigit ( pFmt, nIdx, nFmtLen );
nIdx--;
break;
default:
nExpChars++;
}
nIdx++;
if ( nIdx < 0 || nIdx >= nFmtLen )
{
break;
}
}
va_end (arg);
// 2. 버퍼 크기를 계산된 포멧 문자열의 길이만큼 할당..
rstr.resize(nExpChars, ' ' );
va_start ( arg, pFmt );
// 3. vsprintf 사용
int nPrnChars = vsprintf ( (char*)rstr.c_str(), pFmt, arg );
if ( nPrnChars >= 0 && nPrnChars <= nExpChars )
{
rstr.erase ( nPrnChars );
}
va_end(arg);
return rstr.length();
}
int main ( int argc, char** argv )
{
string strRet;
strprintf ( strRet, "floats: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416);
// 결과비교.
printf ("floats: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416);
printf("%s\n", strRet.c_str());
return 0;
}
Forums:


저도 printf의 출력을 ostream으로 하고 싶어서 구현해 본 적이
저도 printf의 출력을 ostream으로 하고 싶어서 구현해 본 적이 있습니다. ^^;
http://hiya.byus.net/prog/archives/000063.html
기존에 제공되는 함수를 조합해서 만들고도 싶었습니다만, 중간에 버퍼를 이용해야 하는 제약 등이 있어서 포기하고, 직접 printf를 구현할 수 밖에 없었습니다.
가장 큰 장점은 사용중 어떠한 메모리 할당도 필요하지 않다는 것입니다.
vsnprintf와 같은 함수는 출력의 예상길이를 알기 위해서 사용할 수 있습니다. 길이를 알기 위해서 파싱할 필요까지는 없답니다. ^^;
(유닉스 계열에서는 snprintf를 사용하면 버퍼가 모자랄 경우 필요한 길이를 리턴합니다만, msvc는 그냥 0(에러값)을 리턴해버리지요. 하지만, %n 스펙을 사용해서 예상 길이는 알 수 있었던 것 같습니다. ^^;;)
boost 라이브러리에서는 좀 더 type safe한 방식으로 포멧팅 기능을 제공한다고 합니다만, printf와 같은 (위험하지만) 익숙한 인터페이스가 필요하기도 하지요. ^^;
GNU extension인 vasprintf() 또는 asprintf()
GNU extension인 vasprintf() 또는 asprintf()를 타 non-GNU 시스템에서 쓰고 싶으시면 gcc나 gdb의 소스에서 libiberty 라이브러리를 가져다 쓰시면 됩니다.
C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
Korean Ver: http://cinsk.github.io/cfaqs/
[url=http://boost.org/libs/format/doc/fo
boost::format 을 사용하시면 됩니다.
위의 것은 printf 스타일의 용법이고 .net 과 같은 스타일이 기본 용법입니다. type을 명시하지 않고 위치만을 명시합니다.
format 객체는 한번 만들고 여러번 사용할 수 있기 때문에 overhead도 그리 크지 않습니다.
----------------------------------------
http://moim.at
http://mkhq.co.kr
오~ 멋진데요. ^^
%n Format 문자를 사용해서 예상 길이를 알수 있다. <= 이부분에 대한 보충 설명 부탁드려도 되겠습니까?
[ 보태기 ]
본의아니게 소스를 잠시 들여다 보았습니다. 대단하십니다. ^^ (짝짝짝)
- 인생은 스스로 -
생각해보니 %n을 사용하는데도 현실적으로 문제가 있더군요. -_-;오
생각해보니 %n을 사용하는데도 현실적으로 문제가 있더군요. -_-;
오래간만에 다시 떠올리다보니 약간 혼동이 있었나봅니다.
어쨌든 %n의 용법은 대략 다음과 같습니다. (snprintf 기준)
int len;
int rval;
rval = snprintf( NULL, 0, "test %s,%d\n%n", str, i, &len );
// %n을 만나는 순간까지의 출력 문자수를 포인터에 저장합니다.
// unix/linux라면 len == rval 입니다.
char* buf = new char[len+1];
snprintf( buf, len+1, "test %s,%d\n", str, i );
그런데 문제는 vsnprintf에 적용시 사용자의 va_list에 출력길이를 저장할 포인터를 추가할 수가 없다는 것입니다. 이 방법으로도 안되겠네요... msvc에서는 -_-;;
제가 쓰는 포맷문..
이렇게 간단한 포맷문 만들어 쓰고 있습니다...
string _stringFormat(char *pFormat, ... )
{
TCHAR *pBuffer = NULL;
UINT buffetLen = 0;
va_list va = NULL;
va_start(va, pFormat);
buffetLen = _vscprintf(pFormat, va) + 1;
pBuffer = (TCHAR*)malloc(buffetLen * sizeof(TCHAR));
ZeroMemory(pBuffer, (buffetLen * sizeof(TCHAR)));
vsprintf_s(pBuffer, buffetLen, pFormat, va);
va_end(va);
std::string sBuffer = pBuffer;
free(pBuffer);
return sBuffer;
}
LS)
string ss = _stringFormat(_T("Server %s [ START:%04d-%02d-%02d,%02d:%02d:%02d.%03d ]"), m_ChannelName.c_str(), CurTime.wYear, CurTime.wMonth, CurTime.wDay, CurTime.wHour, CurTime.wMinute, CurTime.wSecond, CurTime.wMilliseconds);
댓글 달기