关于c ++:std :: string.resize()和std :: string.length()

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

我对C++是比较新的,我仍然在掌握C++标准库。为了帮助从C转换,我想使用printf样式的格式化程序格式化std::string。我意识到stringstream是一种更安全的类型方法,但我发现自己发现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;
}

这是可行的,有点。一些我不确定的事情是:

  • 我需要为空终止符腾出空间吗,或者这是不必要的?
  • capacity()是在这里调用的正确函数吗?我一直认为length()会返回0,因为字符串中的第一个字符是'\0'
  • 偶尔,在将字符串'的内容写入套接字(使用它的c_str()length())时,接收端会弹出空字节,这会造成一些麻烦,但它们似乎不一致。如果我根本不使用这个函数,就不会出现空字节。


    对于当前标准(upcomng标准在这里有所不同),不能保证由std::string管理的内存缓冲区是连续的,也不能保证.c_str()方法返回指向内部数据表示的指针(允许实现为该操作生成一个连续的只读块,并且返回一个指针。使用.data()成员方法可以检索到指向实际内部数据的指针,但请注意,它还返回一个常量指针:即,它不用于修改内容。.data()返回的缓冲区不一定是空终止的,只需要在调用c_str()时保证空终止,所以即使在调用.data().c_str()的实现中,当调用\0时,该实现也可以将\0添加到缓冲区的末尾。

    该标准旨在允许绳索实施,因此原则上不安全地执行您正在尝试的操作,从该标准的角度来看,您应该使用中间std::vector(保证连续性,并保证&myvector[0]是指向实际缓冲区第一个分配块的指针)。

    在我所知道的所有实现中,std::string处理的内部内存实际上是一个连续的缓冲区,使用.data()是未定义的行为(写入常量变量),但即使不正确,它也可能工作(我会避免)。您应该使用其他为此目的而设计的库,如boost::format

    关于零终止。如果你最终决定遵循未定义的路径…您需要为空终止符分配额外的空间,因为库将把它写入缓冲区。现在的问题是,与C样式的字符串不同,std::strings可以在内部保存空指针,因此您必须向下调整字符串的大小,以便从一开始就适应不包含\0的最大连续内存块。这可能是您使用虚假的空字符发现的问题。这意味着使用EDOCX1(或家族)的错误方法必须在str.resize( strlen( str.c_str() ) )之后,在第一个\0之后丢弃字符串的所有内容。

    总的来说,我会提出反对这种方法的建议,并且坚持要么使用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] );


    如果您喜欢printf()而不是溪流,请使用boost::format

    编辑:为了说明这一点,实际上我完全同意艾伦的观点,他说你应该使用流。


    我认为不能保证&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进行编码。这是一种糟糕的心态,糟糕的实践,它传播了std::o*stream类所要避免的问题。

    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写入窗口)

    • 您可以插入不同的错误处理策略。

    std::o*stream类家族非常强大,一旦你学会正确使用它,毫无疑问你将不会回去。

    除非你有非常明确的要求,否则你的时间可能比在C++中编写PrimTf要好得多。


    1)您不需要为空终止符留出空间。2)capacity()告诉您字符串在内部保留了多少空间。length()告诉您字符串的长度。您可能不需要容量()。


    std::string类为您处理空终止符。

    然而,正如所指出的,由于您正在使用vnsprintf作为原始的隐藏字符串缓冲区(C计时错误很难消除…),因此您必须确保有空间使用空终止符。