C ++拆分字符串反馈

时间:2019-07-12 14:53:14

标签: c++ c++11

我是C ++的新手,当我应该使用指针,引用,std :: move时,仍然很难理解。我已经编写了一个短函数来使用定界符分割字符串。

std::vector<std::string> mylib::split(std::string string, char delimiter) {
    std::vector<std::string> result = std::vector<std::string>();

    std::string cache = std::string();
    cache.reserve(string.size());

    for (char c : string) {
        if (c == delimiter) {
            result.push_back(std::string(cache));
            cache.clear();
        } else {
            cache += c;
        }
    }
    cache.shrink_to_fit();
    result.push_back(cache);
    return result;
}

我对此功能有一些疑问: 我应该使用

std::vector<std::string> mylib::split(std::string string, char delimiter) {

std::vector<std::string> mylib::split(std::string &string, char delimiter) {

应该是

result.push_back(std::string(cache));

result.push_back(std::move(std::string(cache)));

我是否必须关心任何使用过的对象的破坏,还是可以像这样使用该功能? 另外,如果还有其他方法可以改进此方法,我很高兴听到您的想法。

4 个答案:

答案 0 :(得分:2)

最好的是

std::vector<std::string> mylib::split(const std::string &string, char delimiter) {

因为您不会复制超过所需的内容,并且可以保证不会给呼叫者带来影响,所以您不会修改他们的字符串。并且使API的意图更加清晰。

  

result.push_back(std :: move(std :: string(cache)));

IMO(并非所有人都会同意),您不必担心std :: move'ing the string。是的,您可以这样做,因为在两种情况下都不会使用缓存(无论如何也不会清除缓存)。仅当性能成为问题时才应开始护理。而且由于您是逐个复制char,所以我怀疑最高的性能改进将来自移动语义。

如所讨论的那样,删除初始化程序并使用令牌副本:

std::vector<std::string> split(const std::string& string, char delimiter) 
{
    std::vector<std::string> result;
    size_t pos = 0;

    for (size_t scan = 0; scan < string.size(); ++scan) 
    {
        if (string[scan] == delimiter) 
        {
             result.push_back(string.substr(pos, scan - pos));
             pos = scan + 1;
        }
    }
    result.push_back(string.substr(pos, string.size() - pos));
    return result;
}

答案 1 :(得分:1)

经验法则是:

  1. 当您要表示函数可以修改参数并且更改应该在外部可见时,请使用&。通过&传递参数也不会创建副本。

  2. 如果要表示该函数将不修改该对象,请使用const。虽然会复制它。

  3. 使用const &来组合以上两种情况:该对象不会被该函数修改,也不会被复制(这在复制很昂贵时很重要,例如字符串)。 / p>

因此对您来说,最佳解决方案是:使用const std::string& value(请更改变量的名称)。您无需修改​​字符串,它可能太大而无法复制。


关于std::move。它的作用是(基本上)将非临时对象变为临时对象。因此,如您所见,对临时对象(您的情况)使用std::move是没有意义的。

我们为什么要这样做?为了允许C ++编译器进行积极的优化。考虑以下代码:

std::string text = "abcd";
result.push_back(text);

C ++不知道将不再使用text。因此它必须复制它。但是有了这个:

std::string text = "abcd";
result.push_back(std::move(text));

您告诉C ++编译器:“嘿,我不再使用text变量,因此您不必复制它,只需将其内部移动到向量上即可。而且您所需要知道的是,在字符串复制的情况下(线性复杂度)比移动(总是恒定时间)更昂贵。

警告-收到意见:我发现std::move这个名字确实令人困惑。它实际上并没有移动任何东西。这只是静态转换。为什么不叫它std::cast_to_temp呢?

无论如何,此result.push_back(std::move(std::string(cache)));是错误的。无意义。您不会避免复制,并且std::move不会执行任何操作。但是这个result.push_back(std::move(cache));确实有意义。但是必须进行仔细分析:此后真的不需要cache吗?看起来是这样(尽管我没有深入研究您的代码)。


最后,您只在构造时会担心破坏,即对于每个new,您都需要一个delete。您没有new,也不需要delete *。

*并不总是正确的,有时您要处理一个讨厌的代码,该代码为您执行隐式,不可见的new,但实际上迫使您执行delete。是的,有时候很难。但是AFAIK不会在标准(或任何其他自重)库中发生。这是非常不好的做法。

最后的提示:当然,这是C ++,实际上一切都更加复杂,每个规则都有例外,依此类推。但是暂时不要担心细节,可以逐步学习。

答案 2 :(得分:1)

按值或引用传递:

这将创建字符串的副本:

std::vector<std::string> mylib::split(std::string string, char delimiter)

这将传递对字符串的引用:

std::vector<std::string> mylib::split(std::string &string, char delimiter) 

在上述情况下,您宁愿传递引用,因为您返回一个std :: vector,并且仅使用字符串读取字符串的一部分以将其推入向量。现在,因为您只阅读了它,所以最好将它设置为const:

std::vector<std::string> mylib::split(const std::string &string, char delimiter) 

然后,您可以100%确保为拆分函数提供的变量保持不变。想象一下:

std::string string = "some,values";

如果您传递字符串以按值分割:

std::vector<std::string> mylib::split(std::string string, char delimiter) {
    string = "something else";
    ...
}

调用split之后,您将读取字符串变量:

std::cout << string << std::endl;

这将打印“一些值”。

但是,如果您通过引用:

std::vector<std::string> mylib::split(std::string &string, char delimiter) {
    string = "something else";
}

它将打印“其他内容”,基本上是您修改​​了真实字符串。

如果将其设为const,则编译器将不允许您覆盖split函数中的字符串。因此,除非需要在函数中更改变量,否则请向其传递const引用。

移动或复制:

这将创建字符串的副本:

result.push_back(std::string(cache));

这将移动缓存的内容。

result.push_back(std::move(cache));

如果您知道创建副本通常比搬走东西要花更多的钱,那么您就会知道搬迁会更有效率,即更快。但是再说一次,为字符串添加移动调用听起来像过早的优化。除非您要处理大量数据,否则我看不到要移动字符串而不是复制字符串的原因,因为这会使代码的可读性降低,并且性能提升可能很小。

指针与参考

基本上,您可以像参考引用一样思考指针。这是一段内存的地址。语法不同,指针不能为null,而引用不能为null。指针也可以在堆上分配,而引用总是在堆上分配。

std::string string = "some,values";

std::vector<std::string> mylib::split(std::string *string, char delimiter) {
    *string = "something else";
    ...
}

std::cout << *string << std::endl; // will print "something else"
std::cout << string << std::endl; // will print the address of the pointer

请注意,拆分中的*表示您传递了一个指针,字符串'* string =“ something else”'之前的*表示该指针已取消引用,并且该值已写入该指针的位置。与打印相同,我们读取值并通过解引用指针进行打印。

我希望这可以消除您的疑问。

答案 3 :(得分:0)

您应该阅读有关C ++通过引用与按值传递的更多信息。但为了简单起见,

  • 在将变量传递给函数时不想更改变量本身时,请使用std::vector<std::string> mylib::split(std::string string, char delimiter) {。这意味着您按值传递字符串对象,并在该字符串的函数内进行复制
  • std::vector<std::string> mylib::split(std::string &string, char delimiter) {表示您正在通过引用传递字符串对象。因此,当您在函数中更改字符串时,将在声明字符串的位置独立地更改字符串本身。另外,由于不必复制对象,因此按引用对pas的性能更友好。
  

我是否必须关心任何使用过的对象的破坏,还是可以像那样使用此功能?

不,您不必担心损坏任何对象,因为您仅使用STL而不是用户定义的对象。此外,应该像这样:result.push_back(std::string(cache))。将对象推入容器时,请勿使用std::move