std :: string.resize()和std :: string.length()

时间:2010-05-21 07:26:00

标签: c++ string printf c++-standard-library

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

这很有效。我不确定的一些事情是:

  1. 我是否需要为空终结符腾出空间,或者这是不必要的?
  2. capacity()是否正确调用此功能?我一直认为length()将返回0,因为字符串中的第一个字符是'\0'
  3. 有时候在将这个字符串的内容写入套接字(使用其c_str()length())时,我在接收端弹出了空字节,这引起了一些悲伤,但它们似乎出现不一致如果我根本不使用此函数,则不会出现空字节。

7 个答案:

答案 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要好得多。