我对C ++比较陌生,我仍然在掌握C ++标准库。为了帮助从C转换,我想使用printf样式的格式化程序格式化std::string
。我意识到stringstream
是一种更加类型安全的方法,但我发现自己发现printf风格更容易阅读和处理(至少目前为止)。这是我的功能:
using namespace std;
string formatStdString(const string &format, ...)
{
va_list va;
string output;
size_t needed;
size_t used;
va_start(va, format);
needed = vsnprintf(&output[0], 0, format.c_str(), va);
output.resize(needed + 1); // for null terminator??
va_end(va);
va_start(va, format);
used = vsnprintf(&output[0], output.capacity(), format.c_str(), va);
// assert(used == needed);
va_end(va);
return output;
}
这很有效。我不确定的一些事情是:
capacity()
是否正确调用此功能?我一直认为length()
将返回0,因为字符串中的第一个字符是'\0'
。有时候在将这个字符串的内容写入套接字(使用其c_str()
和length()
)时,我在接收端弹出了空字节,这引起了一些悲伤,但它们似乎出现不一致如果我根本不使用此函数,则不会出现空字节。
答案 0 :(得分:12)
使用当前标准(此处的upcomming标准不同),无法保证std::string
管理的内部内存缓冲区是连续的,或者.c_str()
方法返回指向内部的指针数据表示(允许实现为该操作生成一个连续的只读块,并将指针返回到它。可以使用.data()
成员方法检索指向实际内部数据的指针,但请注意它也是返回一个常量指针:即它不是为了你修改内容。缓冲区返回.data()
它不一定是空终止,实现只需要保证调用c_str()
时的空终止,所以即使在调用.data()
和.c_str()
的实现中,实现也可以在调用后者时将\0
添加到缓冲区的末尾。
标准旨在允许绳索实施,因此原则上做你正在尝试的东西是不安全的,并且从标准的角度来看你应该使用中间std::vector
(保证连续性,并且有保证&myvector[0]
是指向实际缓冲区的第一个分配块的指针。
在我所知道的所有实现中,由std::string
处理的内部内存实际上是一个连续的缓冲区,并且使用.data()
是未定义的行为(写入常量变量)但即使它不正确也可能有效(我会避免它)。您应该使用为此目的而设计的其他库,例如boost::format
。
关于空终止。如果你最终决定遵循未定义的路径......你需要为null终止符分配额外的空间,因为库会将它写入缓冲区。现在,问题是与C风格的字符串不同,std::string
可以在内部保存空指针,因此您必须向下调整字符串的大小以适应从一开始不包含{{1}的最大连续内存块}}。这可能是您发现虚假空字符的问题。这意味着使用\0
(或系列)的错误方法必须由vsnprintf
跟随,以便在第一个str.resize( strlen( str.c_str() ) )
之后丢弃字符串的所有内容。
总的来说,我会反对这种方法,并坚持要么习惯了C ++的格式化方式,使用第三方库(boost是第三方,但它也是最标准的非标准库),使用向量或像C中那样管理记忆...但应该像瘟疫一样避免最后一个选项。
\0
答案 1 :(得分:5)
如果您希望printf()
优先于流,请使用boost::format
。
编辑:为了明确这一点,实际上我完全赞同Alan,他说你应该使用流。
答案 2 :(得分:2)
我认为不能保证& output [0]引用的字符串布局是连续的,你可以写入它。
使用std :: vector作为缓冲区,保证自C ++ 03以来具有连续存储。
using namespace std;
string formatStdString(const string &format, ...)
{
va_list va;
vector<string::value_type> output(1); // ensure some storage is allocated
size_t needed;
size_t used;
va_start(va, format);
needed = vsnprintf(&output[0], 0, format.c_str(), va);
output.resize(needed); // don't need null terminator
va_end(va);
// Here we should ensure that needed != 0
va_start(va, format);
used = vsnprintf(&output[0], output.size(), format.c_str(), va); // use size()
// assert(used == needed);
va_end(va);
return string(output.begin(), output.end());
}
注意:您必须为向量设置初始大小,因为语句&amp; output [0]否则可能会尝试引用不存在的项目(因为内部缓冲区可能尚未分配)。 / p>
答案 3 :(得分:1)
我对函数的变量参数列表的实现是这样的:
std::string format(const char *fmt, ...)
{
using std::string;
using std::vector;
string retStr("");
if (NULL != fmt)
{
va_list marker = NULL;
// initialize variable arguments
va_start(marker, fmt);
// Get formatted string length adding one for NULL
size_t len = _vscprintf(fmt, marker) + 1;
// Create a char vector to hold the formatted string.
vector<char> buffer(len, '\0');
int nWritten = _vsnprintf_s(&buffer[0], buffer.size(), len, fmt,
marker);
if (nWritten > 0)
{
retStr = &buffer[0];
}
// Reset variable arguments
va_end(marker);
}
return retStr;
}
答案 4 :(得分:0)
std :: string类为你处理null终止符。
然而,正如所指出的那样,由于你正在使用vnsprintf来处理原始的字符串缓冲区(C anachronisms很难...),你必须确保空终结符有空间。
答案 5 :(得分:0)
1)您不需要为空终止符腾出空间 2)capacity()告诉你字符串在内部保留了多少空间。 length()告诉你字符串的长度。你可能不想要capacity()
答案 6 :(得分:0)
为了帮助从C过渡,我想 使用格式化std :: string printf风格的格式化程序。
不要:(
如果你这样做,你实际上并不是在学习C ++,而是使用C ++编译器编写C语言。这是一种糟糕的心态,糟糕的做法,它传播了std::o*stream
类被创建以避免的问题。
我意识到stringstream是一个更多 类型安全的方法,但我发现自己 找到printf风格更容易 阅读和处理(至少,为 时间)。
这不是更多类型安全方法。这是一种类型安全的方法。更重要的是,它最大限度地减少了依赖关系,它减少了你必须跟踪的问题的数量(比如显式缓冲区分配和跟踪null char终结符),这样可以更容易地维护你的代码。
在上面它是完全可扩展/可定制的:
您可以扩展区域设置格式
您可以定义自定义数据类型的i / o操作
您可以添加新类型的输出格式
您可以添加新的缓冲区i / o类型(例如std :: clog写入窗口)
您可以插入不同的错误处理政策。
std::o*stream
课程系列非常强大,一旦你学会正确使用它,毫无疑问你不会回去。
除非你有非常具体的要求,否则花在学习o * stream类上的时间可能比用C ++编写printf要好得多。