std::string.resize() and std::string.length()
我对C++是比较新的,我仍然在掌握C++标准库。为了帮助从C转换,我想使用printf样式的格式化程序格式化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } |
这是可行的,有点。一些我不确定的事情是:
偶尔,在将字符串'的内容写入套接字(使用它的
对于当前标准(upcomng标准在这里有所不同),不能保证由
该标准旨在允许绳索实施,因此原则上不安全地执行您正在尝试的操作,从该标准的角度来看,您应该使用中间
在我所知道的所有实现中,
关于零终止。如果你最终决定遵循未定义的路径…您需要为空终止符分配额外的空间,因为库将把它写入缓冲区。现在的问题是,与C样式的字符串不同,
总的来说,我会提出反对这种方法的建议,并且坚持要么使用C++的格式化方式,要么使用第三方库(Boost是第三方,但它也是最标准的非标准库),使用像C.那样的向量或管理内存。但最后一个选择应该像瘟疫一样避免。
1 2 3 4 | // A safe way in C++ of using vsnprintf: std::vector<char> tmp( 1000 ); // expected maximum size vsnprintf( &tmp[0], tmp.size(),"Hi %s", name.c_str() ); // assuming name to be a string std::string salute( &tmp[0] ); |
如果您喜欢
编辑:为了说明这一点,实际上我完全同意艾伦的观点,他说你应该使用流。
我认为不能保证&output[0]引用的字符串的布局是连续的,并且您可以写入它。
使用STD::Vector作为一个缓冲区,保证从C++ 03起连续存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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()); } |
注意:您必须设置向量的初始大小,因为语句&output[0]可能会试图引用不存在的项(因为内部缓冲区可能尚未分配)。
我对函数的变量参数列表的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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; } |
To help transition from C, I want to
format a std::string using
printf-style formatters.
只是不要:
如果你这样做,你实际上不是在学习C++,而是用C++编译器对C进行编码。这是一种糟糕的心态,糟糕的实践,它传播了
I realise stringstream is a more
type-safe approach, but I find myself
finding printf-style much easier to
read and deal with (at least, for the
time being).
这不是更安全的类型方法。这是一种类型安全的方法。更重要的是,它最小化了依赖关系,减少了必须跟踪的问题数量(如显式缓冲区分配和跟踪空字符终止符),并且使维护代码更加容易。
此外,它是完全可扩展/可定制的:
可以扩展区域设置格式
可以为自定义数据类型定义I/O操作
可以添加新类型的输出格式
可以添加新的缓冲区I/O类型(例如,使std::clog写入窗口)
您可以插入不同的错误处理策略。
除非你有非常明确的要求,否则你的时间可能比在C++中编写PrimTf要好得多。
1)您不需要为空终止符留出空间。2)capacity()告诉您字符串在内部保留了多少空间。length()告诉您字符串的长度。您可能不需要容量()。
std::string类为您处理空终止符。
然而,正如所指出的,由于您正在使用vnsprintf作为原始的隐藏字符串缓冲区(C计时错误很难消除…),因此您必须确保有空间使用空终止符。