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);
댓글 달기